分布式系统
分布式系统
salt-fish1. 关于本笔记
本文件介绍的是分布式架构知识
2. 目录
分布式理论
什么是分布式系统?
简单讲,分布式系统就是多台机器通过网络协同完成同一业务,对外像一个整体系统。
在 Java 项目中引入分布式通常是三个原因:
- 单机性能上限(CPU、内存、连接数、磁盘 IO)。
- 高可用要求(不能单点故障)。
- 业务和团队规模变大(必须拆分服务与职责)。
核心收益:
- 水平扩展能力提升(横向扩容)。
- 故障隔离能力提升(部分故障不拖垮全局)。
核心代价:
- 网络不可靠导致一致性问题。
- 链路变长导致延迟、超时、重试风暴问题。
- 故障排查复杂度显著上升。
分布式和微服务有什么区别?
一句话:
- 分布式是“部署形态”(多机协同)。
- 微服务是“架构形态”(按业务边界拆服务)。
典型误区:
- 把单体应用复制到 10 台机器上并负载均衡,这只是分布式部署,不是微服务。
- 微服务通常天然运行在分布式环境,但并不等于“拆得越细越好”。
Java 项目落地原则:
- 先按业务边界拆(订单、库存、支付等),再做服务治理。
- 拆分目标是“低耦合 + 可独立发布 + 可独立扩缩容”,不是追求服务数量。
CAP
CAP 指分布式系统里三者无法同时完全满足:
- C(一致性):所有节点同一时刻看到同一份最新数据。
- A(可用性):每次请求都能得到响应。
- P(分区容错):网络分区发生时系统仍能工作。
项目视角下的理解:
- 分区(P)几乎不可避免,因此实际是在 C 和 A 之间取舍。
- 例如配置中心、注册中心通常偏 CP;高并发交易链路常常采用最终一致来换可用性。
BASE
BASE 是对强一致的一种工程化妥协:
- Basically Available(基本可用)
- Soft State(软状态)
- Eventually Consistent(最终一致)
Java 项目中常见体现:
- 下单成功后,积分异步到账允许短暂延迟。
- 支付回调和订单状态更新通过重试 + 补偿保证最终对齐。
适用判断:
- 涉及资金强一致(如核心记账)要谨慎降级。
- 非核心链路优先选择最终一致,提升系统吞吐和可用性。
分布式特性
分布式锁
分布式锁用于控制“跨实例并发访问同一资源”。
Java 项目常见场景:
- 防重复下单、重复任务执行。
- 库存扣减串行保护。
- 定时任务主节点选举。
Redis 实现关键点:
- 加锁必须原子:
SET key value NX PX ttl - 必须设置过期时间,避免死锁。
- value 要唯一(区分锁持有者)。
- 解锁必须先校验 value 再删除(Lua 保证原子)。
示例:
1 | SET lock:order:1001 requestId-uuid NX PX 10000 |
1 | if redis.call("get", KEYS[1]) == ARGV[1] then |
工程建议:
- 优先使用成熟客户端(如 Redisson)处理续期和可重入。
- 锁粒度要细(按订单号、用户ID),避免全局大锁。
- 业务上同时配合幂等(唯一索引/去重表),不能只靠锁兜底。
分布式ID
分布式 ID 的核心目标:全局唯一,并尽量趋势递增。
何时必须使用:
- 分库分表后,数据库自增 ID 不再全局唯一。
- 多服务多节点并发写入核心数据。
常见方案:
| 方案 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| UUID | 简单,全局唯一 | 无序、索引性能差、占空间大 | 日志/追踪ID |
| 数据库号段 | 稳定,改造成本低 | 依赖发号DB | 中等并发 |
| Redis INCR | 递增且快 | 依赖Redis高可用 | 业务序列号 |
| Snowflake(雪花算法) | 本地生成,高性能,趋势递增 | 时钟回拨风险 | 高并发核心链路 |
时钟回拨处理建议:
- 小幅回拨:短暂等待追平。
- 大幅回拨:快速失败并告警,切换发号节点。
- 建立 NTP 偏移监控,避免隐性数据风险。
分布式组件
Java 微服务中常见组件职责:
- RPC:服务间调用(Dubbo、gRPC)。
- 注册发现:服务实例管理(Nacos、ZooKeeper、Eureka)。
- 配置中心:动态配置管理(Nacos、Apollo、Spring Cloud Config)。
- 消息队列:异步解耦与削峰(Kafka、RocketMQ)。
RPC 选型实践:
- 纯 Java 内网服务治理:Dubbo 成本低、治理能力成熟。
- 多语言协作或强契约接口:gRPC 更合适。
组件治理重点:
- 统一超时、重试、熔断策略,避免链路放大故障。
- 注册中心与配置中心都要有本地缓存降级策略。
- 所有跨服务调用默认“会失败”,先设计降级与重试上限。
分布式事务
需要分布式事务的典型场景:
- 跨服务写操作(订单、库存、账户)。
- 跨库操作(分库分表后一个业务动作涉及多个数据源)。
工程优先级建议:
- 优先最终一致(本地事务 + MQ + 幂等 + 补偿)。
- 再考虑 Seata AT/TCC/Saga 等框架化方案。
- 强一致方案只留给极少数高价值核心链路。
Seata 落地注意点:
- 全局事务尽量短,避免把慢调用放在全局事务里。
- 全局事务超时要和 RPC 超时对齐,避免出现“事务未结束但调用先超时”。
- 多线程并发分支事务要谨慎,优先串行或补偿化设计。
常见方案对比:
| 方案 | 一致性 | 性能 | 复杂度 | 适用 |
|---|---|---|---|---|
| 2PC/XA | 强一致 | 低 | 中 | 低并发、强一致核心 |
| TCC | 最终一致 | 高 | 高 | 高并发资金类业务 |
| Saga | 最终一致 | 中 | 中高 | 长流程编排 |
| 本地消息表/MQ事务消息 | 最终一致 | 高 | 中 | 互联网高并发主流 |
场景
限流降级与熔断处理
这部分是主题3的实现核心,关注“高并发下系统如何不崩”。
推荐在 Java 项目中按层实施:
- 网关层限流:按 IP、用户、接口做总入口保护。
- 服务层熔断:对下游异常快速失败,防止线程池被耗尽。
- 业务层降级:返回兜底数据或友好提示,保证核心流程可用。
建议链路:
- 超时控制 -> 重试(有限次)-> 熔断 -> 降级 -> 告警。
实践要点:
- 重试必须有上限和退避策略,避免故障放大。
- 熔断规则要基于错误率和慢调用,不要只看异常次数。
- 降级必须区分核心功能和非核心功能。
- Sentinel 场景下,
blockHandler处理限流/熔断拦截,fallback处理业务异常,职责要分开。 - Resilience4j 场景下,重点调优
limitForPeriod、limitRefreshPeriod、timeoutDuration三个参数。
限流算法
常见算法:
- 固定窗口:实现简单,但窗口边界有突刺。
- 滑动窗口:比固定窗口更平滑。
- 漏桶:输出稳定,但不擅长突发。
- 令牌桶:兼顾平滑与突发,工程中最常用。
Java 实现建议:
- 服务内限流可用 Sentinel/Resilience4j。
- 网关限流可基于网关过滤器和集中配置。
- 集群全局限流要用统一入口或共享计数,不能默认“多实例限流=全局限流”。
参数调优建议:
- 限流阈值基于压测数据(QPS、P95/P99),不要拍脑袋。
- 限流拒绝后要有明确响应码与可观测日志。
订单链路中的一致性实现
场景:下单 -> 扣库存 -> 扣余额 -> 发积分。
推荐实现(最终一致):
- 订单服务本地事务写订单和消息记录。
- MQ 异步通知库存/账户服务。
- 消费端按业务唯一号做幂等(去重表或唯一索引)。
- 失败消息进入重试与死信队列。
- 定时补偿任务做对账修复。
MQ 处理细节(Java 常见做法):
- RocketMQ:消费失败进入重试策略,达到阈值后进入死信队列。
- Kafka:建议业务处理成功后再提交 offset,避免“先提交后失败”造成丢处理。
- Spring Kafka:可以用手动 ack 模式,把“幂等成功”和“提交位点”绑定。
这套方案在 Java 互联网项目里更常见,兼顾吞吐和可恢复性。
一致性模型选型场景
一致性不是越强越好,核心是匹配业务损失成本和恢复难度。
常见场景建议:
| 场景 | 一致性要求 | 推荐方案 | 说明 |
|---|---|---|---|
| 支付记账、余额扣减 | 强一致 | 串行化事务 + 严格幂等 + 对账 | 优先正确性,允许牺牲部分可用性 |
| 订单主状态(创建、支付、关闭) | 准强一致/最终一致 | 本地事务 + MQ + 补偿 | 用可恢复机制换吞吐 |
| 库存扣减 | 最终一致(可控超卖) | 预扣库存 + 超时释放 + 幂等扣减 | 峰值下优先可用性 |
| 积分、优惠券发放 | 最终一致 | 事件驱动 + 重试 + 死信 | 允许短延迟,保证最终到账 |
| 配置中心、选主、协调元数据 | 强一致(CP) | Raft/ZAB 类协调组件 | 保证集群视图一致 |
| 搜索索引、报表、推荐数据 | 最终一致 | 异步同步 + 周期校正 | 对实时性要求相对低 |
工程化落地建议:
- 先定义 RPO/RTO:允许丢失多少数据、允许多长时间恢复。
- 明确一致性窗口:例如 1 秒内最终一致、5 分钟内补偿完成。
- 为每条异步链路设计幂等键、重试上限和死信处理。
- 把对账任务当成主流程的一部分,不要当可选功能。
分布式一致性
这一章目标不是手写算法,而是理解中间件行为边界。
Raft / Paxos / ZAB 的项目视角:
- Raft:工程可实现性强,常见于配置与协调类系统。
- Paxos:理论经典,很多系统做了工程化变体。
- ZAB:ZooKeeper 的核心一致性协议。
Java 开发者要关注的不是推导过程,而是:
- 选主期间是否会短暂不可写。
- 网络分区后系统是偏可用还是偏一致。
- 配置/注册信息是否可能短时延迟。
- 组件参数(超时、心跳、重试)是否与业务 SLA 匹配。
Raft 协议
Raft 通过选主、日志复制、提交确认三步,保证集群状态机的一致性。
角色与任期:
- Leader:唯一写入口,负责日志复制与提交。
- Follower:被动接收日志和心跳。
- Candidate:选举阶段临时角色。
- Term:任期号单调递增,用于识别新旧 Leader。
核心流程:
- Follower 在选举超时内收不到心跳,升级为 Candidate 并发起拉票。
- Candidate 获得超过半数投票后成为 Leader。
- Leader 接收客户端写请求,先写本地日志,再通过
AppendEntries复制到多数节点。 - 当某条日志被多数节点确认后,Leader 将其标记为已提交并通知 Followers 应用。
安全性要点:
- 已提交日志不会被后续 Leader 覆盖。
- 新 Leader 必须包含最新的已提交日志。
- 多数派交集保证任意两个“已提交集合”至少有一个共同节点。
工程视角:
- Raft 是强一致(CP)取向,选主期间通常会出现短暂不可写。
- 典型用于配置中心、元数据管理、分布式协调,不适合高吞吐业务消息队列本身。
Paxos 协议
Paxos 的目标是让多个节点在故障和网络抖动下,仍对同一个值达成一致。
角色:
- Proposer(提议者):发起提案。
- Acceptor(接受者):承诺与接受提案。
- Learner(学习者):学习最终被选定的值。
Basic Paxos 两阶段:
- Prepare:Proposer 发送编号
n的准备请求,Acceptor 若未承诺更大编号则回复承诺,并带回已接受的最大提案。 - Accept:Proposer 取得多数 Prepare 响应后发起 Accept 请求;若收到多数接受,则提案被选定,Learner 学习结果。
关键约束:
- 提案编号必须全局唯一且递增。
- 一旦某值被多数派接受,后续更大编号提案必须继承该值。
工程视角:
- Paxos 理论通用性强,但实现和排障复杂。
- 实际系统常采用 Multi-Paxos 等工程化变体降低常态开销。
Raft 和 Paxos 的原理对比
二者目标一致:在不可靠网络和节点故障下保证状态机一致。
差异重点:
| 对比维度 | Raft | Paxos |
|---|---|---|
| 设计思路 | 分解为选举、复制、提交,强调可理解性 | 从“单值共识”出发,理论抽象更强 |
| 工程实现 | 规则明确,落地门槛较低 | 正确实现难度高,细节多 |
| 常见形态 | 强 Leader,日志复制清晰 | 多采用 Multi-Paxos/变体 |
| 可运维性 | 排障路径直观(Leader/Term/Index) | 需要更强协议与实现认知 |
选型建议:
- 新建工程优先选 Raft 生态组件,减少理解与维护成本。
- 已有 Paxos 系统优先遵循现有框架,不建议轻易替换协议层。
使用 Raft 的常见框架与组件
常见采用 Raft 的系统:
- etcd:分布式 KV 与协调存储,Kubernetes 控制面关键依赖。
- Consul:服务发现、健康检查、配置与 KV 存储。
- Ratis(Apache):Java 生态中常用的 Raft 库,可用于构建定制一致性组件。
使用这类组件时,重点关注:
- 多数派写入延迟(磁盘与网络)对业务 RT 的影响。
- Leader 切换频率是否异常(可能由超时参数不合理导致)。
- 快照与日志压缩策略是否匹配数据增长速度。
ZAB 协议
ZAB(ZooKeeper Atomic Broadcast)是 ZooKeeper 的原子广播协议,目标是保证 ZK 集群事务顺序一致和崩溃可恢复。
节点角色:
- Leader:处理写请求并发起事务广播。
- Follower:参与投票并复制事务日志。
- Observer:同步数据但不参与投票,常用于提升读扩展性。
工作模式:
- 广播模式(正常期):Leader 为每个事务分配
zxid,提案广播给 Followers,收到多数确认后再提交。 - 崩溃恢复(故障期):Leader 异常后触发选举,新 Leader 先完成日志对齐,再恢复对外服务。
与 Raft 的共同点:
- 都依赖 Leader + 多数派确认来保证一致性。
- 都需要在故障恢复时确保新 Leader 拥有最新已提交日志。
与 Raft 的差异点:
- ZAB 是 ZooKeeper 场景定制协议,围绕
zxid和原子广播语义设计。 - Raft 是通用复制状态机协议,抽象和生态更通用。
总结
分布式系统不是“组件堆砌”,而是明确取舍后的工程体系。
可执行的上线检查清单:
- 核心链路设置了超时、重试、熔断、降级。
- 幂等方案可证明有效(唯一约束 + 重复消费测试)。
- 分布式锁具备过期、续期与安全释放。
- 事务链路有补偿和死信处理。
- 限流规则经过压测验证并可动态调整。
- 关键监控覆盖 QPS、P99、错误率、线程池、连接池。
- 日志携带 traceId,可完成跨服务定位。
一句话收束:
分布式的本质,是用架构复杂度换性能、可用性和扩展性;真正的成熟,不在于会背理论,而在于故障时可控、可观测、可恢复。

