刚接触分布式系统时,很多人会先被一串名词砸中:CAP、BASE、Paxos、Raft、分布式锁、分布式事务。
这些概念都绕不开,但入门时直接钻进去,容易把分布式系统学成一堆互不相干的术语。更好的切入口是一个更土但更实用的问题:原来一台机器、一个进程、一个数据库就能完成的事情,为什么后来要拆到多台机器上?拆完以后,为什么一个超时、一次重试、一条消息重复投递,都会牵出这么多设计问题?
这篇文章先把“分布式系统是什么”讲清楚。Paxos、Raft、分布式事务不会展开推导,只把它们放回主线里,知道它们大概在解决哪类麻烦。
什么是分布式系统?
《分布式系统概念与设计》这本书对分布式系统的定义是:
分布式系统是这样一种系统:位于联网计算机上的硬件或软件组件,仅通过消息传递进行通信并协调行动。
分布式系统是这样一种系统:位于联网计算机上的硬件或软件组件,仅通过消息传递进行通信并协调行动。
工程里可以这样理解:分布式系统由多个相对独立的计算单元组成,这些计算单元靠网络通信和协作,对外提供一项完整服务。
这里的“独立”别理解成每个节点都独占 CPU、内存、磁盘和操作系统。节点可以是物理机、虚拟机、容器,也可以只是一个软件进程。同一台机器上的两个进程会共享底层 CPU 和物理内存,容器也常共享宿主机内核。说它独立,主要是说它有自己的执行状态和局部数据,可以单独运行,也可能单独卡住、重启或丢失连接。
麻烦也从这里开始。
节点之间不共享同一个进程地址空间,原来的本地方法调用,拆出去后可能变成一次 RPC、一次 HTTP 请求、一次消息投递,或者一次数据库副本同步。网络有延迟,会丢包,请求可能到了服务端但响应丢了;客户端看到的“超时”,并不能证明服务端没有执行。
多台机器还带来另一个限制:没有哪个节点能在某个瞬间看到整个系统的真实状态。每个节点看到的是本地状态和已经收到的消息。节点时钟也会有偏差,即使用 NTP 做同步,也不能当作完全一致的全局时钟来用。
比如用户点击“提交订单”,页面上只是一次请求,服务端可能已经经过网关、用户服务、商品服务、订单服务、库存服务、优惠券服务、支付服务,还可能写数据库、发消息、更新缓存。用户看到的是一个按钮,后端看到的是一串跨节点协作。

