第一章:权力的游戏------共识协议的演进
Raft:基于网络消息的"民主议会"

在分布式系统的世界里,最难的问题不是如何增加机器,而是如何让成百上千台机器像"单机"一样行动。Raft 协议的出现,为这个问题提供了一个优雅且易于理解的解法。
1. 核心逻辑:在不确定性中寻找确定性
分布式系统的本质是:如何在不可靠的硬件上实现可靠的软件。
在分布式环境下,网络会延迟、数据包会丢失、服务器会宕机。Raft 的核心任务就是确保即便部分节点"罢工",整个系统对外表现出来的数据依然是强一致的。
2. 痛点:为什么不能简单地用单机?
-
单机模式的死穴:单点故障 (SPOF)。如果你的数据库只跑在一台机器上,一旦硬件损坏,服务直接瘫痪,数据可能永久丢失。
-
多机模式的噩梦:一致性挑战。为了高可用,我们部署多台机器。但问题随之而来:
-
如果客户端向 A 写入 A=1,向 B 写入 A=2,最终听谁的?
-
如果网络断开,一部分机器认为 A=1,另一部分认为 A=2,系统就会出现"人格分裂"。
-
Raft 的作用,就是让这群散兵游勇通过一套规则,选举出一个"司令官",并保证所有士兵的笔记本(日志)记录得一模一样。
3. 算法拆解
Raft 将复杂的共识问题拆解为三个子问题:领导者选举、日志复制和安全性。
A. 领导者选举 (Leader Election):权力的游戏
在 Raft 中,节点有三种身份:Follower(跟随者)、Candidate(候选人)和Leader(领导者)。
-
心跳 (Heartbeat) 与超时:
-
Leader 会周期性地向所有 Follower 发送"心跳",宣告:"我还活着,不要反抗"。
-
如果 Follower 在一段时间内没收到心跳,它就会认为 Leader 驾崩了,于是自荐为 Candidate,发起投票。
-
-
任期 (Term):
- Raft 把时间分为一段段的"任期",就像大选年份。每届任期最多只有一个 Leader。这防止了旧时代的 Leader 诈尸。
-
为什么是奇数个节点?(脑裂预防):
-
脑裂 (Split-brain):指网络分区将集群切成两半,两边都选出了自己的 Leader。
-
法定人数 (Quorum):Raft 要求必须获得超过半数 (N/2 + 1) 的选票才能当选。如果你有 3 个节点,必须拿 2 票;如果你有 5 个节点,必须拿 3 票。
-
结论:在 3 节点集群中,即使网络断开,最多只能有一边凑够 2 票。奇数节点能在保证容错的同时,最大程度利用资源并避免平票。
-
B. 日志复制 (Log Replication):步调一致
一旦 Leader 选出,所有的写操作都必须经过它。
-
提交过程:
-
Leader 接收到客户端指令,先写进自己的日志,但不立即生效。
-
Leader 将该日志分发给所有 Follower。
-
大多数确认 (Quorum):一旦 Leader 收到超过半数节点的"已写入"确认,Leader 才会正式"提交 (Commit)"这条记录,并告诉 Follower 们也一起提交。
-
-
强制一致:
- 如果某个 Follower 的日志跟 Leader 不一致(可能因为之前的网络故障),Leader 会采取强硬手段:覆盖掉 Follower 冲突的部分,强制其与自己同步。
4. 深度洞察:Raft 的局限与进阶
尽管 Raft 以易理解著称,但在极端工业场景下,它也有软肋:
局限性:读操作的陷阱
-
过期的 Leader:假设 Leader A 被网络隔离了,其他节点选出了新 Leader B。但在 A 意识到自己被罢免前,如果客户端去 A 那里读数据,A 可能会返回旧的(过期的)数据。
-
解决方案:Read Index。为了实现"线性一致性读",当 Leader 收到读请求时,它不能直接返回结果,而是要先去问一下其他节点:"大家看,我现在还是不是 Leader?"(通过一轮心跳确认)。只有确认自己依然握有大权,才返回数据。
性能瓶颈
-
因为所有压力都在 Leader 身上,且每一条日志都要经过网络往返(Round Trip),在高并发场景下,Leader 的网络 I/O 会成为系统瓶颈。
-
这就是为什么像 TiDB(使用 Raft 的国产数据库)会采用 Multi-Raft 架构------将数据分成很多小份(Region),每一份都有自己的 Raft 组,把压力分散到多台机器上。
总结
Raft 协议的精髓在于:通过"大多数"的共识,屏蔽了"少数派"的不可靠。 它的设计哲学不仅仅是数学上的严谨,更像是一套人类社会的民主运作机制------有任期、有竞选、有少数服从多数,最终在混乱的分布式世界中,建立起稳固的信任基石。
VDS:基于共享存储的"圣杯挑战"。
Raft 解决了分布式环境下'靠消息传递'达成一致的问题。但在云原生时代,我们拥有了'共享存储'这一物理挂载优势,共识机制是否能从'民主投票'进化为'夺旗竞速'?这就是 PolarDB VDS 带来的存储层共识。它不再依赖议员们口头传达指令,而是通过共享存储上的'原子金杯'(CAS Block)来决定王权的归属。这种从'网络共识'向'存储共识'的跃迁,正是数据库实现秒级高可用的关键。

