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

2016年Q3季度初,在美团外卖上单2.0项目上线后,商家和商品数量急速增长,预估商品库的容量和写峰值QPS会很快遇到巨大压力。随之而来也会影响线上服务的查询性能、DB(数据库,以下统一称DB)主从延迟、表变更困难等一系列问题。

要解决上面所说的问题,通常有两种方案。第一种方案是直接对现有的商品库进行垂直拆分,可以缓解目前写峰值QPS过大、DB主从延迟的问题。第二种方案是对现有的商品库大表进行分库分表,从根本上解决现有问题。方案一实施起来周期较短,但只能解决一时之痛,由此可见,分库分表是必然的。

在确定分库分表的方案之后,我们调研了外卖订单、结算以及主站等业务的分库分表实现方案,也调研了业界很多分库分表中间件。在综合考虑性能、稳定性及实现成本的前提下,最终决定自主研发客户端分库分表中间件MTDDL来支撑外卖商品分库分表项目,这也就是MTDDL的由来。

当然,在MTDDL的设计研发过程中,我们充分考虑了MTDDL的通用性、可扩展性、功能的全面性和接入的便利性。到目前为止一共开发了四期,实现了MySQL动态数据源、读写分离、分布式唯一主键生成器、分库分表、连接池及SQL监控、动态化配置等一系列功能,支持分库分表算法、分布式唯一主键生成算法的高可扩展性,而且支持全注解的方式接入,业务方不需要引入任何配置文件。

下面就部分业界方案及MTDDL的设计目标详细展开下,然后从源码的角度来剖析下MTDDL的整个逻辑架构和具体实现。

业界组件 | 简介 | 实现方案 | 功能特性 | 优点 | 缺点 ———— | ————- | ———— | ————- | ———— Atlas | Qihoo 360开发维护的一个基于MySQL协议的数据中间层项目。它实现了MySQL的客户端与服务端协议,作为服务端与应用程序通信,同时作为客户端与MySQL通信 | proxy-based | 实现读写分离、单库分表 | 功能简单,性能跟稳定性较好 | 不支持分库分表 MTAtlas | 原美团DBA团队在开源Atlas基础上做的一系列升级改造 | proxy-based | 在读写分离、单库分表的基础上,完成了分库分表的功能开发 | 在Atlas基础上支持了分库分表 | 当时还处于测试阶段,暂不推荐业务方使用 TDDL | 淘宝根据自己的业务特点开发了TDDL框架,主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制,它是一个基于集中式配置的JDBC datasource实现 | client-based | 实现动态数据源、读写分离、分库分表 | 功能齐全 | 分库分表功能还未开源,当前公布文档较少,并且需要依赖diamond(淘宝内部使用的一个管理持久配置的系统) Zebra | Zebra是原点评内部使用数据源、DAO以及监控等和数据库打交道的中间件集 | client-based | 实现动态数据源、读写分离、分库分表、CAT监控 | 功能齐全且有监控 | 接入较为复杂,当时只支持c3p0、Druid、Tomcat JDBC等连接池,且分库分表算法只支持Groovy表达式不易扩展

MTDDL(Meituan Distributed Data Layer),美团点评分布式数据访问层中间件,旨在为全公司提供一个通用数据访问层服务,支持MySQL动态数据源、读写分离、分布式唯一主键生成器、分库分表、动态化配置等功能,并且支持从客户端角度对数据源的各方面(比如连接池、SQL等)进行监控,后续考虑支持NoSQL、Cache等多种数据源。

  • 动态数据源
  • 读写分离
  • 分布式唯一主键生成器
  • 分库分表
  • 连接池及SQL监控
  • 动态化配置

下图是一次完整的DAO层insert方法调用时序图,简单阐述了MTDDL的整个逻辑架构。其中包含了分布式唯一主键的获取、动态数据源的路由以及SQL埋点监控等过程:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/86e749b3.png)

动态数据源及读写分离

在Spring JDBC AbstractRoutingDataSource的基础上扩展出MultipleDataSource动态数据源类,通过动态数据源注解及AOP实现。

动态数据源

MultipleDataSource动态数据源类,继承于Spring JDBC AbstractRoutingDataSource抽象类,实现了determineCurrentLookupKey方法,通过setDataSourceKey方法来动态调整dataSourceKey,进而达到动态调整数据源的功能。其类图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/0b9f765c.png)

动态数据源AOP

ShardMultipleDataSourceAspect动态数据源切面类,针对DAO方法进行功能增强,通过扫描DataSource动态数据源注解来获取相应的dataSourceKey,从而指定具体的数据源。具体流程图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/5b85d008.png)

配置和使用方式举例

/*** 参考配置*/
<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用来实现具体的唯一主键生成算法。其类图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/0f3aaa65.png)

分库分表

