ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

高可用分布式系统必备:12个核心架构概念深度拆解

高可用分布式系统必备:12个核心架构概念深度拆解

做高可用分布式系统这几年,我最大的感受是:很多线上事故,根本不是什么神秘莫测的底层故障,而是架构设计时把一个朴素概念理解偏了。比如有人把“加机器”等同于“高可用”,把“拆微服务”等同于“解耦”,把“消息队列”等同于“削峰填谷万能药”。你问他为什么这么做,他能跟你聊半天,但再问一句“如果这台机器真的挂了,你的请求现在走哪条路?”,往往就答不上来了。

这篇文章不打算讲那种“十八般武艺样样齐全”的架构全集,而是聚焦一个开发者真正该反复咀嚼的清单——12 个核心架构概念。它们不是某一门语言或某一个中间件的专属知识,而是构建高可用分布式系统时绕不开的通用基石。我会把自己在实际项目中踩过的坑、验证过的思路、以及一些“文档里不会写”的取舍逻辑,都揉在里面聊。适合正从单机应用走向分布式、或者已经在微服务堆里挣扎、想系统梳理一遍的同学阅读。

1. 为什么分布式系统让开发者的日子不再好过

1.1 从单机到分布式,问题的本质并未改变

很多人刚接触分布式架构时,会被一堆新名词搞得焦虑:注册中心、配置中心、网关、容器编排、服务网格……好像分布式系统就是一个由陌生技术堆起来的新世界。但我想先说一个可能不太讨喜的结论:从单机到分布式,你要解决的问题本质并没有变,你只是在用更多机器和更多进程去解决同一个问题。

单机时代,一个请求进来,应用进程处理完直接操作本地数据库,所有状态都在同一台机器上,一切尽在掌握。放大到分布式之后,请求还会进入应用进程,但这个进程可能同时有十个副本在跑;它要调的数据库也可能不只在本地,而是远程的一组集群。“状态在哪里”和“请求怎么走”这两个问题,才是分布式系统真正引入的复杂度。如果你的服务是无状态的,状态全在数据库里,那么请求打到任何一个副本结果都一样;如果服务自带本地缓存或会话状态,那负载均衡策略、故障转移逻辑就全都得跟着变。

所以你看,我们要学的 12 个概念,本质上都是在回答两个问题:状态放哪,请求怎么走。想明白这一点,很多架构名词就不再是孤立知识点,而是一套互相咬合的逻辑链。

1.2 你写的代码,一半时间都在等待

第二个想提醒的观点是:分布式系统里,你的代码效率瓶颈往往不是 CPU,而是网络开销。一次远程调用要经历序列化、传输、反序列化,再加上对端排队,几百毫秒是常有的事。一个看似简单的用户下单操作,如果串行调用积分服务、库存服务、订单服务,光网络等待就能到秒级。

这也是为什么很多系统做到后来,开发者的直觉会被迫发生改变:能用异步解决的,不用同步;能减少网络往返的,一定合并;能提前算好的,坚决不请求时现算。而这样一套思维,恰恰是后面缓存、消息队列、幂等这些概念共同的底层逻辑。

2. 12 个核心架构概念逐个拆解:状态、流量与缓冲

2.1 无状态与有状态设计:先把最容易错的设计理念说清楚

无状态(Stateless)的意思,是指服务实例本身不保存和业务相关的用户状态,所有状态都放到外部存储。比如登录会话放在 Redis 里,而不是放在某个服务进程的内存里。为什么这一点如此重要?因为它是水平扩缩容的前提。服务无状态,意味着任何请求都可以打到任何副本,负载均衡策略可以无脑轮询,节点增减不需要额外迁移数据。

有状态(Stateful)设计也不是洪水猛兽,数据库、缓存、消息队列本质都是有状态组件。问题在于很多人无意识地在应用层写出了有状态代码:用本地内存保存登录用户、用静态变量缓存业务配置、把临时文件写到容器本地磁盘。这些在一台机器上没问题,一旦副本数变多,问题就开始暴露:用户第二次请求打到另一台机器,需要重新登录;缓存数据每个节点各存一份,永远不一致。

