MyBatis+Mysql分库分表的案例分析

**多数据源动态切换 *分库分表***

参考 [AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换](https://blog.csdn.net/u012881904/article/details/77449710)

案例原理:
          主要是DynamicDataSource继承AbstractRoutingDataSource重写determineCurrentLookupKey进行了lookupkey的set,
    利用AOP在业务里实现spring-jdbc的AbstractRoutingDataSource。

/**
 * 数据源动态切换类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
	@Override
	protected Object determineCurrentLookupKey() {
		 return DbContextHolder.getDbKey(); //获取当前数据源 
	}
}

 

      dataSource每次getConnection之前都要通过lookupkey获取指定的DataSource(这里的get(lookupkey)由别名获取到数据源,是因为在application-db.xml里注册了数据源bean)

   /**
       * 以下为org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource.class源码
       *
	 * Retrieve the current target DataSource. Determines the
	 * {@link #determineCurrentLookupKey() current lookup key}, performs
	 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
	 * falls back to the specified
	 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
	 * @see #determineCurrentLookupKey()
	 */
	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup               key [" + lookupKey + "]");
		}
		return dataSource;
	}

数据源配置:


	
		
	
	
	
	
	
		
			${mysql.jdbc.url}
		
		
			${common.mysql.jdbc.user}
		
		
			${common.mysql.jdbc.password}
		
	
	
	
	
	
	
	
	    
	

	
	
	
	
	    
	
	
	
	
		
			
				
				    
				
				
				    
				
			
		
		
	
	
	
	
	
        
        
        
            
                msd1
                    
                msd6
            
        
        
            
                sld1
                    
                sld6
            
        
        
            
                xxxxx
                xxxxx_express
            
        
    


      AOP通过用户id计算key值匹配配置文件里定义好的Datasource集合 得到具体数据源。 从而继续执行MyBatis后续分表和Sql操作。
      在service层利用@Router的切面织入业务: 计算分库分表key,去setLookupkey的代码,每次检查到@Router就去切换数据源,执行数据操作。 

service层加@Router注解

/** 
	 * 
	 * @param userId
	 * @param orderIds
	 * @return
	 */
	@Router
	public Map getOrder(String userId, String[] orderIds) {
		//do something
		return null;
	}

切面前置增强:

/**
	 * 切换到分库
	 * 
	 * @param jp
	 * @return
	 * @throws NoSuchMethodException
	 * @throws RouterException
	 * @throws Throwable
	 */
	@Before("aopPoint()")
	public Object doRoute(JoinPoint jp) throws NoSuchMethodException, RouterException {
		Method method = getMethod(jp);
		Router router = method.getAnnotation(Router.class);
		String routeField = router.routerField();
		Object[] args = jp.getArgs();
		Signature signature = jp.getSignature();
		MethodSignature methodSignature = (MethodSignature) signature;
		String[] argsName = methodSignature.getParameterNames();
        //根据缓存的userId和请求提供的进行比较,计算routeFieldValue。“计算逻辑”见下文 
		String routeFieldValue = getRouteFieldValue(args, argsName, routeField);
		if (StringUtils.isNotEmpty(routeFieldValue)) {
			dBRouter.doRoute(routeFieldValue, router);
		} else {
			log.error("分库分表字段为空,未切换数据源");
		}
		return true;
	}


分库分表原理:
    spring检查到aop组件进入切面处理代码DBRouterInterceptor
    前置增强进行数据源切换:
        根据当前用户id进行 doRoute,若参数有userId,以入参为准,
        计算逻辑: 
            a.id转换为utf-8下Base64位的string,获取此string的哈希值的绝对值
            b.再将此哈希值除以10000去余数得到整数值
            c.配合数据源配置的路由数组例如mode=6*6, b步骤得到的整数值除以mode取余除以路由的库数 为计算出的dbIndex
            d.b步骤得到的整数值除以路由的单库的表数取余为tbIndex
        将dbIndex tbIndex全放入路由对象RouterInfo,格式化表路由tableIndex之后存入DbContexHolder(此处格式化是因为表名前缀一致,后缀_00递增)
    再将库路由dbIndex存入DbContexHolder
    
    *至此*路由全部计算出存入DbContexHolder。需要理解DbContexHolder装载的线程常量ThreadLocal特性
    DbContexHolder的工作原理参见 “原理” 因为是继承了dataSource的AbstractRoutingDataSource才可以进行lookupkey的set操作。
    
    完成库的路由计算,切换了数据源,然后就是MyBatis的分表。
    ShardingInterceptor拦截器拦截Executor实际的sql执行操作,路由到DbContexHolder里指定的表。具体原理再看

    一个多库多表的请求从路由到指定库、指定表即以上步骤。目前设定的是:执行完之后AOP后置增强会切回主数据源,异常也会切回主数据源。

小结:项目是水平分表,将一个大表分了几个字段一模一样的表。数据表里的seq实际是以 库数字后缀_表数字后缀_数字串 拼接进行唯一标识。数据这样分布式否均匀,还是取决于用户id的划分是否合理(计算逻辑是否合理)。

如果有查全量的数据,这样分好合数据吗?一个用户的数据会分到指定库指定表,如果有跨库业务怎么办。目前,日统计和周期统计都是单独task服务去做的。

垂直分表目前还没理解。待续

                                                                                                                                                                                        持续更新

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

你可能感兴趣的:(MyBatis)