维度单机或单进程系统分布式系统通信方式方法调用、共享内存RPC、消息、网络协议故障范围往往共享一个故障边界节点可以独立故障时间视图主要依赖同一机器时钟多节点时钟存在偏差状态观察较容易观察整体状态节点通常只有局部视图事务处理本地数据库事务更常见跨服务协调、补偿或共识扩容方式纵向升级为主横向增加节点问题排查单进程日志和调用栈日志、指标、链路追踪和跨节点状态
维度单机或单进程系统分布式系统
维度
单机或单进程系统
分布式系统
通信方式方法调用、共享内存RPC、消息、网络协议
通信方式
方法调用、共享内存
RPC、消息、网络协议
故障范围往往共享一个故障边界节点可以独立故障
故障范围
往往共享一个故障边界
节点可以独立故障
时间视图主要依赖同一机器时钟多节点时钟存在偏差
时间视图
主要依赖同一机器时钟
多节点时钟存在偏差
状态观察较容易观察整体状态节点通常只有局部视图
状态观察
较容易观察整体状态
节点通常只有局部视图
事务处理本地数据库事务更常见跨服务协调、补偿或共识
事务处理
本地数据库事务更常见
跨服务协调、补偿或共识
扩容方式纵向升级为主横向增加节点
扩容方式
纵向升级为主
横向增加节点
问题排查单进程日志和调用栈日志、指标、链路追踪和跨节点状态
问题排查
单进程日志和调用栈
日志、指标、链路追踪和跨节点状态
为什么需要分布式系统?
单体应用并不低级。一个管理后台、一个访问量不大的业务系统,用一个应用加一台数据库,反而更容易开发、部署和排查问题。很多系统真正出问题,不是因为一开始用了单体,而是拆得太早,复杂度先涨上来,收益还没出现。
分布式系统一般是在压力出现后才变得有必要。
先是计算压力。 一台机器的 CPU、内存、磁盘 I/O 都有上限。机器配置可以往上堆,但价格、硬件规格和单点风险都会把纵向扩容拦住。把请求分摊到多台机器上,才是多数业务系统后面会走的路。
存储压力也类似。 一张订单表从 100 万行涨到 10 亿行,查询、备份、索引维护、故障恢复都会变重。所有数据继续压在一台机器上,成本和风险都会升高。数据分片会把不同数据拆到不同节点,比如按用户 ID 或订单 ID 分片;副本复制会把同一份数据保存多份,用来提高可用性、容灾能力,顺便分担一部分读请求。它们能解决容量和故障问题,也会把跨分片查询、副本同步、数据一致性带进来。
可用性也会逼着系统往多节点走。 如果只有一台应用服务器,它挂了服务就停;如果只有一台数据库,磁盘损坏或主机故障都会直接影响业务。多个实例、多个副本、多个可用区,至少能让系统在部分节点出问题时继续服务,或者保住一部分能力。
放到业务里,常见动机大概是这几类:
性能撑不住:单机处理不了那么多请求,需要多台机器分担计算。
数据放不下:单机存储、索引、备份和恢复成本太高,需要分片或副本。
故障扛不住:单点故障影响太大,需要冗余、故障转移和降级。
也有一些系统不是被单机容量逼出来的,而是被地域、组织和安全边界推着走。服务和数据部署到离用户更近的地域,可以降低访问延迟,也方便做跨区域容灾;不同团队、业务域或安全域独立部署,可以避免所有功能共享同一个发布窗口和故障边界。
所以,分布式不是“加机器”这么简单。它解决的是容量、可用性、隔离和协作问题,同时把网络、故障、数据一致性和排障成本一起带进系统。
从单体电商到分布式电商
假设有一个早期电商系统,用户、商品、订单、库存、支付都写在一个 Spring Boot 应用里,数据放在同一个 MySQL 实例中。
业务刚开始时,这种结构很舒服。一次下单就是一条本地调用链:校验用户,查商品,扣库存,创建订单,发起支付。代码在一个进程里,事务在一个库里完成。出了问题,看一个应用日志和一个数据库,基本就能把事情查清。
访问量上来后,压力会先落到几个地方:商品详情页查询量高,订单创建写入量高,库存扣减并发冲突多,支付链路又不能随便失败。继续把所有逻辑塞在一个应用里,任何一个模块变慢,都可能拖住整个系统。
这时很多团队会先横向扩容:部署 3 个甚至更多应用实例,用 Nginx、网关或负载均衡器分发请求。只要应用尽量无状态,多加实例就能分担一部分流量。
再往后,系统可能会继续拆:
商品服务负责商品信息和价格展示;
订单服务负责订单创建、订单状态流转;
库存服务负责库存扣减、库存回滚和库存流水;
支付服务负责对接第三方支付渠道;
消息队列负责把支付成功、库存变更、物流通知等事件异步传出去。

拆分后的好处很直接。商品服务访问量大,可以单独扩容;支付服务对稳定性要求高,可以单独做限流、重试和熔断;库存服务并发冲突多,可以围绕库存扣减设计专门的数据结构和锁策略。
麻烦也很快出现。
订单服务调用库存服务扣库存,如果请求超时了,订单服务到底该不该重试?上一次扣库存是没发出去,还是已经扣成功但响应丢了?如果库存扣成功了,订单创建失败了,库存怎么补?支付成功消息重复投递,订单状态会不会被重复更新?
这些问题在单体里也可能出现,只是分布式系统会把它们放大。系统不再只有一个进程、一份内存、一个事务上下文,很多原来“顺手就做了”的事情,拆开后都要重新设计。
分布式系统有哪些典型特征?
只看机器数量不够。下面这些特征,才是分布式系统复杂度的来源。
多节点协作
一个请求往往要多个节点一起完成。下单请求可能经过网关、订单服务、库存服务、支付服务、消息队列和数据库。每个节点只负责一小段逻辑,拼起来才是一条完整业务链路。
链路拉长后,延迟和故障都会被放大。某个节点线程池打满、数据库慢查询、网络抖动,用户看到的可能只是“下单转圈”。
节点并发执行,没有瞬时全局视图
节点是并发运行的。每个节点只能直接看到自己的本地状态,以及已经收到的消息,不能在某个瞬间读取整个系统的真实状态。
于是就会出现一些看起来矛盾、但都能解释得通的判断:一个节点已经拿到最新配置,另一个节点还停留在旧版本;一个节点认为 Leader 还活着,另一个节点因为超时已经开始选举。很多分布式问题不一定是代码写错了,而是不同节点在不同时间看到了不同信息。
网络通信
节点之间要靠网络交换数据。网络和本地内存不是一类东西,它不保证请求一定到达,也不保证响应按预期时间返回。
一次远程调用可能出现这些情况:
请求尚未离开客户端;
请求已经到达服务端,但尚未执行完成;
服务端已经执行成功,但响应没有到达客户端;
客户端已经超时,服务端仍在继续执行;
服务端执行失败,但错误响应也没有成功返回。
超时、重试、幂等、熔断和降级,就是为这些情况准备的。远程调用进入主链路后,这些设计不能等线上报错以后再补。
局部故障
单机系统里,进程挂了,问题边界相对清晰。分布式系统里经常是半边好、半边坏:一部分节点正常,一部分节点异常;一部分请求成功,一部分请求失败;A 服务访问不了 B 服务,但 C 服务还能访问 B 服务。
一个节点没响应,也不一定就是宕机了。网络抖动、GC 暂停、线程池打满、磁盘 I/O 卡住,都可能让它短时间“像死了一样”。系统如果只靠“有没有响应”判断故障,很容易误判。
数据复制和数据分片
数据规模和可用性上来后,系统很容易走到分片和复制。
分片是把不同数据分散到不同节点,常见规则包括用户 ID、订单 ID、地域、哈希值。分片之后,单个节点压力小了,跨分片查询、跨分片事务、分片扩容会变麻烦。
复制是把同一份数据保存多份,比如 MySQL 主从复制、Redis 主从复制、Kafka 分区副本、ZooKeeper 多节点副本。有了副本,节点故障时更容易继续服务,读请求也可能分摊到多个副本上。代价是副本同步有延迟:主节点写成功后,从节点可能还没追上;用户刚写完数据,下一次读请求如果落到旧副本,就可能读到旧值。

