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;
	}

数据源配置:

<!-- 配置数据源 开始-->
	<bean id="baseDataSource" class="com.alibaba.druid.pool.DruidDataSource" abstract="true" init-method="init" destroy-method="close">
		<!-- ......  -->
	</bean>
	<!-- 配置数据源 结束-->
	
	<!-- 主db开始 -->
	<bean id="datasource"  parent="baseDataSource">
		<property name="url">
			<value>${mysql.jdbc.url}</value>
		</property>
		<property name="username">
			<value>${common.mysql.jdbc.user}</value>
		</property>
		<property name="password">
			<value>${common.mysql.jdbc.password}</value>
		</property>
	</bean>
	<!-- 主db结束 -->
	
	<!-- 主 DB-1 开始-->
	<bean id="masterDatasource1"  parent="baseDataSource">
	</bean>
	<!-- 主DB-1  结束-->
	    <!-- ..... -->
	<!-- 主DB-6  结束-->

	<!-- 从 DB-1 开始-->
	<bean id="slaveDatasource1"  parent="baseDataSource">
	</bean>
	<!-- 从DB-1  结束-->
	    <!-- ..... -->
	<!-- 从DB-6  结束-->
	
	<!-- 切换数据源(继承了AbstractRoutingDataSource) 开始 -->
	<bean id="dataSource" class="com.xxx.datasource.db.DynamicDataSource">
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<entry key="msd1" value-ref="masterDatasource1" />
				    <!-- ..... -->
				<entry key="msd6" value-ref="masterDatasource6" />
				<entry key="sld1" value-ref="slaveDatasource1" />
				    <!-- ..... -->
				<entry key="sld6" value-ref="slaveDatasource6" />
			</map>
		</property>
		<property name="defaultTargetDataSource" ref="datasource" />
	</bean>
	<!--切换数据源 结束 -->
	
	<!-- 配置数据源路由 -->
	<bean id="dbRuleSet" class="com.xxx.datasource.bean.RouterSet">
        <property name="dbNumber" value="6"></property><!-- db个数 -->
        <property name="tableNumber" value="6"></property><!-- 每个库里分表个数 -->
        <property name="masterDbKeyArray">
            <list>
                <value>msd1</value>
                    <!-- ..... -->
                <value>msd6</value>
            </list>
        </property>
        <property name="slaveDbKeyArray">
            <list>
                <value>sld1</value>
                    <!-- ..... -->
                <value>sld6</value>
            </list>
        </property>
        <property name="shardingTableArray">
            <list>
                <value>xxxxx</value>
                <value>xxxxx_express</value>
            </list>
        </property>
    </bean>


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

service层加@Router注解

/** 
	 * 
	 * @param userId
	 * @param orderIds
	 * @return
	 */
	@Router
	public Map<String, Object> 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服务去做的。

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

                                                                                                                                                                                        持续更新

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

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值