Tair 作为一款在阿里巴巴和阿里云开发超过15年的数据库系统,凭借高性能、低延迟、分布式和高可靠等核心优势,已广泛应用于AI、电商、游戏、交通、教育、医疗等多个行业。本文将重点解析 Tair 在无感切换技术领域的创新实践,通过无感切换技术,可以将实例在切换过程的不可用时长大幅缩减。
所谓无感切换技术,是指客户实例在小版本升级、高可用切换等运维操作时,客户端不会感知到明显的性能波动,具体表现为无报错、无连接中断及响应时间(RT)无显著变化。
一、从 Redis®/Valkey 社区的高可用架构谈起
对于开源数据库 Valkey 或者 Redis®,目前支持的两种架构如下所示:
Standalone:在标准的主备架构中,通常由主库对外提供读写服务,备库仅作为故障时的接替节点。对于 Standalone 模式,社区主流的高可用方案就是在主从复制的基础上引入 Sentinel 组件。Sentinel 会作为一个独立的监控与仲裁层,持续探测主库和从库的存活状态及复制健康状况,一旦发现主库在设定时间内持续不可达,并且多数 Sentinel 实例达成一致判断,便会自动发起故障转移。
Cluster:Cluster 架构在设计上将分片和高可用整合在一起,集群将全部键空间划分为 16384个槽(slot),由多台 master 节点分担存储与流量,每个 master 可以配置一个或多个 replica 作为副本。当某个 master 出现故障时,其对应的 replica 会在集群内部通过投票选举机制自动提升为新的 master,并接管该 master 负责的所有槽位,从而实现自动主从切换。集群节点之间通过 gossip 协议持续交换心跳和状态信息,分布式维护槽位到节点的映射,客户端在收到 MOVED/ASK 重定向时即可根据最新拓扑重新路由请求。
图1. Sentinel与集群架构
二、集群架构无感切换与生态支持
根据 Redis® 的标准协议 RESP 的实现,在开源集群架构中,主备切换之后,备库会给已经存在的请求返回 MOVED 指令,客户端收到对应的MOVED 指令之后,会重新刷新路由表,并且重试命令,从而实现无感切换。
Tair 在实现直连集群架构(非 Proxy 集群架构)时候也不是简单的对开源架构进行全盘接收,而是进行了改造。一方面为了实现无感切换,在链路上对主备节点均配置了 VIP,从而保证 MOVED 之后的访问可以畅通无阻;其次并没有采用 gossip 传播协议,而是由中心化的 config server 作为集群的single source of truth,对集群中所有节点进行路由表的分发和链路切换,大大提升集群的稳定性。
图2. 阿里云Tair直连集群架构示意
key1: 100 表示key1位于slot 100中,访问VIP1-1的master节点会直接被接受。
key1: 100 访问VIP1-2的replica节点,会返回MOVED。
key2: 200 访问VIP2-1的master节点,由于其负责的slot范围为8192~16383,不包括key所在的slot
三、主从架构下无感切换的实施
和集群架构下天然存在无感切换的基础不同,主从架构下实现无感切换存在比较大的障碍。
主从和集群协议不兼容:首先是协议层面,Redis® 社区最先创造了主从架构,非常简洁。从 3.0 版本开始才支持集群架构,集群架构设计的路由表和 slot 机制是主从架构完全没有的,即这部分是割裂而非统一的。这就也导致了从主从架构变配到集群架构本身是不兼容的,需要客户端修改代码从主从模式到集群模式,对用户造成较高的改造成本。
Sentinel 方案无法做到完全无感:其次是开源社区的主从高可用方案是 Sentinel,基于 Sentinel 实现无感方案是行不通的,因为 Sentinel 实际上是个外部组件,无法控制内核连接维度的命令行为,并且受限于部署难度和成本,Sentinel 组件并非是所有的开源用户都会使用的。例如阿里云的主从版本是由独自的高可用组件负责实例的生命周期探活,并不依赖 Sentinel。
在上述的条件约束下,实现主从无感切换就需要联合内核、客户端、链路层面来重新的协同完成。Tair团队基于此,开始在社区提出主从无感切换的提案,并推动无感切换的进行。
3.1 社区标准协议的推动
内核层面主从无感协议的支持[1]最开始提交在了 Redis® 社区、经过超过百次的交流,也经历了 Redis® 社区的闭源、最终在新的 Valkey 社区被合并接受[2],在整个讨论过程中,遇到的主要问题是:
- 海外的用户大部分使用的集群版,因此社区对主从的优化愿望不是很强烈,但是阿里云上存在大量的标准版客户,因为标准版使用起来更加自由,例如多 key 命令不受 slot 的限制。
- 关于无感体验,一些客户业务并不重要,认为中断可以接受,但从实际来看,我们的客户对 RT 和体验的要求是日益增加的。
- 社区对于引入新的协议本身比较抗拒,因为意味着需要客户端重新适配,担心落地性不强。Redis® 的客户端生态相对处于一种较为松散的状态,客户端的开发都是由一些爱好者在用爱发电。但是这里社区的考虑是有缺位的,无论客户端的生态是否由 Redis® 社区强控制,引擎都应该为其制定标准。为了打消这个担忧,Tair 也是直接打入 Redis® 客户端生态,目前是 valkey-java[2] 客户端的 owner。
最终,尽管期间遭遇了 Redis 闭源,但秉持开源精神,阿里云 Tair 团队联合前 Redis 社区多名核心贡献者和各大厂商,组建了 Valkey 社区,并在Valkey 社区中成功推动无感技术的开发与合并,让无感切换成为了Valkey数据库的标准能力,新的协议格式为:REDIRECT HOST PORT,其中 REDIRECT 表示客户端需要将请求重定向到目标节点。
图3. Valkey无感切换的支持PR
3.2 SDK技术的跟进
Redis® 闭源之后,Tair 团队之前的 Jedis 社区核心贡献者将 Jedis Fork 创建了 Valkey-Java 客户端,为客户提供了延续的客户端服务,在 Valkey-Java 上,实现了主从无感切换的支持。客户只需要将客户端升级到下列版本便可以享受主从无感切换的能力支持:
<dependency> <groupId>io.valkey</groupId> <artifactId>valkey-java</artifactId> <version>5.3.0</version> </dependency>
在支持无感切换的过程中,特别解释下如何处理 in flight 的连接:即对于连接池中同时并发访问的链接,如果某条或者多条同时遇到了-REDIRECT之后,通过一个二段锁来控制重新初始化连接池的过程:
- 第一层锁使用ReentrantLock的tryLock,当tryLock成功时候才可以进入候选Renew连接池的可能。
- 第二层锁使用ReentrantReadWriteLock,主要用来控制连接池的的读写,但加入写锁之后,将会block外部API请求,直到更新完毕连接池,从而实现并发安全访问。
图4. Valkey-Java处理in flight的连接代码
目前无感切换已经成为了 Valkey 客户端社区的标准能力。除了 Valkey-Java 客户端,社区其余的客户端对于无感切换的支持也在陆续进行:
客户端 |
地址 |
无感切换支持的情况 |
开始版本 |
Valkey-Java |
已支持 |
5.3.0 |
|
Valkey-Go |
已支持 |
1.0.67 |
|
Valkey-py |
支持中 |
\ |
|
Glide |
支持中 |
\ |
3.3 网络技术之 Connection Draining
顾名思义,为连接排空能力,作为优雅关闭使用。优雅关闭的前提是后端服务器可用,如下图中一个ALB后面挂有4个 Server,如果做一个缩容操作移除Server4,对于即将要发给这个Server的Request4和6,在draining配置的时间内(0-900s),Server4还是会对Request做出响应,等到draining时间到达之后才断开连接,注意:draining之后后续的7,8,9等请求都不会再发给Server4了,这也是能排空的前提。在 draining时间之后,剩余连接也会收到RST。
图5. Conncetion Draining功能示意图
云数据库和自建数据库除了内核层面的改动和优化不同之外,网络环境的不同也是非常重要的方面,用户需要从VPC能访问通云数据的原理实际上是通过 LB(Load Balancer)组件在用户 VPC 中申请可用 IP,然后负责打通用户 VPC 内网络到云数据 VPC 的网络,从而让访问服务达成。除此之外,在发生高可用切换时候,只需要切换后端的服务节点,VIP 保持不变,可以降级客户端的接入成本。阿里云 Tair 标准版的架构如下所示。
那主从无感的实现为什么需要依赖 Connection Draining 呢?其在此过程又发挥了什么作用呢?此技术可以在切换后对切换前节点保持流量转发,做到优雅下线。从无感切换细节流程来看,当我们对 master 执行了禁写操作,等待 replica 追平数据切换 VIP 的时候,原始行为切换掉之后对于旧的 master 的链接会回复 RST,导致链接断开重连,这就使得内核的 REDIRECT 没有机会被回复给客户端;而 Connection Draining 则可以对旧的 master 上的链接保持转发一段时间(可配置),转发结束之后才回复 RST,从而实现无感切换。
图6. 阿里云Tair标准版架构的主从无感切换
四、无感切换的测试效果
在无感切换上线之后,使用 Tair-pulse[3](一款开源的,可以评测切换期间客户端报错和 RT 的工具)对实例切换期间进行对比测试,可以看到此功能带来两部分的提升:
- 原始切换方案会遇到 readonly、connection reset等5种报错,但新的切换方案不会遇到任何报错。
- 原始切换方案切换过程约有5s的不可用,新的切换方案影响为1s的RT上升。需要注意的是,新的切换方案使用pause write会阻塞实例写入,可能导致命令超时,最长约1s左右,如果您希望切换不处理任何报错,则可以适当调大超时时间。
图7. 旧方案切换过程有5种报错,不可用时间5s |
图8. 无感切换方案0报错,不可用时间1s左右 |
五、总结及展望
本文从 Redis® 开源社区的高可用架构谈起,详细解释了无感切换的定义、集群和主从实现无感切换的过程,遇到的难点。目前 Tair 云上架构的主从无感切换仍旧存在优化空间,我们后续也会继续联合网络团队将切换耗时进一步降低,为客户提供高质量的服务,欢迎大家对 Tair 主从无感切换的能力试用体验并提出宝贵意见。
附:Tair无感切换实操指引:
Tair 从 7.0 大版本开始支持主从无感切换能力,如果您的大版本低于 7.0,需要将版本升级到 7.0;如果您已经是 7.0 版本,将小版本升级至 0.2.9 版本即可。注意,您需要配合使用 Valkey-Java 5.3.0 版本及以上才可以体验到完整的无感切换能力。
附:
- https://github.com/redis/redis/pull/12192
- https://github.com/valkey-io/valkey/pull/325
- https://github.com/valkey-io/valkey-java
- https://github.com/tair-opensource/tair-tools/tree/main/tair-pulse