我实际项目里就出过一次事故:某个服务用本地内存缓存配置表,上线后前几分钟请求正常,等流量切换到新扩容的节点,配置缓存为空,服务行为直接异常。排查方向、回滚动作都做了,最后发现只是“应用层有状态”这个低级问题。所以我的建议很简单:应用层能保持无状态就保持无状态,本地只放不可变、可重建的临时缓存,所有业务状态交给专门的基础设施。

2.2 负载均衡:不是加一台机器就能叫高可用

负载均衡的核心价值有两层:流量分发和故障摘除。很多人只盯着前者,设计了一个很好的轮询算法,但忘了后端节点健康检查这个兜底逻辑。结果一台后端已经内存溢出、请求超时,前面的负载均衡还在孜孜不倦地给它分流量。没有健康检查的负载均衡,就像蒙着眼发牌,发到谁算谁。所以无论你是用硬件负载均衡、Nginx 这类七层代理,还是 L4 层的四层转发,都一定要把“被动健康检查”和“主动探测”配好。

选型上有一个常见的权衡:四层负载均衡转发效率高、能处理任意端口协议,但看不到 HTTP 路径,没法做精细化路由;七层负载均衡可以按 URL、Header、Cookie 路由,更适合微服务场景下的网关逻辑,但并发能力通常弱一些。具体怎么选,取决于你的流量规模和路由精细度。很多团队初期图省事只用四层,等后面要按接口灰度发布时才后悔。

关于负载策略,有一个点值得展开:一致性哈希。带状态场景下,如果用户 Session 放在服务端本地,就必须用一致性哈希保证同一个用户尽量落到同一台机器。但反过来说,如果你已经做了无状态化,一致性哈希反而不是必需品,简单轮询和最少连接数就够了。架构设计就是这样,方案优劣永远要绑定你的状态设计来谈。

2.3 缓存与数据一致性:把热数据放到离计算最近的地方

缓存是提升高并发能力最直接的手段。一条数据库查询可能需要 10 毫秒,但从 Redis 走一次内存读可能只要 1 毫秒甚至更低。问题是缓存带来了巨大的“一致性”陷阱:数据库写入了新值,缓存还是旧值,怎么办?

业界最常用、也相对稳妥的方式是Cache Aside(旁路缓存):读的时候先查缓存,没有就查库,回填缓存;写的时候先更新数据库,再删除缓存。为什么不直接更新缓存,而是删除?因为更新缓存有并发写覆盖的风险,而删除掉之后下次读再回填,整体更简单可靠。但删除缓存也并不是银弹,它存在一个经典竞争场景:线程 A 更新完数据库,还没删缓存;线程 B 正在读缓存发现没有,于是查库拿到旧值回填,恰好此时线程 A 删除了缓存,结果旧值又被 B 塞回去了。理论上这个窗口极小,但流量足够大时就会发生。处理办法通常是给缓存设置过期时间兜底,或者在比较关键的数据上用消息队列异步刷新。

另一个容易踩坑的是缓存穿透:大量查询一个根本不存在的数据,每次都会打到数据库。对策主要有三种:缓存空值、布隆过滤器前置判断、或者参数合法性校验前置拦截。我个人最推荐的是布隆过滤器+缓存空值组合,因为它既挡住了大部分非法 key,也为少量漏网之鱼提供了兜底。还要警惕缓存雪崩:大量 key 同一时间过期,请求集体落到数据库。解决思路是给过期时间加上一个随机偏移,避免同时失效。

2.4 异步通信与消息队列:别再同步调个没完了

同步调用是最直观的交互方式:A 调 B,等 B 响应。但当你需要同时调用批量服务、或一旦 B 响应很慢 A 就会阻塞时,同步链路的缺点会被无限放大。消息队列的核心思想是:把“实时等待结果”变成“先把任务收下,后续慢慢处理”。

