的背景2016年Q3季度伊始,美团外卖商单2.0项目上线后,商户和产品数量快速增长,产能快速增长产品库的数量和峰值QPS估计很快就会有很大的压力。还会影响线上业务的查询性能、DB(数据库,以下统称DB)主从延迟、改表困难等。要解决上述问题,通常有两种选择。第一种解决方案是直接将现有的商品库垂直拆分,可以缓解目前写入峰值QPS过大和DB主从延迟的问题。方案二是对现有商品库表进行分库分表,从根本上解决现有问题。方案一实施周期短,但只能解决一时之痛。可见,分库分表是必然的。确定了分库分表的方案后,我们调研了外卖订单、结算、主站业务的分库分表实现方案,也调研了业内很多的分库分表中间件.在综合考虑性能、稳定性和实施成本的前提下,最终决定自主研发客户端分库分表中间件MTDDL,以支持外卖分库分表项目。这就是MTDDL的由来。当然,在MTDDL的设计和开发过程中,我们充分考虑了MTDDL的通用性、可扩展性、功能的全面性和访问的便利性。至此一共开发了四个阶段,实现了MySQL动态数据源、读写分离、分布式唯一主键生成器、分库分表、连接池和SQL监控、动态配置等一系列功能等。表算法和分布式唯一主键生成算法的高扩展性,支持全注解访问,业务方无需引入任何配置文件。下面详细介绍一些行业解决方案和MTDDL的设计目标,然后从源码的角度分析MTDDL的整个逻辑结构和具体实现。行业研究行业组件介绍实现方案功能特点优缺点AtlasQihoo开发维护的基于MySQL协议的数据中间层项目360。它实现了MySQL客户端-服务器协议,作为服务器与应用程序通信,作为基于代理的客户端与MySQL通信,实现读写分离。单库分表功能简单,性能和稳定性好。不支持拆分。数据库分表MTAtlas原美团DBA团队在开源Atlasproxy-based的基础上做了一系列的升级改造在读写分离和单库分表的基础上,分库的功能开发分表已经完成在Atlas的基础上支持分库分表当时还在测试阶段,暂时不建议业务方使用TDDL,是一个JDBC基于集中配置的数据源实现。基于客户端实现动态数据源,读写分离,完整的分库分表功能。淘宝内部用来管理持久化配置的系统)ZebraZebra是元电内部使用数据源、DAO、监控与数据库打交道的客户端中间件集,实现动态数据源、读写分离、分库分库表,CAT监控功能齐全,监控接入复杂。当时只支持c3p0、Druid、TomcatJDBC等连接池,分库分表算法只支持Groovy表达式,不易扩展。设计目标MTDDL(MeituanDistributedDataLayer),美团点评分布式数据访问层中间件,旨在为全公司提供一个通用的数据访问层服务,支持MySQL动态数据源,读写分离,分布式唯一主键生成器,子-分库分表、动态配置等功能,支持从客户端角度对数据源的各个方面(如连接池、SQL等)进行监控。未来会考虑支持NoSQL、Cache等数据源。功能特性动态数据源读写分离分布式唯一主键生成器分库分表连接池和SQL监控动态配置逻辑架构下图是一个完整的DAO层insert方法调用时序图,简要说明了整个逻辑架构的MTDDL。包括分布式唯一主键的获取、动态数据源的路由、SQL埋点的监控:具体实现动态数据源和读写分离。在SpringJDBCAbstractRoutingDataSource的基础上,扩展了MultipleDataSource动态数据源类,通过动态数据源注解和AOP实现。动态数据源MultipleDataSource动态数据源类,继承自SpringJDBCAbstractRoutingDataSource抽象类,实现了determineCurrentLookupKey方法,通过setDataSourceKey方法动态调整dataSourceKey,进而实现动态调整数据源的功能。其类图如下:动态数据源AOPShardMultipleDataSourceAspect动态数据源切面类,增强了DAO方法的功能,通过扫描DataSource动态数据源注解获取对应的dataSourceKey,从而指定具体的数据源。具体流程图如下:配置使用示例/***参考配置*//**数据源配置*//**写入数据源*//**读取datasource*//***DAO使用动态数据源注解*/publicinterfaceWmProductSkuDao{/**Add,删除、修改、写入数据源*/@DataSource("dbProductWrite")publicvoidinsert(WmProductSkusku);/**查询行走数据源*/@DataSource("dbProductRead")publicvoidgetById(longsku_id);}分布式唯一主键生成器众所周知,分库分表首先要解决的是分布式唯一主键的问题,业界也有很多相关的解决方案:综上所述的缺点方案三可以通过某种方式避免,但是其他方案的缺点不好处理,所以选择方案三。目前该方案已经由美团点评技术工程部实现——分布式ID生成系统Leaf,MTDDL已经集成了该功能。分布式ID生成系统Leaf美团点评分布式ID生成系统Leaf其实是一个基于DB的Ticket服务。它通过一个普通的Ticket表实现分布式ID的持久化,执行update语句获取一批ID。Ticket,获取到的这些Ticket会在内存中进行分配,分配完成后,会从DB中获取下一批Ticket。整体架构图如下:每个业务标签对应一条DB记录,DBMaxID字段记录当前分配给该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会落在第二个库。核心实现如下:publicclassModStrategyHandleimplementsShardStrategy{@OverridepublicStringgetShardType(){return"mod";}@OverridepublicDataTableNamehandle(StringtableName,StringdataSourceKey,inttableShardNum,intdbShardNum,ObjectshardValue){/**计算散到表的值*/longshard_value=Long。valueOf(shardValue.toString());longtablePosition=shard_value%tableShardNum;longdbPosition=tablePosition/(tableShardNum/dbShardNum);StringfinalTableName=newStringBuilder().append(tableName).append("_").append(tablePosition).toString();StringfinalDataSourceKey=newStringBuilder().append(dataSourceKey).append(dbPosition).toString();returnnewDataTableName(finalTableName,finalDataSourceKey);}}分库分表算法扩展MTDDL不仅支持分库分表表取模算法,也支持分库分表扩展算法,实现ShardStrategy接口,增加分库分表策略类。ShardStrategy接口包含两个方法:getShardType用于指定分片策略,handle用于实现具体的数据源和分片计算逻辑。其类图如下:全注解访问为了尽可能方便业务访问,MTDDL采用全注解的方式来使用分库分表功能,通过三个注解实现:ShardInfo、ShardOn、和IDGen。ShardInfo注解用于指定具体的分库分表配置:包括分表名前缀tableName,分表个数tableShardNum,分库个数dbShardNum,分库分表表策略shardType、唯一键生成策略idGenType、唯一键业务方标识idGenKey;ShardOn注解用于指定分库分表字段;IDGen注释用于指定唯一键字段。具体类图如下:配置及使用示例//动态数据源@DataSource("dbProductSku")//tableName:分表名前缀,tableShardNum:分表数,dbShardNum:分表数数据库,shardType:分库分表策略,idGenType:唯一键生成策略,idGenKey:唯一键业务方ID@ShardInfo(tableName="wm_food",tableShardNum=100,dbShardNum=1,shardType="mod",idGenType=IDGenType.LEAF,idGenKey=LeafKey.SKU)@ComponentpublicinterfaceWmProductSkuShardDao{//@ShardOn("wm_poi_id")使用注解修改的对象的wm_poi_id字段作为shardValue//@IDGen("id")指定字段设置唯一keypublicvoidinsert(@ShardOn("wm_poi_id")@IDGen("id")WmProductSkusku);//@ShardOn使用这个注解修改的参数为shardValuepublicListgetSkusByWmPoiId(@ShardOnlongwm_poi_id);}不合理的使用连接池和SQL监控DB连接池可以轻松容易导致很多问题,比如最大连接池连接数设置太小,线程无法获取连接;如果获取连接的等待时间设置得太高,很多线程会挂起;如果空闲连接收集器运行时间过长,空闲连接将不会被及时回收;等等,如果没有有效准确的监控,就会导致无法快速定位问题和追溯历史。此外,如果缺少与SQL执行相关的监控,将难以及时发现DB查询慢等潜在风险,而慢查询往往是导致DB服务器性能下降甚至宕机的根本原因(约查询慢,推荐阅读《MySQL索引原理及慢查询优化》文章)。MTDDL从1.0.2版本开始正式引入了连接池、SQL监控等相关功能。连接池监控实现方案结合Spring完美适配c3p0、dbcp1、dbcp2、mttrift等多种方案,自动发现新添加到Spring容器中的数据源进行监控,并通过统一的监控组件JMonitor上报监控数据美团点评。整体架构图如下:监控连接数,监控连接池中活跃连接数、空闲连接数、总连接数。,计数器格式:(ds.getConnection.datasource.time),效果图如下:SQL监控实现方案采用SpringAOP技术增强所有DAO方法的功能,使用美团分布式会话跟踪组件MTrace大众点评,对SQL调用数据进行埋点上报,进而实现从客户端角度监控SQL执行耗时、QPS、调用量、超时率、失败率等指标。整体架构图如下:实现效果登录美团点评的服务管理平台OCTO,选择一个服务,查看目的地分析。效果图如下:动态配置为了满足业务方的一些动态需求,比如解决线上DB突发事件,需要对数据源进行动态调整或者分库分表的相关配置需要在线修改立即生效,无需重启。MTDDL从1.0.3版本开始正式引入了动态配置相关的功能。实现方案在Spring容器启动时自动将数据源和分库分表相关配置注册到美团点评统一配置中心MCC。MCC配置管理页面可以进行动态调整,MCC客户端感知到变化事件后会刷新本地配置,如果数据源配置发生变化,会根据新的数据源构造一个新的数据源来替换旧的数据源配置,最后老数据源会被优雅关闭。具体流程图如下:动态数据源目前支持dbcp、dbcp2、c3p0等数据源,效果图如下:分库分表动态配置支持动态配置个数分库分表、分库分表策略、唯一密钥生成策略、唯一密钥业务方标识等,效果图如下:版本迭代MTDDL到目前为止一共发展了四个阶段,未来会逐步开源。具体版本迭代如下: