资讯动态

软件架构深度解析:单体、微服务、事件驱动等八大架构选型指南

发布时间:2026/10/20 21:42:45 来源:尧图企业网站定制
1. 先讲清楚架构到底解决什么问题1.1 架构不是画图而是约束说到现代软件开发中常用架构很多人的第一反应是打开画图工具画一堆方块、箭头、数据库图标然后开始争论微服务是不是比单体高级。我做了十几年后端开发和架构设计见过不少团队在架构上反复折腾也踩过很多坑今天想换个角度把这事聊透架构不是一张漂亮的架构图而是一组约束是你在响应速度、一致性、成本、维护性之间做出的取舍。为什么这么说因为架构的本质是决策而且是那种改了代价极大的决策。你选了一个分层结构后续写代码就得遵守依赖方向你选了微服务就得承担网络调用、数据一致性、链路追踪这些额外成本。架构图只是决策的可视化结果真正决定系统命运的是背后那一连串权衡。比如订单系统要实时库存扣减用强一致的本地事务还是最终一致的消息补偿这就是架构决策它直接决定了你在双十一高峰能扛多少流量也决定了出问题时你要赔上多少个通宵。所以这篇文章不聊玄学把单体、分层、微服务、事件驱动、CQRS、Serverless 这些常见架构从头到尾梳理一遍说清楚每个架构解决什么问题、引入什么成本、适合什么团队最后给一套可以照抄的选型方法。适合正在做系统设计的开发者、被分布式问题折磨的运维同学以及准备给团队做技术规划的负责人。1.2 常用架构的家族谱系先给这些架构排个序很多混乱都源于概念层次没分清。单体、微服务、事件驱动、CQRS 这些词其实不是一个维度上的东西有的说的是部署形态有的说的是代码组织方式有的说的是数据读写模型。把它们混在一起讨论永远吵不出结果。架构核心维度一句话定位单体架构部署形态一个应用包含所有功能一次构建、一次部署分层架构代码组织表现、业务、数据访问按层次划分关注点分离整洁/六边形架构代码组织让业务核心不依赖框架和外部细节依赖方向由外向内微服务架构部署形态按业务边界拆成多个可独立部署的服务事件驱动架构交互方式服务之间通过异步事件通信削峰填谷、解耦上下游CQRS数据模型读和写分离各自使用最合适的模型和存储事件溯源数据模型不存当前状态只存状态变更事件流重放即还原Serverless运行形态按需运行、按调用计费连服务器运维都省掉后面每一节都会拆开细讲。有一点先说明这些架构不是互斥的一个复杂系统完全可以同时是模块化单体 分层内部结构 关键链路事件驱动没必要非此即彼。你真正要练的是搞清楚每个架构在什么条件下成立、在什么条件下开始痛。2. 单体架构被低估的默认选项2.1 单体为什么被低估现在一说单体很多人语气里就带着鄙视好像不拆微服务就不够现代。但我要说句大实话单体是现代软件开发里被低估最严重的架构。它不是落后而是默认。绝大多数业务系统在团队规模小于两个披萨、用户量还没到百万级、发布节奏还在每天几次以内的时候单体就是最优解。为什么因为单体的成本结构太清晰了。一台进程一个数据库连接池事务直接走数据库本地事务日志打在一个文件里Debug 直接断点打进去链路都不用追。出了问题看日志、看监控、回滚发布三步搞定。这个效率是微服务在同等规模下给不了你的。单体的真正问题从来不是一个进程放不下代码而是边界腐化。代码量大了以后如果没有清晰的模块边界就会变成一团互相乱引用的意大利面下单逻辑里直接 new 了个物流对象支付模块偷查了订单表工具类里什么业务都有。这时候的问题不是单体本身而是没有纪律。2.2 模块化单体的落地姿势我自己经历过一个项目一个单体应用代码量到五十万行十八个人维护发布还是能控制在二十分钟内关键靠的就是模块化纪律。讲几个可以照抄的做法第一包结构按业务模块划分禁止按技术类型划分。比如订单、用户、支付、库存各建一个模块包模块内自己管 controller、service、dao。你要是按 controller、service、dao 分层建包业务逻辑就会散落在各个技术层里改一个需求要跨四个包边界等于没有。第二模块间只允许通过模块暴露的接口交互禁止跨模块直接访问数据库表。订单模块要读用户信息只能调用户模块的接口。这样数据库 schema 的改动就不会连锁炸掉所有模块。说实话能做到这一条的单体维护体验已经超过一半没拆干净的微服务了。第三用 ArchUnit 这类工具在 CI 里强制校验依赖方向。人靠自觉不靠谱我在代码审查里被漏掉过无数次顺手引了个不该引的类。后来把架构规则写进自动化测试谁往错误方向引依赖构建直接红。这个习惯花了半天时间配置换来了两年的清净。// 模块接口示例订单模块只依赖这个接口不关心用户模块内部实现 public interface CustomerService { Customer getCustomerById(String customerId); }2.3 什么时候才需要拆出去模块化单体做到位之后你的系统已经比大多数微服务健康。那什么时候才真的需要拆我总结出三个信号全部命中才值得动手团队规模达到 7~10 人以上代码变更频繁互相冲突一个需求要协调三个组。发布频率被拖垮某个模块每周要发三次另一个模块一个月发一次但被迫跟着一起发布风险被无限放大。出现独立的扩展需求比如某个模块的流量是其他模块的十倍但单体必须整机扩容成本不可接受。注意这三个信号说的是痛点能不能被具体描述出来而不是代码有十万行了感觉该拆了。我见过一个团队五个人日活五百硬拆了十几个微服务结果一半时间在配网关、传链路 ID、处理消息重试。拆微服务前先问自己你要解决的是痛点还是焦虑3. 分层与整洁架构把代码组织成可维护的形态3.1 三层架构的正确理解无论你用单体还是微服务一个服务内部的代码组织通常还是逃不开三层架构表现层、业务逻辑层、数据访问层。这个概念人尽皆知但落地的时候九成团队会掉进一个坑——把所谓三层做成了数据库的四层。具体表现就是Controller 里堆业务判断Service 里全是拼接 SQLRepository 里躺着几百行 if-else。职责分层的目标永远是让复杂业务可以被理解但很多人理解成了每个层都必须有一堆代码。正确的状态是业务规则尽量沉淀在 Service 层Controller 只做参数校验和协议转换数据访问层只做持久化映射不承载业务判断。另外还要分清两种业务组织风格事务脚本和领域模型。事务脚本就是 Service 方法里一步步写流程适合业务简单的系统直白好维护领域模型是把业务规则放进对象和行为里适合业务复杂、规则多变的核心系统。我建议一般团队别一上来就上领域驱动设计先把事务脚本写清楚等业务复杂到 Service 方法超过两百行、if-else 满天飞的时候再逐步往领域模型迁移。3.2 整洁架构与依赖规则整洁架构也叫洋葱架构和六边形架构本质上是一回事把系统的核心业务逻辑放在最中心数据库、消息队列、外部接口这些技术细节放在最外层依赖方向永远由外向内。用生活类比就是你的业务规则是房子的承重墙数据库只是水管电线水管电线怎么换都不该动承重墙。核心规则只有一条依赖不能越过边界向内。外层可以依赖内层内层绝不能依赖外层。也就是说订单核心逻辑不应该引用任何框架的注解不应该 import 数据库驱动不应该知道消息是 Kafka 还是别的什么。它只依赖自己定义的接口至于接口的实现是谁由外层在组装的时候注入进来。这样做的好处我在一个支付系统项目里体会很深。当时要换数据库从 MySQL 迁到另外一套存储牵一发动全身的系统不少但我们这个按整洁架构整理的模块改动只涉及最外层仓储实现核心计算逻辑一行没动测试用例全部原样跑过。这种安全感是代码能跑就行的团队体会不到的。3.3 依赖倒置怎么落地依赖倒置是整洁架构的技术基石简单说就是高层模块定义接口低层模块实现接口。注意方向接口的所有权在高层不在低层。很多项目把接口定义在 DAO 层让 Service 去实现这就反了。正确做法是 Service 需要什么就定义什么接口数据访问层去实现这个接口。现代开发里控制反转容器依赖注入框架让这件事做起来非常顺手。Spring 这类框架帮你把实现类实例化好在运行时塞给接口引用。但我要泼一盆冷水依赖注入不是银弹很多人用它只是为了让代码不 new 对象结果随手一搜还是上百个接口只有一个实现类为抽象而抽象反而增加理解成本。// 正确的依赖方向接口定义在业务侧数据库实现反着依赖业务接口 public interface OrderRepository { Order findById(String orderId); void save(Order order); } // 这个实现类依赖 MyBatis/JPA 等框架属于外层实现业务层不关心 Repository public class MyBatisOrderRepository implements OrderRepository { // 框架相关的代码都圈在这个类里 }我的实操建议是当一个接口只有一个实现类、且看不到第二个实现的可能就不要急着抽接口先把代码写扎实等真正出现多个实现或测试需要替身的时候再通过 IDE 快速抽取。重构成本远比你想象的低过早抽象的维护成本却很高。4. 微服务架构拆分的红利与代价4.1 微服务到底拆什么微服务架构的核心价值不是代码变小了而是独立部署、独立扩展、独立故障域。拆对了你的团队可以各自发布、各自扩容、互不拖累拆错了就是把一个单体的问题放大成几十个服务的网络问题。很多人拆微服务时有个误区按技术层次拆。把控制层拆成一个服务业务层拆成一个服务数据访问再拆一个结果每次业务请求要走三次网络调用延迟翻了几倍事务被拆散团队之间互相等接口。这是最典型的失败模式。正确的拆法是按业务能力拆。订单、支付、库存、用户每个都是完整的业务闭环数据自己管服务自己发。康威定律说得很清楚系统架构会镜像组织沟通结构。所以你拆服务之前先想清楚你的团队怎么分组——两个小组互相有大量协作需求的时候不要把他们的代码拆到两个服务里否则每天的工作就是在联调。4.2 服务拆分的三个可落地原则第一数据边界是最后防线。服务之间可以互相调用接口但绝不能共享数据库表。一旦两个服务读写同一张表它们就耦合死了你想独立演进表结构却把你们焊在一起。这是拆分时必须守住的底线破了就等于没拆。第二从调用链路长、变更频率低的地方开始拆。比如用户基础数据、商品主数据这种被很多服务依赖的基座先独立出来收益最大风险也相对可控。千万不要拿核心交易链路练手下单链路跨三四个新建服务中间任何一个不稳定整个业务全挂一次就把团队信心拆没了。第三一个服务至少要对应一个可以自治的团队。服务拆出来是要有人养的没人养的服务就是技术债。拆之前想想康威定律如果你们团队总共就六个人硬拆十个服务人均维护两个半光升级框架就够喝一壶。4.3 数据一致性与分布式事务最大的坑拆了服务最痛的往往是数据一致性。单体里一个本地事务就能保证扣库存和创建订单同时成功微服务里这是两个服务的两次写入天然没有事务保护。业界方案就那么几种我直接给结论方案一致性实现成本适用场景两阶段提交2PC强一致高极少用性能差、协调者易单点本地消息表最终一致中内部系统最经典稳妥的方案事务消息最终一致中使用支持事务消息的中间件时首选SAGA 补偿最终一致高跨多服务长流程需要完备补偿逻辑实际项目里本地消息表和事务消息是我用得最多的。思路都一样业务操作和发消息放在同一个本地事务里写业务数据的同时写一条待发送消息然后由一个后台任务扫表发消息发成功的删掉记录。消息中间件即使挂了重启后还能从表里捞出来继续发不会丢。// 伪代码业务操作与消息落库同事务保证不丢事件 Transactional public void createOrder(Order order) { orderMapper.insert(order); outboxMapper.insert(new OutboxMessage(order.created, JsonUtils.toJson(order))); }注意一个关键词最终一致。微服务架构下的数据做不到强一致你必须让业务方接受订单状态在几秒内最终一致这个设定。产品和技术对不上这个认知技术方案再漂亮也是白搭。4.4 微服务的运维成本清单讲完红利讲代价微服务的隐性成本绝大多数团队都没提前算过账。你至少要添置这些服务发现与注册中心服务实例上下线要能被感知。统一配置中心几十个服务的配置不能靠改环境变量。API 网关认证鉴权、路由转发、限流统一收敛。链路追踪系统跨服务的请求要有唯一的追踪 ID 串起来。监控告警体系每个服务的基础监控、业务监控、日志采集。CI/CD 流水线几十个独立部署单元每套都要有自动化发布能力。这些基础设施单靠开源组件搭一套能跑的至少两三个熟练工的半年工作量。小团队不是不能上微服务而是上之前要算清楚你拆服务省下的那点发布冲突时间够不够支付这些运维成本。这也是为什么我一直强调模块化单体 清晰边界是很多团队更务实的微服务平替方案代码可以按微服务的思路拆好模块部署仍然合一。等真需要独立扩展了把模块直接复制出来加一层接口立刻就是微服务。5. 事件驱动架构用异步换来弹性5.1 事件驱动的两种形态事件驱动架构这几年越来越常见但很多人把它简单理解成用消息队列解耦其实不够。事件驱动有两条路线一条是事件通知。服务 A 在状态变更时发一个事件通知服务 B 收到后自己处理。A 不关心 B 的处理结果也不等它。典型场景订单创建后发事件通知服务、积分服务、审计服务各自去响应。好处是上游不用串行等待削峰填谷的能力很强代价是流程断点化了出问题时得有对账和补偿兜底。另一条是事件溯源。不存当前状态只存发生了什么事当前状态由事件重放出来。这个后面单独讲它要付出的代价远大于普通事件通知。日常中最实用的是前者。我做过一个电商项目下单高峰期流量是平时的几十倍如果用同步调用订单服务会被下游的积分、物流通知拖垮。改成事件驱动后订单服务只管写自己的订单库、发一条order.created事件所有下游各自消费高峰期下游处理不过来就自动堆积永远不会反向拖垮上游。这个改造直接让下单接口的 p99 延迟从 800ms 降到了 150ms。5.2 消息语义与幂等设计必修课用事件驱动第一个要建立的认知是消息中间件几乎都是至少一次投递语义。也就是说你发一条消息消费者可能收到一次、两次甚至更多。如果消费逻辑不是幂等的重复消息就会造成重复扣款、重复加积分这种事故。幂等设计有几个常见套路我按推荐度排序业务唯一键去重。每条消息里带一个业务 ID比如订单号消费端在本地建唯一索引插入冲突就说明已经处理过。状态机校验。比如订单只能从待支付到已支付重复消息来的时候发现状态不是待支付直接丢弃。数据库操作天然幂等。比如update t set statusPAID where order_idxxx and statusPENDING受影响行数为 0 就说明不用再处理。// 消费端幂等处理用唯一索引挡住重复消息 public void onOrderCreated(OrderCreatedEvent event) { try { int inserted rewardRecordMapper.insertIgnoreDuplicate( new RewardRecord(event.getOrderId(), event.getUserId()) ); if (inserted 0) { return; // 重复消息已经处理过了 } // 继续后续业务 } catch (DuplicateKeyException e) { return; } }这里有个细节很多人栽过幂等判断和业务操作必须在同一个事务里。你先查了唯一键不存在然后执行业务写另一个线程同时插了同样的记录你就会重复执行。正确做法是靠数据库层的唯一索引兜底而不是先查后写的应用层判断。5.3 事件总线选型要点消息中间件选型是个老话题市面上的主流产品我简单对比一下选型时的关注点吞吐量要求如果每秒几万的量级选择支持分区高吞吐的那类如果每日百万来百万去常规消息队列完全够。顺序性同一个订单的事件必须按顺序消费选型时要么用分区键保证写入有序要么接受乱序后在应用层排序。很多场景普通乱序 业务幂等就能解决强行保序会牺牲性能。可靠性消息丢一条是大事选型时看刷盘机制、副本机制和确认机制。消费端记得关闭自动确认处理成功后再手动确认否则业务没处理完消息先丢了。堆积能力下游消费慢是常态中间件要能扛住消息堆积并且堆积后不影响延时。我的建议是如果团队没有专门的中间件运维经验用成熟的、文档多的开源方案别自己造消息队列如果业务场景就是简单的异步通知甚至可以先基于数据库轮询实现一个事件表 定时扫描的方案等量上来了再替换。技术选型最忌讳一步到位买一堆你根本用不上的能力。6. CQRS 与事件溯源特定场景的重武器6.1 CQRS 什么时候值得用CQRSCommand Query Responsibility Segregation核心是读写分离写操作走命令模型读操作走查询模型两个模型可以完全独立优化。看名字感觉很高端实际上有一个特别常见的现实驱动力你的业务写入逻辑很复杂校验规则一堆而查询需求五花八门要各种维度的报表和列表。用一个模型同时服务两端往往两头都做得差。典型的适用场景我举两个。第一个是互联网电商的后台报表业务写入是订单的创建、支付、取消这些强一致操作查询却是按时间段、按地区、按商品类目聚合的销量报表这种聚合查询如果直接压在主库上不仅慢还会拖垮交易。第二个是高并发读取系统写入端接受用户提交读取端每天重放几百万请求。这时候把读模型单独建一套存成最适合查询的宽表甚至缓存性能立竿见影。但要强调CQRS 不是一个用了就高级的架构它是不对称的。如果你的读写场景差异不明显强行拆分等于把一套逻辑写两遍维护翻倍。判断标准很简单你是不是长期在读模型上做和写模型完全不同的优化如果是才值得拆。6.2 事件溯源的复杂度评估事件溯源和 CQRS 常一起出现因为它们配合得特别好一个把状态写成事件一个把事件读出来投影成多种读模型。事件溯源的核心思想是把系统状态的所有变更保存为不可变的事件序列当前状态由事件重放计算而来。这套思想有几个真实好处。一是完整的审计日志过去发生了什么每个事件都记录在案没有秘密。比如账户系统每一笔余额变动都是有据可查的事件合规审计时直接翻事件流。二是时空旅行你想知道上周三那个时点的订单状态把事件流重放到上周三即可不用靠还原备份。三是模型演进灵活你可以从同样的历史事件里投影出月度报表和实时风控两种完全不同的读模型。但代价是巨大的而且藏得很深事件格式版本化事件一旦发布就不可变字段改名、结构变化都要处理老事件的兼容需要事件升级策略。重放性能超长的事件流重放很慢需要定期生成快照快照 增量事件重放又是一套机制。模型意外复杂领域里一堆更新当前状态为的操作要改成追加一条事件很多开发者的思维习惯需要硬反转过来。我的结论是事件溯源不是给普通业务系统准备的对于大多数团队用它等于同时背上了最终一致性 事件版本管理 重放性能三座大山。除非你的业务有强审计需求比如金融账户流水或核心状态机确实需要完整溯源否则老老实实用状态 事件通知就够了。用闹钟类比你只需要知道现在几点没必要把每一次秒针跳动都记下来。7. 架构选型一套可复用的决策方法7.1 主流架构横向对比很多人纠结哪个架构最好问这个问题本身就是错的。架构没有最好只有在给定约束下最合适。我做了张对比表核心是看四个维度适用规模、实施复杂度、运维成本、演进灵活度。架构适用规模实施复杂度运维成本演进方向单体含模块化团队 10 人日活百万级以下低低可平滑演进到微服务分层/整洁任意规模服务内部中低提升可维护性不改变部署微服务团队 10 人独立扩展需求明确高高需配套完善的基础设施事件驱动峰值流量波动大、下游多样中中可与单体或微服务叠加CQRS/事件溯源特定复杂场景读写分化激进高高引入需谨慎回退代价大7.2 选型五步法我这些年帮不同团队做架构选型最后沉淀一套五步决策法分享出来。第一步先列非功能需求再谈架构。你要知道系统对可用性四个九还是两个九、一致性强一致还是最终一致、性能p99 多少、成本预算多紧的真实要求。这些数字定下来架构选择空间就收敛了一大半。第二步评估团队真实能力。不是团队模板上的能力而是当前这群人里有几个人真正调过消息队列的堆积、排查过分布式事务问题没有的话任何复杂架构都是给自己埋雷。第三步从最小可行架构开始。所有新项目的第一个版本都应该尽量简单单体 分层 明确的模块边界。这不丢人这是给自己留演化空间。架构应该像挤牙膏一样被真实流量和业务复杂度逼着演进而不是在纸上先画出来。第四步量化拆分/升级的收益。你说要拆微服务那请回答拆完能独立发布吗能单独扩容吗能减少多少发布冲突如果答案全是感觉会更好那这个拆法大概率不成立。第五步写架构决策记录。选了什么、放弃什么、基于什么理由写成文档放在代码库里。三个月后的开发者看到这份记录就不会又因为网上说微服务好再折腾一轮。我见过的无效重构一半是因为没人记得当初为什么这么定。7.3 演进优于革命最后一条原则可能是我最想强调的软件架构应该持续演进不要动不动推倒重来。业界成熟的做法是绞杀者模式在旧系统旁边建新系统通过网关逐步把功能流量从旧系统切到新系统一块一块地替换直到旧系统自然消亡。这个过程新老系统并存风险可控每一块替换完成都有实际的验证节点。比如你要把一个单体拆成微服务正确的姿势不是花三个月重构完再发布而是把单体当成承载机先把用户模块完整地拎出来独立部署老单体里的旧代码先留着网关把用户请求导向新服务新服务里的数据靠同步任务从老库里搬。验证稳定后再删掉老代码。就这样一个模块一个模块地绞杀每一块失败都能回滚团队信心不会被一次双态运行失败打崩。演进式设计的反面是我见过不少团队犯的错架构评审会上画了一张超大的目标架构图然后宣布三个月内全部迁移完毕。结果通常是迁移到一半业务需求变了团队被夹在旧系统和新系统之间两头都烂。记住架构是长出来的不是规划出来的。这跟养植物一样你要提供合适的土壤团队能力、肥料基础设施、修剪架构治理但你不能规定它每天长多高。8. 常见问题与排查技巧实录8.1 分布式环境下的经典故障把几个我亲手排查过的经典故障列出来给大家当排雷手册。故障一超时重试导致雪崩。服务 A 调服务 B 超时A 抛出异常后业务代码重试B 此时已经高负载A 的重试又加剧了 B 的压力最后整个调用链被打挂。这类事故的根因不是B 挂了而是A 的重试策略没有上限。解决思路是给重试加总次数限制、指数退避以及必须配置熔断器连续失败一定次数后快速失败而不是继续压上去。故障二消息消费端幂等失效。业务逻辑判断了如果订单状态已支付就返回结果还是重复加了积分。后来查出来是先查状态再更新这段逻辑没有放在同一个事务里两个并发请求同时查到未支付都往下执行了。最终靠唯一索引兜底才解决。幂等设计记住一句口诀数据库约束兜底应用逻辑优先。故障三分布式事务悬挂。用了事务消息方案本地事务提交了但消息一直没发出去中间件抖动导致下游永远不知道这单已经创建。这类问题要靠对账任务兜底定时扫描本地消息表里滞留过久的记录重新投递。很多团队不做对账出事全靠人工扫数这在大促期间是致命的。8.2 链路追踪与日志关联分布式排查的第一个基本功是把一次请求经过的所有服务串联起来。用过一个蠢办法让各个服务在日志里打各自的消息 ID然后人工 grep 拼接简直噩梦。后来上了标准的链路追踪核心动作是在网关生成全局唯一的 traceId通过 HTTP 头或消息头往下游传递所有服务在日志里打印 traceId。这个改造本身不难难的是让每个语言栈、每个框架都自动带上这个 ID。成熟方案是接一套标准化探针或者至少在所有入口做一次 MDC日志诊断上下文注入让日志框架自动带上 traceId。有了它排查跨服务问题等于从大海捞针变成按图索骥。// 服务入口统一注入 traceId日志自动带上 MDC.put(traceId, traceId); try { // 调用链上的所有日志都会打出 traceId log.info(处理订单创建逻辑); } finally { MDC.remove(traceId); }另一个实用技巧消息消费端的 traceId 必须在消费线程里重新绑定。很多异步消费链路的追踪断掉就是因为拿到消息后新线程里的 MDC 是空的导致下游日志全成了孤儿。8.3 重试与超时参数怎么配才不雪崩服务调用的超时和重试参数是每个开发者都会写但很少有人认真想过的配置。我见过最大的一个坑所有服务统一设 3 秒超时、重试 3 次结果一次数据库慢查询拖到 2.9 秒下游全部跟着重试瞬间打爆中间件。正确思路是分层设置超时要比上游更短。你调的是 AA 还要调 B你的超时就得覆盖 A B 的整体链路。一般调用链每多一层超时时间就要按比例收紧避免底层慢查询把整条链路拖死。重试只对幂等接口开放。读接口随便重试写接口必须确认幂等。不知道是否幂等就重试等于把重复扣款按钮交给了运气。用指数退避 随机抖动。每次重试间隔翻倍并加上随机数避免集群内所有实例在同一秒集体重试把下游砸出二次故障。// 重试策略建议指数退避 抖动且设总次数上限 public T T retryWithBackoff(CallableT task, int maxAttempts) { long baseDelay 200; // 初始 200ms for (int attempt 1; ; attempt) { try { return task.call(); } catch (Exception e) { if (attempt maxAttempts) { throw e; } long delay baseDelay * (1L (attempt - 1)) ThreadLocalRandom.current().nextLong(100); Thread.sleep(delay); } } }熔断器这块再补一句比重试不重试更重要的是快速失败。当依赖方已经处于故障状态继续调用和重试都是浪费资源此时应该直接把请求短路。经典的熔断状态机——关闭、打开、半开——值得每个人手写一遍流程走一遍比只看文档强得多。作为亲身踩过这些坑的人最后再说一句掏心窝的话架构没有银弹只有取舍。我给团队做技术评审的时候最常问的不是你用了什么架构而是你这个架构引入了什么新的成本你打算怎么付这个成本。能回答清楚这个问题的团队用什么架构都不会太差回答不清楚的团队哪怕把全世界最先进的模式堆上去也只是把问题藏得更深而已。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