分布式相关理论

67055 字
335 分钟
分布式相关理论

1.什么是分布式系统?核心目标和挑战分别是什么? 2. 请解释 CAP 定理的 C、A、P 分别代表什么?为什么分布式系统只能在 C 和 A 之间二选一? 3. BASE 理论的核心思想是什么?与 CAP 定理的关系是什么? 4. 什么是最终一致性?强一致性、弱一致性、最终一致性的区别是什么? 5. Paxos 算法的核心思想是什么?解决了分布式系统的什么问题? 6. Raft 算法相比 Paxos 算法的优势是什么?核心流程(Leader 选举、日志复制)是怎样的? 7. ZooKeeper 的 ZAB 协议原理是什么?与 Raft 算法的异同点? 8. 什么是分布式事务?分布式事务的核心难点是什么? 9. 2PC(两阶段提交)的流程是什么?存在哪些缺点? 10. 3PC(三阶段提交)相比 2PC 做了哪些改进?是否完全解决了 2PC 的问题? 11. TCC 分布式事务的 Try、Confirm、Cancel 阶段分别做什么?有哪些优缺点和注意事项? 12. ##### 基于消息队列的最终一致性分布式事务实现原理是什么?如何保证消息可靠投递和消费幂等? 13. 什么是分布式锁?分布式锁需要满足哪些核心特性? 14. Redis 分布式锁的实现原理是什么?存在哪些问题(如锁超时、误删)及解决方案? 15. ZooKeeper 分布式锁的实现原理是什么?与 Redis 分布式锁的区别及适用场景? 16. 什么是分布式 ID?分布式 ID 的生成方案有哪些?各自的优缺点? 17. 雪花算法(Snowflake)的结构是什么?存在哪些问题(如时钟回拨)及解决办法? 18. 什么是服务注册与发现?主流的服务注册中心(Eureka、Nacos、ZooKeeper)的区别是什么? 19. 什么是负载均衡?客户端负载均衡和服务端负载均衡的区别是什么? 20. 常见的负载均衡算法有哪些?各自的适用场景? 21. 什么是服务熔断和服务降级?两者的区别是什么? 22. 什么是服务雪崩?如何避免服务雪崩? 23. ##### [什么是分布式会话?分布式会话的实现方案有哪些?](#####一、先理解:传统 Session 分布式会话的痛点) 24. JWT 实现分布式会话的原理是什么?有哪些优缺点? 25. 什么是分布式链路追踪?核心原理是什么?常用的链路追踪工具(SkyWalking、Zipkin)的区别? 26. 什么是一致性哈希算法?解决了什么问题?虚拟节点的作用是什么? 27. 什么是脑裂现象?分布式系统中如何避免脑裂? 28. 什么是数据分片(Sharding)?水平分片和垂直分片的区别是什么? 29. 分布式系统中数据一致性的保障方案有哪些? 30. 什么是幂等性?分布式系统中如何实现接口幂等性?

一、先理解:分布式事务的核心问题#

在微服务场景中(如 “下单扣库存”),订单服务和库存服务是独立的数据库,若用传统单机事务(ACID),无法跨库保证 “订单创建成功则库存必须扣减成功”;若仅简单调用库存接口,可能出现:

  1. 订单创建成功,但库存服务调用失败 → 数据不一致;
  2. 库存扣减成功,但订单服务保存失败 → 数据不一致;
  3. 网络超时,无法确定库存是否扣减成功 → 数据不一致。

最终一致性的核心思路

  • 放弃 “实时强一致”,追求 “最终数据一致”;
  • 以消息队列为桥梁,将跨服务操作拆分为 “本地事务 + 异步消息通知”;
  • 即使中间步骤失败,通过重试、补偿机制,最终让所有服务的数据达成一致。

二、基于消息队列的最终一致性实现原理(核心模式:本地消息表 + 可靠消息投递)#

最经典、落地性最强的实现模式是 “本地消息表 + 消息队列”(也叫 “事务消息”),以 “下单扣库存” 为例,完整流程如下:

1. 核心流程拆解#

预览

查看代码

用户下单

订单服务执行本地事务

本地事务成功?

返回下单失败

订单服务:在本地消息表插入“扣减库存”消息(状态:待发送)

订单服务:提交本地事务(订单创建 + 消息插入 原子性)