用了消息队列之后,系统得到几个能力:异步解耦(生产者不关心消费者是谁)、流量削峰(瞬时高峰先存入队列,下游按自身能力消费)、故障缓冲(消费方短暂不可用,请求不会直接失败)。听起来全是优点,但用 MQ 的代价绝对不低:你需要处理消息丢失、消息重复、消费顺序错乱、死信堆积等问题。

一个真实场景是:用户下单后发注册奖励,因为奖励服务耗时较长,团队引入了 MQ。上线后很快出现用户收到了但奖励服务一直没处理完毕的堆积问题。排查后发现消费者消费能力不足,单条消息还要调用几个外部接口,高峰期消息积压超过节点内存。这个问题的本质不是 MQ 不行,而是你没有做降级保护:奖励这类业务完全可以在流量低峰期慢慢消费,或者直接改成分批批量处理。架构设计不能只看链路是否通,还要看极端流量下的系统行为。

3. 12 个核心架构概念逐个拆解:服务关系与数据一致性

3.1 服务拆分与微服务边界

微服务这两年被讨论得很多,但很多人把微服务做成了“微到不能再微的碎服务”。服务拆分的价值在于独立演进、独立伸缩、故障隔离,而不是拆分数量本身。我见过一个团队,把一个用户体系拆成基础资料服务、账号密码服务、用户画像服务、用户设置服务,结果一次查询要聚合四个服务的数据,网络开销翻了几倍,开发和联调成本也直线上升。这不是微服务,是把单体应用拆成了分布式单体。

怎么判断一个服务边界是否合理?有一个很实用的标准:看它所依赖的数据模型是否足够内聚,以及它是不是在为一个明确的业务能力负责。一个服务应该只修改它自己拥有的数据,其他服务要访问这些数据时走接口,而不是直接连它的数据库。如果把“清理用户过期积分”这个逻辑同时写在三个服务里,那恭喜你,你已经给自己埋了一颗炸弹:下次规则变了,你会有一个服务忘改。

我个人的经验是:先从单一业务域拆分,不要按技术分层拆。很多团队把“公共方法”抽出来做成公共库,或者把相似逻辑合并成一个大服务,这在初期效率确实高,但长期看,职责不清晰会导致每次变更都像走雷区。边界划分的合理程度,会直接决定后面服务发现、日志追踪、版本发布的复杂度。

3.2 幂等性设计:重试是分布式系统的基本求生动作

网络是不可靠的,一个请求发出去了,可能响应丢了,但服务端其实已经把事做完了;也可能请求根本没到,客户端等不到响应就重试。只要你会重试,就一定需要幂等保护。幂等(Idempotency)的含义:同一个操作执行一次和执行多次,结果一样。

最容易理解也最容易落地的方案是唯一键约束。比如创建订单接口,客户端生成一个全局唯一请求号,服务端拿这个请求号去订单表的唯一索引做插入,重复请求会直接冲突,业务代码捕获冲突后返回第一次成功的订单即可。这个方法在数据库层面天然可靠,比“先查库再判断”这种检查再插入的方式稳得多。至于全局唯一 ID 怎么生成,可以用雪花算法、UUID,或由发号服务统一分配。

还有一类幂等比较隐蔽:消息消费端。消息队列经常是“至少一次投递”,同一个消息可能被消费两次。如果你没有在消费逻辑里做幂等处理,用户可能收到两条一模一样的短信、积分加两次。所以我在设计消费任务时,几乎都会让消费逻辑具备可重入能力:要么基于业务唯一键做去重,要么在业务表里增加“已处理标识”。“我消费了这条消息,但还没改数据库就宕机了”的恢复问题,一定要在机制上兜住,不能靠运气。

3.3 分布式事务与最终一致性:别指望强事务包打天下

在单体数据库里,ACID 事务是非常自然的事:一个操作要么全成功,要么全回滚。但服务拆开后,一次业务操作可能跨多个服务、多个数据库,传统本地事务已经管不住了。分布式事务的难点在于:你无法保证多个参与者同时提交成功,也不具备单点回滚的能力。

