分布式系统

1. 关于本笔记

本文件介绍的是分布式架构知识

2. 目录

分布式理论

什么是分布式系统?

简单讲,分布式系统就是多台机器通过网络协同完成同一业务,对外像一个整体系统。

在 Java 项目中引入分布式通常是三个原因:

  1. 单机性能上限(CPU、内存、连接数、磁盘 IO)。
  2. 高可用要求(不能单点故障)。
  3. 业务和团队规模变大(必须拆分服务与职责)。

核心收益:

  • 水平扩展能力提升(横向扩容)。
  • 故障隔离能力提升(部分故障不拖垮全局)。

核心代价:

  • 网络不可靠导致一致性问题。
  • 链路变长导致延迟、超时、重试风暴问题。
  • 故障排查复杂度显著上升。

分布式和微服务有什么区别?

一句话:

  • 分布式是“部署形态”(多机协同)。
  • 微服务是“架构形态”(按业务边界拆服务)。

典型误区:

  • 把单体应用复制到 10 台机器上并负载均衡,这只是分布式部署,不是微服务。
  • 微服务通常天然运行在分布式环境,但并不等于“拆得越细越好”。

Java 项目落地原则:

  • 先按业务边界拆(订单、库存、支付等),再做服务治理。
  • 拆分目标是“低耦合 + 可独立发布 + 可独立扩缩容”,不是追求服务数量。

CAP

CAP 指分布式系统里三者无法同时完全满足:

  • C(一致性):所有节点同一时刻看到同一份最新数据。
  • A(可用性):每次请求都能得到响应。
  • P(分区容错):网络分区发生时系统仍能工作。

项目视角下的理解:

  • 分区(P)几乎不可避免,因此实际是在 C 和 A 之间取舍。
  • 例如配置中心、注册中心通常偏 CP;高并发交易链路常常采用最终一致来换可用性。

BASE

BASE 是对强一致的一种工程化妥协:

  • Basically Available(基本可用)
  • Soft State(软状态)
  • Eventually Consistent(最终一致)

Java 项目中常见体现:

  • 下单成功后,积分异步到账允许短暂延迟。
  • 支付回调和订单状态更新通过重试 + 补偿保证最终对齐。

适用判断:

  • 涉及资金强一致(如核心记账)要谨慎降级。
  • 非核心链路优先选择最终一致,提升系统吞吐和可用性。

分布式特性

分布式锁

分布式锁用于控制“跨实例并发访问同一资源”。

Java 项目常见场景:

  • 防重复下单、重复任务执行。
  • 库存扣减串行保护。
  • 定时任务主节点选举。

Redis 实现关键点:

  1. 加锁必须原子:SET key value NX PX ttl
  2. 必须设置过期时间,避免死锁。
  3. value 要唯一(区分锁持有者)。
  4. 解锁必须先校验 value 再删除(Lua 保证原子)。

示例:

1
SET lock:order:1001 requestId-uuid NX PX 10000
1
2
3
4
5
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end