订单服务启动定时任务/消息发送线程,扫描本地消息表“待发送”消息

发送消息到MQ(如RocketMQ/Kafka)

MQ确认接收?

订单服务:更新本地消息表状态为“已发送”

定时任务重试发送(最多N次),仍失败则告警人工介入

库存服务监听MQ消息

库存服务执行扣减库存本地事务

库存扣减成功?

库存服务:向MQ发送“扣减成功”确认消息(可选)

重试扣减(最多N次),仍失败则告警/触发补偿逻辑

订单服务监听“扣减成功”消息,更新订单状态为“已扣库存”(可选)

graph TD
A[用户下单] --> B[订单服务执行本地事务]
B --> C{本地事务成功?}
C -- 否 --> D[返回下单失败]
C -- 是 --> E[订单服务:在本地消息表插入“扣减库存”消息(状态:待发送)]
E --> F[订单服务:提交本地事务(订单创建 + 消息插入 原子性)]
F --> G[订单服务启动定时任务/消息发送线程,扫描本地消息表“待发送”消息]
G --> H[发送消息到MQ(如RocketMQ/Kafka)]
H --> I{MQ确认接收?}
I -- 是 --> J[订单服务:更新本地消息表状态为“已发送”]
I -- 否 --> K[定时任务重试发送(最多N次),仍失败则告警人工介入]
H --> L[库存服务监听MQ消息]
L --> M[库存服务执行扣减库存本地事务]
M --> N{库存扣减成功?}
N -- 是 --> O[库存服务:向MQ发送“扣减成功”确认消息(可选)]
N -- 否 --> P[重试扣减(最多N次),仍失败则告警/触发补偿逻辑]
O --> Q[订单服务监听“扣减成功”消息,更新订单状态为“已扣库存”(可选)]

用户下单

订单服务执行本地事务

本地事务成功?

返回下单失败

订单服务:在本地消息表插入“扣减库存”消息(状态:待发送)

订单服务:提交本地事务(订单创建 + 消息插入 原子性)

订单服务启动定时任务/消息发送线程,扫描本地消息表“待发送”消息

发送消息到MQ(如RocketMQ/Kafka)

MQ确认接收?

订单服务:更新本地消息表状态为“已发送”

定时任务重试发送(最多N次),仍失败则告警人工介入

库存服务监听MQ消息

库存服务执行扣减库存本地事务

库存扣减成功?

库存服务:向MQ发送“扣减成功”确认消息(可选)

重试扣减(最多N次),仍失败则告警/触发补偿逻辑

订单服务监听“扣减成功”消息,更新订单状态为“已扣库存”(可选)

2. 核心原理说明#

  • 本地事务原子性:订单创建和 “扣减库存” 消息插入本地消息表,在同一个数据库事务中完成 —— 要么都成功,要么都失败,保证 “有订单必有消息,有消息必有订单”;
  • 消息可靠投递:通过定时任务重试发送本地消息表中的 “待发送” 消息,确保消息最终能投递到 MQ;
  • 最终一致性:库存服务消费消息后,重试扣减库存,直到成功;即使库存服务宕机,重启后仍能消费消息,最终完成扣减,达成数据一致。

3. 简化模式:MQ 事务消息(如 RocketMQ 原生支持)#

主流 MQ(如 RocketMQ)内置了 “事务消息” 功能,无需手动维护本地消息表,核心流程:

  1. 订单服务发送 “半消息” 到 MQ(半消息对消费者不可见);
  2. 订单服务执行本地事务(创建订单);
  3. 若本地事务成功,向 MQ 发送 “提交” 指令,半消息变为可见;若失败,发送 “回滚” 指令,MQ 删除半消息;
  4. MQ 会定时回查订单服务的本地事务状态,确保半消息最终要么提交要么回滚;
  5. 库存服务消费提交后的消息,完成扣减库存。

三、如何保证消息可靠投递#

消息丢失可能发生在 “生产端→MQ”“MQ 内部存储”“消费端→业务处理” 三个阶段,需针对性防护:

1. 生产端:保证消息能成功发送到 MQ#

  • 本地消息表 + 定时重试(通用方案):如上文所述,生产端先将消息写入本地数据库(与业务事务原子),再异步发送,失败则定时重试;
  • MQ 生产者确认机制:开启 MQ 的生产者确认(Producer Ack),只有收到 MQ 的 “消息已接收” 确认,才标记消息为 “已发送”;
  • 幂等发送:生产端重试时,给消息设置唯一 ID,避免重复发送(MQ 端可去重)。

2. MQ 端:保证消息不丢失#

  • 持久化:开启 MQ 消息持久化(如 RocketMQ 持久化到磁盘、Kafka 持久化到日志),即使 MQ 宕机,重启后消息不丢失;
  • 集群部署:采用主从 / 副本集群,避免单节点故障导致消息丢失;
  • 消息刷盘策略:设置 “同步刷盘”(牺牲一点性能,保证消息写入磁盘后再返回确认),而非 “异步刷盘”。

3. 消费端:保证消息能被成功处理#

  • 消费确认机制:关闭 MQ 的 “自动确认”,改为 “手动确认”—— 只有业务处理成功(如库存扣减完成),才向 MQ 发送 “消费成功” 确认;若处理失败,不确认,MQ 会重新投递消息;
  • 死信队列:设置最大重试次数(如 3 次),重试失败的消息进入死信队列,避免无限重试,同时人工排查死信队列的消息;
  • 消费状态记录:消费端将已处理的消息 ID 写入本地数据库 / Redis,处理前先检查是否已处理,避免重复消费(幂等)。

四、如何保证消费幂等#

幂等消费是指 “同一消息被消费多次,结果仍一致”(如库存扣减不会因重复消费扣减多次),核心实现思路:

1. 核心原则:业务操作必须是幂等的#

设计业务接口时,确保重复执行的结果与单次执行一致,常用方案:

幂等实现方式适用场景实现思路
唯一 ID + 防重表所有场景(推荐)1. 生产端给每条消息生成唯一 ID(如订单 ID + 操作类型);2. 消费端处理前,先将消息 ID 插入防重表(数据库唯一索引);3. 插入成功则处理业务,插入失败(已存在)则直接返回成功;
乐观锁数据更新场景(如扣库存)库存表添加版本号字段,扣减时:UPDATE stock SET num=num-1, version=version+1 WHERE id=? AND version=?;重复执行时版本号不匹配,更新失败;
状态机控制有状态流转的场景(如订单)订单状态从 “待扣库存”→“已扣库存”,重复消费时检查状态,若已为 “已扣库存”,则跳过处理;
Redis 分布式锁高并发场景消费时先获取消息 ID 的 Redis 锁,处理完成释放锁;重复消费时获取不到锁,直接返回成功;

2. 实战示例:防重表实现幂等消费(扣库存)#

ja

