3.6 后端风云
从单库单进程承载全部请求走到缓存、路由、分片、副本、负载均衡和故障转移分层,沿状态边界诊断扩容与恢复。
学习目标
- 能沿请求入口、缓存/路由、分片、读写副本和故障转移追踪一个键的后端路径
- 能比较取模、 一致性 Hash、Hash Slot、负载均衡与读写分离各自解决的容量或可用性问题
- 能在正常、边界和故障场景中回答:扩容、缓存穿透、陈旧副本或节点失效时,最早应在哪个状态边界诊断
3.6 后端风云
本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 3.6 后端风云。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。
先想象一座城市的配送系统:门口分流车辆,附近仓库存放常用货物,地图把订单分到不同仓区,仓库有备用副本,主路堵住时还要换入口。把所有车、货和路线塞进一个仓库,起步简单,但一次扩容或故障就会让全城停摆。
本章解决的是“后端怎样在流量、数据量和节点故障同时增加时保持可用”。拆开组件不等于自动可靠:缓存会过期,路由会迁移,副本会滞后,负载均衡会把请求送到坏节点,故障转移也可能丢失最新写入。每个层次都必须写出容量、状态和恢复合同。
三个会让后端扩展失真的陷阱
十三个目录节点到后端证据
3.6 后端风云
总合同是:请求入口负责路由与探活,缓存负责可回收的加速,分片/槽负责键空间归属,副本负责读取与恢复,故障转移负责角色切换。任何路径都要记录键、版本、节点、角色、复制位点和最终业务结果。
数据库老头儿
↡承载持久业务事实的存储系统,是缓存、分片和副本最终要回到的权威状态来源。代表系统的持久事实。扩展前先区分读压力、写压力、存储容量和事务热点;数据库变慢不能只靠加缓存掩盖,否则更新和一致性问题会更晚爆发。
危机
↡单一存储或单一进程同时承受容量、并发和故障压力,必须拆分职责的临界状态。是架构决策的触发器。诊断时把延迟、错误率、命中率、连接数、复制延迟和热点键分开看,先确定瓶颈属于计算、网络、存储还是路由。
党委扩大会议
↡把缓存、分片、副本、代理和应用节点放在同一张容量/可用性决策表中,明确各组件边界的设计讨论。不是增加组件清单,而是做边界审查:谁保存事实,谁可以丢失,谁负责路由,谁负责恢复,谁能宣布切换完成。每个答案都要对应监控和故障演练。
分家
↡把单体后端按状态、路由、缓存、计算和故障职责拆成可独立扩展与恢复的组件边界。让容量和故障域可以分别控制,但也引入网络、版本和一致性成本。拆分后必须给每条跨组件边定义超时、重试、幂等和降级,而不是只移动类名。
Redis
↡常用于内存缓存、键值访问和部分分布式数据结构的服务,本页把它作为缓存、分片和槽路由的技术参照。可以很快,但速度不等于持久事实或自动高可用。设计时明确数据是否可丢、持久化方式、内存淘汰、集群路由、复制延迟和故障切换证据。
余数算法
↡用键的 Hash 值除以节点数取余来选择节点的路由方法,节点数变化时会让大量键重新映射。适合节点固定、实现简单的分配,但扩容代价高。实验中固定一组键,分别用 3 和 4 个节点计算归属,统计迁移比例并把缓存失效与路由变化关联起来。
一致性Hash算法
↡把节点和键映射到同一个环或有序空间,节点变化时主要迁移相邻区间键的路由方法。减少迁移量,但不自动提供均匀负载、复制一致性或故障恢复。需要虚拟节点、热点监控、迁移限速和失效回源策略共同配合。
Hash槽 (Hash Slot)
↡把键空间切成固定数量槽位,再把槽位分配给集群节点,以便迁移和故障恢复可按区间管理的路由单元。把“某个键去哪”变成“某个槽由谁负责”。迁移时先确认槽状态、双写/阻塞规则和客户端路由更新,不能只把节点地址替换掉。
故障转移
↡检测当前角色或节点不可用后,按协议提升备用角色、更新路由并恢复服务的过程。必须包含检测、判定、切换、连接恢复和数据验证。探活误报会造成双主,切换过慢会造成不可用;复制位点和业务写入确认决定了恢复后的数据边界。
高可用的Nginx
↡在请求入口提供探活、负载分配、连接排空和备用入口切换的高可用代理边界。解决的是入口可达与流量分配,不是数据库一致性。要明确健康检查是进程级还是业务级,失败重试是否会重复提交,以及代理切换时如何保留请求 ID。
高可用的Tomcat
↡承载 Java Web 应用的多实例运行边界,通过实例扩展、会话策略和优雅下线提升服务可用性。需要和会话、连接池、线程池、发布版本一起设计。实例活着不代表依赖健康;滚动发布与故障切换要验证旧请求、重试和幂等行为。
数据库的读写分离
↡把写入送到主库、读取按策略送到副本,以容量和可用性换取复制延迟与读一致性复杂度的架构方式。要让业务知道何时需要读己之写、强一致读或允许陈旧读。副本延迟超过预算时应降级到主库或返回可解释状态,不能静默展示旧事实。
最小路由与副本合同
route(key, topology) -> shard_or_slot
read(key, consistency) -> replica_with_lag_budget
write(key, version) -> primary_ack
failover(role, replication_position) -> new_primary合同把键归属、读取一致性、写入确认和角色切换分开。真实系统还要保存拓扑版本、槽迁移状态、缓存版本、复制位点、请求 ID 和幂等键;单一故障样本不能只看最终 HTTP 状态。
五步复核一条后端请求
1. 进入代理并固定拓扑
记录请求 ID、目标服务、探活结果、拓扑版本和重试预算。基线走健康实例;边界场景让一个实例进入排空,验证新请求不会再分配给它。
Lab
后端拓扑与故障转移实验
只改变拓扑或角色状态,观察键迁移、回源、复制延迟和恢复判定如何变化。
缓存命中,键落在当前槽,副本已追平
request → cache hit → slot=417 → replica lag=0 → response v12
判定
accept:路径短,版本和角色均可解释
当前样本:稳定拓扑;保存请求 ID、拓扑版本、键归属、缓存状态、复制位点和业务版本。
正常、边界与故障证据矩阵
| 样本 | 只改变的变量 | 预期判定 | 必存证据 |
|---|---|---|---|
| 正常 | 拓扑稳定、缓存有效、副本追平 | 路由正确,读写结果满足一致性预算 | 拓扑版本、命中率、复制位点 |
| 边界 | 新增节点、热点过期或副本滞后 | 迁移/回源/读策略可解释,不雪崩 | 迁移比例、TTL、延迟、回源数 |
| 故障 | 代理、应用或主库节点失效 | 有限切换后业务恢复,角色唯一 | 首个探活失败、切换时间、业务读写 |
故障诊断:先找键、角色和版本
- 入口侧:查请求 ID、代理探活、连接排空、重试预算和实例版本;入口 200 不代表后端写入成功。
- 缓存侧:查键、TTL、版本、命中率、回源并发和淘汰;命中旧版本要回到失效与更新顺序。
- 路由侧:查取模/一致性 Hash/槽拓扑、迁移状态和客户端缓存;节点变化造成的重映射要可量化。
- 数据侧:查主副本角色、复制位点、读一致性预算和故障转移;恢复后必须验证业务事实而非只看进程健康。
如果扩容后大量缓存未命中,先比较拓扑映射;如果读取旧数据,查副本延迟与读己之写策略;如果切换后重复写入,查代理重试与幂等键;如果集群抖动,查探活误报、迁移并发和连接刷新。每次只改变一个状态边界并重放基线。
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 数据库老头儿
保存持久业务事实、供其他层回源与核对的权威存储。
- 危机
单一后端承受容量、并发与故障压力并需要拆分职责的临界状态。
- 党委扩大会议
把容量、缓存、路由、副本与恢复边界放在一起审查的设计讨论。
- 分家
按状态与故障职责拆成可独立扩展和恢复的组件边界。
- Redis
用于缓存、键值访问和分布式数据结构的技术参照服务。
- 余数算法
按 Hash 值除以节点数取余路由,节点变化会导致大量键迁移。
- 一致性Hash算法
把键和节点放入共同有序空间,减少节点变化时迁移范围的算法。
- Hash槽 (Hash Slot)
把固定键空间切成槽位并分配给节点的可迁移路由单元。
- 故障转移
检测节点/角色失效后提升备用角色、更新路由并验证服务的过程。
- 高可用的Nginx
负责入口探活、流量分配、排空与切换的高可用代理边界。
- 高可用的Tomcat
通过多实例、会话和优雅下线提升 Java Web 服务可用性的运行边界。
- 数据库的读写分离
写主库、读副本以换取容量和可用性,同时承担复制延迟与一致性复杂度的方式。
练习
练习
问题 1: 节点从 3 个扩到 4 个后缓存几乎全部失效。为什么取模算法会这样?怎样降低迁移?
问题 2: 写主库成功后马上读到旧副本,应该怎样选择策略?
问题 3: 修改本页“后端拓扑与故障转移实验”,让一个节点失效后显示主库切换、缓存回源和副本延迟,并说明重置后恢复哪些状态。
本页小结
- 缓存、路由、分片、副本、入口和故障转移解决不同问题。
- 取模简单但扩容迁移大,一致性 Hash/Hash Slot 仍需迁移与热点策略。
- 读写分离必须显式处理副本延迟和读己之写。
- 故障切换要验证角色、连接、复制位点和业务结果。
读完后的自测问题是:扩容后大量缓存失效或主库切换后读到旧数据时,你能否用键映射、拓扑版本、复制位点和一致性预算定位最早偏离?