在动态数据源AOP的基础上扩展出分库分表AOP,通过分库分表ShardHandle类实现分库分表数据源路由及分表计算。ShardHandle关联了分库分表上下文ShardContext类,而ShardContext封装了所有的分库分表算法。其类图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/882c1a4d.png)

分库分表流程图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/eab08429.png)

分库分表取模算法

分库分表目前默认使用的是取模算法,分表算法为 (#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 {@Overridepublic String getShardType() {return "mod";}@Overridepublic DataTableName handle(String tableName, String dataSourceKey, int tableShardNum, int dbShardNum, Object shardValue) {/** 计算散到表的值 */long shard_value = Long.valueOf(shardValue.toString());long tablePosition = shard_value % tableShardNum;long dbPosition = tablePosition / (tableShardNum / dbShardNum);String finalTableName = new StringBuilder().append(tableName).append("_").append(tablePosition).toString();String finalDataSourceKey = new StringBuilder().append(dataSourceKey).append(dbPosition).toString();return new DataTableName(finalTableName, finalDataSourceKey);}
}

分库分表算法扩展

MTDDL不仅支持分库分表取模算法,还支持分库分表算法的扩展,通过新增分库分表策略类实现ShardStrategy接口即可。ShardStrategy接口包含两个方法:getShardType用来指定分库分表策略,handle用来实现具体的数据源及分表计算逻辑。其类图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/999d5b40.png)

全注解方式接入

为了尽可能地方便业务方接入,MTDDL采用全注解方式使用分库分表功能,通过ShardInfo、ShardOn、IDGen三个注解实现。

ShardInfo注解用来指定具体的分库分表配置:包括分表名前缀tableName、分表数量tableShardNum、分库数量dbShardNum、分库分表策略shardType、唯一键生成策略idGenType、唯一键业务方标识idGenKey;ShardOn注解用来指定分库分表字段;IDGen注解用来指定唯一键字段。具体类图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/028cbd5f.png)

配置和使用方式举例

// 动态数据源
@DataSource("dbProductSku")// tableName:分表名前缀,tableShardNum:分表数量,dbShardNum:分库数量,shardType:分库分表策略,idGenType:唯一键生成策略,idGenKey:唯一键业务方标识
@ShardInfo(tableName="wm_food", tableShardNum=100, dbShardNum=1, shardType="mod", idGenType=IDGenType.LEAF, idGenKey=LeafKey.SKU)  @Component
public interface WmProductSkuShardDao {// @ShardOn("wm_poi_id") 将该注解修饰的对象的wm_poi_id字段作为shardValue// @IDGen("id")  指定要设置唯一键的字段public void insert(@ShardOn("wm_poi_id") @IDGen("id") WmProductSku sku);// @ShardOn 将该注解修饰的参数作为shardValuepublic List<WmProductSku> getSkusByWmPoiId(@ShardOn long wm_poi_id);
}

连接池及SQL监控

DB连接池使用不合理容易引发很多问题,如连接池最大连接数设置过小导致线程获取不到连接、获取连接等待时间设置过大导致很多线程挂起、空闲连接回收器运行周期过长导致空闲连接回收不及时等等,如果缺乏有效准确的监控,会造成无法快速定位问题以及追溯历史。

再者,如果缺乏SQL执行情况相关监控,会很难及时发现DB慢查询等潜在风险,而慢查询往往就是DB服务端性能恶化乃至宕机的根源(关于慢查询,推荐阅读《MySQL索引原理及慢查询优化》一文)。MTDDL从1.0.2版本开始正式引入连接池及SQL监控等相关功能。

连接池监控

实现方案

结合Spring完美适配c3p0、dbcp1、dbcp2、mtthrift等多种方案,自动发现新加入到Spring容器中的数据源进行监控,通过美团点评统一监控组件JMonitor上报监控数据。整体架构图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/a63da9ea.png)

连接数量监控

监控连接池active、idle、total连接数量,Counter格式:(连接池类型.数据源.active/idle/total_connection),效果图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/73cb3832.png)

获取连接时间监控

监控获取空闲连接时间,Counter格式:(ds.getConnection.数据源.time),效果图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/2e88e1ec.png)

SQL监控

实现方案

采用Spring AOP技术对所有DAO方法进行功能增强处理,通过美团点评分布式会话跟踪组件MTrace进行SQL调用数据埋点及上报,进而实现从客户端角度对SQL执行耗时、QPS、调用量、超时率、失败率等指标进行监控。整体架构图如下:

实现效果

登录美团点评的服务治理平台OCTO选择服务查看去向分析,效果图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/8edcd3fd.png)

动态化配置

为了满足业务方一些动态化需求,如解决线上DB紧急事故需动态调整数据源或者分库分表相关配置,要求无需重启在线修改立即生效,MTDDL从1.0.3版本开始正式引入动态化配置相关功能。