于是业界出现了 2PC(两阶段提交)、TCC(Try-Confirm-Cancel)、Saga、本地消息表等方案。2PC 提供较严格的强一致性,但有同步阻塞和协调者单点问题,性能和可用性在跨服务场景里不太占优。TCC 是对业务侵入性很强的方案,每个操作都要写 Try、Confirm、Cancel 三套逻辑,适合资金类等对一致性要求极高的场景。Saga 的核心是通过一系列本地事务+补偿事务来达到最终一致性,每个步骤只提交自己的本地事务,如果后面失败了,前面已经提交的步骤执行反向补偿。Saga 在电商订单这种链路里用得很多。

我想特别强调一点:选择分布式事务方案之前,先想想业务是不是真的需要强一致。很多业务其实能接受“稍微等一下才看到新数据”,比如支付回调之后订单状态延迟几秒更新,用户根本感知不到。这时候完全可以通过消息队列+本地消息表/事务发件箱模式来做最终一致,代价小得多。能把一次强一致请求拆成多次幂等的本地事务+异步消息,就不要轻易上重分布式事务框架。这个取舍做得好,系统的复杂度和故障率会明显降下来。

3.4 分布式锁:跨进程的临界资源保护

单机多线程时代,我们用 synchronized 或 ReentrantLock 保护临界区。到了多节点部署,每个节点都有自己的锁,临界区就会被多个节点同时进入。分布式锁要解决的就是:在多台机器之间,让同一时刻只有一个节点能执行某段代码。典型场景:定时任务在一个集群里被触发多次,库存扣减操作并发执行,优惠券记得保证领取数量不超过总数。

实现分布式锁有几种常见路子:基于 Redis 的 SET NX EX、基于数据库唯一键、基于 ZooKeeper/Etcd 的临时顺序节点。它们的可靠性和实现成本差别很大。基于 Redis 的锁简单、性能高,但存在锁超时自动释放导致两个节点同时持锁的风险;基于 ZooKeeper 的锁语义通常更严格,看门狗机制能处理宕机问题,但性能相对低。没有绝对完美的锁,关键看你愿意在可靠性上付出多少成本。

一个极易踩的坑是锁超时时间设置太短。假设你设置 5 秒锁超时,但业务执行了 10 秒,锁在前 5 秒就被自动释放了,第二个节点进程进入临界区,两个任务同时执行,分布式锁形同虚设。我曾经排查过一个重复发放积分的线上事故,最终原因就是这个:锁超时设短了,业务执行期间发生了一次长耗时 GC,任务被重复执行。事后我们改用带续期机制的锁实现,或者尽量让锁内逻辑足够短,耗时长的操作拆到锁外。另外一定记得,释放锁时要校验是不是自己持有的锁(用唯一客户端标识)。否则你释放的可能已经不是自己的锁,而是别人刚拿到的锁。

4. 12 个核心架构概念逐个拆解:注册、配置与可观测性

4.1 服务发现与注册中心:让调用方知道该找谁

在静态环境里,服务 B 的地址写死在服务 A 的配置文件里,这也是可以跑的。问题是当服务 B 有 10 个节点,而且其中 2 个可能随时崩溃、被拉起、被缩容时,硬编码就完全行不通了。注册中心的价值,是让服务之间的调用关系动态化。

常见的注册中心有 ZooKeeper、Etcd、Consul、Nacos 等,它们都能提供基础的一致性协议。但从使用者的视角看,我通常关注三件事:临时节点/租约机制是否健全、是否能支撑大规模心跳、以及客户端缓存和容错是否做得好。注册中心挂了不能等于系统全挂,所以调用方应该把从注册中心拿到的服务列表缓存在本地,注册中心短暂不可用时,依然用缓存列表继续转发请求。

服务发现的另一个坑是:健康检查过于简单。很多框架默认的注册健康检查只是进程存活(TCP 端口通不通),但进程活着不代表它能正常处理请求。真正的健康检查应该尽可能接近业务健康,比如做一个带极简数据库或缓存依赖的探活接口。我见过一个案例:注册中心里某个服务节点显示健康,负载均衡持续给它分发流量,但这个节点的线程池早就打满了,请求全部排队超时。问题就在于健康检查只看进程活着,没看它的实际工作状态。