1. 核心共同点:解决"权力"与"共识"问题
无论是 Raft 还是 VDS,它们的核心目标是一致的:确保集群中永远只有一个合法的 Leader(写节点),并防止"脑裂"。
-
角色分配:
-
Raft 有 Leader, Follower, Candidate。
-
VDS 也有 Leader(主节点)、Follower(热备节点)和 Observer(只读节点)。
-
-
任期/租约制: 两者都利用时间来管理领导权。Raft 用的是 Term(任期);VDS 用的是 Lease(租约)。如果 Leader 在规定时间内没有"续命",权力就会被收回。
-
故障自动触发: 当 Follower 监测到 Leader 异常(心跳超时或租约到期),都会自动触发选主流程,不需要人工干预。
2. 本质区别:通信媒介的不同(网络 vs. 存储)
这是两者最根本的区别。Raft 是"基于消息传递"的共识,而 VDS 是"基于共享状态"的共识。
|------|----------------------------------|----------------------------------------|
| 维度 | Raft 协议 | VDS 技术 (PolarDB) |
| 信使 | 网络 (Network)。节点之间通过发消息(RPC)来投票。 | 存储 (Storage)。节点通过读写共享存储上的特定数据块来通信。 |
| 选主依据 | 少数服从多数 (Quorum)。必须得到集群超过半数节点的选票。 | 原子锁 (CAS)。看谁能先在共享存储的 CAS Block 上抢到锁。 |
| 仲裁者 | 集群中的其他节点是仲裁者。 | 底层的共享存储 (PolarStore) 是仲裁者。 |
| 数据同步 | 必须通过网络将日志流(Log Stream)发送给多个节点。 | 物理日志直接写到共享存储,从库直接从存储读,不依赖主库发送。 |
3. VDS 的核心黑科技:CAS Block 与 PCR
在文章中,VDS 引入了两个 Raft 中没有的概念,这是针对共享存储优化的:
-
CAS Block (原子数据块):
-
Raft 的选主像是一场"拉票大会",大家互发传单(传消息),效率受网络波动影响。
-
VDS 的选主像是一场"抢旗比赛"。存储层提供了一个支持 CAS(Compare-And-Swap)操作的内存块。谁先成功把自己的 ID 写进去,谁就是 Leader。这种硬件级别或存储引擎级别的原子操作,比网络投票要快得多。
-
-
PCR (Polar Cluster Registry): 这是一个维护集群拓扑的"公告栏"。新 Leader 诞生后,直接修改 PCR,所有 Observer(只读节点)观察到 PCR 变化,自动连接新 Leader。这消除了 Raft 改变配置时复杂的成员变更协议。
4. 为什么 PolarDB 要用 VDS 而不是原生 Raft 做选主?
虽然 PolarDB 底部存储(PolarFS)内部用了 ParallelRaft 来保证三副本数据一致性,但在计算层(计算节点切换)使用 VDS 有三个明显优势:
-
更快的故障发现: Raft 的心跳如果设得太短(比如 <100ms),极易受网络抖动影响导致误选。VDS 基于磁盘租约,只要存储链路没断,判断就非常稳定且极快。
-
无视节点数量限制: Raft 通常需要 3 或 5 个节点(奇数),因为要凑够半数。而 VDS 哪怕只有 1 个主和 1 个备(2 节点),也可以通过抢占共享存储的锁来选主,不会因为只有 2 个节点无法达成"过半数"而卡死。
-
确定性: Raft 选主可能平票(Split Vote),需要随机退避重试。VDS 抢锁是确定性的,第一名抢到就是抢到了。
共识的本质,是在不可靠的网络上构建可靠的权力交接。
第二章:疏通神经------连接池打满的攻守道
即便 VDS 实现了秒级选主,保证了'大脑'不宕机,但如果业务请求的'血管'塞住了,系统依然会瘫痪。这就是我们要讨论的------连接池打满。
在分布式数据库中,选主再快,如果连接被事务拖垮,业务依然会感知到不可用。
连接池打满(Connection Pool Exhaustion)是分布式系统和高并发应用中常见的"性能杀手"。当客户端请求无法从池中获取可用连接时,会导致应用响应变慢、超时甚至崩溃。

