配置和使用方式举例
/** * 参考配置 */<bean id="multipleDataSource" class="com.sankuai.meituan.waimai.datasource.multi.MultipleDataSource"> /** 数据源配置 */ <property name="targetDataSources"> <map key-type="java.lang.String"> /** 写数据源 */ <entry key="dbProductWrite" value-ref="dbProductWrite"/> /** 读数据源 */ <entry key="dbProductRead" value-ref="dbProductRead"/> </map> </property> </bean>/** * DAO使用动态数据源注解 */public interface WmProductSkuDao { /** 增删改走写数据源 */ @DataSource("dbProductWrite") public void insert(WmProductSku sku); /** 查询走读数据源 */ @DataSource("dbProductRead") public void getById(long sku_id);}
分布式唯一主键生成器
众所周知,分库分表首先要解决的就是分布式唯一主键的问题,业界也有很多相关方案:
序号实现方案优点缺点1UUID本地生成,不需要RPC,低延时;扩展性好,基本没有性能上限无法保证趋势递增;
UUID过长128位,不易存储,往往用字符串表示2Snowflake或MongoDB ObjectId分布式生成,无单点;
趋势递增,生成效率快没有全局时钟的情况下,只能保证趋势递增;
当通过NTP进行时钟同步时可能会出现重复ID;
数据间隙较大3proxy服务+数据库分段获取ID分布式生成,段用完后需要去DB获取,同server有序可能产生数据空洞,即有些ID没有分配就被跳过了,主要原因是在服务重启的时候发生;
无法保证有序,需要未来解决,可能会通过其他接口方案实现
综上,方案3的缺点可以通过一些手段避免,但其他方案的缺点不好处理,所以选择第3种方案。目前该方案已由美团点评技术工程部实现——分布式ID生成系统Leaf,MTDDL集成了此功能。
分布式ID生成系统Leaf
美团点评分布式ID生成系统Leaf,其实是一种基于DB的Ticket服务,通过一张通用的Ticket表来实现分布式ID的持久化,执行update更新语句来获取一批Ticket,这些获取到的Ticket会在内存中进行分配,分配完之后再从DB获取下一批Ticket。整体架构图如下:
每个业务tag对应一条DB记录,DB MaxID字段记录当前该Tag已分配出去的最大ID值。
IDGenerator服务启动之初向DB申请一个号段,传入号段长度如 genStep = 10000,DB事务置 MaxID = MaxID + genStep,DB设置成功代表号段分配成功。每次IDGenerator号段分配都通过原子加的方式,待分配完毕后重新申请新号段。
唯一主键生成算法扩展
MTDDL不仅集成了Leaf算法,还支持唯一主键算法的扩展,通过新增唯一主键生成策略类实现IDGenStrategy接口即可。IDGenStrategy接口包含两个方法:getIDGenType用来指定唯一主键生成策略,getId用来实现具体的唯一主键生成算法。其类图如下:
分库分表
在动态数据源AOP的基础上扩展出分库分表AOP,通过分库分表ShardHandle类实现分库分表数据源路由及分表计算。ShardHandle关联了分库分表上下文ShardContext类,而ShardContext封装了所有的分库分表算法。其类图如下:
分库分表流程图如下:
分库分表取模算法
分库分表目前默认使用的是取模算法,分表算法为 (#shard_key % (group_shard_num * table_shard_num)),分库算法为 (#shard_key % (group_shard_num * table_shard_num)) / table_shard_num,其中group_shard_num为分库个数,table_shard_num为每个库的分表个数。
例如把一张大表分成100张小表然后散到2个库,则0-49落在第一个库、50-99落在第二个库。核心实现如下:
public class ModStrategyHandle implements ShardStrategy { @Override public String getShardType() { return "mod"; } @Override public DataTableName handle(String tableName, String dataSourceKey, int tableShardNum, int dbShardNum, Object shardValue) { /** 计算散到表的值 */ long shard_value = http://www.netofthings.cn/JieJueFangAn/2016-12/Long.valueOf(shardValue.toString());"_").append(tablePosition).toString(); String finalDataSourceKey = new StringBuilder().append(dataSourceKey).append(dbPosition).toString(); return new DataTableName(finalTableName, finalDataSourceKey); }}