刘生洲
得物中间件平台技术专家
10 年以上互联网开发架构经验,现负责得物中间件平台建设和团队管理工作,专注于数据库中间件的研发和治理,作为得物异地多活项目总架构师,主导得物异地多活架构演进和方案实践
在数据库生态中,数据库中间件是很重要的部分。得物在实践中搭建了自行研发的数据库中间件平台,取得了不错的实践成果。在 2022 年第四期 DBTalk 技术公开课中,得物中间件平台工程师刘生洲带来了主题为《得物数据库中间件“彩虹桥”架构演进和落地实践》的分享,介绍了彩虹桥平台的架构演进、落地实践、能力拓展等内容。
数据库中间件概念
数据库中间件常用的就是分库分表中间件、数据订阅中间件、数据迁移中间件、数据同步中间件。本次课程主要围绕分库分表中间件展开。分库分表中间件负责连接底层数据库和上层应用,对这个应用可以表现为一个独立的数据库,屏蔽底层一些系统细节,来实现分库分表和读写分离的功能。简单来说,业务只需要关心自己的逻辑执行层,分库分表中间件会到实际执行层执行对应的物理库,中间的数据解析、分库分表、访问路由等细节都是由分库分表中间件进行。这一系列的处理逻辑对于业务来讲它是透明的。
常见的分库分表中间件主要有两种形式:第一种是 JDBC 形式,由客户端直连到底层数据库。第二种 Proxy 形式中间有一层数据库代理层,业务应用是连接到数据库代理层,然后再连接到底层的 DB,对于业务来讲中间的代理层就是一个数据库。市面上比较常见的 shardingSphere-JDBC、TDDL、美团的 Zebra 都是 JDBC 类型的中间件。这一类型的中间件有一个优点,它们本身是去中心化的,因为它们是在业务应用里通过 SDK 的形式进行,实现的工程难度比较简单。由于 JDBC 模式是直连数据库,所以性能损耗也较低。那么它有哪些缺点?首先是只支持单一语言,其次对业务的侵入比较明显,比如说对应的分库分表中间件如果需要一些架构的迭代和升级,业务应用需要一起升级对应的 SDK 包。还有它的连接消耗比较高,就是说所有的业务应用节点和底层的所有数据库都需要保持连接,业务应用服务节点数比较多的情况下,连接数也会保持较高的数量。
Proxy 形式常见的中间件有 shardingSphere-Proxy、Mycat、Atlas。它们的优点是,它们本身是一个代理层,就能支持很多异构语言。还有它们是统一代理并和下层的数据库建连,连接消耗比较低。业务并不需要关心具体实现逻辑是怎样的,对于业务应用来讲只是连了一个数据库,数据库中间件就是提供了一个统一的静态访问入口。缺点是,因为会多一次网络请求转发,所以 Proxy 形式的性能损耗会比 JDBC 性能损耗高一些,同时因为它需要实现一些底层 MySQL 协议,包括对应的 SQL 解析、路由之类的工作,所以整体的工程难度比较大。由于 Proxy 是中心化模式,对代理本身的稳定性和高可用要求也非常高,因为当你的代理出现故障的时候就会影响到所有的上游应用。
彩虹桥的基本介绍
彩虹桥是得物的数据库分库分表中间件,基于 Apache shardingSphere 二开的透明化数据库中间件。彩虹桥分几个部分,它有一个对应的连接池的 SDK,主要是通过 Proxy 形式来实现,所以它也支持 MySQL 客户端连接。
彩虹桥架构主要分四大模块,目前是以多集群的方式呈现。得物这边有 5 个集群,每个集群有多个节点,上层有 SLB。SLB 的目的主要是做负载均衡,还有提供统一的入口给一些异构语言或者客户端去连接。
Bifrost-Admin 是彩虹桥的一个后台,支持集群切换、连接池治理、数据源管理、影子库配置、分片规则,流控规则,读写分离规则等规则配置,还有异常排障工具,SQL 诊断工具等。
Rainbow 是彩虹桥对外提供的连接池客户端,支持了集群动态切换、无损灰度发布、SQL-Hint、双活模式感知等增强功能。
Bifrost-JDBC 是一个客户端版本,实现的功能和 Bifrost-Proxy 是一样的,主要是为了让彩虹桥具备提供 JDBC 形式去连接数据库的能力。
Bifrost-Proxy 就是核心代理端,里面提供数据分片、读写分离、影子库支持、Hint 支持,彩虹桥所有核心的功能执行逻辑都在这一层。
Bifrost-APM 是彩虹桥数据采集、统计分析和监控告警的模块,主要搜集一些系统指标和异常日志,如 QPS、RT、ERROR、SQL-Trace、慢 SQL、连接池的指标。
架构演进和落地实践
Apache ShardingSphere 是一款优秀开源分布式数据库生态项目。由于我们初次使用的时候是用 Apache ShardingSphere5.0.0α的开源版本,针对得物的内部环境,存在部分功能缺失和定制化需求问题。
我们针对以上问题做了非常多的架构演进和优化,这边挑几个比较重点的和大家分享一下。
首先是彩虹桥管控台。彩虹桥管控台会把一系列规则通过可视化后台进行统一管理和配置,一是提高了整体易用性,还有提高了规则变更的整体安全性,因为它有更完善的流程和规范。
这里主要包含几个模块,首先是系统通用配置,包括通用配置、权限配置、配置中心、注册中心配置、SLB 集群配置、业务域管理。数据源管理主要是是对物理库的管理、逻辑库的管理、连接池的管理。规则配置现在支持比较多的规则,比如说分片规则配置支持分库分表还有 Groovy 自定义分片算法;流控规则这边支持 DB 维度、Table 维度、SQL 维度、DML 维度进行 sentinel 组件流控;数据库加解密规则支持 AES、RC4、MD5 算法;慢 SQL 规则支持业务根据自己的业务类型和属性,自定义配置逻辑库维度和物理库维度的慢 SQL 阈值;热点限流规则支持对于关键字热点、SQL、DML、Table 等维度进行热点限流;全链路压测可能用到影子库规则,或者读写分离规则,都会在这里面做配置。问题诊断工具是彩虹桥管控台提供的可视化诊断平台。SQL 控制台就是直接可以在控制台上执行你编写的逻辑 SQL,看看能不能返回想要的预期结果集。SQL 洞察是我们把所有 Proxy 执行的逻辑 SQL、物理 SQL 以及上游信息,包括 JDBCDatabaseCommunicationEngine 以外的一些 SQL 做了全量收集,支持通过管控台进行实时查询。规则抽样引擎主要针对自定义分片算法,可以把自定义分片算法的规则抽样出来验证检查。异常自主排障工具主要针对彩虹桥抛出来的异常,业务方可以自助排障。
彩虹桥在 Proxy 模式下发布的时候,集群下面某一个节点发布时对上游是有感知的,那么我们是怎么做到无损灰度发布的呢?首先发布平台会把需要发布的节点 IP 发送到 SLB 进行入口流量摘流,也就是说上层的 SLB 已经不会再下发新的请求到需要发布的节点来。接下来发布平台会推送发布节点 IP 到配置中心,配置中心会把配置推送到所有监听配置的客户端的 rainbow 链接池。因为每个 JAVA 应用里都会集成一个 rainbow 连接池,它其实是彩虹桥的一个客户端。
rainbow 监听到下发配置就会对节点上面指定的一些连接做一些标记,标记连接执行完会返回到这个连接池,新的请求在连接池 borrow 连接的时候会判断是否是被标记的,如果是被标记的话它就会被优雅关闭,重新从连接池获取新的连接。新的连接通过 SLB 就一定会到其他可用的节点上面建连。发布平台等待节点连接全部断开以后,包括当前节点上面的执行请求都已经没有的时候,它就可以对当前的节点进行一个重启发布,实现一整套无损发布的流程。
彩虹桥有多个集群,为什么要做多物理集群隔离呢?因为这是在物理层面上实现了隔离,能降低故障发生时的故障爆炸半径。可以简单理解为,上层的应用对应的逻辑库底层都有一个自己映射的集群,比如说 A 应用连接的是 A 集群,B 应用连接的是 B 集群。A 集群在整体挂掉的情况下可能只会影响到上层的 A 应用,B 应用是无感的。
这里主要实现方式是,因为每个应用里面引入一个 Rainbow 连接池客户端,在连接池初始化之前会根据逻辑库读取当前库所在集群,并动态把 Proxy 的域名替换成其所在集群的域名。无感调度就是通过配置中心下发对应的映射关系,Rainbow 会根据切换后的集群域名创建一个新的连接池,然后替换掉老的连接池,老的连接池也会进行延时优雅关闭,整个过程对上游应用无感知。比如 A 集群在挂掉的情况下会影响到连接的 A 应用,如果出现这种场景可以通过配置中心把 A 应用的映射关系映射到可用的 B 集群,配置中心在拿到这个配置下发到对应的应用时,这个应用在重新建连的情况下就会连到新的集群上面去,这里就能做到无感流量调度。在实际使用过程中,我们可以对应用做实时的流量调度切换。
连接池大小动态管理怎么理解?其实每一个 Bifrost-Proxy 的 Node 节点也会从配置中心去拉配置,它能知道当前的这个 schema 应该是连到底层的哪些数据库。如果这个 schema 有映射对应的物理库,当前可用的集群连下游的数据库会保持比较高的连接,但这个节点会对下游所有数据库都保持连接,所以在不活跃的数据库上面我们只会保留最低的连接数。在流量调度切换的时候它有一个预热功能,就是说当前的 schema 集群连接下游的数据库会开始做一个连接数预热,保证 schema 在切换到新的集群上时,底层的连接数是可用的。当然,底层连接数会有动态计算的算法。
我们之前出现过底层某一个数据库 CPU 被打满的情况,对应的请求就会被 handler 住,如果请求非常多,可用的线程就会被占满从而耗尽连接池资源。因为是一个无界的等待队列,它就会无限地排队,影响到所有上游的 schema。所以我们这边引入了一个独占和共享线程和概念。独占线程的意思是,所有的 schema 都会分配自己独立的线程池,默认优先使用独占线程池,当独享线程池出现排队情况,会有一个等待队列,我们设定一个阈值。比如说这边写了给等待队列的长度如果小于 100,就默认使用自己的线程池;当它的等待队列过长的时候,说明当前这个 schema 流量比较高,或者说整体响应是不够的,它需要使用到共享线程。
共享线程就是一个公用线程,默认是不使用的,仅仅是在独立线程资源紧张的时候,我们为了缓解等待队列过长,所以会提供共享线程去负载对应的请求。但是我们这边会有一个新的判断,就是这个共享线程当前的活跃线程数如果已经超过了 90%,那我们认为当前共享线程出于负荷过载的状态,会让上层的请求强制路由回独占线程池,因为我们需要把共享线程的压力给降下来。当共享线程里面的活跃线程数是小于 90%,可以理解为共享线程池是比较空闲的,就可以使用共享线程池。这个好处是,当共享线程池在连接数超过一定阈值的时候,它可以强制走独享线程,这样就避免其中一个下游 schema 出现异常把整个工作节点的线程池全部堵塞。考虑到 Proxy 中心化的模式,它其实对稳定性要求很高,我们需要在保障隔离性的前提下最大化利用节点上的线程资源,这就是工作线程池资源隔离。
当短时间内爆发超过系统水位的流量时,很可能就会击垮 DB 导致线上故障,而且缺少针对 DB 快速失败的机制,当 DB 发生故障(比如 CPU 100%)无法快速失败的情况下,会迅速造成阻塞诱发雪崩效应。彩虹桥针对以上问题实现了一套熔断限流。这边提供了两个作用,第一个是快速失败,就是下游 DB 故障的时候会导致连接异常,当连接异常超过一定阈值的时候我们就认为底层的 RDS 可能出现故障,那我们就进行快速失败,只要是连接到当前 DB 的请求都是直接做快速失败处理。这样的好处是防止整个上游阻塞,又可以拦截下游 DB 的流量,因为可能下游 DB 已经负荷过高的流量,上层不再需要打新的请求过来,这样能减轻 DB 的压力,加快整体 DB 恢复的速度。
看上图右侧,在开启熔断和不开启熔断的时候,整个物理库和对应连接池的流量有明显变化。在开启熔断的时候,上层的线程数不会出现明显阻塞。当然如果没有关闭熔断的话,单个线程池就是独享线程池,一定会被打满。这样做还有一个好处是缩短整个故障半径,因为在分库分表场景下突发流量很容易击垮中心化的 Proxy,如果做熔断限流就可以最大程度减少对其他分片的影响。
数据库中间件有两种形式,Proxy 形式和 JDBC 形式。彩虹桥初期只是 Proxy 形式,后期我们引入了 Proxy 和 JDBC 混合架构模式。因为彩虹桥会提供对应的客户端版本包给业务应用,所以它是支持 JDBC 直连形式。这样做有几个好处:第一个是透明化,因为对于使用方来讲我们无论是使用 JDBC 模式还是 Proxy 形式,使用方配置的一些规则、配置方式和逻辑 SQL 都是一致的,不需要做改变,我们只要在我们的 Bifrost-Admin 里面改变一下连接模式,就可以自动切换过去。
第二个好处是整个场景包容度更高,因为我们可以结合 JDBC 和 Proxy 模式的优势,业务可以根据实际使用场景去选择合适的部署模式。比如说应用对连接数不是特别敏感,但是对 RT 响应要求比较高,就可以使用 JDBC 的形式。或者说对连接数比较敏感那就需要使用 Proxy 的形式。
容灾能力主要体现在,因为 Proxy 模式是中心化模式,当出现大面积故障的时候,比如说 Proxy 形式可以支持实时切换到 JDBC 模式,那么在某个机房或者说某个可用区都出现故障,所有集群不可用这种短时间无法恢复的灾难场景,我们可以切换到 JDBC 直连模式,跳过 Proxy 中间代理这一层快速恢复,这就是彩虹桥做的混合架构模式。
得物在 2022 年开始探索异地双活高可用架构模式,异地双活可以简单理解为有两个独立 IDC 机房,比如说中心机房和单元机房,两个机房同时提供服务保证系统高可用。这里面场景就比较多。
比如纯中心化的场景,这种业务场景适用于一些数据强一致的需求场景,也就是说请求读写都是在中心机房进行。中心单元场景就是适用于数据一致性要求比较高,同时读请求比较多,RT 的要求也会比较高,这种模式一般都是在中心机房去做写操作,各单元机房进行读操作。底层的数据库会由 DRC 数据同步去做单向数据同步。单元化场景就是在双活模式下比较普遍的场景,这种场景有一个 ShardingKey 做单元化区分,单元机房都会在各自的机房里形成自己的单元闭环,也就是说上层的读写都是在同一个机房进行,底层的数据是通过 DRC 做数据双向同步。这边会有一个 ShardingKey 的匹配过程,如果当前的 ShardingKey 和当前的 IDC 不匹配,彩虹桥就会做禁写,或者直接做流量纠偏动作。彩虹桥在这几种场景下可以自动感知当前的逻辑库应该使用哪个模式,从而进行底层数据库的自适应读写连接。
彩虹桥演进和落地成果主要分两个方面:第一块是业务承载。彩虹桥目前承载了得物所有的交易核心业务,同时也支持公司异地双活架构模式。上层现在所覆盖的业务域比较多,像订单、商品、交易、营销、社区、客服等等,都是我们覆盖到的业务领域,日均数据访问流量有数百亿之多。
在稳定性提升方面,彩虹桥进行了很多稳定性提升和易用性提升改进,取得了不少优秀成果。通过稳定性和高可用的建设,我们在上线一年多的时间里不可用时长不到 4 分钟。上线快 2 年,SLA 可用性达到 99.999%。
能力扩展和未来规划
能力扩展部分,第一块是 SQL 洞察,就是全量 SQL 采集,这是阿里云开放的一个功能,但我们也已经自研成功了。它可以展示逻辑 SQL 到物理 SQL 的执行链路,包括上层客户端 IP、在代理服务器里执行的 Node 节点、执行的耗时、SQL 执行详情等一系列数据,方便提升排障能力。
第二块是慢 SQL 检测,它是基于业务方自定义阈值去采集慢 SQL。我们把慢 SQL 做一个聚合统计分析,对接飞书做实时通知,还有一些告警。
第三块是流量染色,主要是通过 SQL 注释和 Hint 语句实现染色流量标识区分,在彩虹桥识别到染色标识以后会对数据库请求做染色数据偏移。
第四块是双活禁写。除了双活模式的感知,双活架构下更多的场景是流量切换过程。这个过程对稳定性来讲是比较敏感的,因为底层的数据在单元化场景下一直在做实时同步,我们这里可能就需要通过和 DRC 做配合,把迁移的流量做全量禁写、部分禁读,这样可以保证同步到另外一边需要切换机房的数据和数据源端的数据是一致的,保证整个数据的一致性。当数据同步全部完成以后才能把流量切换过来。
未来规划方向,首先我们会完善彩虹桥数据库的加解密能力,提供历史存量数据的清洗、加解密平滑切换、列级动态开关、离线数据批量解密一体化方案。然后是故障感知转移,因为目前彩虹桥在出现故障的时候一般是通过人为流量调度去做处理,后续我们希望可以基于探针对逻辑库维度或者集群维度做故障实时感知和自动转移。下一个是数据库弹性伸缩,这里更多是配合业务从单一库拆到多库的需求,或者多个库归档到同一个库这种需求,进行不停机的扩缩容的解决方案。还有异构语言 SDK 支持,彩虹桥客户端有一个 Rainbow 连接池,目前仅提供 Java 应用,其他异构语言像 GoLang、Python 语言我们暂时不支持,未来我们会把这块也补充上。