攻(业务侧):开发者自律------事务剥离与瘦身
介绍一个针对该问题的核心优化技巧:"事务剥离与外部调用瘦身" (Transaction Slimming & External Call Stripping)。
核心技巧:事务剥离与外部调用瘦身
1. 现象描述
很多Java开发者习惯在 Service 层方法上直接加 @Transactional 注解。如果这个方法内部包含了非数据库操作(如调用第三方 HTTP 接口、发送邮件、复杂的本地计算或文件 IO),连接池就会被迅速占满。
原因: 连接池中的连接是在事务开始时被获取的,并且只有在事务提交或回滚后才会释放。如果事务中包含了一个耗时 2 秒的外部 API 调用,那么这个数据库连接就会被白白占用 2 秒,而实际上它在这 2 秒内并没有执行任何 SQL。
2. 优化方案:缩小事务边界
原则:仅在真正需要数据库操作的代码块上开启事务,将所有非数据库耗时操作移出事务范围。
错误示范 (连接长时间占用):
@Transactional
public void processOrder(Order order) {
// 1. 数据库操作:保存订单 (获取连接)
orderRepo.save(order);
// 2. 外部调用:调用远程支付网关 (耗时 2s,此时连接一直被占用)
boolean success = paymentService.callRemoteApi(order);
// 3. 数据库操作:更新状态
if(success) {
orderRepo.updateStatus(order.getId(), "PAID");
}
} // 事务结束,释放连接
优化方案 (快速释放连接):
public void processOrder(Order order) {
// 1. 外部调用:先做耗时且不依赖事务的操作
boolean success = paymentService.callRemoteApi(order);
// 2. 事务操作:仅包裹必要的数据库逻辑
if(success) {
transactionTemplate.execute(status -> {
orderRepo.save(order);
orderRepo.updateStatus(order.getId(), "PAID");
return null;
});
}
} // 连接仅在 transactionTemplate 内部被占用,耗时可能仅需 10ms
3. 设置合理的连接泄漏检测 (Leak Detection)
有时候连接池满是因为代码里拿了连接没还(连接泄漏)。
-
技巧: 以 Java 的 HikariCP 为例,设置
leakDetectionThreshold(如 2000ms)。 -
效果: 如果一个连接被借出超过 2 秒还没归还,连接池会打印出一条包含堆栈信息的警告日志,帮你快速定位是哪行代码没关连接。
4. 引入"快速失败"与"请求排队"保护
不要让客户端无限期等待连接。
-
技巧: 设置合理的
connectionTimeout(连接获取超时,建议 2-5 秒)。 -
效果: 如果池子满了,与其让请求挂起导致整站瘫痪,不如快速返回"系统繁忙"错误。这能保护上游服务,防止雪崩。
5. 读写分离与多池化
-
技巧: 针对读多写少的场景,配置两个独立的连接池。
-
效果: 复杂的报表查询(慢 SQL)放在"读池"中,核心的业务写入放在"写池"中。这样即便读操作把池子占满了,也不会影响用户下单等关键写入操作。
6. 参数调优:并非越大越好
-
技巧: 遵循 PostgreSQL 的建议公式:
connections = ((core_count * 2) + effective_spindle_count)。 -
原则: 过大的连接池会导致频繁的 CPU 上下文切换。通常一个 8 核的数据库服务器,连接池设为 20-50 往往比设为 500 性能更好。
守(架构侧):平台侧赋能------透明的连接复用。
业务侧优化技巧有一定的用户使用门槛,因此云厂商为了降低使用门槛,往往也会针对这一问题进行一些架构上的优化,进而用户可以减少这类问题带来的业务影响,例如PolarDB的连接池技术。它通过将成千上万个应用会话映射到少量的物理连接上,彻底解决了高并发下的连接爆炸问题,让数据库能够专注于计算本身,而不是被连接管理拖垮
一、两种连接池类型
1. 会话级连接池
-
适用场景:纯短连接业务,频繁建立/断开连接
-
工作原理:
-
连接断开时,系统判断是否为闲置连接,若是则保留在池中短暂时间
-
新连接建立时,根据
user、clientip、dbname等条件匹配复用池中连接 -
减少与数据库的建连开销,降低 MySQL 主线程负载
-
-
局限性:
-
❌ 不能减少数据库的并发连接数(仅降低建连速率)
-
❌ 不能解决慢 SQL 导致的连接堆积问题
-
2. 事务级连接池
-
适用场景:连接数需求大(如上万)或 Serverless 服务(连接数随业务扩容线性增长)
-
工作原理:
-
客户端与代理间可存在上千连接,但代理与后端数据库仅维持几十/几百个连接
-
事务结束后连接自动归还连接池供复用
-
显著降低后端数据库的实际连接压力
-
-
关键限制(触发以下操作会锁定连接,不再复用):
-
执行
PREPARE语句、创建临时表、修改用户变量 -
大报文(≥16 MB)、
LOCK TABLE、多语句、存储过程调用 -
FOUND_ROWS()/ROW_COUNT()/LAST_INSERT_ID()函数结果可能不准确 -
除
sql_mode等 4 个变量外,其他会话级系统变量需客户端显式SET -
connection_id()可能变化,SHOW PROCESSLIST显示的 IP/端口可能与实际不符
-
总结
解决连接池打满的最佳实践是"快进快出":
-
缩短事务: 绝对不要在事务里写远程调用。
-
优化 SQL: 解决慢查询,让连接处理变快。
-
精确监控: 监控
ActiveConnections(活跃连接数)和PendingThreads(等待线程数)的比例。 -
云原生数据库透明连接池功能:它能优化业务侧的连接池管理不当问题,显著降低使用门槛。
最好的连接池优化,不是扩容,而是让连接'用完即走'。
第三章:架构的终局------从存算分离到全解耦
Aurora 论文:开启"日志即数据库"的 1.0 时代
《Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases》
这篇发表于 SIGMOD 2017 的论文是数据库领域的"分水岭"之作。它不仅成就了 AWS 最赚钱的数据库业务,更定义了"云原生数据库(Cloud-Native Database)"的标准范式。
如果说传统的数据库是"搬运砖块(数据页)",那么 Aurora 的核心理念就是 "只传信件(日志)"。以下是针对这篇论文的深度解析。
1. 核心观点:网络是瓶颈 (The Network is the Bottleneck)
论文指出,在现代分布式云环境下,数据库的瓶颈已经从传统的 CPU 和存储 I/O 转移到了网络带宽上。传统数据库(如 MySQL)在向云端存储写入时,会发送大量重复数据(如数据页、Redo Log、Binlog、双写缓冲等),导致严重的写放大,限制了吞吐量。
2. 核心架构:日志即数据库 (The Log is the Database)
这是 Aurora 最著名的创新。其核心思路是"存算分离":
-
计算层 (Database Tier): 剥离存储功能,保留 SQL 解析、事务管理和缓存。它不再向存储层刷入"数据页",而是只发送"Redo Log"。
-
存储层 (Storage Tier): 一个独立的、多租户的、专门设计的存储服务。它不仅存储日志,还具备"智能":它在后台异步地将收到的 Redo Log 合并到数据页中(物化),从而生成最新的数据页。
-
结果: 网络 I/O 大幅减少(减少了约 7-8 倍),吞吐量提升至 MySQL 的 5 倍。
3. 高可用与容错机制 (Durability at Scale)
Aurora 采用了一套严苛的复制策略来保证在云环境下的高可靠性:
-
6 副本/3 可用区 (AZ): 数据被复制 6 份,分布在 3 个不同的可用区(每个区 2 份)。
-
Quorum 机制: 采用 4/6 写、3/6 读的法定人数协议。这意味着即便丢失一个整个可用区(2 份数据)再外加一个节点(共 3 份),依然能保证数据不丢且可读。
-
分段存储 (Segmentation): 将整个数据库卷切成一个个 10GB 的小块(Segments),这样在某个节点坏掉时,可以利用多节点并发并行修复,大幅缩短 MTTR(平均修复时间)。
4. 异步共识与快速恢复 (Fast Recovery)
-
无 2PC 依赖: Aurora 避免了昂贵的分布式两阶段提交(2PC)协议,而是利用 LSN(日志序列号)和异步确认来推进一致性位点。
-
秒级恢复: 传统数据库重启时需要重放大量的 Redo Log(这个过程非常慢)。在 Aurora 中,存储层一直在后台重放日志,因此计算节点重启后几乎可以"瞬间"恢复服务,无需再次重放。
-
只读副本: 主库和从库共享同一份存储,从库通过异步接收主库发来的日志流来更新缓存,延迟通常在 20ms 以内。
5. 论文总结的三个贡献
-
大规模可靠性设计: 证明了如何在云端通过 Quorum 和分段设计实现容错。
-
智能存储下推: 演示了将数据库底层的日志处理下推到存储层的巨大优势。
-
消除同步点: 通过异步处理消除了分布式系统中的性能抖动和检查点(Checkpoint)带来的开销。
结论
Amazon Aurora 通过"存算分离"和"日志即数据库"的设计,彻底改变了数据库与云基础设施交互的方式。它证明了通过将"重活"丢给智能存储层,可以极大提升关系型数据库的性能、可扩展性和韧性。
PolarDB Serverless 论文:内存池化,进入全分离的 2.0 时代
如果说 Amazon Aurora 开启了"存算分离"的时代,那么这篇发表于 SIGMOD 2021 的论文 《PolarDB Serverless: A Cloud Native Database for Disaggregated Data Centers》 则标志着云原生数据库进入了"全解耦"的 2.0 时代。
-
Aurora (1.0):解决了"计算与存储分离",核心是 Log is the Database。
-
PolarDB (2.0):解决了"计算与内存分离",核心是 Memory Disaggregation。
Aurora 解决了存储的弹性,但计算节点的内存依然像一块'压舱石',让缩容变得笨重。PolarDB 2.0 的意义在于,它通过 DMP(分布式内存池)把这块石头也浮了起来。
这篇论文详细介绍了阿里巴巴 PolarDB 如何通过解耦内存(Memory Disaggregation),实现真正的 Serverless 化。
1. 核心背景:从"二层解耦"到"三层解耦"
|------|----------------------------|-----------------------------|
| 维度 | Amazon Aurora | PolarDB Serverless |
| 解耦程度 | 存算分离 (2 层) | 计算、内存、存储全分离 (3 层) |
| 缓存位置 | 计算节点本地 (Local Buffer Pool) | 分布式内存池 (Shared Memory Pool) |
| 缩容影响 | 缓存随实例销毁,性能抖动大 | 缓存独立存在,缩容无感 |
| 扩容速度 | 分钟级 (受限于冷数据预热) | 秒级 (热数据直接挂载) |
在 Aurora 的架构中,计算和存储已经分离,但计算和内存仍然是绑定的。这带来了一个棘手的 Serverless 问题:
-
资源耦合严重: CPU 和内存通常绑定在同一个计算节点内,导致"装箱问题"(Bin-packing),难以实现独立、灵活的按需扩容。
-
资源利用率低: 为了应对峰值,用户不得不预留过剩的 CPU 或内存,造成浪费。
-
故障恢复慢: 计算节点宕机时,内存状态(Buffer Pool)随之丢失,新节点启动需要漫长的预热过程(冷启动)。
-
Serverless 局限性: 现有的 Serverless 数据库在暂停(Auto-pause)后恢复速度慢,且扩缩容步长受限。
PolarDB Serverless 提出的方案是:把内存也从计算节点中剥离出来,做成一个独立的资源池。
2. 核心架构:三层解耦 (The Disaggregation Architecture)
PolarDB Serverless 提出了从"存算分离"演进到**"全解耦"**的架构,将资源池化分为三层:
-
计算层 (Compute Layer): 纯粹的 CPU 资源,负责 SQL 解析和事务执行,不再保留本地状态。
-
内存层 (Remote Memory Pool): 独立的分布式内存池,通过 RDMA 网络连接,存储共享的 Buffer Pool(数据页)。
-
存储层 (Storage Layer): 基于共享存储(PolarStore),负责数据的持久化和日志处理。
3. 关键技术: 远程内存池(Remote Memory Pool)

