分布式数据库系统的容错处理 – 100% 成功率,超时和性能

本文探讨了分布式数据库系统中如何处理错误并确保高可用性。通过重试和超时机制来应对网络和服务器内部冲突导致的请求失败。讨论了客户端、SDK和服务器端的重试责任,强调在设计时需要权衡成功率和性能。同时,提到了幂等性和去重的重要性,以及在实际应用中如何做出取舍。

之前写过一篇文章, 介绍"可靠通信三原则". 对于一个分布式数据库, 如果想实现 100% 高可用(也即客户端的请求永远不会返回失败), 同样可以用可靠通信三原则中的重试理论和去重理论来解决. 但在实践上, 需要在成功率, 耗时(速度和性能)各方面进行取舍. 本文分享实际经验, 介绍什么样的选择是普适的, 各位可以参考.

客户端访问数据库服务器, 发起大量的请求, 绝对不可能做到每一个请求都是成功的. 因为网络原因, 请求可能失败. 因为服务器内部处理冲突, 或者分布式节点间协调冲突, 都可能导致请求失败.

所谓容错处理, 就是在遇到错误的时候进行重试. 因为错误必然发生, 只有重试才能消除错误的影响, 就好像 IP 层必然会丢包, 但 TCP 协议通过重传达到某种程度的可靠传输.

某些实现了 Basic Paxos + 日志复制状态机模型的系统, 因为所谓的"Leaderless", 会产生大量冲突. 即使是使用 Raft, 在某些情况下意外发生选举, 也会导致请求冲突.

面对冲突(失败)到底应该由谁来重试呢? 这涉及到工程实践上模块职责划分的问题, 模块职责的划分, 往往比代码实现更重要. 一般来说, 发生重试的位置越底层, 性能会越好; 发生重试的位置越上层, 判断是否应该重试的依据就能更全面.

我们简单把数据库系统(生态)划分为几个大的模块, 从底层(左)到上层(右)是:

replication -> server -> client SDK -> user

最常见的做法是让 user 自己重试, 例如常见的 Redis SDK, 如果某台 server 宕机导致请求失败, 那么要求用户换一个 IP, 重新创建连接, 再次重复请求.

某些系统会封装专属的 client SDK, 例如, 把官方的 Redis SDK 做一下简单封装, 拦截每一个请求的结果, 如果发现错误, SDK 内部就自动重试. 这样做, user 就不需要有重试逻辑, 代码可以简化. 是这样的, 多个协作的模块, 如果某个模块揽了一些职责, 那它的上层模块就能省些工夫.

如果 user 既不想重试, client SDK 也不想重试, 那怎么办呢? 能不能把职责全推给 server 呢? 绝对不可能, 参见这篇文章的总结. 那么, 为什么 SDK 重试之后, user 就不需要重试呢? 因为 SDK 和 user 是在同一个运行空间内, 它们是一个整体, 两者之间没有可靠传输问题.

那么, 既然 client SDK 必须有重试逻辑, server 是否就不需要有重试逻辑了呢? 理论上可以, 但实践上, server 自身依然要降低自己的故障率, 降低故障率的必要手段就是 重试. 例如, server 请求 paxos 模块同步一条操作日志, 但因为非预期的 multi-master 出现, 导致和其它节点争抢同一个位置失败, 这时, server 如果直接报错给 client, 那么, server 的故障数量就加一. 但是, server 可以重试, 再次调用 paxos 模块, 去争抢下一个位置, 直到成功. 这样, client 就会很少见到 server 报错.

但是, 无论是 server 还是 client 都不可能无限次重试, 因为每一次重试都会消耗时间, 最极端的情况可能要重试几个小时直到永远, 这当然不行, 所以, 需要引入 超时机制 , 重试一定次数之后即使还是失败, 也必须报错给上层.

重试会增加总的耗时, 这样, 给上层带来的不好效果就是, 上层觉得下层速度慢, 性能差. 所以, 必须有系统思维, 做出判断, 做综合的取舍. 从经验上看, 无论 server 还是 client SDK, 都必须分析细化, 尽可能重试, 以提高成功率. 大部分情况下, 开发者往往过多地放弃重试, 而较少地进行重试, 毕竟, 多一种重试场景, 就多写一段代码, 人总是会想偷懒的.