4.2 配置中心:把配置从代码里解放出来

配置管理的核心诉求不是“换一种方式存配置”,而是要解决动态变更和环境隔离。拿一个简单的开关举例:新功能上线,想先对 5% 的用户开放,如果这个开关写在代码里,意味着你要改代码、发版、再等运维重启一批节点。这个流程走完,黄金时间早就过去了。

所以配置中心的价值涵盖下面几层:集中管理(所有服务的配置在一个平台看)、版本控制与审计(谁改了什么可追溯)、动态推送(服务不重启就能改配置)、环境隔离(开发、测试、生产用不同的配置空间)。落地时有一个最容易踩的坑:业务代码读取配置后,把值缓存在本地静态变量里,配置中心推了新值,本地缓存却没更新。如果要用动态配置,就必须让代码里拿配置的路径足够统一,且支持刷新回调。你可以封装一个配置客户端类管理,或者用框架自带的配置绑定机制,但千万别今天这里 new 一个类读配置、明天那里又读一次,那样迟早会出“配置改了但服务没生效”的诡异问题。

还有一个细节,在配置中心变更一个高风险配置(比如限流阈值、降级开关)时,强烈建议走灰度发布:先发给一台实验机器,观察指标,确认没问题再分批全量推送。不要一键全推,否则一个坏配置会让整个集群瞬间异常。

4.3 可观测性:日志、指标、链路追踪一个都不能少

分布式系统的问题,往往不是单靠查一台机器日志能找到的。用户的一次请求会经过网关、多个微服务、数据库、消息队列,如果每个服务的日志互相独立,一旦出现跨节点性能问题时,你很难定位性能瓶颈和错误传播路径。所以我一直强调一个原则:没有可观测性的微服务,就像夜里不开灯走一条陌生路,迟早摔跤。

可观测性有三个支柱:

  • 指标(Metrics):定期采集的数值型数据,比如 QPS、响应时间、错误率、线程池活跃度、JVM/容器资源使用率。指标适合做告警和容量预测,能反映系统“正在发生什么”。
  • 日志(Logging):记录离散事件详情,比如请求参数、异常堆栈、关键业务节点状态。日志适合做问题根因定位,能帮你回答“为什么发生”。
  • 链路追踪(Tracing):给一个请求分配全局 Trace ID,贯穿所有经过的服务,记录每一跳的开始时间、耗时、状态。链路追踪最适合回答“这个请求到底慢在哪一跳”。

三个支柱配合起来,定位问题的速度会快几倍。我自己的习惯是,在接入层或者客户端网关生成 Trace ID,把它注入 HTTP Header 和业务日志;底层中间件和数据库访问必须透传这个 ID;指标告警出现异常时,直接拿 Trace ID 去查全链路数据。如果哪天系统里的日志、指标、链路数据格式完全打通了,你才算真正拥有可观测性。

4.4 故障隔离、熔断降级与容量保护:别让一个下游拖垮全局

单体应用一个接口出问题,可能只是这个接口报错,其他功能还算正常。但微服务架构里,服务之间的调用就像密密麻麻的蜘蛛网,一个下游服务响应变慢,上游调用方会大量堆积线程等待,线程池被打满后,上游自己也处理不了任何请求,故障就像多米诺骨牌一样传播出去。所以故障隔离的核心是:把失败控制在小范围,别让它殃及池鱼。

熔断器的思路很好理解:如果对某个下游的调用连续失败率达到阈值,比如 10 秒内 50% 的请求失败,就打开熔断器,后续请求直接快速失败,不再真的去调用下游;等一段时间后进入半开状态,放少量请求去探测下游是否已恢复,恢复则闭合熔断器。这样既保护了下游不被打死,也保护了上游不无限等待。