为了解决解耦带来的网络延迟和一致性问题,论文介绍了多项黑科技:
-
远程内存管理 (librmem): 开发了专用接口管理远程内存页的注册、读写和失效。
-
缓存一致性协议 (Cache Coherency): 通过 PIB(失效位图)和 PRD(引用目录)实现了跨节点的缓存失效机制,确保多个只读节点(RO)能看到主节点(RW)最新的修改。
-
全局物理锁 (Global Page Latches): 使用 RDMA CAS(原子操作)实现了轻量级的远程锁,保护 B+ 树在多节点并发访问时的结构完整性(SMO 操作)。
-
乐观锁与预取优化: 为了弥补远程访问的延迟,引入了乐观锁定机制减少锁竞争,并开发了"索引感知预取"(BKP)技术,根据执行计划提前将数据从存储/内存加载到本地。
-
物化下推 (Page Materialization Offloading): 遵循"日志即数据库"理念,将 Redo Log 的应用(物化)下推到存储层异步完成,减轻了计算节点的负担。
4. Serverless 的终极体验:无感缩放
-
极致弹性: CPU、内存、存储可以独立地根据负载进行毫秒级/秒级的扩缩容。
-
极速恢复: 由于数据页保留在独立的内存池中,计算节点宕机后,新节点无需从磁盘预热数据。实验显示其故障恢复速度比传统架构快 5.3 倍。
-
性能持平: 尽管引入了网络延迟,但通过 RDMA 优化和本地缓存(Local Cache)技术,其性能与本地内存架构相当,在某些场景下甚至由于共享内存减少了副本开销而表现更好。
-
Serverless 友好: 支持"无感"缩放,在资源回收时能保留热数据,实现真正的秒级恢复。