没有完美同步的全局时钟
每台机器都有自己的物理时钟,但时钟会有偏差和漂移。NTP、GPS 这类时间同步机制可以缩小误差,不能保证所有节点在任意时刻都有完全一致的时间视图。
分布式系统很少只靠墙上时钟判断事件先后。表达因果关系时,可以使用 Lamport Clock、Vector Clock 等逻辑时钟;做复制、选举和状态变更时,也常用 term、epoch、版本号或单调递增序列。
物理时间依然有用,日志、超时、租约、缓存过期都离不开它。只是依赖物理时间时,要知道自己能接受多大的时钟误差和漂移。逻辑时钟解决事件顺序问题,不能直接替代“锁多久后过期”这类物理时间需求。
常见的分布式系统有哪些?
分布式系统不是某一种中间件,而是一类系统形态。常见类型有这些。
类型解决的问题常见例子分布式协调系统选主、配置管理、服务发现、分布式锁ZooKeeper、etcd、Consul分布式数据库数据分片、副本复制、水平扩展TiDB、CockroachDB、Cassandra、HBase分布式缓存多节点缓存、热点数据加速、缓存容量扩展Redis Cluster、Memcached 集群分布式消息队列异步解耦、削峰填谷、事件驱动Kafka、RocketMQ、Pulsar分布式文件/对象存储大文件存储、多副本、高吞吐读写HDFS、Ceph、MinIORPC 框架接口定义、序列化、跨服务请求响应gRPC、Apache Thrift服务治理体系注册发现、负载均衡、流量管理、熔断、配置Dubbo、Spring Cloud
类型解决的问题常见例子
类型
解决的问题
常见例子
分布式协调系统选主、配置管理、服务发现、分布式锁ZooKeeper、etcd、Consul
分布式协调系统
选主、配置管理、服务发现、分布式锁
ZooKeeper、etcd、Consul
分布式数据库数据分片、副本复制、水平扩展TiDB、CockroachDB、Cassandra、HBase
分布式数据库
数据分片、副本复制、水平扩展
TiDB、CockroachDB、Cassandra、HBase
分布式缓存多节点缓存、热点数据加速、缓存容量扩展Redis Cluster、Memcached 集群
分布式缓存
多节点缓存、热点数据加速、缓存容量扩展
Redis Cluster、Memcached 集群
分布式消息队列异步解耦、削峰填谷、事件驱动Kafka、RocketMQ、Pulsar
分布式消息队列
异步解耦、削峰填谷、事件驱动
Kafka、RocketMQ、Pulsar
分布式文件/对象存储大文件存储、多副本、高吞吐读写HDFS、Ceph、MinIO
分布式文件/对象存储
大文件存储、多副本、高吞吐读写
HDFS、Ceph、MinIO
RPC 框架接口定义、序列化、跨服务请求响应gRPC、Apache Thrift
RPC 框架
接口定义、序列化、跨服务请求响应
gRPC、Apache Thrift
服务治理体系注册发现、负载均衡、流量管理、熔断、配置Dubbo、Spring Cloud
服务治理体系
注册发现、负载均衡、流量管理、熔断、配置
Dubbo、Spring Cloud
这些系统解决的问题不同,但经常一起出现在一个业务架构里。一个订单系统可能用 Redis 做缓存,用 RocketMQ 传递订单事件,用 ZooKeeper 或 Nacos 做注册发现,用 MySQL 分库分表存订单,再用链路追踪系统排查一次请求经过了哪些服务。
学分布式系统时,不要只盯着某个中间件背参数。要问它放在系统里解决了什么问题,又把哪些复杂度留给了业务方。
分布式系统、集群和微服务有什么区别?
这几个词经常混在一起,但指向的不是同一件事。
集群更强调部署形态。多台机器一起提供服务,就可以叫集群。比如 3 个 Nginx 实例、5 个 Redis 节点、3 个应用实例。它们可能做同样的事情,也可能有主从、分片、选主等分工。
分布式系统更强调节点之间的协作。多个节点靠网络通信,共同完成一个任务,对外表现为一个整体。集群可以是分布式系统的一种形态,但分布式系统还会涉及数据复制、一致性、容错、调度和协调。
微服务是一种应用架构风格。它把业务系统拆成多个围绕业务能力组织的服务,每个服务可以独立开发、部署和扩容。微服务系统一般也是分布式系统,因为服务之间要走网络调用。不过,分布式系统不一定是微服务。Kafka、HDFS、ZooKeeper 本身都是分布式系统,但不是业务微服务。
还有一种情况也很常见:一个业务应用还是单体,但它依赖 Redis Cluster、Kafka、Elasticsearch、MySQL 主从。这个业务应用本身没有拆成微服务,但它运行在一组分布式基础设施之上。
分布式系统难在哪里?
分布式系统难,不是因为概念听起来高级,而是失败情况太多。网络会让一次操作的结果变得不确定,独立故障又会让不同节点同时看到不同的系统状态。
本地调用和调用方处在同一个进程或故障范围内,执行结果相对好判断。远程调用多了一层不确定性:超时只说明客户端在指定时间内没有收到响应,不能证明服务端没有执行。请求可能没发出去,也可能已经执行成功但响应丢了。这个差别会直接影响重试策略。