实现方案

在Spring容器启动的时候自动注册数据源及分库分表相关配置到美团点评的统一配置中心MCC,在MCC配置管理页面可以进行动态调整,MCC客户端在感知到变更事件后会刷新本地配置,如果是数据源配置变更会根据新的配置构造出一个新数据源来替换老数据源,最后再将老的数据源优雅关闭掉。具体流程图如下:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2016/aff2c470.png)

动态化数据源

目前支持dbcp、dbcp2、c3p0等数据源,效果图如下:

分库分表动态化

支持动态化配置分库分表数量、分库分表策略、唯一键生成策略、唯一键业务方标识等,效果图如下:

MTDDL到目前为止总共开发了四期,后续考虑逐步开源,具体版本迭代如下:

项目名 | 功能 | 开始时间 | 结束时间 | 正式版本 | 快照版本 | 版本备注 ———— | ————- | ———— | ————- | ———— | ————- | ———— | ———— MTDDL一期 | 动态数据源
读写分离
分布式唯一主键生成器
分库分表 | 2016.05.30 | 2016.06.16 | 0.0.1 | 0.0.1-SNAPSHOT | MTDDL第一版 MTDDL二期 | 分布式唯一主键生成算法可扩展
支持零配置接入MTDDL
优化shardkey配置方式 | 2016.08.23 | 2016.09.05 | 1.0.1 | 1.0.1-SNAPSHOT | MTDDL接入优化 MTDDL三期 | 连接池及SQL监控
缓存优化 | 2016.09.06 | 2016.09.20 | 1.0.2 | 1.0.2-SNAPSHOT | MTDDL监控完善 MTDDL四期 | 唯一主键生成注解化
动态化配置 | 2016.10.11 | 2016.11.08 | 1.0.3 | 1.0.3-SNAPSHOT | MTDDL配置动态化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/478238.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解预训练模型、信息抽取、文本生成、知识图谱、对话系统技术

我们正处在信息爆炸的时代、面对每天铺天盖地的网络资源和论文、很多时候我们面临的问题并不是缺资源&#xff0c;而是找准资源并高效学习。其次&#xff0c;即便网络上的资源非常多&#xff0c;学习是需要成本的&#xff0c;而且越有深度的内容越难找到好的学习资源。如果一门…

会议交流 | 欢迎注册 CCKS2021 全国知识图谱与语义计算大会!

欢迎注册 CCKS2021 全国知识图谱与语义计算大会知万物过去 谱AI未来2021年8月18日至8月21日 广州http://sigkg.cn/ccks2021主办&#xff1a;中国中文信息学会语言与知识计算专业委员会 承办&#xff1a;广东外语外贸大学注册介绍早期注册 1800元/人2021年7月28日前&…

LeetCode 1171. 从链表中删去总和值为零的连续节点(哈希表)

1. 题目 给你一个链表的头节点 head&#xff0c;请你编写代码&#xff0c;反复删去链表中由 总和 值为 0 的连续节点组成的序列&#xff0c;直到不存在这样的序列为止。 删除完毕后&#xff0c;请你返回最终结果链表的头节点。 你可以返回任何满足题目要求的答案。 &#x…

移动端性能监控方案Hertz

性能问题是造成App用户流失的罪魁祸首之一。App的性能问题包括崩溃、网络请求错误或超时、响应速度慢、列表滚动卡顿、流量大、耗电等等。而导致App性能低下的原因有很多&#xff0c;除去设备硬件和软件的外部因素&#xff0c;其中大部分是开发者错误地使用线程、锁、系统函数、…

刷新SOTA!Salesforce提出跨模态对比学习新方法,仅需4M图像数据!

文 | 子龙多模态已经不是一个新鲜的话题&#xff0c;如何在一个模型中融合CV和NLP的信息同时吸引了两个领域的目光&#xff08;CV、NLP的会都能投&#xff09;&#xff0c;但是很容易就能想到&#xff0c;来自图片的视觉特征和来自语料的文本特征来自不同的模型&#xff0c;所隐…

论文浅尝 - ACL2021 | 探讨跨句事件联合抽取问题

转载公众号 | 浙大KG论文题目&#xff1a;MLBiNet: A Cross-Sentence Collective Event Detection Network本文作者&#xff1a;娄东方、廖智霖、邓淑敏、张宁豫、陈华钧&#xff08;浙江大学&#xff09;接收会议&#xff1a;ACL 2021论文链接&#xff1a;https://arxiv.org/p…

LeetCode 343. 整数拆分(DP)

1. 题目 给定一个正整数 n&#xff0c;将其拆分为至少两个正整数的和&#xff0c;并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 示例 1: 输入: 2 输出: 1 解释: 2 1 1, 1 1 1。示例 2: 输入: 10 输出: 36 解释: 10 3 3 4, 3 3 4 36。 说明: 你可以假设 …

