从一次下单开始,彻底理解 Spring Cloud 与微服务:调用、容错、一致性、高并发到部署 📅 2026/7/22 13:39:13 hello大家我是逆境不可逃微服务真正难的地方不是把一个项目拆成很多个 Spring Boot 应用而是拆开以后怎样处理网络故障、数据一致性、重复请求、流量高峰、监控排障和持续发布。前言先理解问题再记组件刚接触 Spring Cloud 时很容易看到一长串名词Nacos、OpenFeign、Gateway、Sentinel、Redis、RocketMQ、Seata、Docker、Kubernetes……如果只是逐个记配置学完以后依然不知道这些组件为什么要出现也不知道实际项目应该把它们放在哪里。更容易理解的方式是从一条真实业务链路出发客户端 → 网关 → 订单服务 → 商品服务 → 库存服务 → 支付服务 → 消息队列 → 履约和通知服务这篇文章就围绕“一次下单”展开。每增加一个组件都先回答四个问题没有它会出现什么问题它解决了什么问题它大致怎样工作使用它又会增加什么成本文章不依赖某个具体项目也不会堆砌完整 Demo重点是建立一套能够迁移到真实项目中的微服务思维。一、为什么会从单体走向微服务1. 单体应用并不落后假设一个商城项目最初只有一个应用mall-application ├── 商品模块 ├── 订单模块 ├── 库存模块 ├── 支付模块 └── 用户模块所有模块一起编译、一起启动、一起发布这就是单体应用。单体的优势非常直接本地方法调用快调试方便一个数据库事务就能保证多张表同时成功或失败部署、监控和测试都比较简单小团队沟通成本低。因此业务刚起步、团队人数不多时边界清晰的模块化单体往往比微服务更合适。2. 单体什么时候开始出现压力随着项目和团队变大问题可能逐渐出现修改商品模块却要重新发布整个商城支付模块流量很小商品模块流量很大却只能一起扩容一个模块发生内存泄漏整个应用都受影响多个团队修改同一个代码库发布节奏互相阻塞项目启动越来越慢测试范围越来越大。这时可以按业务能力拆分product-service 商品服务 order-service 订单服务 inventory-service 库存服务 payment-service 支付服务拆分后每个服务可以独立开发、部署和扩容。但原来的本地调用inventoryService.reserve(orderId,skuId,quantity);会变成网络调用order-service → HTTP → inventory-service网络调用会新增一系列问题库存服务部署在哪台机器有三个库存实例时应该调用哪一个调用超时后对方到底有没有执行成功订单写入成功、库存扣减失败怎么办一个服务变慢会不会拖垮整条链路请求经过多个服务后怎样定位失败位置Spring Cloud 的主要价值就是提供一组解决这些分布式问题的工具。二、Spring、Spring Boot、Spring Cloud 到底是什么关系这几个名称经常一起出现可以把它们理解成不同层次的能力。1. Spring Framework基础能力Spring Framework 提供最核心的能力IoC 和依赖注入AOP事务管理Web MVC数据访问抽象。它解决的是“Java 应用应该怎样组织对象和基础代码”。2. Spring Boot快速构建一个应用Spring Boot 在 Spring Framework 之上提供自动配置Starter 依赖内嵌 Web 服务器外部化配置健康检查和运行指标。它解决的是“怎样快速构建并运行一个独立服务”。Spring Framework 基础零件 Spring Boot 快速组装一台能运行的机器3. Spring Cloud管理一群服务当系统中出现很多 Spring Boot 应用后需要解决服务注册与发现远程调用负载均衡配置管理API 网关熔断、重试和限流分布式链路追踪。Spring Cloud 并不是一个单独的“超级框架”而是一组分布式系统工具的集合。4. Spring Cloud Alibaba一套常见实现Spring Cloud 定义和整合了很多微服务能力Spring Cloud Alibaba 提供了一组常见实现例如能力常见组件注册发现、配置管理Nacos流量治理Sentinel消息队列RocketMQ分布式事务Seata组件不是越多越好。是否引入某个组件要看项目是否真的存在对应问题以及团队能否承担它的部署、监控和维护成本。三、服务注册、发现、负载均衡与 OpenFeign1. 服务和实例不是同一个概念inventory-service表示一个逻辑服务它可以有多个运行实例inventory-service ├── 10.0.0.11:8080 ├── 10.0.0.12:8080 └── 10.0.0.13:8080服务是业务身份实例是真正运行的进程。2. 为什么需要注册中心如果把库存地址写死http://10.0.0.11:8080/inventory/reservations实例扩容、迁移或重启后IP 可能变化调用方必须跟着修改配置。注册中心相当于动态通讯录库存实例启动 → 向注册中心登记地址 订单服务调用库存 → 根据 inventory-service 查询实例列表 → 选择一个健康实例 → 发起 HTTP 请求Nacos、Eureka、Consul 都能承担类似职责在 Kubernetes 环境中Service 和集群 DNS 也能够提供服务发现能力。3. 负载均衡发生在哪里订单服务拿到三个库存实例后还要选择一个请求1 → 实例A 请求2 → 实例B 请求3 → 实例C这就是负载均衡。它只能分散流量不能保证业务请求一定成功也不能自动解决慢实例和数据一致性问题。4. OpenFeign 做了什么直接手写 HTTP 请求需要拼接地址、序列化参数、处理响应。OpenFeign 可以把远程接口声明成 Java 接口FeignClient(nameinventory-service)publicinterfaceInventoryClient{PostMapping(/inventory/reservations)ReserveResultreserve(RequestBodyReserveCommandcommand);}业务代码看起来像普通方法调用ReserveResultresultinventoryClient.reserve(command);但必须牢记它本质上仍然是网络调用Java 方法代理 → 服务发现 → 负载均衡 → HTTP 请求 → 序列化与反序列化 → 返回或异常不能因为代码长得像本地方法就忽略超时、重试、幂等和失败处理。四、配置中心和 API 网关1. 为什么需要配置中心少量服务时可以在每个应用中维护配置文件。服务变多后会遇到同一个配置散落在多个仓库测试、预发布、生产环境容易混淆修改配置需要重新打包不知道谁在什么时候改过配置。配置中心用于集中管理不同服务、不同环境的配置order-service-dev.yaml order-service-test.yaml order-service-prod.yaml适合外部化的内容包括下游地址和超时时间功能开关限流阈值部分业务规则。数据库密码、私钥等敏感信息不能当作普通配置随意传播应使用专门的密钥管理和访问控制。动态配置也不是任何值都能随时修改。线程池、连接池、序列化格式等配置如果没有安全的刷新机制运行时修改反而可能造成故障。2. 网关为什么要放在最前面没有网关时客户端需要知道每个服务的地址/product → 商品服务 /order → 订单服务 /payment → 支付服务有网关后客户端 → gateway → 具体服务Spring Cloud Gateway 中最重要的三个概念是Route请求要转发到哪里Predicate什么请求匹配这条路由Filter转发前后执行什么处理。spring:cloud:gateway:routes:-id:order-serviceuri:lb://order-servicepredicates:-Path/api/orders/**网关适合处理统一认证路由跨域限流traceId灰度流量标记。网关不适合堆放订单计算、库存判断等业务逻辑否则它会变成新的超级单体和性能瓶颈。五、超时、重试、幂等、熔断、限流和降级这些词经常一起出现但解决的问题不同。1. 超时不允许无限等待远程调用通常至少需要关注连接超时多长时间无法建立连接就放弃读取超时连接成功后多长时间没有收到结果就放弃。超时必须逐层收紧客户端超时 3000ms 网关超时 2500ms 订单服务总预算 2000ms 库存调用超时 500ms 数据库查询超时 200ms如果网关 2 秒就放弃订单服务却愿意等待库存 5 秒客户端已经离开服务仍在占用线程和连接。2. 超时不等于失败这是分布式系统最重要的认识之一订单服务发送“预占库存”请求 → 库存服务预占成功 → 返回响应时网络中断 → 订单服务超时订单服务只知道“没有收到结果”并不知道库存是否成功。因此结果可能是成功、失败也可能未知。未知状态需要通过业务编号查询、异步确认或定时对账处理不能简单当成失败。3. 重试为什么危险如果一个下单请求被重试三次可能创建三张订单、扣减三次库存。重试只适用于操作已经幂等故障可能是暂时的有最大次数有退避和随机抖动没有超过总时间预算。参数错误、权限不足、库存不足等明确业务错误不应该重试。4. 幂等重复执行结果不变创建订单可以由客户端传递幂等键Idempotency-Key: 8b52f1...数据库增加唯一约束UNIQUEKEYuk_order_request(user_id,request_id)第一次请求创建订单重复请求返回原订单。数据库唯一约束是非常重要的最终防线。5. 熔断、限流、隔离和降级的区别手段要解决的问题超时不无限等待重试重新尝试短暂失败限流不让过量请求进入隔离一个资源耗尽不拖垮全部资源熔断下游持续失败时暂停调用降级暂时牺牲非核心能力熔断器通常有三个状态CLOSED 正常调用 OPEN 错误过多快速失败 HALF_OPEN 放少量请求试探恢复情况例如推荐服务故障时可以降级为空推荐但支付状态不能随便返回一个伪造成功结果。降级结果必须符合业务语义。六、微服务的数据边界与分布式事务1. 为什么不建议共享数据库理想的数据所有权是order-service → order_db inventory-service → inventory_db payment-service → payment_db如果订单服务直接修改库存表两个服务就无法独立演进表结构也会变成隐含接口。服务之间应该通过 API 或事件协作而不是跨库随意读写。2.Transactional为什么管不了远程调用下面的代码看起来在一个事务中TransactionalpublicvoidcreateOrder(){orderRepository.save(order);inventoryClient.reserve(command);}但 Spring 本地事务只能控制当前数据库连接不能让远程 HTTP 服务自动参与同一个原子事务。还要避免在数据库事务中长时间等待远程调用因为它会占用数据库连接和锁。3. 用状态机表达业务过程订单状态可以设计为STOCK_CONFIRMING ├── 库存成功 → PENDING_PAYMENT └── 库存不足 → CANCELED PENDING_PAYMENT ├── 支付成功 → PAID └── 超时未支付 → CANCELED PAID → FULFILLING → COMPLETED更新状态时检查旧状态UPDATEordersSETstatusPAIDWHEREid:orderIdANDstatusPENDING_PAYMENT;如果影响行数为 0说明回调可能重复或者订单状态已经不允许支付。4. 最终一致性跨服务业务常用的组合是本地事务 状态机 可靠消息 幂等 补偿 对账例如订单超时未支付订单服务关闭订单 → 发送 OrderCanceled 事件 → 库存服务释放预占库存如果释放失败消息可以重试多次失败进入异常队列或对账任务。5. Saga、TCC 和 Seata 应该怎样理解Saga每个服务执行自己的本地事务失败后执行相反的补偿动作TCC业务显式提供 Try、Confirm、Cancel 三个阶段控制力强但开发成本高Seata提供多种分布式事务模式降低部分接入成本但不能消除锁、性能、补偿和运维代价Outbox把业务数据与“待发送事件”写入同一本地事务再由后台可靠投递。对多数互联网业务不要一开始追求跨服务强一致而应先确认业务是否能接受“处理中”和最终一致。七、消息队列异步、解耦和削峰1. 什么时候用同步调用如果当前步骤必须立即知道结果例如下单时判断库存是否充足可以使用同步调用订单服务 → 库存服务 → 返回结果优点是流程直观缺点是调用方需要等待双方运行状态耦合。2. 什么时候用消息支付成功后的通知、积分、履约等操作可以异步处理支付服务 → PaymentSucceeded 事件 ├── 订单服务 ├── 库存服务 ├── 履约服务 └── 通知服务支付服务不需要等待短信发送完成也不需要知道未来会增加多少消费者。3. 消息为什么会重复生产者发送消息后Broker 已经保存成功但确认响应丢失生产者会再次发送消费者处理成功后在确认消费前宕机消息也会再次投递。因此工程上经常采用“至少一次投递”并让业务消费幂等KafkaListener(topicspayment-succeeded)publicvoidconsume(PaymentSucceededEventevent){if(processedEventRepository.exists(event.eventId())){return;}orderService.markPaid(event.orderId());processedEventRepository.save(event.eventId());}业务更新与已消费记录应放在同一个本地事务中。4. Outbox 解决什么问题直接“更新数据库再发消息”存在宕机窗口数据库提交成功 → 应用宕机 → 消息没有发送Outbox 做法同一个本地事务 1. 支付记录更新成功 2. 插入待发送事件 后台任务 3. 读取事件 4. 发送到 MQ 5. 标记已发送它保证业务数据和待发送事件一起保存。发送仍可能重复因此消费者幂等依然不能省略。5. MQ 不是无限仓库如果生产速度持续大于消费速度生产 5000 条/秒 消费 3000 条/秒 每秒积压 2000 条MQ 只能把峰值暂时变成积压不能凭空增加数据库处理能力。必须监控积压量、最老消息年龄、消费失败和清空积压所需时间。八、Redis 缓存、高并发和分布式锁1. 高并发为什么会突然雪崩可以用一个简单关系理解并发请求数 ≈ QPS × 平均响应时间1000 QPS、平均 200ms大约有 200 个请求同时处理中如果数据库变慢到 2 秒同时处理的请求会增加到约 2000 个。数据库变慢 → 连接池耗尽 → 应用线程阻塞 → 网关请求堆积 → 客户端超时重试 → 流量进一步放大高并发设计首先要保证系统过载时能够有控制地拒绝请求而不是让所有请求一起超时。2. 缓存旁路模式Productproductredis.get(productId);if(productnull){productproductRepository.findById(productId);redis.set(productId,product,randomTtl());}returnproduct;更新时常见策略是先更新数据库再删除缓存并对删除失败进行重试或消息补偿。数据库通常仍然是事实来源。3. 穿透、击穿和雪崩缓存穿透不断查询不存在的数据每次都访问数据库。可以缓存空结果、校验参数或使用布隆过滤器。缓存击穿一个热点 Key 过期大量请求同时查询数据库。可以使用主动刷新、单航班或短时间互斥重建。缓存雪崩大量 Key 同时过期或 Redis 整体故障。可以给 TTL 增加随机值、分批预热并准备限流和降级方案。4. 线程池不是越大越好假设 Web 线程池有 500 个线程数据库连接池只有 30 个连接30 个请求执行 SQL 470 个请求等待连接增大线程池没有提高数据库能力只是增加了等待、内存占用和超时数量。扩容应用时还要计算所有实例的总连接数。5. Redis 分布式锁多个应用实例中的synchronized互相不可见需要共享的锁状态SET lock:order:10001 owner-id NX PX 30000NXKey 不存在时才成功PX设置过期时间避免实例宕机后永久死锁owner-id标识锁的真实持有者。释放时不能直接DEL否则可能删掉别人后来获得的锁ifredis.call(GET,KEYS[1])ARGV[1]thenreturnredis.call(DEL,KEYS[1])elsereturn0end分布式锁只保证尽量互斥不等于幂等。防止重复下单优先使用唯一约束防止库存超卖优先使用条件更新UPDATEsku_stockSETavailableavailable-:quantityWHEREsku_id:skuIdANDavailable:quantity;只有无法通过唯一约束、状态机、乐观锁或原子更新解决时才考虑分布式锁。6. MQ 削峰数据库每秒只能创建 3000 个订单活动瞬间收到 20000 个请求时可以先进行轻量校验并写入队列请求 → 网关限流 → Redis资格校验 → MQ → 消费者匀速创建订单接口此时应该返回“排队中”而不是直接宣称订单已经创建成功。九、安全与可观测性1. 认证和授权不是一回事认证 Authentication你是谁 授权 Authorization你能做什么JWT 常用于携带用户身份和声明Header.Payload.Signature网关可以验证 Token、删除客户端伪造的身份头再注入可信用户信息。但资金、退款等重要操作仍应在业务服务内部进行授权检查。RBAC 可以表示用户 → 角色 → 权限除了用户访问还要考虑服务之间的身份认证、最小权限、密钥轮换和敏感数据脱敏。2. 日志、指标和 Trace 的区别类型回答的问题日志这一次请求具体发生了什么指标系统整体是否异常Trace请求经过哪些服务慢在哪里一次下单应贯穿以下标识traceId orderId paymentId eventId userId日志中不要输出密码、Token、身份证号等敏感数据。3. 应该监控什么技术指标QPSP95、P99 延迟错误率CPU、内存和 GC线程池和连接池Redis 命中率MQ 积压。业务指标下单成功率库存预占失败率待支付订单量支付回调延迟自动退款数量。Actuator、Micrometer、OpenTelemetry、Prometheus、Grafana 等工具分别帮助采集、传递、存储和展示这些信息但工具本身不能替代合理的指标设计。十、Docker、Kubernetes 与发布1. Docker 解决交付一致性传统部署常见问题是“本地能运行服务器不能运行”原因可能是 Java 版本、目录、参数和依赖不同。Docker 把应用和运行环境制作成镜像FROM eclipse-temurin:21-jre WORKDIR /app COPY target/order-service.jar app.jar USER 10001 ENTRYPOINT [java, -jar, app.jar]镜像是不可变模板容器是镜像的运行实例。生产环境应避免把密钥写进镜像不长期使用latest标签并尽量以非 root 用户运行。2. Kubernetes 解决实例管理Kubernetes 的核心思想是期望状态spec:replicas:3表示无论发生什么都希望系统维持三个实例。常见对象对象作用Pod运行容器Deployment管理副本、版本和滚动升级Service为变化的 Pod 提供稳定入口ConfigMap普通配置Secret敏感配置Ingress/Gateway外部流量入口HPA根据指标扩缩容3. 三种健康检查startupProbe 应用是否启动完成 readinessProbe 当前是否适合接收流量 livenessProbe 进程是否卡死需要重启不要把数据库是否正常直接作为存活检查。如果数据库短暂故障导致所有 Pod 同时重启问题会进一步扩大。4. 滚动升级与优雅停机滚动升级大致是旧 旧 旧 新 旧 旧 新 新 旧 新 新 新新实例通过就绪检查后才接收流量旧实例停止前还要停止接收新请求 → 等待正在处理的请求完成 → 停止消费者 → 关闭连接池 → 退出进程数据库变更要采用“扩展—迁移—收缩”先增加兼容结构再发布新代码和迁移数据最后删除旧结构。5. Kubernetes 和 Nacos 是否重复Kubernetes Service 和 DNS 已经能提供服务发现因此纯 Kubernetes 环境不一定还需要 Nacos 注册发现。Nacos 仍可用于配置中心、混合部署或非 Kubernetes 服务。同一种职责最好只有一个权威来源避免两套服务发现中的实例状态不一致。十一、用一次完整下单把所有知识串起来第一步网关接收请求POST /api/orders Authorization: Bearer ... Idempotency-Key: 8b52...网关完成认证、限流、路由和 traceId 传递。第二步订单服务保存商品快照订单不能永远查询商品当前价格否则商品涨价后历史订单也会变化。订单项需要保存下单时的名称、单价和数量快照。第三步创建确认库存中的订单本地事务创建 STOCK_CONFIRMING 订单 → 提交事务 → 调用库存服务预占不要在一个长数据库事务中等待远程服务。第四步库存原子预占UPDATEsku_stockSETavailableavailable-:quantity,reservedreserved:quantityWHEREsku_id:skuIdANDavailable:quantity;影响一行表示成功影响零行表示库存不足。预占记录还应通过订单号唯一约束实现幂等。第五步处理未知结果库存明确成功订单进入PENDING_PAYMENT明确不足订单进入CANCELED网络超时则保持STOCK_CONFIRMING由查询和对账任务确认真实结果。第六步支付平台回调支付服务需要验证签名、金额和商户订单号再通过条件更新保证重复回调不会重复处理。第七步支付事件异步扩散支付结果与 Outbox 事件在同一本地事务中保存后台发送PaymentSucceeded订单服务 → 改为 PAID 库存服务 → RESERVED 改为 DEDUCTED 履约服务 → 创建发货任务 通知服务 → 发送支付通知通知失败只重试通知不回滚已经成功的支付。第八步超时关闭与补偿未支付订单超时后通过条件更新变成CANCELED再发送事件释放预占库存。如果订单取消和支付成功同时发生状态机决定谁先成功冲突进入自动退款或人工对账。整条链路最终依赖本地事务 状态机 幂等 超时 消息 补偿 对账 可观测性十二、DDD、服务拆分与 API 设计1. 服务按业务能力拆分DDD 强调先理解业务边界再决定代码和服务边界商品上下文商品、分类、价格 订单上下文订单、订单项、状态 库存上下文可用库存、预占记录 支付上下文支付单、退款单不要按 Controller、Service、DAO 拆服务也不要一个数据库表对应一个服务。2. 不要把数据库实体当作 API接口应该返回稳定 DTOrecordOrderResponse(LongorderId,Stringstatus,BigDecimalamount){}这样数据库字段调整不会直接破坏消费者也能避免内部字段泄露。3. API 兼容性通常比较安全的修改是增加可选字段删除字段、修改类型、改变原语义属于破坏性修改应通过新版本或迁移期处理。服务拆分合理的标志是能够独立开发、独立发布、拥有自己的数据并通过清晰契约协作。如果所有服务必须一起发布它们只是一个更难维护的分布式单体。十三、数据库扩展、读写分离和分库分表1. 先优化 SQL 和索引SELECTid,total_amountFROMordersWHEREuser_id?ANDstatus?ORDERBYcreated_atDESCLIMIT20;可以考虑联合索引CREATEINDEXidx_user_status_createdONorders(user_id,status,created_at);索引要结合执行计划、扫描行数和真实参数分析。索引越多写入维护成本也越高。深分页可以在允许时改成游标分页SELECT*FROMordersWHEREid:lastIdORDERBYidDESCLIMIT20;2. 读写分离存在复制延迟用户刚在主库创建订单立即查询从库时数据可能还没有同步。关键的写后读可以暂时走主库或者接受短暂的“处理中”。读写分离提高读能力不能解决主库写入瓶颈。3. 分库分表的代价按照用户、订单或时间分片后会出现跨库查询和分页全局唯一约束数据迁移和扩容热点分片跨分片事务。因此推荐顺序是优化 SQL 和索引 → 缓存与归档 → 缩短事务 → 读写分离 → 垂直拆库 → 最后水平分片分布式 ID 可以使用 UUID、Snowflake 思想或号段模式各自需要权衡索引性能、时钟、机器编号和可用性。十四、测试、CI/CD 和灰度发布1. 微服务测试分层大量单元测试 较多集成测试 必要的契约测试 少量关键端到端测试单元测试验证状态机、计算和业务规则集成测试验证真实数据库、Redis、MQ 和 HTTP 配置契约测试保证服务升级后仍满足消费者需要的接口格式端到端测试只覆盖下单、支付等关键主链路。只使用 Mock 无法发现 SQL 方言、事务、序列化和真实网络配置问题只使用端到端测试又会导致测试慢、定位困难。2. 一条完整流水线提交代码 → 编译 → 单元测试 → 静态检查 → 集成测试 → 构建镜像 → 安全扫描 → 部署测试环境 → 冒烟测试 → 灰度发布 → 全量发布不同环境使用同一个镜像只注入不同外部配置才能保证生产运行的是已经测试过的制品。3. 滚动、蓝绿和金丝雀发布滚动发布逐步替换实例资源成本较低蓝绿发布新旧环境同时存在切换和回滚快但资源成本高金丝雀发布先让少量流量进入新版本根据指标逐步扩大。灰度期间不能只看 CPU还要看错误率、P99 延迟和下单成功率等业务指标。十五、线上故障应该怎样排查先确认四件事什么时候开始 影响哪些接口和用户 最近发布或修改了什么 是错误增加还是延迟升高常见现象与方向现象优先检查CPU 很高热点线程、死循环、序列化、频繁 GCCPU 不高但接口慢数据库、网络、锁、连接池等待数据库连接池耗尽慢 SQL、长事务、连接泄漏Web 线程池耗尽下游慢、超时过长、请求堆积内存持续增长无界缓存、对象泄漏、大查询MQ 积压消费变慢、失败重试、分区热点正确顺序是1. 止血回滚、限流、熔断、降级 2. 保留日志、线程、指标和 Trace 证据 3. 缩小故障范围 4. 提出假设并验证 5. 修复根因 6. 确认业务恢复 7. 复盘并改进机制不要看到连接池耗尽就盲目扩大连接池。数据库只能承受 100 个稳定连接时把应用连接池扩大到 1000 只会让数据库更快失去响应。十六、什么时候不应该使用微服务以下情况通常更适合模块化单体团队人数少产品还在快速试错业务边界经常变化所有模块总是一起发布没有成熟的自动化测试、监控和发布能力拆分后仍然需要共享大量数据库表。微服务适合多个团队需要独立发布业务边界已经比较稳定不同模块的流量和扩容需求差异明显需要独立的故障和安全边界团队具备自动化交付和可观测能力。服务数量多不等于架构先进。能够用简单方案稳定解决业务问题才是更成熟的架构判断。十七、最终知识地图服务通信同步 HTTP必须立即获得结果 异步消息允许延迟需要解耦或削峰高可用超时不无限等待 重试处理短暂失败 限流控制入口 隔离限制影响范围 熔断下游故障时停止调用 降级保护核心业务数据一致性单服务本地事务 跨服务状态机、事件、补偿和对账 最终防线唯一约束和幂等高并发缓存减少工作 扩容增加容量 限流控制流量 MQ平滑峰值 降级保护核心可观测性日志发生了什么 指标整体是否异常 Trace哪一跳出现问题部署交付Docker统一交付环境 Kubernetes调度、自愈和扩容 CI/CD自动验证和发布 灰度控制新版本风险总结真正需要掌握的不是组件名称学完一套微服务技术最终应该形成以下认识远程调用一定可能失败超时不代表对方没有成功消息可能重复、延迟和乱序重试前必须先保证幂等数据库唯一约束是重要的最终防线每个服务应该拥有清晰的数据边界系统必须在过载时有控制地拒绝请求任何组件都有收益、限制和运维成本Spring Cloud 是治理微服务的工具集合不等于微服务本身微服务的收益必须大于它引入的复杂度。如果能沿着一次下单链路解释每一条远程调用怎样发现服务、怎样超时、怎样保证幂等、失败后怎样补偿、消息怎样可靠消费、系统怎样监控和发布就已经建立了比单纯背配置更可靠的微服务知识体系。