ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

分布式数据库核心考点:分片、一致性协议与事务实战解析

分布式数据库核心考点:分片、一致性协议与事务实战解析 简介这是一份面向高校计算机及相关专业学生的《分布式数据库系统》复习资料适合期末备考、考研复试或课程知识回顾使用。文档以填空、简答、论述三类题型呈现系统覆盖同构型与异构型DDBS分类、全局控制类型、水平/垂直/混合分片、数据分配策略、多层模式结构、分布式事务ACID特性、并发控制与死锁处理等核心考点并对典型简答与论述题给出较为完整的参考答案。资源还整理了分布式数据库系统的特点、数据分片三原则、DATAID-D设计过程以及分布式事务的通用结构论述部分对集中式、分割式、复制式、混合式等分配策略作了展开有助于快速建立知识框架并检验掌握程度。包内为单个doc文件大小为38KB内容精炼、重点突出可直接打开背诵或打印使用。该复习文档在CSDN已有656人学习是浓缩分布式数据库关键概念与常见考题的实用资料。1. 分布式数据库系统一份复习文档背后藏着多少硬核考点翻开这份《分布式数据库系统》复习文档你会发现它不是一份简单的概念罗列而是把 CAP 定理、Paxos 协议、分片策略、两阶段提交这些分布式数据库最核心的骨架全部串了起来。很多人复习这类材料时容易陷入误区——背了一堆“一致性”“可用性”的定义真到设计系统或面试追问时依然说不清为什么 etcd 要选 Raft、为什么 Spanner 要引入 TrueTime。这份复习文档的真正价值在于逼你把数据库从单机思维切换成多节点思维数据怎么拆、副本怎么放、故障怎么容、事务怎么跨节点协调。我梳理了一套边复习边动手验证的路径适合正在备考的工程师也适合想系统补分布式数据库底子的后端开发者。下面这份笔记就是我反复盘过这套知识点后沉淀下来的实战视角。2. 从单机到分布式必须先掰扯清楚的四个基础概念2.1 数据分片水平拆分和垂直拆分到底怎么选复习分布式数据库第一个绕不开的操作就是把数据从单机挪到多机。常见做法是水平分片和垂直分片。垂直分片按业务字段拆分比如把用户表的基础信息字段和扩展信息字段拆到不同节点适合行宽差距大的表。水平分片则按某个键的哈希或范围把行拆到多个节点是分布式数据库最常用的手段。我在实际复习时会画一张表把分片键的选择维度列清楚分片键策略适用场景典型问题哈希分片数据分布均匀点查快范围查询需要广播范围分片时间序数据、日志范围扫描友好热点写集中在新分区列表分片按地域、租户隔离明显数据倾斜难以自动均衡分片键选错是复习中最容易踩的坑。比如用用户ID做哈希分片看起来均匀但如果你有一个超级大客户它一家的数据量顶别人一万家单分区还是会被打爆。复习时要记住一个检查清单分片键要选高基数字段要避免跨分片事务占比过高要能在流量增长时继续扩分片。复习文档里强调的“数据分布均匀”不是平均主义而是要结合业务访问模型来判断。2.2 复制与一致性副本不是用来“备份”的很多人复习副本机制时想当然地以为多副本就是多做几份备份。其实分布式数据库里副本的首要价值是容灾和读扩展——主副本挂掉后从副本能顶上读请求可以分流到从副本。这里面最核心的权衡就是同步复制和异步复制。同步复制意味着主节点要等所有副本确认写入才算成功数据最安全但写延迟被拉长异步复制响应快但故障时可能丢数据。复习文档里常考的“半同步复制”是折中方案主节点至少等一个从节点确认即可返回Google 的 MySQL 高可用方案和很多金融场景采用的都是这种思路。复习时我用一个实验来验证理解模拟主节点写入后立即宕机观察异步复制下从节点丢失了多少条刚才“成功”的写入这样才能真正理解为什么有些系统要牺牲写性能换零丢失。2.3 CAP 不是让你三选二而是让你认识现实约束复习文档里 CAP 定理永远是重头戏。C 是一致性A 是可用性P 是分区容忍性。但很多复习资料把 CAP 讲成“三选二”这个说法误导性很强。更准确的表述是网络分区一定会发生P 是必选项你真正要选的是在分区发生时保 C 还是保 A。我复习时用具体的业务场景推演电商购物车如果分区了是让用户依然能加购保 A但可能看到稍旧的数据还是宁可报错也不给旧数据保 C几乎不可能有业务能接受报错所以大多数互联网业务选 AP银行核心系统选 CP。复习文档如果只贴结论不推过程你就很难在面试中回答“为什么 ZooKeeper 是 CP 而 Cassandra 是 AP”这种追问。2.4 事务的隔离级别在分布式下会变得更复杂单机数据库的隔离级别你熟悉可一旦分布式本地事务变成全局事务隔离级别的实现难度陡增。复习文档里常提到的全局快照隔离、读已提交和可重复读在分布式环境下需要引入全局时间戳或者版本号排序。我复习时会把单机和分布式隔离级别实现方式做个对比单机靠锁和 MVCC 版本链分布式要靠全局事务管理器分配事务 ID 和快照时间戳而且这个时间戳必须全局单调递增。一个常见的复习误区是把分布式事务的隔离级别和一致性协议混在一起。隔离级别解决的是并发事务之间的可见性问题一致性协议解决的是副本之间数据收敛的问题两者是正交的维度。如果你能在复习笔记上用两列表格把“隔离级别关注什么、一致性协议解决什么”分开写这个知识点就算是真正吃透了。3. 拆解经典架构从 Google Spanner 到开源 TiDB 的演进逻辑3.1 架构分层存储层和计算层为什么必须解耦复习分布式数据库系统时你会看到几乎所有现代架构都在做存储和计算分离。传统单机数据库里 SQL 引擎和存储引擎同在一条进程内扩展时要一起动分布式架构把数据放到对象存储或分布式文件系统上计算节点可以独立伸缩。TiDB 就是这种架构的典型代表PD 集群管元数据和全局时间戳TiKV 负责实际数据存储TiDB Server 只做 SQL 解析和计算。复习架构时我习惯画一张三层链路图来解决记忆问题。客户端请求先打到 SQL 层做解析和优化生成执行计划后下推到存储层各节点并行执行存储层内部再通过 Raft 协议做多副本复制。这个链路捋顺了很多后续知识点都能挂到这张图上——比如谓词下推就是把过滤条件下沉到存储节点避免把大量原始数据传到计算层。3.2 全局时间戳Spanner 的 TrueTime 和 TSO 的中心化方案分布式事务要排序必须有一个全局时间来源。Google Spanner 用了 TrueTime 机制靠 GPS 和原子钟做物理时钟同步误差区间大约在几毫秒内事务通过等待最大误差时间来确保时间单调。这种方案的好处是完全去中心化但实现代价极高一般公司根本玩不转。开源方案里 TiDB 选择了中心化 TSOTimestamp OraclePD 节点统一分配递增时间戳。我在复习这两种方案时总结过一句话“TrueTime 是物理时钟校准TSO 是逻辑时钟签发。”TSO 的问题在于单点瓶颈——所有事务都要来 PD 拿时间戳所以 TiDB 做了批量预分配来优化每次分配一段区间缓存在本地。复习文档如果只讲原理不讲优化你可能会误以为拿个时间戳是很轻量的操作实际线上业务高峰时 TSO 的 QPS 压力非常大。3.3 存储引擎选型行存、列存和混合负载的取舍分布式数据库的存储引擎决定了它能扛什么负载。HBase 和 TiKV 用的是 LSM-Tree 结构的行存写入吞吐极高适合 OLTP 场景的随机写和点查。ClickHouse 这类分析型数据库用列存压缩率高、扫描速度快适合 OLAP 场景的聚合查询。复习文档里常出现的 HTAP混合事务分析处理说的就是一套系统同时支持两类负载。复习这部分时我会把 LSM-Tree 的 Compaction 机制当重点看。LSM 的写入是先写内存 MemTable达到阈值再刷盘成 SSTable后台做合并排序。这个设计让写入变顺序追加性能极好但读路径要查多层 SSTable所以读放大问题严重。复习时用 Blame Compaction 参数做实验——调整触发合并的阈值观察写入延迟和读放大曲线的变化能帮你直观理解为什么有的系统写很快读却很慢。3.4 从单机 SQL 到分布式 SQL执行计划为什么需要重写单机数据库的 SQL 优化器只需要考虑索引和表连接顺序分布式数据库的优化器还要决定数据怎么跨节点搬动。复习文档里常见的概念有几种Exchange 算子负责数据重分布Broadcast Join 是把小表广播到每个节点Shuffle Join 是按连接键重分区还有尽量不下推的 Sort 和 Aggregate 算子。我在复习时用一个经典面试题来检验是否理解——“两张大表做 Join为什么有时候用 Broadcast 反而更快”答案是如果其中一张表经过 WHERE 过滤后只剩几千行广播小表到各节点做本地 Join避免了大表全量 Shuffle 的网络开销。分布式 SQL 优化器的目标就是尽量减少跨节点数据传输这是复习时最容易忽略但实际调优最有效的一点。4. 一致性协议实操从 Raft 到 Paxos 的代码级验证4.1 用 etcd 的 Raft 实现验证 Leader 选举和日志复制Raft 是复习分布式一致性绕不开的协议。比起 PaxosRaft 的最大贡献是把共识问题拆成 Leader 选举、日志复制和安全性三个子问题工程实现更容易验证。复习时我不满足于只读论文而是拉一个 etcd 集群实际观察选举过程。# 启动三个 etcd 节点组成集群验证 Leader 选举 etcd --name etcd1 --data-dir /tmp/etcd1 \ --listen-client-urls http://localhost:2379 \ --advertise-client-urls http://localhost:2379 \ --listen-peer-urls http://localhost:2380 \ --initial-advertise-peer-urls http://localhost:2380 \ --initial-cluster etcd1http://localhost:2380,etcd2http://localhost:2381,etcd3http://localhost:2382 \ --initial-cluster-state new # 另起终端查看集群成员状态 etcdctl member list etcdctl endpoint status --write-outtable这段命令启动了三个 etcd 节点它们通过 2380/2381/2382 端口互相通信2379 端口对外提供 KV 服务。启动后可以用endpoint status看到哪个节点是 Leader哪个节点是 Follower。参数说明--initial-cluster声明了集群所有成员地址--initial-cluster-state new表示这是全新集群而不是加入已有集群。如果要模拟分区场景可以直接 kill 掉 Leader 进程观察剩余节点重新选举需要多久默认选举超时是 1000ms 到 2000ms 之间的随机值。4.2 通过 Raft 日志理解 committed 和 applied 的差别Raft 协议里日志条目有三个状态被 Leader 接收并追加到本地日志、被多数节点复制成功committed、被状态机执行并返回给客户端applied。复习时最容易混淆的就是 committed 和 applied这两个概念在 etcd 源码里有清晰的体现。我复习时会用一个小脚本观察这个差异import etcd3 # 连接 etcd 集群 client etcd3.client(hostlocalhost, port2379) # 写入一个 key观察对应的 raft index put_response client.put(/demo/key, value1) print(f写入返回 header: {put_response.header}) # 原地更新这个 key client.put(/demo/key, value2) # 读取并解析所在 raft 日志位置 value, metadata client.get(/demo/key) print(f当前值: {value.decode()}, 修改版本: {metadata.version})这段 Python 代码通过 etcd3 客户端向 Raft 集群写入数据。这里想说明的是put_response.header里带着 raft 相关的元信息——Leader 把日志分发到多数节点并提交后才会给客户端返回成功。也就是说客户端收到成功响应时这条日志已经 committed 但可能还没在所有节点上 applied。理解了这个窗口就能明白为什么 Raft 能保证线性一致性读请求还是要走 Leader 节点——因为 Follower 节点虽然日志可能已经 committed但状态机可能还没执行到最新位置。4.3 日志复制性能调优批量、管道和流式控制Raft 日志复制如果一条条地发网络往返开销会压垮吞吐。实际工程实现都做了批量化和流水线化处理。etcd 的 raft 模块有max-size-per-msg控制单条消息最大体积有max-inflight-msgs限制未确认的消息数量这两个参数直接决定复制吞吐和平滑度。我复习时做过一次压力对比把max-inflight-msgs从默认的 128 调到 16写入 QPS 直接下降约四成原因就是每轮 RPC 能携带的待确认日志数量被缩减流水线效率变低。要注意的是批处理参数不是越大越好。max-size-per-msg设得过大单个网络包会超过 MTU 触发 IP 分片反而增加重传概率。所以参数调优要综合带宽和 RTT 来定。复习时不妨记录一组自己的实验结果不同 RTT 下本地网络 vs 跨机房最优批大小会明显不同——本地可能 1MB 最优跨机房可能 256KB 更稳。4.4 Multi-Raft 遇到 RocksDB存储层如何配合共识层到 TiKV 这个层面数据不是只有一个 Raft 组而是按 Region 拆分成了成千上万个 Raft 组每个 Region 的数据落在 RocksDB 的 CFColumn Family里。复习文档如果在 Multi-Raft 这里只讲“把数据分片再各自跑 Raft”那就漏掉了存储和共识层交互的关键点。TiKV 的落盘顺序是先写 RocksDB 的 WALWrite-Ahead Log再写入 MemTable同时把这条日志通过 Raft 模块广播给副本。这里有个细节Region 的 split 和 merge 操作本身也是一条 Raft 日志主节点在 apply 时会修改 Region 元信息。复习时候如果搞不清 Region 分裂是元数据操作还是数据搬移可以自己推演一遍假设写入的 key 超过 Region 的负载阈值PD 会指示该 Region 分裂成两个 Region这个过程只影响路由表不移动已有数据。这个机制是 TiDB 水平扩展能自动完成的基础。5. 分布式事务踩坑记从两阶段提交到最终一致5.1 两阶段提交为什么会被吐槽“反可用性”两阶段提交2PC是复习资料里必考的知识点。第一阶段协调者向所有参与者发 prepare参与者写 undo/redo 日志并加锁第二阶段协调者根据投票结果发 commit 或 abort。这个协议的问题在于协调者单点和阻塞——如果协调者在第二阶段宕机所有参与者都持有锁等待决策事务卡死无法推进。复习时我用一个模拟实验验证这一点# 模拟 2PC 协调者宕机时参与者的阻塞状态 class Participant: def __init__(self, name): self.name name self.voted False self.locked False def prepare(self): self.locked True self.voted True return ready def commit(self): self.locked False return committed def abort(self): self.locked False return aborted # 模拟协调者发送 prepare 后宕机 p1 Participant(participant_1) p2 Participant(participant_2) print(p1.prepare(), - 已加锁) print(p2.prepare(), - 已加锁) print(协调者宕机参与者持锁等待... 状态:, p1.locked, p2.locked)这段代码模拟的就是 2PC 最尴尬的时刻——两个参与者都已经 prepare 成功并加了锁协调者却在这个时候挂掉。没有协调者的最终指令参与者只能干等所有相关行的读写都会被阻塞。在复习资料里这就是 2PC “反可用性”的根本原因。所以现代系统很少直接用裸 2PC而是通过各种方式绕过它的阻塞点。5.2 三阶段提交和 Percolator降低阻塞的两种思路三阶段提交3PC在 2PC 基础上增加了 canCommit 阶段让参与者先确认自己是否可能提交成功降低第二阶段才失败的几率。但 3PC 在网络分区时依然可能产生数据不一致严格来说也没彻底解决问题。真正在业界大规模落地的思路是 Google 的 Percolator 模型——把两阶段提交的协调逻辑拆到业务表里用行锁和内存锁配合实现分布式事务。TiDB 的 Percolator 实现是我复习时的重点案例事务开始时去 PD 拿一个 startTs写操作先把数据写到 TiKV 对应 Region 的 lock CF 里commit 时再用 commitTs 写一版新的数据并清理锁。每个事务的 commit 记录本身存在一个特殊的表里所以不需要独立的协调者进程协调工作分摊到了每个事务自己身上。5.3 复习里最容易混淆的“读已提交”和“快照隔离”分布式数据库的 MVCC 实现和隔离级别经常被放在一起复习但两者要分开理解。MVCC 是数据版本管理的机制隔离级别定义的是不同事务看到哪些版本。以 TiDB 为例它默认的隔离级别是快照隔离Snapshot IsolationSI实现方式是每个事务在 startTs 时拿到一个全局快照读操作只认 commitTs 小于等于这个快照时间戳的版本。这个机制看着完美但它有一个著名的反常现象——写偏斜Write Skew。两个事务各自读到一个旧值然后基于这个旧值做不同的更新最终结果违反了业务约束但快照隔离检测不出冲突。复习时我建议用一个经典医生值班案例去理解两个医生都读了周一的排班表发现自己在值班中然后各自把自己改成不值班最终周一没人值班。这个现象靠纯 MVCC 无法阻止需要额外加冲突检测或串行化快照隔离算法。如果你能把写偏斜讲清楚说明 MVCC 和隔离级别这个坑你确实越过去了。6. 复习踩坑高频预警五个最常见的翻车点6.1 把 CAP 的 C 和 ACID 的 C 当成一个东西现象复习材料翻了好几遍答概念题时把 CAP 的一致性等同于事务 ACID 的一致性。原因两个 C 的英文缩写一样但语义完全不同。ACID 的一致性说的是事务不破坏数据库的完整性约束比如外键约束、唯一约束。CAP 的一致性说的是多副本对外呈现单副本效果强调的是节点间数据一致。解决复习笔记里用两行字区分——“ACID 的 C 是业务规则约束CAP 的 C 是副本收敛状态。”面试时如果被问到这个先抛出这句区分基本上不会被认为概念不清。6.2 以为 Raft 只支持强一致读不支持线性一致读之外的模式现象做完 etcd 实验后得出结论Raft 集群读写都是强一致没有别的选择。原因只看了 Raft 的提交过程没留意 Raft 之上的读优化策略。etcd 默认走 Leader 读保证线性一致但它同时支持串行读Serializable Read允许从 Follower 读数据。解决复习文档里要明确读请求的分类线性一致读严格按日志提交顺序返回结果串行读不做实时性保证但延迟更低。实际业务如果只是读监控数据这种允许稍旧的场景从 Follower 读可以显著降低 Leader 负载。6.3 分片实验只测了数据均匀性没测访问倾斜现象用哈希分片压测时数据量分布挺均匀但线上依然有个别节点磁盘 IO 打满。原因数据均匀不等于访问均匀。一个用户 ID 对应大量读写哈希后它依然只落在一个节点上其他人都在访问这个节点时热点就产生了。解决复习时不只要看数据分布的直方图还要看每个分片键的访问日志。如果确实有大客户倾斜场景可以考虑把热点键做二次拆分或者对该租户单独建表让它独立分片并独立扩容。6.4 两阶段提交实验里只测了正常流程没测协调者宕机现象2PC demo 跑通了事务提交和回滚都正常自己觉得没问题了。原因demo 只是单测的 happy path没有把协调者进程主动 kill 掉。2PC 最大的坑恰恰在协调者故障后的恢复过程。解决复习时一定要做两个故障注入实验——协调者在 prepare 后宕机、参与者在 commit 阶段宕机。用 etcd 事务或自定义状态机记录参与者状态看恢复后能不能从日志中找出未决事务。这一步做完你对 2PC 的认知才算是全面的。6.5 分布式数据库的性能测试只在单节点上跑现象复习时拉了一套分布式数据库但压测时只连了一个节点性能和单机 MySQL 差不多。原因数据量不够大没有触发分片和并行执行。分布式数据库在数据量小、查询简单时额外还有网络开销和调度开销反而可能不如单机快。解决复习压测要用真实规模的数据至少分出 4 个以上分片再用 TPCC 或 sysbench 这类标准负载测。要记住分布式数据库的性能优势在扩展性不在单查询延迟的绝对值。多节点并行处理大查询才是它的主场。7. 把复习文档变成实战手册设计一个可验证的最小分布式数据库复习到后期你会发现一份文档背得再熟不亲手搭一次系统就无法内化。我最后做的练习是用 Docker Compose 在本地拉起一套最小分布式数据库集群把复习文档里的每个概念都映射到实际组件上。# docker-compose.yaml 最小 TiDB 集群验证分片 Raft 分布式事务 version: 3.7 services: pd0: image: pingcap/pd:latest ports: - 2379:2379 # PD 的 etcd 接口用于分配 TSO command: - --namepd0 - --client-urlshttp://0.0.0.0:2379 - --peer-urlshttp://0.0.0.0:2380 - --initial-clusterpd0http://pd0:2380,pd1http://pd1:2380,pd2http://pd2:2380 tikv0: image: pingcap/tikv:latest command: - --pdpd0:2379 - --addr0.0.0.0:20160 - --advertise-addrtikv0:20160 tidb: image: pingcap/tidb:latest ports: - 4000:4000 command: - --storetikv - --pathpd0:2379这段配置把 TiDB 集群的三个角色拆开PD 提供全局时间戳和分片元数据TiKV 承载实际数据并跑 Raft 复制TiDB Server 负责接收 SQL 并生成分布式执行计划。启动后可以执行一次插入验证分片路由再创建事务验证时间戳排序。我把复习文档里所有考点在这套环境上逐一验证过——分片键、分布式事务、Raft 日志复制、PD 调度——把这些跑通后那份 .doc 不再是纸面知识而是你自己动手验证过的实践经验。希望你复习时也试试这条路先跑起来再回头看文档会发现很多原本模糊的概念突然就通了。这一套实践下来比你单纯背十遍复习资料都更值。希望这些踩过的坑能帮到你。本文还有配套的精品资源点击获取
返回列表