要设计一个高可靠的系统, 可靠传输三原则是非常有用的基础理论, 但不是银弹. 本质上, 软件开发就是大量的分析细化体力活, 以及对系统复杂度的把控.

重试带来的额外问题就是去重, 这也是可靠传输三原则里的第二项原则. 你可能听过"幂等性"这个词汇, 和去重是一回事. 如果一个操作是非幂等的, 那么, 就不能重试.

但是, 实践上, 我们可以把幂等性的职责向上推, 尽可能推给上层. 毕竟, 至少对于 user 来说, 100% 的成功率, 优先级比对幂等性的疑虑要高得多. 用户同意下层不考虑幂等性, 而大胆地去重试, 但是, 对下层偶然的失败会非常敏感. 简单说就是: 别管什么幂等性, 在超时时间限制以内, 大胆重试!

### 分布式数据库如何应对网络延迟引起的事务超时? 在分布式数据库中,网络延迟是影响系统性能事务执行效率的关键因素之一。当事务涉及多个节点的数据操作时,若某些节点因网络拥塞、故障或响应缓慢导致通信延迟,可能会引发事务超载甚至超时[^1]。为保障事务的完整性与一致性,同时提升系统的可用性,分布式数据库通常采用以下策略来应对网络延迟带来的事务超时问题。 #### 1. 超时机制与重试策略 分布式数据库通常设置合理的事务等待时间阈值,当某个事务在指定时间内未收到所有参与节点的响应,则认为该事务超时并触发回滚操作。此外,系统可结合重试机制,在检测到部分节点响应延迟后,尝试重新发送请求或切换至其他副本进行数据访问,以提高事务完成的可能性。此机制依赖于高效的节点探测健康状态管理能力,确保在发生临时性网络问题时仍能维持事务的连续性。 #### 2. 异步提交与乐观并发控制 为了降低网络延迟对事务提交过程的影响,部分系统采用异步提交(Asynchronous Commit)模式。在此模式下,事务可以在主节点提交成功后立即返回结果,而不必等待所有副本确认写入完成。尽管这可能引入短暂的数据不一致风险,但通过后续的同步机制可以最终达成一致性[^4]。此外,乐观并发控制(Optimistic Concurrency Control)也常用于高延迟环境中,它允许事务先执行再验证冲突,从而减少对网络实时性的依赖。 #### 3. 多副本强一致性协议 为保证在延迟环境下数据的一致性,分布式数据库广泛采用 Paxos、Raft 等一致性协议。这些协议通过选举主节点、日志复制、多数派确认等手段,确保即使部分节点出现延迟或失效,整个系统仍能维持全局一致性。例如 Raft 协议要求大多数副本确认日志条目后才视为提交成功,这种方式虽然增加了网络开销,但也有效提升了系统容错能力。 #### 4. 智能路由与负载均衡 面对网络延迟,智能路由机制可以根据节点的实时状态选择最优路径执行事务。例如,将事务引导至响应更快的副本节点,或者避免将请求发送到已知存在延迟的节点上。与此同时,动态负载均衡技术可通过监控各节点的网络状况资源使用情况,自动调整数据分布请求流向,缓解热点问题,从而优化整体事务处理性能[^2]。 #### 5. 事务优先级调度与隔离级别调整 在高延迟场景下,系统还可通过设置事务优先级来优化关键业务的操作体验。高优先级事务可获得更短的等待时间更高的资源分配权重。此外,适当降低事务的隔离级别(如从串行化降级为快照隔离)也能减少锁竞争通信开销,提高事务成功率。然而,这种做法需权衡一致性与性能之间的关系,确保不会影响核心业务逻辑的正确性[^3]。 ```sql -- 示例:设置较低的事务隔离级别以减少锁竞争 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; ``` 上述 SQL 语句展示了如何将事务隔离级别设置为“读已提交”,这有助于减少事务间的阻塞现象,从而在一定程度上缓解网络延迟带来的影响。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值