// 1. 防重表结构(MySQL)
/*
CREATE TABLE `mq_message_confirm` (
`id` bigint NOT NULL AUTO_INCREMENT,
`message_id` varchar(64) NOT NULL COMMENT '消息唯一ID',
`business_type` varchar(32) NOT NULL COMMENT '业务类型(如扣库存)',
`status` tinyint NOT NULL COMMENT '0-处理中 1-处理成功 2-处理失败',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_message_id` (`message_id`) COMMENT '唯一索引,防止重复插入'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/
// 2. 消费端处理逻辑
@Service
public class StockConsumer {
@Resource
private StockMapper stockMapper;
@Resource
private MqMessageConfirmMapper confirmMapper;
@Transactional
public void consume(String messageId, Long productId, Integer deductNum) {
// 步骤1:插入防重表(唯一索引保证幂等)
MqMessageConfirm confirm = new MqMessageConfirm();
confirm.setMessageId(messageId);
confirm.setBusinessType("DEDUCT_STOCK");
confirm.setStatus(0);
try {
confirmMapper.insert(confirm);
} catch (DuplicateKeyException e) {
// 重复消费,直接返回成功
return;
}
// 步骤2:执行扣库存业务(乐观锁保证幂等)
int updateCount = stockMapper.deductStock(productId, deductNum);
if (updateCount == 0) {
// 库存不足/版本号不匹配,更新失败
confirm.setStatus(2);
confirmMapper.updateById(confirm);
throw new RuntimeException("库存扣减失败");
}
// 步骤3:更新防重表状态为成功
confirm.setStatus(1);
confirmMapper.updateById(confirm);
}
}

五、总结#

  1. 最终一致性原理:基于 “本地事务 + 消息队列”,将跨服务操作拆分为 “本地操作 + 异步消息通知”,通过重试 / 补偿机制,最终让所有服务数据一致;核心是 “本地事务原子性” 和 “消息可靠投递”;
  2. 可靠投递保障:生产端(本地消息表 + 重试)+ MQ 端(持久化 + 集群)+ 消费端(手动确认 + 死信队列),覆盖消息流转全链路;
  3. 消费幂等保障:核心是 “业务操作幂等”,常用方案为 “唯一 ID + 防重表”(通用)、乐观锁(更新场景),确保重复消费结果一致。

关键点:最终一致性不是 “忽略不一致”,而是通过异步重试、补偿、监控,让数据最终达成一致;可靠投递和幂等消费是该方案的两大基石,缺一不可

一、先理解:传统 Session 分布式会话的痛点#

在单体应用中,Session 存储在服务器内存,客户端通过 Cookie 携带 JSESSIONID 即可完成会话认证,但分布式 / 微服务场景下会出现核心问题:

  1. 会话共享问题:用户请求分发到不同服务器时,其他服务器无该用户的 Session,导致认证失效;
  2. 存储压力:Session 存储在服务端内存 / 数据库,高并发下占用大量资源,且需考虑 Session 过期、同步问题;
  3. 跨域问题:Cookie 受同源策略限制,跨域服务间无法携带 JSESSIONID

JWT 的核心解决思路:将会话信息(用户 ID、角色、权限等)加密后存储在客户端令牌中,服务端无需存储任何会话数据,仅通过校验令牌的合法性完成认证,天然适配分布式场景。

二、JWT 实现分布式会话的核心原理#

JWT(JSON Web Token)本质是基于 JSON 的轻量级令牌,由服务端生成并签名,客户端存储(Cookie/Header/ 本地存储),每次请求携带令牌,服务端通过 “验签 + 解析” 完成身份认证,核心流程如下:

1. JWT 令牌的结构(三段式,Base64 编码)#

JWT 令牌由 Header.Payload.Signature 三部分组成,用 . 分隔,比如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MzU2ODM2MDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
(1)Header(头部):声明算法和令牌类型#
  • 内容:JSON 格式,指定签名算法(如 HS256)和令牌类型(JWT);
  • 示例:{"alg": "HS256", "typ": "JWT"}
  • 作用:服务端验签时,根据 Header 中的算法验证 Signature 是否合法。
(2)Payload(载荷):存储会话核心信息#
  • 内容:JSON 格式,包含标准声明(如过期时间 exp、签发时间 iat)和自定义声明(如用户 ID、角色、权限);
  • 示例:{"userId": "123", "role": "admin", "exp": 1735683600}
  • 关键:Payload 仅 Base64 编码(可解码,不加密),因此不能存储敏感信息(如密码)。
(3)Signature(签名):令牌合法性校验核心#
  • 生成规则:HMACSHA256(base64UrlEncode(Header) + "." + base64UrlEncode(Payload), 密钥)
  • 作用:服务端通过相同的密钥和算法重新计算签名,与令牌中的 Signature 对比 —— 若一致,说明令牌未被篡改;若不一致,令牌无效。

2. JWT 分布式会话的完整流程#

用户登录

认证服务校验账号密码

校验通过?

返回登录失败

认证服务生成JWT令牌:Header+Payload+Signature

返回JWT令牌给客户端(存储在LocalStorage/Header)

客户端发起请求(如/user/info),在Header中携带JWT令牌

网关/任意微服务节点接收请求

服务端解析Header,获取签名算法

服务端用相同密钥重新计算Signature

Signature一致?

返回401:令牌被篡改/无效

Payload中exp未过期?

返回401:令牌过期

解析Payload获取用户信息(如userId)

完成身份/权限校验,处理业务请求

返回响应给客户端

graph TD
A[用户登录] --> B[认证服务校验账号密码]
B --> C{校验通过?}
C -- 否 --> D[返回登录失败]
C -- 是 --> E[认证服务生成JWT令牌:Header+Payload+Signature]
E --> F[返回JWT令牌给客户端(存储在LocalStorage/Header)]
F --> G[客户端发起请求(如/user/info),在Header中携带JWT令牌]
G --> H[网关/任意微服务节点接收请求]
H --> I[服务端解析Header,获取签名算法]
I --> J[服务端用相同密钥重新计算Signature]
J --> K{Signature一致?}
K -- 否 --> L[返回401:令牌被篡改/无效]
K -- 是 --> M{Payload中exp未过期?}
M -- 否 --> N[返回401:令牌过期]
M -- 是 --> O[解析Payload获取用户信息(如userId)]
O --> P[完成身份/权限校验,处理业务请求]
P --> Q[返回响应给客户端]

用户登录

认证服务校验账号密码

校验通过?

返回登录失败

认证服务生成JWT令牌:Header+Payload+Signature

返回JWT令牌给客户端(存储在LocalStorage/Header)

客户端发起请求(如/user/info),在Header中携带JWT令牌

网关/任意微服务节点接收请求

服务端解析Header,获取签名算法

服务端用相同密钥重新计算Signature

Signature一致?

返回401:令牌被篡改/无效

Payload中exp未过期?

返回401:令牌过期

解析Payload获取用户信息(如userId)

完成身份/权限校验,处理业务请求

返回响应给客户端

3. 分布式场景的核心优势(为什么适配分布式)#

  • 无状态化:服务端无需存储任何会话数据,所有会话信息都在 JWT 令牌中,任意微服务节点拿到令牌都能独立完成认证,解决了 Session 共享问题;
  • 跨服务 / 跨域:令牌可通过 HTTP Header(如 Authorization: Bearer {token})携带,不受 Cookie 同源策略限制,适配跨域 / 跨服务调用;
  • 轻量高效:令牌仅为字符串,传输开销小,服务端验签 + 解析的计算成本低,适配高并发分布式场景。

三、关键细节:JWT 分布式会话的注意事项#

  1. 密钥安全:签名密钥(Secret)必须保密且足够复杂(建议 32 位以上),若密钥泄露,攻击者可伪造任意令牌;
  2. 过期时间:必须设置合理的 exp(过期时间),避免令牌长期有效(建议 1-2 小时),过期后需重新登录或刷新令牌;
  3. 刷新令牌机制:为提升用户体验,可生成 “访问令牌(短期)+ 刷新令牌(长期)”—— 访问令牌过期后,用刷新令牌申请新的访问令牌,无需重新登录;
  4. 避免存储敏感信息:Payload 仅 Base64 编码(可通过 jwt.io 解码),不能存储密码、手机号等敏感信息;
  5. 令牌吊销:JWT 本身不支持主动吊销(服务端无存储),若需实现 “登出 / 令牌作废”,需结合 Redis 维护令牌黑名单(验签时先查黑名单)。

四、JWT 分布式会话 vs 传统 Session 共享#

特性JWT 分布式会话传统 Session 共享(Redis)
服务端存储无状态,不存储任何会话数据需存储 Session 到 Redis,有状态
跨域 / 跨服务天然支持(Header 携带)依赖 Cookie,跨域需特殊配置
性能验签 / 解析本地计算,性能高需访问 Redis,有网络开销
令牌吊销需结合 Redis 黑名单实现直接删除 Redis 中的 Session 即可
过期管理令牌内置 exp,客户端 / 服务端校验依赖 Redis 过期策略

总结#

  1. JWT 实现分布式会话的核心是无状态化:将会话信息编码到客户端令牌中,服务端通过验签(Signature)保证令牌未被篡改,通过解析 Payload 获取用户信息完成认证,无需存储会话数据;
  2. 三段式结构(Header+Payload+Signature)是核心:Header 声明算法,Payload 存储会话信息,Signature 保证令牌合法性;
  3. 适配分布式的关键:任意微服务节点都能独立完成令牌校验,解决了传统 Session 共享、跨域、存储压力等问题。

关键点:JWT 并非 “银弹”,需结合刷新令牌、黑名单、合理过期时间等机制,才能在分布式场景下安全、高效地实现会话管理。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
分布式相关理论
https://www.liuguang.top/posts/microservices/article-20260501-分布式相关理论/
作者
LiuGuang
发布于
2026-05-01
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
LiuGuang
Hello, I'm LiuGuang.
公告
没啥官方话术,热烈欢迎到访!
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
6
分类
5
标签
8
总字数
946,977
运行时长
0
最后活动
0 天前

目录