比如订单服务调用库存服务扣库存,客户端设置了 2 秒超时。2 秒后订单服务没收到响应,它有几种选择:
直接认为扣库存失败,取消订单;
重新调用一次库存服务;
查询库存扣减流水,确认上一次请求是否成功;
先把订单置为处理中,后续通过消息或定时任务补偿。
每种选择都有代价。直接取消订单可能误判,因为库存服务也许已经扣成功;直接重试可能重复扣库存;查询流水要求库存服务提供幂等号和可查询记录;异步补偿会让用户看到“处理中”状态,产品体验也要配合。
幂等就是在这种场景下变得重要的。只要存在超时和重试,同一个业务请求就可能被处理多次。服务端必须能识别“这是同一次业务操作”,不能因为客户端重试就重复扣款、重复扣库存、重复发券。对有副作用的远程写操作,还要设计业务幂等号、结果查询、有限重试,以及指数退避和随机抖动,避免下游故障时被重试流量继续压垮。
数据一致性也是类似问题。单体应用里,一个数据库事务可以同时更新订单表和库存表;拆成订单服务和库存服务后,订单库和库存库不在同一个事务里。想让它们要么都成功、要么都失败,就需要分布式事务、事务消息、TCC、Saga、本地消息表等方案。
很多跨服务业务会接受短时间状态不一致,再用事务消息、重试、补偿和对账,让订单、库存、支付等状态最终满足业务约束。工程里也常把这种方案叫“最终一致性”。它和副本一致性模型里的 eventual consistency 不是同一个语境:后者强调不再发生新写入时,多个数据副本最终收敛;前者更偏向跨服务业务流程的异步协调。
排查问题也会变慢。一次请求经过 6 个服务,任何一个服务日志不规范、链路追踪缺失、错误码设计混乱,定位都会很费劲。生产环境里缺少观测能力时,很难判断请求卡在哪个节点,错误最早从哪里冒出来。
业务型分布式系统常见的基础能力
下面这些能力常见于微服务和在线业务系统,属于工程配套,不是分布式系统定义的一部分。服务成员关系也不一定非要靠独立注册中心维护,DNS、静态配置、Gossip 或集群协议都可能用得上。
服务发现和负载均衡:服务实例会扩容、缩容、重启,调用方不能把服务地址写死。注册中心记录服务实例,负载均衡从可用实例里选一个进行调用。
超时、重试和幂等:远程调用必须设置超时。重试要谨慎,只适合可重试且有幂等保护的操作。支付、扣库存、发券这类操作,一定要有业务唯一号或幂等表兜底。
熔断、限流和降级:下游服务变慢或失败时,上游不能无限等待和重试,否则故障会沿着调用链扩散。熔断用于快速失败,限流用于控制入口压力,降级用于保住主链路。
配置管理和动态变更:服务数量多了以后,配置不能只靠本地文件手动改。配置中心可以统一管理配置,并支持灰度发布、动态刷新和回滚。
日志、指标和链路追踪:日志回答“发生了什么”,指标回答“现在健康吗”,链路追踪回答“一次请求经过了哪里”。这 3 类数据放在一起,排查分布式问题才有抓手。
数据一致性和补偿机制:跨节点写数据时,要提前设计失败后的处理方式。是强一致、最终一致,还是允许短时间不一致?失败后靠重试、人工处理、对账修复,还是业务回滚?这些问题不能等线上出错后再补。
分布式系统怎么学?
学习分布式系统,不建议一上来背算法名。先从工程问题往理论走,会顺很多。
第一步看网络通信。HTTP、RPC、TCP、超时、重试、连接池、序列化,这些内容决定服务之间怎么说话。没有这部分基础,后面看服务治理会很虚。
第二步看服务拆分和服务治理。服务为什么要拆,拆完以后怎么注册发现、负载均衡、限流熔断、链路追踪,怎么处理版本兼容和灰度发布。微服务的大部分日常问题都在这一层。
第三步补数据层:复制、分片、缓存、消息队列、分布式 ID、分布式锁、分布式事务。这里要多问异常场景,比如消息重复投递怎么办、缓存和数据库不一致怎么办、锁过期但业务还没执行完怎么办。
最后再看 CAP、BASE、中心化与去中心化、Paxos、Raft、ZAB、Gossip、一致性哈希这些理论和协议。学习这些内容,重点是理解 ZooKeeper、etcd、Kafka、Redis Cluster、分布式数据库这些系统为什么这样设计。
这里建议先读 分布式协调详解。它把 Leader、Quorum、脑裂、Lease、Fencing Token 和 Gossip 放在同一条主线里,能帮你在进入 Raft、ZAB、Gossip 细节之前,先明白“谁来做决定、状态怎么传播、错了会怎样”。
还要分清共识和分布式事务。Paxos、Raft、ZAB 主要解决一组副本如何对日志顺序、Leader 或状态变更达成一致;TCC、Saga、事务消息主要解决多个业务参与方之间如何协调提交和补偿。它们可能出现在同一个系统里,但处理的问题不同。
入门阶段先把这 5 个问题讲清楚,比背一串术语更有用:
为什么单机系统要拆成多节点?
远程调用和本地调用有什么差别?
为什么分布式系统里超时不能简单等同于失败?
为什么多副本会引入一致性问题?
为什么跨服务写数据通常要考虑幂等、补偿和最终一致性?
为什么有些系统需要 Leader,有些系统更适合用 Gossip 传播状态?
参考资料
什么是分布式系统,如何学习分布式系统
现在主流开源分布式系统架构都有哪些?
常见分布式系统设计图解(汇总)
A brief introduction to distributed systems
A brief introduction to distributed systems
MIT 6.824 Distributed Systems
Time, Clocks, and the Ordering of Events in a Distributed System
Time, Clocks, and the Ordering of Events in a Distributed System
Timeouts, retries and backoff with jitter
Timeouts, retries and backoff with jitter
Eventually Consistent - Revisited
Eventually Consistent - Revisited
Raft Consensus Algorithm
《Designing Data-Intensive Applications》
《分布式系统概念与设计》
写在最后
如果内容对你有帮助的话,欢迎顺手给 JavaGuide 点一个免费的 Star 支持一下:GitHub | Gitee。
JavaGuide 已持续维护近七年,累计 6100+ 次提交,来自 620+ 位贡献者共同完善。你的 Star、反馈和 PR,都是这个项目继续更新的动力。
如果你正在准备后端/AI 应用开发面试,也可以了解一下我的知识星球,里面包括后端和 AI 实战项目、简历优化、一对一提问和高频考点资料,已经持续维护六年。