1、背景
MySQL是OPPO应用最宽泛的关系数据库,不同编程语言的微服务都是通过MySQL官网的SDK直连实在的数据库实例。这种最传统的应用形式,会给业务开发和数据库运维带来一系列影响效率和稳定性的问题。
- 不合理的数据库拜访,比方不失当的连接池设置、高危SQL无拦挡、有限流和熔断等治理能力
- 无弹性伸缩能力,单机数据库性能或者容量有余时,扩容十分繁琐低效
- 不足罕用的性能个性,不反对读写拆散、影子库表、单元化架构、数据库加密
- 不反对跨语言,并且与应用服务强耦合,编码和降级都十分艰难
以上问题,咱们讲通过CDAS来解决单MySQL带来的一系列问题。
2、CDAS产品简介
MySQL在并发能力、稳定性、高可用方面久经考验,是绝大多数网联网产品首选的OLTP场景存储组件,但单机MySQL在超高并发、海量存储、OLAP能力上先天不足,因而当数据达到一定量级后个别采纳分库分表的形式来程度扩大MySQL,晋升零碎的整体解决能力。
CDAS基于分片理念设计,目标是解决MySQL单机性能瓶颈,对外提供超过并发、海量存储、反对HTAP的MySQL服务。在一组MySQL集群前搭建一套高可用的代理 + 计算集群,提供分片能力、自动化的弹性伸缩能力、读写拆散、影子库、数据加密存储能力,为用户提供一体化的产品。
CDAS Proxy应用Java语言开发,基于开源产品Apache ShardingSphere Proxy革新而来,并增加了许多高级个性来反对外部业务。咱们的开发理念是依靠开源的成熟产品来搭建服务,从社区吸取养分的同时也把发现的问题、个性回馈给社区,积极参与、和社区独特倒退。
2.1 CDAS产品特点
(1)稳定性
CDAS进行了大量的基准测试、调优,实现了非凡的动静队列 + 线程池模型来保障Proxy的总体并发水平不会因为线程数过多导致上下文切换频繁从而性能降落,同时Proxy在长时间高负载的场景下不会产生Full GC导致业务中断,目前正式环境已有多个QPS:5000+的业务场景应用了CDAS,体现稳固。
(2)高度可扩大
咱们倡议业务在申请分片表时预估将来3~5年的数据总量来设定分片总数,后期可申请1~2个数据库,数据质变大后扩容。
实践上可扩展性和分片总数相干,最大可扩大分片数相等的数据库实例,最大可扩大到250TB,同时Proxy集群也反对在线扩容。
(3)平台化运维
由云平台对立运维、部署,MySQL和Proxy都反对自动化流程申请和变更,主动接入元数据管理系统。
(4)兼容MySQL语法
反对绝大多数MySQL语法。
- 路由至单数据分片100%兼容
- 反对分页、去重、排序、分组、聚合、关联等常见查问场景
(5)功能丰富
- 多种分片算法
- 自增分布式ID
- 读写拆散,可设置主从同步容忍阈值
- 影子库
- HTAP
- 欠缺的监控信息:审计日志、慢日志、鉴权
3.外围设计
CDAS的外围指标是解决用户海量数据存储、拜访问题,聚焦到分片场景中,次要体现在解析、路由、重写、聚合等逻辑 。
3.1 内核架构
站在高处看Proxy内核,次要分为连贯接入模块、I/O多路复用模块、解析路由执行引擎3个大的局部,左侧能够看到线程模型也分为3块,理论波及线程资源的还有其余子逻辑,如并行执行线程池等,接下来将围绕多个模型和执行流程理解内核架构的细节。
3.2 线程模型
内核入口层基于Netty实现,从整体上看是一个典型的Netty应用场景,内核图从能够看到次要分为3个局部。
(1)Boss Thread
负责Accept connection,也就是承受、建设连贯,客户端个别都会应用连接池技术,因而建设连贯的申请不会太多,单线程即可解决
(2)I/O Threads
即Netty EventLoopGroup ,负责编解码、发动auth认证,基于Epoll事件驱动 I/O多路复用,一个线程可解决多个连贯,共CPU外围数 * 2个线程。
(3)Worker Threads
负责执行、回写数据等外围流程。
Worker线程整体是同步的,其中Parser\Router\Rewriter\RateLimter是纯CPU计算,Executor Engine、ResultManager底层基于JDBC标准,JDBC目前对外裸露的是一个同步的调用模型,在数据库未响应火线程状态为blocked,因而Worker线程池的配置决定了Proxy整体的并发能力。
线程资源在操作系统中是绝对低廉的资源,在CPU外围数固定的状况下,线程数量过大会消耗大量内存和导致上下文频繁切换,升高性能。咱们在压测过程中额定关注了线程数对服务的影响,找到了一个适合的计算公式。
Math.min(cpuNum * X, maxThreadsThreshold)
- X:默认75,可配置
- maxThreadsThreshold:默认800,可配置
同时咱们自研了DynamicBlockingQueue,在队列积压工作到肯定阈值后提前创立新线程执行工作。
3.3 连贯模型
在承受客户端连贯后,Proxy外部会为每个客户端连贯保护1个Backend Connection的逻辑连贯。Backend Connection的connections列表保留执行过程中用到的实在连贯,申请执行结束或事务完结后清空connections列表,开释物理连贯。
同时,Proxy外部应用连接池技术来保留物理连贯,缩小连贯建设工夫并管制总的连贯数量。
3.4 事务模型
接下来聊一下在事务场景中的执行流程。
3.4.1 单库事务
假如有以下事务场景:
begin;select * from t_order where order_id = 1;update t_order set gmt_modify = now() where order_id = 1;update t_order_item set cnt = 2 where order_id = 1;commit/rollback;
事务过程中一共会和数据库程序交互5次。
语句1:begin时Proxy并不知道前面要执行什么语句,无奈路由到RDS,只是在连贯上记录了一个begin状态
语句2:执行带分片键的select,还是以16分片为例,最终语句会路由到t_order_1这张表中,获取connection_db1,先执行begin,再执行select,并放入逻辑连贯的连贯列表中
connection_db1为t_order_1表所在数据库上的连贯
语句3、语句4:路由到t_order_1,t_order_item_1,和语句2路由到雷同DB,复用connection_db1执行
语句5:单纯的commit或rollback语句,取出以后逻辑连贯的所有RDS连贯,挨个执行commit/rollback
在事务执行过程中,是否会产生分布式事务齐全由用户SQL语句来管制,如果事务执行过程中不会切换数据库,则会进化成单纯的RDS transaction,放弃残缺的ACID个性,如果事务执行过程中呈现路由到多个DB的状况,则会产生分布式事务。
3.4.2 分布式事务
目前分布式事务的解决方案次要有以下3种
(1)最终统一
业务发动的最终一致性计划,如Seata\TCC等,业务有感,多用于跨服务调用场景,如订单和库存零碎,某一环提交失败需特定逻辑来整体回滚,不适宜Proxy场景
(2)强统一XA
多用于跨服务调用场景,目前存在性能不佳,协调器单点\Recovery锁占用等问题,不适宜高并发场景应用
(3)厂商提供分布式事务
分布式数据库厂商如OceanBase自身会对数据进行分片,事务执行过程中操作了多个分布式节点从而引入分布式事务。针对单个分布式库内的事务保障ACID,Proxy后端基于MySQL协定,无奈在多个RDS间实现这类事务。
(4)现状
CDAS Proxy并没有提供XA来保障跨RDS实例的强一致性,也没有在外部反对最终一致性。如果触发多库事务则离开提交,有局部提交胜利、局部失败的危险,因而倡议业务方在设计事务时尽量不再RDS集群外部产生跨库事务。
3.5 分片模型
以4库16分片场景为例论述CDAS分片计划和传统计划的区别
传统计划每个DB中的分片名都是从后缀0开始的,每个库中有雷同的分片数量。
有以下劣势:
- 热点分片解决艰难
- 无奈针对分片进行迁徙
- 分片名无意义,无奈自解释
针对以上问题,咱们采纳了相似范畴分片的思路来优化了分片名。
分片名是惟一的,位于不同DB中的分片名互不雷同,这种设计形式与Redis/MongoDB中不同slot位于不同实例上很类似,有利于扩大。
t_order_1为热点分片或DB1压力过大时迁徙t_order_1到新的DB5中:
当然这种迁徙形式并不适用于所有场景,如表t_order、t_order_item的分片算法完全相同,则他们肯定落到同一个DB中,如果迁徙过程中只迁徙其中一张表势必会导致不必要的分布式事务,因而迁徙时会将绑定关系的表一并迁徙。
3.6 执行流程
以最简略的场景举例,逻辑表:t_order分16片,分片键:order_idselect * from t_order where order_id=10
(1)解析
Worker线程拿到这条语句后首先要做的是解析SQL语句,基于开源产品Antlr实现语法树解析。
{type:select,table:t_order,condition: order_id=10}
(2)路由
在获取到分片表名和条件后,依据表名获取分片规定并计算分片order_id mod 16 => 10 mod 16 => 10
失去实在的table:t_order_10
(3)重写
重写流程会将逻辑表名替换为实在表名,并计算实在位于哪个DB中。
select * from t_order_10 where order_id=10
(4)限流
限流性能能够管制某类SQL的刹时并发度,目前仅对局部场景进行了限流,如OLAP申请。
(5)执行
语句将会发往实在的数据库中执行,目前OLTP申请应用MySQL官网Connector/J发送,OLAP申请应用ClickHouse官网SDK发送,并且AP流量不会路由+重写,因为在整体设计上ClickHouse集群中的分布式表名和逻辑表名是雷同的
(6)聚合后果
聚合的次要场景是查问申请被路由到了超过一个实在表的场景,如
select * from t_order where order_id in (10,11)
这种状况一条SQL最终到DB层面会是两次子申请
select * from t_order_10 where order_id in (10,11)select * from t_order_11 where order_id in (10,11)
聚合时,通过JDBC API遍历两个子申请的ResultSet,在内存中新建一个逻辑ResultSet,当逻辑ResultSet被遍历时会触发子ResultSet被遍历,最终将逻辑ResultSet编码在MySQL协定包发往客户端。
如果SQL语句中蕴含order by,聚合时会在内存外面进行归并。内存外面排序须要将所有行数据加载到内存中排序,内存压力会很大,因而尽管有排序能力,咱们还是不倡议在申请决裂多个子申请的场景中应用order by语法。
3.7 HTAP
HTAP的是指混合OLTP(Online Transactional Processing)和 OLAP(Online Analytical Processing),在数据库中同时反对在线事务申请和后盾剖析申请。
MySQL是一个典型的OLTP型数据库,不太适宜OLAP型业务,因为剖析语句个别须要执行简单的聚合和查问大量数据,这可能会影响其在OLTP上的稳定性。同时OLAP数据库个别在存储形式上采纳列存来最大化压缩效率、空间占用,而MySQL采纳行存,个别状况下须先将整行记录查问进去再过滤出某些列,在执行剖析申请时会有显著的IO读放大,效率不高。
CDAS通过DTS(数据传输)服务将MySQL各分片数据传输并聚合到ClickHouse(剖析型列存数据库),造成对立的数据视图。AP流量达到Proxy后会被转发到ClickHouse,数据响应后再以MySQL数据包的模式返回给客户端,通过这种形式,CDAS实现了基于MySQL协定的HTAP能力。
4.部署架构
部署拓扑图如下:
古代架构中业务方对高可用的要求越来越高,目前大多数重要的服务都会采纳多机房主备或互为主备的计划来部署,个别采纳双机房或双中心化计划,保障其中一个区域挂掉后依然能对外服务。
在设计之初CDAS思考到作为L7层的数据流量入口领有极高的高可用要求,因而容许用户申请双机房Proxy集群,保障其中1个机房挂掉后另1个机房仍然能够承接流量对外服务,实现机房级的高可用。
业务零碎通过L4负载平衡来和Proxy建设连贯,L4和Proxy严密协调保障优雅下线以及新实例发现,并感知Proxy节点的衰弱状态,及时下线不可用节点。同时由L4来做同机房优先路由、流量转发,Proxy后的所有MySQL集群都采纳semi-sync的高可用架构模式。
这种部署架构使CDAS具备了机房级灾备的能力。
5.性能测试
在测试过程中咱们尤其关注了性能方面的损耗,并对Proxy代理、直连RDS进行了比照测试,这样就能直观的失去性能损耗水平。
5.1 压测工具
外部压测平台(外部基于JMeter)
5.2 压测形式
Java SDK + 连接池形式压测
次要分为两组
- 压测Proxy,Proxy连贯2个RDS集群
- 压测单个RDS集群,作为Proxy性能比照的基准
压测语句:
参考SysBench的数据库压测语句,因为分片场景个别不会间接应用主键ID分片,且主键ID在InnoDB中查问效率高于二级索引,因而增加sharding_k列进行分片测试,更符合实际的利用场景。
CREATE TABLE `sbctest_x` ( `id` bigint(11) NOT NULL,`sharding_k` bigint(11) NOT NULL,`k` int(11) DEFAULT NOT NULL, `name` varchar(100) NOT NULL, `ts` timestamp NULL DEFAULT NULL, `dt` datetime DEFAULT NULL, `c` char(100) DEFAULT NULL, `f` float DEFAULT NULL, `t` tinyint(4) DEFAULT NULL, `s` smallint(6) DEFAULT NULL, PRIMARY KEY (`id`), KEY `sharding_k_idx` (`sharding_k`),KEY `k_idx` (`k`),) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;压测SQL#QPS 非事务的简略查问SELECT COUNT(1) FROM $table WHERE sharding_k=?#TPS 蕴含 1条查问,4条变更语句BEGIN;SELECT c FROM $table WHERE sharding_k=?UPDATE $table SET k=k+1 WHERE sharding_k=?UPDATE $table SET c=?,ts=?,dt=? WHERE sharding_k=?DELETE FROM $table WHERE sharding_k=?INSERT INTO $table (id,sharding_k,k,name,ts,dt,c,pad,f,t,s) VALUES(?,?,?,?,?,?,?,?,?,?,?)COMMIT;
5.3 压测报告
以8C16G规格为例:
SSD\100分片\单片250W行\数据量大于InnoDB buffer pool
(1)QPS场景
Proxy QPS只有RDS QPS的1/2,压测过程中察看到RT约为2倍
QPS场景次要是大量非事务查问的场景,Proxy的性能损耗约1/2,QPS压测过程中咱们察看到Proxy后的RDS实例CPU使用率较低,于是将Proxy的CPU规格晋升1倍后,Proxy QPS立刻晋升1倍,靠近直连RDS的性能。
(2)TPS场景
因为TPS波及事务提交、log等操作,I/O频繁,瓶颈也次要在I/O,即便是SSD磁盘性能也都不高。
Proxy后挂载了2个RDS实例,TPS在各并发用户数中都显著高于直连单个RDS,且在并发用户数减少后可达到其2倍,性能损耗简直可疏忽。
(3)论断
如果业务场景中非事务查问的申请占绝大多数,且RDS的CPU利用率较高,倡议抉择Proxy的规格时CPU要高于RDS的规格
如果业务场景中事务执行申请较多,Proxy不会成为性能瓶颈,规格可和RDS保持一致
6.案例
目前反对了多个在线业务,领有欠缺的指标展现、报警机制,咱们例举个业务的控制台界面及监控图。
Proxy列表:
单个节点TPS:
单个节点QPS:
在多个产品的应用过程中CDAS体现稳固,咱们将会持续往更大流量的场景推广,服务更多的业务方。
7.总结与瞻望
CDAS为数据分片而生,构建基于MySQL分片场景的外部规范,通过MySQL协定实现跨语言可拜访,对立应用形式,简化应用老本。
将来咱们将继续专一于CDAS的性能晋升和性能补充,次要体现在上面2个方面:
1.ShardingSphere社区已打算将Proxy的数据库驱动从MySQL Connnector/J 替换为事件驱动组件vertx-mysql-client(一款Reactive MySQL Client),基于Netty实现和MySQL交互I/O多路复用、事件驱动
替换后worker线程池执行语句时将从blocking期待变为事件驱动,大量的线程即可反对大量的并发,咱们将继续关注社区的开发进度、积极参与。
2.CDAS和MySQL交互过程中会主动解码Rows数据包,产生大量的对象并且都是朝生夕灭的,导致GC压力增大,同时编解码也会消耗大量CPU工夫。
Rows数据包在非解密等非凡场景外都能够基于Netty ByteBuf实现零拷贝发送,这是最佳的计划。咱们打算在开源版本替换为vertx-mysql-client后对这部分逻辑进行优化,使绝大多数的申请的响应工夫能达到靠近4层负载平衡的性能。
作者简介
jianliu OPPO高级后端工程师
目前次要专一于数据库代理、注册核心、云原生相干技术曾就任于折800、京东商城
获取更多精彩内容,请扫码关注[OPPO数智技术]公众号