python-dotenv解析env文件

python-dotenv解析env文件 最简单和最常见的用法是在应用程序启动时调用load_dotenv&#xff0c;从当前目录或其父目录中的.env文件或指定的路径加载环境变量&#xff0c;然后调用os.getenv提供的与环境相关的方法 .env 文件内容写法 ADMIN_HOST https://uat-rm-gwaaa.cn A…

HDFS NameNode内存详解

前言 《HDFS NameNode内存全景》中&#xff0c;我们从NameNode内部数据结构的视角&#xff0c;对它的内存全景及几个关键数据结构进行了简单解读&#xff0c;并结合实际场景介绍了NameNode可能遇到的问题&#xff0c;还有业界进行横向扩展方面的多种可借鉴解决方案。 事实上&am…

组队瓜分百万奖金池,资深算法工程师带你挑战飞桨论文复现赛!

你是否正在焦虑找不到好的论文&#xff1f;好不容易找到了paper&#xff0c;无法复现出code&#xff1f;缺少科研同行交流&#xff0c;只能独自一人闭门造车&#xff1f;是的&#xff0c;论文复现是要想最快的学习和了解AI领域的方式&#xff0c;复现困境也被叫做“徘徊在 AI 上…

开源开放 | Beyond 预训练语言模型,NLP还需要什么样的知识?

近年来&#xff0c;深度学习技术已广泛应用于NLP领域&#xff0c;但实际应用效果往往受限于缺乏大规模高质量监督样本。2018年底&#xff0c;预训练语言模型横空出世&#xff0c;极大缓解了这个问题&#xff0c;通过“超大规模无监督语料上的预训练语言模型相对少量的监督样本进…

Docker把容器打包成镜像并提交到harbor仓库

Docker把容器打包成镜像并提交到harbor仓库 如果你想要保存当前容器的状态&#xff0c;就可以通过commit来提交获得一个镜像&#xff0c;就好我们虚拟机的时候创建快照&#xff0c;想要回滚到某一个版本 一、首先创建要给tomcat 的本地容器&#xff0c;镜像指定tomcat-alpine:8…

LeetCode 139. 单词拆分(DP)

1. 题目 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict&#xff0c;判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明&#xff1a; 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1&#xff1a; 输入: s "…

外卖排序系统特征生产框架

背景 图1 外卖排序系统框架 外卖的排序策略是由机器学习模型驱动的&#xff0c;模型迭代效率制约着策略优化效果。如上图所示&#xff0c;在排序系统里&#xff0c;特征是最为基础的部分&#xff1a;有了特征之后&#xff0c;我们离线训练出模型&#xff0c;然后将特征和模型一…

征稿 | “健康知识图谱”投稿通道开启

Data Intelligence正在与语义网国际知名学者Deborah McGuinness以及Oshani Seneviratne等专家一道组织“个人健康知识图谱”专辑。欢迎投稿&#xff01;DI专辑Special Issue on Personal Health Knowledge Graphs This special issue at Data Intelligence Journal seeks origi…

清华提出LogME,无需微调就能衡量预训练模型的下游任务表现!

文 | 游凯超源 | THUML引言在深度学习时代&#xff0c;神经网络的参数量越来越大&#xff0c;从头开始训练(train from scratch)的成本也越来越大。幸运的是&#xff0c;在计算机视觉、自然语言处理等人工智能应用的主要领域&#xff0c;人们能够采用迁移学习的预训练-微调范式…

好的代码标准

需求分析文档需要用精确的数字来描述&#xff0c;避免量变导致质变

LeetCode 140. 单词拆分 II(DP+回溯)

1. 题目 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict&#xff0c;在字符串中增加空格来构建一个句子&#xff0c;使得句子中所有的单词都在词典中。返回所有这些可能的句子。 说明&#xff1a; 分隔时可以重复使用字典中的单词。 你可以假设字典中没有重复的…

论文浅尝|简单高效的知识图谱表示学习负样本采样方法

笔记整理 | 陈名杨&#xff0c;浙江大学在读博士生&#xff0c;主要研究方向为知识图谱表示学习Introduction研究知识图谱表示学习&#xff08;KnowledgeGraph Embedding&#xff09;可以解决当前很多应用的基本问题&#xff0c;这些方法旨在将知识图谱中的实体&#xff08;Ent…

常见性能优化策略的总结

本文要感谢我职级评定过程中的一位评委&#xff0c;他建议把之前所做的各种性能优化的案例和方案加以提炼、总结&#xff0c;以文档的形式沉淀下来&#xff0c;并在内部进行分享。力求达到如下效果&#xff1a; 1. 形成可实践、可借鉴、可参考的各种性能优化的方案以及选型考虑…