工程建议:

  • 优先使用成熟客户端(如 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 更合适。

组件治理重点:

  • 统一超时、重试、熔断策略,避免链路放大故障。
  • 注册中心与配置中心都要有本地缓存降级策略。
  • 所有跨服务调用默认“会失败”,先设计降级与重试上限。

分布式事务

需要分布式事务的典型场景:

  • 跨服务写操作(订单、库存、账户)。
  • 跨库操作(分库分表后一个业务动作涉及多个数据源)。

工程优先级建议:

  1. 优先最终一致(本地事务 + MQ + 幂等 + 补偿)。
  2. 再考虑 Seata AT/TCC/Saga 等框架化方案。
  3. 强一致方案只留给极少数高价值核心链路。

Seata 落地注意点:

  • 全局事务尽量短,避免把慢调用放在全局事务里。
  • 全局事务超时要和 RPC 超时对齐,避免出现“事务未结束但调用先超时”。
  • 多线程并发分支事务要谨慎,优先串行或补偿化设计。

常见方案对比:

方案 一致性 性能 复杂度 适用
2PC/XA 强一致 低并发、强一致核心
TCC 最终一致 高并发资金类业务
Saga 最终一致 中高 长流程编排
本地消息表/MQ事务消息 最终一致 互联网高并发主流

场景

限流降级与熔断处理

这部分是主题3的实现核心,关注“高并发下系统如何不崩”。

推荐在 Java 项目中按层实施:

  1. 网关层限流:按 IP、用户、接口做总入口保护。
  2. 服务层熔断:对下游异常快速失败,防止线程池被耗尽。
  3. 业务层降级:返回兜底数据或友好提示,保证核心流程可用。

建议链路:

  • 超时控制 -> 重试(有限次)-> 熔断 -> 降级 -> 告警。

实践要点:

  • 重试必须有上限和退避策略,避免故障放大。
  • 熔断规则要基于错误率和慢调用,不要只看异常次数。
  • 降级必须区分核心功能和非核心功能。
  • Sentinel 场景下,blockHandler 处理限流/熔断拦截,fallback 处理业务异常,职责要分开。
  • Resilience4j 场景下,重点调优 limitForPeriodlimitRefreshPeriodtimeoutDuration 三个参数。

限流算法

常见算法:

  • 固定窗口:实现简单,但窗口边界有突刺。
  • 滑动窗口:比固定窗口更平滑。
  • 漏桶:输出稳定,但不擅长突发。
  • 令牌桶:兼顾平滑与突发,工程中最常用。

Java 实现建议:

  • 服务内限流可用 Sentinel/Resilience4j。
  • 网关限流可基于网关过滤器和集中配置。
  • 集群全局限流要用统一入口或共享计数,不能默认“多实例限流=全局限流”。

参数调优建议:

  • 限流阈值基于压测数据(QPS、P95/P99),不要拍脑袋。
  • 限流拒绝后要有明确响应码与可观测日志。

订单链路中的一致性实现

场景:下单 -> 扣库存 -> 扣余额 -> 发积分。

推荐实现(最终一致):

  1. 订单服务本地事务写订单和消息记录。
  2. MQ 异步通知库存/账户服务。
  3. 消费端按业务唯一号做幂等(去重表或唯一索引)。
  4. 失败消息进入重试与死信队列。
  5. 定时补偿任务做对账修复。

MQ 处理细节(Java 常见做法):

  • RocketMQ:消费失败进入重试策略,达到阈值后进入死信队列。
  • Kafka:建议业务处理成功后再提交 offset,避免“先提交后失败”造成丢处理。
  • Spring Kafka:可以用手动 ack 模式,把“幂等成功”和“提交位点”绑定。

这套方案在 Java 互联网项目里更常见,兼顾吞吐和可恢复性。

一致性模型选型场景

一致性不是越强越好,核心是匹配业务损失成本和恢复难度。

常见场景建议:

场景 一致性要求 推荐方案 说明
支付记账、余额扣减 强一致 串行化事务 + 严格幂等 + 对账 优先正确性,允许牺牲部分可用性
订单主状态(创建、支付、关闭) 准强一致/最终一致 本地事务 + MQ + 补偿 用可恢复机制换吞吐
库存扣减 最终一致(可控超卖) 预扣库存 + 超时释放 + 幂等扣减 峰值下优先可用性
积分、优惠券发放 最终一致 事件驱动 + 重试 + 死信 允许短延迟,保证最终到账
配置中心、选主、协调元数据 强一致(CP) Raft/ZAB 类协调组件 保证集群视图一致
搜索索引、报表、推荐数据 最终一致 异步同步 + 周期校正 对实时性要求相对低

工程化落地建议:

  1. 先定义 RPO/RTO:允许丢失多少数据、允许多长时间恢复。
  2. 明确一致性窗口:例如 1 秒内最终一致、5 分钟内补偿完成。
  3. 为每条异步链路设计幂等键、重试上限和死信处理。
  4. 把对账任务当成主流程的一部分,不要当可选功能。

分布式一致性

这一章目标不是手写算法,而是理解中间件行为边界。

Raft / Paxos / ZAB 的项目视角:

  • Raft:工程可实现性强,常见于配置与协调类系统。
  • Paxos:理论经典,很多系统做了工程化变体。
  • ZAB:ZooKeeper 的核心一致性协议。

Java 开发者要关注的不是推导过程,而是:

  • 选主期间是否会短暂不可写。
  • 网络分区后系统是偏可用还是偏一致。
  • 配置/注册信息是否可能短时延迟。
  • 组件参数(超时、心跳、重试)是否与业务 SLA 匹配。

Raft 协议

Raft 通过选主、日志复制、提交确认三步,保证集群状态机的一致性。

角色与任期:

  • Leader:唯一写入口,负责日志复制与提交。
  • Follower:被动接收日志和心跳。
  • Candidate:选举阶段临时角色。
  • Term:任期号单调递增,用于识别新旧 Leader。

核心流程:

  1. Follower 在选举超时内收不到心跳,升级为 Candidate 并发起拉票。
  2. Candidate 获得超过半数投票后成为 Leader。
  3. Leader 接收客户端写请求,先写本地日志,再通过 AppendEntries 复制到多数节点。
  4. 当某条日志被多数节点确认后,Leader 将其标记为已提交并通知 Followers 应用。

安全性要点:

  • 已提交日志不会被后续 Leader 覆盖。
  • 新 Leader 必须包含最新的已提交日志。
  • 多数派交集保证任意两个“已提交集合”至少有一个共同节点。

工程视角:

  • Raft 是强一致(CP)取向,选主期间通常会出现短暂不可写。
  • 典型用于配置中心、元数据管理、分布式协调,不适合高吞吐业务消息队列本身。

Paxos 协议

Paxos 的目标是让多个节点在故障和网络抖动下,仍对同一个值达成一致。

角色:

  • Proposer(提议者):发起提案。
  • Acceptor(接受者):承诺与接受提案。
  • Learner(学习者):学习最终被选定的值。

Basic Paxos 两阶段:

  1. Prepare:Proposer 发送编号 n 的准备请求,Acceptor 若未承诺更大编号则回复承诺,并带回已接受的最大提案。
  2. 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:同步数据但不参与投票,常用于提升读扩展性。

工作模式:

  1. 广播模式(正常期):Leader 为每个事务分配 zxid,提案广播给 Followers,收到多数确认后再提交。
  2. 崩溃恢复(故障期):Leader 异常后触发选举,新 Leader 先完成日志对齐,再恢复对外服务。

与 Raft 的共同点:

  • 都依赖 Leader + 多数派确认来保证一致性。
  • 都需要在故障恢复时确保新 Leader 拥有最新已提交日志。

与 Raft 的差异点:

  • ZAB 是 ZooKeeper 场景定制协议,围绕 zxid 和原子广播语义设计。
  • Raft 是通用复制状态机协议,抽象和生态更通用。

总结

分布式系统不是“组件堆砌”,而是明确取舍后的工程体系。

可执行的上线检查清单:

  • 核心链路设置了超时、重试、熔断、降级。
  • 幂等方案可证明有效(唯一约束 + 重复消费测试)。
  • 分布式锁具备过期、续期与安全释放。
  • 事务链路有补偿和死信处理。
  • 限流规则经过压测验证并可动态调整。
  • 关键监控覆盖 QPS、P99、错误率、线程池、连接池。
  • 日志携带 traceId,可完成跨服务定位。

一句话收束:

分布式的本质,是用架构复杂度换性能、可用性和扩展性;真正的成熟,不在于会背理论,而在于故障时可控、可观测、可恢复。