降级则是在系统压力过高时,主动牺牲一些非关键功能来保核心链路。比如商品详情页的“热门推荐”模块挂了,可以改成显示静态兜底数据;用户积分明细查询失败,可以先不让页面报错,给一个“稍后再看”的占位。降级的关键是先想清楚哪些是核心链路,哪些是边缘功能,然后提前准备好降级策略,而不是事发时临时开会决策。隔离还可以用线程池隔离或信号量隔离,让一个下游的阻塞只耗尽对应调用方资源池,不拖垮整个服务的其他接口。这一套组合拳,是保证高可用系统在大流量冲击下不整体崩溃的重要防线。

5. 12 个概念不是零散拼盘:它们的内在关系与落地顺序

5.1 概念依赖关系:先搞清楚谁是地基

很多概念乍看独立,实际有很强依赖关系。比如:无状态设计是水平扩展的前提,而负载均衡和自动扩容都依赖水平扩展;服务拆分会产生更多远程调用,而远程调用不可靠,所以你需要超时、重试、幂等;幂等能力越强,异步通信越敢用;异步通信越普及,最终一致性方案就越自然;最终一致性落地越多,可观测性就越重要,因为你没办法靠直觉追踪一条异步链路。这是一套环环相扣的逻辑链。

我见过很多团队一上来就搞服务网格、搞单元化部署,但基础的无状态改造都还没完成,最后每次扩容都要跟着迁移数据,苦不堪言。概念可以同时了解,但落地顺序不应该头脑发热。

5.2 微服务体系的建议落地顺序

我倾向的顺序是:先从模块化单体或小规模服务拆分开始,把无状态化做好,再引入负载均衡和服务发现,让流量可以水平扩展;接着做缓存和异步化,把单次请求的链路耗时降下来;然后逐步完善配置中心、可观测性和日志追踪;当系统规模继续变大,再考虑复杂的分布式事务方案和更精细的熔断降级。

这个顺序的核心逻辑是:优先解决扩展性和基础可靠性,再解决复杂一致性和精细治理。如果你连“服务挂了一台节点,负载均衡能不能自动摘除”都做不到,谈分布式事务和单元化都是空中楼阁。

5.3 技术选型的一个关键态度

关于中间件选型,我的观点是:不要迷信“热度最高”或“别人用了所以我也要用”。每个中间件都有它的适用场景、团队熟悉度和运维成本。重要的是你能否说清楚选择的理由。比如消息队列,你到底要的是吞吐、是顺序消息、是事务消息还是低延迟?不同需求对应的选型差别很大。参考社区实践可以,但永远要结合自己的业务判断。架构决策最怕的不是选错,而是不知道为什么这么选,导致问题出现后无从下手。

6. 实操记录:模拟项目X的高可用改造过程

6.1 改造前的问题清单

前段时间我在一个模拟项目上做了完整的架构改造,这是个典型的业务系统:用户访问量大、核心请求链路长、服务间调用多。一开始系统非常“原始”:服务全部有状态、配置写死、调接口全是同步、没有链路追踪。上线后每次大促前都要人肉扩容,但扩容完流量一来还是会出现各种超时和报错。

我们把问题列成了一张表:

现象根因判断涉及到哪个核心概念
扩容机器后部分用户登录状态丢失会话存在本地内存无状态化
某台节点宕机,但请求依然被转发过去负载均衡健康检查不完善负载均衡
数据库 QPS 一高就告警热点数据没做缓存缓存
下单后奖励积分接口经常超时同步调用链路过长异步与消息队列
所有服务日志格式混乱,排查问题像大海捞针缺少统一日志和链路追踪可观测性
某个下游服务抖动,导致整个系统变慢缺少熔断降级、线程池被占用故障隔离与熔断

6.2 每一步做了什么,效果如何

第一步先做无状态化改造。把会话从本地内存迁移到分布式缓存,把原来存在本地磁盘的文件改成对象存储或数据库统一存储,把业务配置移到配置中心。这一步做完之后,扩容机器终于不需要再迁移数据,负载均衡能真正做到轮询分发。

第二步完善负载均衡和健康检查,让负载均衡能自动感知节点真实状态。同时把超时时间和重试次数统一规范,避免“无限等待”和“无脑重试”两个极端。

