关于后端:OPPO云数据库访问服务技术揭秘

11次阅读

共计 7393 个字符,预计需要花费 19 分钟才能阅读完成。

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_id
select * 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 数智技术] 公众号

正文完
 0