MTDDL——美团点评分布式数据访问层中间件

配置和使用方式举例

/** * 参考配置 */<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);    }}