第三步针对热点数据设计缓存,同时把请求中的高频查询做并发聚合,降低数据库压力。这里必须要做缓存穿透和击穿保护,不然后续流量一冲高还是会出问题。

第四步把一些非核心同步调用改成消息队列异步化,比如注册奖励通知、积分流水计算、操作日志记录。这一步最关键的是把消费端幂等做好,否则消息重复投递会产生脏数据,反而增加新的麻烦。

第五步把日志系统和链路追踪搭建好。统一日志格式、注入 Trace ID、采集关键指标,并且把告警规则和追踪系统打通。这之后定位问题从“人肉翻日志”变成了“输入 Trace ID,看整条链路”,效率提升非常明显。

第六步给核心下游接口都加上了熔断和降级策略,并且把非核心接口改成线程池隔离。改完之后再模拟一次下游故障测试,发现系统整体虽然会有一部分请求被快速失败,但其他接口的正常流量完全不受影响。这次改造之后,系统扛住了比之前高出三倍的流量峰值,而且没有再出现全局抖动。

6.3 常见问题与排查技巧实录

我在这个过程中记录了一批典型问题和排查思路,整理成一个速查表。这部分内容比较干,但对刚接触分布式架构的开发者应该很有帮助:

问题现象首选排查路径经验性解读
服务时好时坏,请求经常超时看线程池活跃度和 GC 日志,再看下游依赖耗时往往不是代码问题,而是资源池被打满或下游慢调用拖累
定时任务被重复执行检查分布式锁实现和锁超时时间锁超时要足够覆盖业务最大耗时,最好有续期机制
缓存明明设置了,数据库压力还是高检查缓存命中率、穿透和击穿指标热数据没缓存住,或者空 key 穿透打到底层库
消息消费经常重复确认消费逻辑是否幂等,业务表是否唯一索引“至少一次投递”语义下,幂等是必选项
数据“睡了几个小时后突然不一致”重点查异步链路和补偿逻辑最终一致性不等于无限期不一致,要有延迟监控和补偿任务
某节点内存持续上涨检查本地缓存大小、是否有静态集合无限增长有状态设计的典型风险,警惕“隐形全局变量”
发版后新配置对部分节点生效,部分没生效查本地缓存刷新逻辑和配置中心订阅是否成功动态配置必须走统一客户端,禁止手动复制配置

另外分享一个排查小技巧:当系统出现偶发超时,我通常不会一上来就看代码,而是先看错误日志里有没有“连接池获取超时”“线程池拒绝任务”这样的关键字。大部分分布式系统问题的第一现场,不在业务代码里,而在资源池和网络层。把这些地方排查完,再回到业务层会省下非常多时间。

7. 写到最后:我最想提醒诸位的几个细节

如果让我从 12 个概念里挑一个最该反复咀嚼的,我会选“状态设计”。因为几乎后面每一个概念——负载均衡、扩容、幂等、分布式事务、故障转移——都可以追溯到这个问题上。一个系统高不高级,不看你用了多少中间件,而看你把状态放得是否合理。

在踩过几次坑之后,我的体会是:分布式架构的内功,其实是把很多看似简单的原则落到每天写代码的习惯里——新增一个会话变量前先想它能不能放进外部存储;设计接口前先问一句“重复调用会死吗”;配置任何超时前先离线压测一下最慢情况;上线一个会跨服务调用的功能前,先确认是否已经有链路追踪。看起来都琐碎,但正是这些琐碎决定了一套系统到底能不能扛得住真实流量。

这 12 个概念之间本身没有绝对的先后次序,它们更像一座建筑的各个承重墙,互相支撑。希望你读完不是只记住了几个名词,而是能从“状态放哪,请求怎么走”这两个问题出发,重新审视自己负责的系统。你能改掉一个隐藏的有状态点,或者给一个易抖动的下游接口加上熔断,这篇文章就没白看。下一步,挑一个你觉得最心虚的概念,去你当下的代码里找找对应的坑,改掉它,你的高可用功底就又扎实了一层。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进