原文链接:https://github.com/Elin-Zhou/develop-doc/blob/master/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%9E%82%E7%9B%B4%E5%88%87%E5%88%86%E8%BF%81%E7%A7%BB.md
背景
原来的业务所有数据库都在一个实例上,配置为8C64G 1TB。由于业务快速增长,在业务高峰期时,数据库QPS大约12k+,频繁出现数据库CPU使用率达到100%的情况。
问题分析
短期问题可以通过升级数据库配置解决,但是单机配置有上限,且约往后越不划算,单机的瓶颈会越来越明显,所以需要考虑对数据库进行拆分。
水平拆分OR垂直拆分
一般情况下,考虑数据拆分时,分为水平拆分和垂直拆分两个方向。根据经验,水平拆分适合数据量非常大的单表,而垂直拆分适合多个数据表按业务情况拆分到不同的数据库实例中,所以此处选择垂直拆分。
实现
流程
整体流程参照下图,大致分为五个步骤
数据同步
此处需要DBA配置,创建一个新的数据库实例,并且把需要迁移到新库中的业务表结构与数据进行同步,需要保证旧表与新表的数据是准实时的。一般来说两表的同步时延很短,最多不过几秒钟。
上线/下线路由代码
由于业务逻辑比较简单,此处不引入第三方中间件,通过Sping自带的AbstractRoutingDataSource来实现数据的路由功能,具体实现下文会详细描述。
因为切换的操作是单向的,所以路由功能只需要在操作时使用,如果确定业务正常,则可以下线动态路由功能,改为静态配置选择具体使用的数据源。
路由开关
由于线上机器较多,路由代码上线后不能直接开启,需要有一个开关统一来开启,保证所有机器路由的一致性。
关键点
数据源路由
上文提到,动态路由功能可以通过使用Spring提供的动态数据源来实现,即继承AbstractRoutingDataSource类,实现其determineCurrentLookupKey方法,简单看一下这个方法的签名及注释
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
protected abstract Object determineCurrentLookupKey();
在AbstractRoutingDataSource中有一个Map类型的成员变量targetDataSources,其key可以进行自定义,value即为对应的数据源,需要在初始化实例的是往这个map中写入所有动态的数据源,每次有请求数据源的请求时,均会调用determineCurrentLookupKey,需要方法实现中根据情况返回key来选择对应的数据源。
private Map<Object, Object> targetDataSources;
有了这个接口,我们需要在其中根据当前数据的请求情况,来判断是走旧库还是新库。而在原来使用数据源的地方,需要修改为这个数据源。
在MyBatis中,可以根据SqlSessionTemplate来隔离两个数据源,前面提到,只需要将一部分业务从原来的数据库中移到新的数据库,那么其中一部分表的访问路径是不变的。所以根据业务情况,将一个SqlSessionTemplate拆成两个。
我们将原来的业务库称为basic库,把新的业务库称为new库,那么SqlSessionTemplate也需要两个,一个是basicSqlSessionTemplate,固定请求原来的数据源;另一个是newSqlSessionTemplate,其请求的数据源需要动态切换的。** 下文我们只讨论需要切换的数据源,两个SqlSessionTemplate的拆分由读者自行实现,本文不再赘述。**
例如项目中使用MyBatis作为ORM,所以在配置SqlSessionTemplate中是这么配置的(本文中的所有代码与配置均为实例,只展示大致的逻辑,不一定是真实可用的)
<!--原数据库的数据源配置 -->
<bean name="basicDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!--需要迁移的业务走这个SqlSessionTemplate -->
<bean id="newSqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="basicDataSource"/>
<property name="mapperLocations" value="classpath:mybatis/new/*.xml"/>
<property name="configLocation" value="classpath:mybatis/sqlMapConfig.xml"/>
</bean>
</constructor-arg>
</bean>
改造后的数据源配置大致是如下的情况其中com.xxelin.datasource.RoutingDataSource类为我们自己实现的动态数据源类,后文会提供代码。
<!--原数据库的数据源配置 -->
<bean name="basicDataSource"