# Spring Cloud 全家桶大乱炖:一份不太正经、但确实能用的微服务技术巡礼 写在前面本文适合听说过微服务、但被一堆名词砸晕的同学也适合用了三年 Spring Cloud、却说不出所以然的老哥。全程尽量说人话偶尔说骚话但技术点绝对不糊弄。一、故事要从一坨代码说起很久很久以前我们的系统是一个**单体应用**一个 jar 包一次启动一个数据库一份配置文件。它年轻的时候很美好- 改一个功能编译一次跑起来就行- 出 bug看日志一条线捋到底- 上线把 jar 扔上去重启完事。它老的时候就很可怕了- 代码量 80 万行Ctrl F 要转三秒圈- 改一个优惠券规则把订单模块搞挂了- 团队三十个人共用一个仓库谁合并谁挨骂- 一启动要 8 分钟你还得祈祷这次别 OOM。于是有人喊了一句**分家**把原来那一大坨拆成一个个小服务订单服务、用户服务、商品服务、支付服务……每个服务自己一个仓库、一个数据库、一个启动脚本、一个背锅的人。恭喜你你得到了**微服务架构**。以及——一份全新的痛苦。因为分家之后立刻出现了一堆新问题| 分家前 | 分家后 || --- | --- || 方法调用 orderService.create() | 这玩意在另一台机器上怎么调 || 一个配置文件 | 30 个服务 30 份配置改一次重启 30 次 || 挂了就是挂了 | 挂了一个会不会把一串都带崩 || 一个日志文件 | 一次请求横跨 8 个服务日志在哪 || 一个事务 | 跨服务了钱扣了货没发怎么办 |**Spring Cloud 就是为了解决分家之后的家庭矛盾而生的。**它不是一门技术它是一套**治理规范 组件集合**本质上是一份微服务家庭相处指南。下面我们挨个介绍这个家族的成员。请注意这个家族人多、辈分乱、还有几个已经退休但户口本上没销的。---## 二、服务注册与发现微服务的通讯录微服务最大的问题是什么**人太多记不住地址。**你的订单服务想调用用户服务用户服务有 10 个实例IP 是 10.0.3.17、10.0.3.18……而且容器一重启 IP 就变了。你总不能在代码里写死 IP 吧写了你就是**微服务界的定时炸弹**。所以我们需要一个通讯录- 每个服务启动时去通讯录上登记我是用户服务我在 10.0.3.17:8080我还活着。- 想调用的服务先去通讯录查给我一份用户服务的可用列表。- 某个实例挂了通讯录负责把它划掉。这个通讯录就叫**注册中心**这套动作叫**服务注册与发现**。### 1. Eureka那个开山鼻祖现在还活着但基本不更新了Netflix 出品Spring Cloud 早期标配。它的设计哲学是 **AP可用性 分区容错** 我宁可给你一份可能过期的名单也不愿意在你最需要通讯录的时候告诉你我正在选举请稍候。经典特征是**自我保护机制**当它发现大量服务心跳超时它不是立刻踢人而是想会不会是我自己网络抽风了先别删保命要紧。于是你会看到控制台上大大的红字EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEYRE NOT.翻译成人话**我不知道谁死了我选择装死。**2018 年 Netflix 宣布 Eureka 2.0 停止开发Eureka 1.x 进入维护状态。到今天你还能用但新项目基本不选它了——毕竟没人愿意娶一个明确说我不打算再努力了的对象。### 2. Consul来自 HashiCorp 的瑞士军刀Go 语言写的除了服务发现还顺手给你 KV 存储、健康检查、多数据中心支持。它是 **CP** 派用 Raft 一致性协议保证数据准确代价是网络分区时可能拒绝服务。适合已经用 HashiCorp 全家桶Vault、Nomad的团队属于买一送三。### 3. Zookeeper老派贵族Dubbo 时代的老朋友Java 世界的活化石CP 模型靠 Znode 临时节点 Watch 机制实现服务发现。优点稳非常稳稳到你可能一辈子都不会因为它出事。缺点**它是为分布式协调设计的不是为服务发现设计的**。每次服务上下线都要通知所有 Watcher规模一大就是惊群效应注册中心比业务服务还累。而且运维 ZooKeeper 集群本身就是一门玄学建议直接交给懂行的人。### 4. Nacos国产之光现在的主流答案阿里出品一句话总结 **注册中心 配置中心一个顶俩还带控制台。**亮点- 支持 AP / CP 模式切换临时实例走 Distro 协议持久实例走 Raft- 配置管理自带长轮询推送改配置不用重启这条是杀手锏- 有中文界面运维看了直呼亲切- Spring Cloud Alibaba 原生集成Dubbo、Sentinel 都能串上。如果你的新项目还在纠结选谁**默认选 Nacos**除非你有非常明确的理由选别的。### 一句话选型建议| 注册中心 | 一致性 | 特点 | 适用场景 || --- | --- | --- | --- || Eureka | AP | 老牌、简单、不更新 | 老项目、学习 || Consul | CP | 功能全、多数据中心 | 已有 HashiCorp 生态 || ZooKeeper | CP | 稳定、强一致 | Dubbo 老系统 || Nacos | AP/CP | 注册 配置一体 | 新项目首选 |---## 三、负载均衡你有 10 个实例我该宠幸谁注册中心给了你一份名单但名单有 10 个实例啊请求该发给谁这就是**负载均衡**。它分两种### 服务端负载均衡在服务前面立一个 Nginx / LVS所有请求先到它那儿它再分发。像公司前台所有访客先到前台前台说你去三楼找老王。### 客户端负载均衡调用方自己拿着服务列表自己挑一个。像你手机里存了外卖商家电话你自己决定打给谁——**不用经过前台效率高但也更考验你自己**。Spring Cloud 走的是客户端负载均衡。### Ribbon曾经的担当如今已退休LoadBalanced 注解一加RestTemplate 就能直接用服务名调用策略有轮询、随机、权重、最少连接等。但 Ribbon 已进入维护模式**Spring Cloud 2020 之后被 Spring Cloud LoadBalancer 取代**。### Spring Cloud LoadBalancer官方亲儿子替代 Ribbon 的官方方案支持轮询和随机默认轮询可以自定义 ServiceInstanceListSupplier并且和响应式WebFlux天然兼容。想加缓存、加权重、加灰度自己实现接口就行。**踩坑提醒**自定义负载策略时不要写成 Configuration 全局生效否则你的定向灰度会变成全公司灰度。---## 四、服务调用从手搓 HTTP到假装在调本地方法早期调服务是这样javaString url http:// host : port /user/ id;String json restTemplate.getForObject(url, String.class);User user objectMapper.readValue(json, User.class);写了三行其中两行是在处理这不是我想要的类型。于是有了 Feign。### OpenFeign把 HTTP 调用伪装成本地方法javaFeignClient(name user-service)public interface UserClient {GetMapping(/users/{id})User getById(PathVariable(id) Long id);}然后你就能javaUser user userClient.getById(1L);看着像本地方法实际上偷偷发了个 HTTP 请求。**这就是 Feign 最大的魅力也是它最大的陷阱它让你忘了这是一次网络调用。**网络调用意味着- 可能超时- 可能失败- 可能被重试- 可能返回 200 但内容是错的。所以一定要配超时、配降级、配日志级别不然你就是在用本机内存调用的心态去操作一根随时会断的网线。**版本小知识**Netflix 时代的叫 Feign后来社区 fork 出 OpenFeignSpring Cloud 现在的 spring-cloud-starter-openfeign 用的就是后者。所以Feign 和 OpenFeign 有什么区别这个面试题答案是**一个是爹一个是儿子现在打工的是儿子。**---## 五、熔断、降级、限流微服务的保险丝有了注册中心和服务调用你以为万事大吉真正的噩梦开始了**雪崩**。场景还原1. 用户请求下单 → 订单服务2. 订单服务调库存服务库存服务卡了响应要 30 秒3. 订单服务的线程都堵在等库存4. 线程池满了订单服务自己也卡死了5. 网关等订单服务网关线程也满了6. **整条链路从下到上全员阵亡。**这就叫**服务雪崩**。一个服务的咳嗽变成整个系统的心肌梗死。解决方案有三个关键词**熔断、降级、限流**。- **熔断**下游一直失败我先断了别调了快速失败。- **降级**断了以后给用户一个兜底默认值、缓存、友好提示。- **限流**从源头控制流量别让我被冲垮。### Hystrix曾经的顶流现已退休Netflix 出品2018 年后不再开发新功能进入维护模式。它的核心思想- 每个依赖一个独立线程池舱壁隔离——这样 A 依赖卡住不会拖死 B 依赖- 失败率超过阈值就熔断走 fallback- 提供 Dashboard 看实时指标。经典写法javaHystrixCommand(fallbackMethod defaultUser)public User getUser(Long id) { ... }缺点也很明显线程池上下文切换开销大、配置繁琐、和响应式配合困难。### Resilience4j官方的接棒人Spring Cloud Circuit Breaker 抽象默认支持的实现轻量、模块化、专为 Java 8 和函数式设计。它把能力拆得明明白白CircuitBreaker、RateLimiter、Retry、Bulkhead、TimeLimiter想要哪个拿哪个不捆绑销售。### Sentinel国产的流控全家桶阿里出品面向分布式系统的流量防卫兵。相比 Hystrix它的优势- 基于滑动窗口统计实时性更好- 规则可以通过控制台动态推送不用重启- 支持 QPS 限流、并发线程数限流、热点参数限流、系统自适应保护- 有可视化 Dashboard还能和 Nacos 打通做规则持久化。**实战建议**- 纯 Spring Cloud 项目用 Resilience4j 就够- 面向国内大流量场景秒杀、大促Sentinel 更顺手- 不要为了技术先进而两个都用最后规则互相打架。**一个心法**熔断阈值、超时时间、重试次数三者必须一起设计。只配重试不配超时你就是在给雪崩按加速键。---## 六、API 网关系统的唯一门面微服务分家之后前端同学要哭了 我调用户服务是 10.0.3.17调订单是 10.0.3.22调支付是 10.0.3.31……你们是故意为难我吧而且还有一堆公共需求鉴权、限流、日志、跨域、灰度、协议转换……总不能每个服务写一遍吧那叫复制粘贴式架构。于是有了**网关**所有外部请求统一入口由它做统一处理再转发给内部服务。### Zuul 1老牌门神已退休Netflix 出品基于 Servlet 的阻塞模型一个请求一个线程。请求量一上来线程池就是你的瓶颈。Zuul 2 换成了非阻塞但 Spring Cloud 那边没怎么跟进于是大家集体奔向 Gateway。### Spring Cloud Gateway现在的标准答案基于 Spring WebFlux Reactor 的响应式网关非阻塞、性能好、生态完整。三大核心概念- **Route路由**一条转发规则- **Predicate断言**什么条件下匹配这条路由路径、方法、Header、时间……- **Filter过滤器**匹配后做什么处理加请求头、限流、改路径、鉴权……。yamlspring:cloud:gateway:routes:- id: user-serviceuri: lb://user-servicepredicates:- Path/api/user/**filters:- StripPrefix2注意那个 lb://它会走负载均衡去注册中心拿实例链条一下就串起来了。**网关的经典职责清单**1. 统一鉴权Token 校验、黑名单、权限2. 限流配合 Redis RequestRateLimiter或接 Sentinel3. 灰度发布按用户、按 Header 路由到不同版本4. 日志与链路追踪埋点TraceId 从这里开始注入5. 协议转换外部 REST内部 gRPC 或 Dubbo。**踩坑提醒**Gateway 是响应式的**你在 Filter 里写阻塞代码比如 Thread.sleep、同步 JDBC、同步 Redis会把整个网关卡住**。这是新手最常见的翻车现场。---## 七、配置中心改一个参数为什么要重启 30 个服务微服务最反人类的日常 运营数据库连接数调大一点。 你好我改 30 个服务的配置然后逐个重启。 运营现在就要。 你……配置中心就是为了干掉这个流程。它要做三件事1. **集中管理**所有配置放一处按环境、按服务、按集群分2. **动态刷新**改完即时生效不用重启3. **版本与审计**谁改的、什么时候改的、能不能回滚。### Spring Cloud Config官方的配置仓库把配置文件放到 Git 仓库里Config Server 读 Git客户端从 Config Server 拉。好处是天然带版本管理改配置等于提交代码能 review、能回滚。缺点是**默认不会主动推送**——你得配合 Spring Cloud Bus基于消息中间件广播一个刷新事件或者手动调 /actuator/refresh。### Nacos Config注册配置一体化长轮询 推送改完配置客户端秒级感知还有命名空间、分组、灰度、历史版本。配合 RefreshScope 就能实现不重启刷新javaRefreshScopeRestControllerpublic class DemoController {Value(${demo.switch:false})private boolean switchOn;}**踩坑提醒血泪**- RefreshScope 的 Bean 是**懒加载的代理**刷新时会销毁重建如果你在这个 Bean 里持有连接池、定时任务、本地缓存小心它们被顺手干掉- static 字段不会被刷新别问问就是踩过- 配置中心的配置优先级要理清楚本地 远程还是远程 本地不同版本行为不一样上线前一定验证。### Apollo携程出品企业级配置中心功能非常全灰度发布、权限管理、多环境多集群、审计日志、客户端实时推送界面也很舒服。代价是**要独立部署一整套服务**Portal、Config Service、Admin Service、数据库小团队可能觉得重。**选型建议**- 小团队 / 新项目Nacos省事- 大公司 / 强审计要求Apollo- 已用 Spring 全家桶且配置变更不频繁Spring Cloud Config Bus。---## 八、消息驱动Spring Cloud Stream微服务之间除了你调我、我调你同步还有一种更优雅的方式**发消息**异步。同步调用的特点**你得等**。对方慢了你就慢对方挂了你可能也挂。异步消息的特点**我发完就走**。对方什么时候处理是他的事。问题是不同消息中间件的 API 完全不同- KafkaKafkaTemplate.send(...)- RabbitMQrabbitTemplate.convertAndSend(...)- RocketMQ又是另一套换个 MQ代码重写一遍这就很烦。**Spring Cloud Stream** 的思路是把 MQ 抽象成 Binder你只面向输入通道和输出通道编程底层是 Kafka 还是 RabbitMQ 由配置决定。核心概念- **Binder**连接具体 MQ 的适配层- **Destination**消息的目的地Topic / Exchange- **Consumer Group**消费组同组内竞争消费不同组重复消费。还会配套**Spring Cloud Bus**利用消息总线把配置刷新状态广播这类事件推给所有实例——这就是 Config Bus 实现全量刷新的原理。**心法**能用异步解耦的地方尽量异步但**异步不等于免费**——你要额外面对消息丢失、重复消费、顺序性、幂等这些新问题。天下没有免费的午餐只有换一种方式付钱。---## 九、链路追踪一次请求横跨 8 个服务出问题找谁用户说我下单失败了。你查日志- 网关200- 订单服务调用库存超时- 库存服务调用商品服务超时- 商品服务一切正常。……然后呢这四条日志**没有任何关联**你怎么把它们串成一条故事线这就是**分布式链路追踪**要解决的给一次请求打一个全局唯一的 TraceId每经过一个服务生成一个 SpanId最后拼成一棵调用树。### Spring Cloud Sleuth早期官方方案能自动给日志加上 [appname,traceId,spanId,exportable]配合 Zipkin 可视化。不过从 Spring Boot 3 开始Sleuth 不再是主角官方推荐 **Micrometer Tracing**micrometer-tracing-bridge-brave / otel。### Zipkin / Jaeger专门做追踪数据存储和展示的系统能看到完整调用链、每个环节耗时一眼看出是谁在拖后腿。### SkyWalking国产 APM 之光基于字节码增强**对业务代码零侵入**除了链路追踪还给服务拓扑图、JVM 监控、慢 SQL、告警。在国内环境里它经常是一个东西解决所有可观测性问题的存在。**可观测性三件套**记住这三个词面试稳过| 类型 | 回答什么问题 | 常见工具 || --- | --- | --- || Metrics 指标 | 系统现在健康吗 | Prometheus Grafana || Tracing 链路 | 这次请求慢在哪 | Zipkin / SkyWalking / Jaeger || Logging 日志 | 到底发生了什么 | ELK / Loki |---## 十、分布式事务跨服务了钱和货怎么保证一致这是微服务最硬核、也最容易被滥用的一块。场景下单要扣库存 扣余额 生成订单三个操作在三个服务、三个数据库里。如果扣完库存扣余额失败了怎么办库存得还回去——这就是分布式事务。### 先泼一盆冷水**90% 的业务场景不需要强一致的分布式事务。**大多数时候你可以- 把强一致需求改成**最终一致**本地消息表、事务消息、定时对账补偿- 或者干脆**把该放在一起的业务放回同一个服务**是的服务拆分粒度不合理才是事务问题的根源。### Seata阿里开源的分布式事务框架三个角色- **TCTransaction Coordinator**事务协调者独立部署的 Server- **TMTransaction Manager**事务发起方负责开启和提交全局事务- **RMResource Manager**各参与者管自己的分支事务。四种模式| 模式 | 思路 | 侵入性 | 一致性 || --- | --- | --- | --- || AT | 自动补偿靠 undo_log 回滚 | 低 | 最终一致 || TCC | Try-Confirm-Cancel手写三阶段 | 高 | 接近强一致 || SAGA | 长事务拆成多个本地事务 补偿 | 中 | 最终一致 || XA | 数据库原生两阶段提交 | 低 | 强一致但性能差 |**推荐姿势**能不用就不用一定要用优先 AT 模式核心资金链路且能承受改造成本考虑 TCC。为了技术先进性而上分布式事务通常换来的是为了排查分布式事务问题而通宵。---## 十一、安全Spring Cloud Security 与网关鉴权微服务的安全不是每个服务自己写一套登录那会变成 30 套不同的鉴权逻辑想想就窒息。标准姿势是**网关统一鉴权**1. 客户端带上 TokenJWT / OAuth2访问网关2. 网关校验 Token 合法性解析出用户身份3. 把用户信息放进请求头如 X-User-Id转发给内部服务4. 内部服务信任网关直接读头不再重复校验。关键点- 内部服务**必须只能被网关访问**用内网隔离 / 安全组否则别人绕过网关伪造请求头你就等着上演数据泄露大片- Token 有效期、刷新机制、注销/黑名单要考虑清楚- 敏感字段不要放 JWT payload 里它是 Base64 编码不是加密。---## 十二、周边装备那些你可能没听说过但很香的组件### Spring Cloud Contract消费者驱动契约测试服务提供方和消费方经常各说各话我按文档传了 userId你上线改成 user_id然后线上炸。Contract 的做法消费方把我期望你这样响应写成契约提供方据此自动生成测试并验证。**接口变更前先跑契约测试比上线后发现强。**### Spring Cloud Task短生命周期任务适合跑一次性批处理数据迁移、报表生成、定时结算跑完就结束并记录执行状态和结果。### Spring Cloud Alibaba 全家桶如果只能记一个组合记这个| 组件 | 作用 || --- | --- || Nacos | 注册中心 配置中心 || Sentinel | 限流、熔断、降级 || Seata | 分布式事务 || RocketMQ | 消息队列 || Dubbo | RPC 调用 |国内中小团队落地微服务这套组合的顺手程度是目前最高的。---## 十三、版本号为什么 Spring Cloud 要用伦敦地铁站名第一次看到 Finchley.SR2、Hoxton.SR12、2021.0.5 的人表情通常是这样的因为 Spring Cloud 的版本号历史上用的是**伦敦地铁站名称的字母顺序**Angel → Brixton → Camden → Dalston → Edgware → Finchley → Greenwich → Hoxton → Ilford → Jubilee → Kilburn → Lake从 2020 年开始改成日历版本2020.0、2021.0、2022.0、2023.0……终于让人类可以理解了。但真正的坑不是命名而是**版本兼容矩阵** Spring Cloud 版本 ↔ Spring Boot 版本 ↔ Spring Cloud Alibaba 版本这三者必须严格对齐错一个你会收获一堆 NoSuchMethodError、ClassNotFoundException 和深夜的自我怀疑。**实操建议**去 Spring 官网的兼容性表格查别信我看别人也这么配的。别人可能也没跑起来。---## 十四、落地避坑指南这一段最值钱把所有踩过的坑浓缩成 10 条1. **超时配置必须显式设置**。Feign、Ribbon/LoadBalancer、底层 HTTP Client 各有一层超时默认值可能长到你怀疑人生。2. **重试要有上限且要幂等**。下游已经雪崩了你还在重试等于拿铲子往雪堆上铲雪。3. **熔断、超时、重试、线程池是一组参数**必须一起设计不要单独调其中的一个。4. **不要在网关里写阻塞代码**WebFlux 的 EventLoop 会被你一个 Thread.sleep 干趴下。5. **注册中心不是越强一致越好**。服务发现更看重可用性别为了强一致把系统变成一个随时拒绝服务的玻璃人。6. **配置中心能动态刷新 ≠ 什么都能动态刷新**。连接池大小、线程池大小这类参数刷新了也常常不生效或不安全。7. **服务拆分粒度比技术选型更重要**。拆得太细一个下单请求跨 12 个服务谁来都救不了你的延迟和稳定性。8. **不要过早引入分布式事务**。先问一句这个场景能不能用最终一致性 对账9. **链路追踪和统一日志要从第一天就上**。等你出故障了才想起加 TraceId那这次故障只能靠猜。10. **版本对齐比追新重要**。微服务项目的稳定性一半来自架构一半来自依赖管理。---## 十五、还有未来吗Service Mesh 与 Spring Cloud有人问Spring Cloud 是不是要过时了Istio 这些 Service Mesh 是不是要取代它一句话解释差异- **Spring Cloud**把治理能力写在**代码里**SDK 模式每个服务自己负责注册、熔断、负载均衡- **Service Mesh**把治理能力下沉到**Sidecar 代理**里业务代码完全无感。Service Mesh 的优势是语言无关Go、Python、Node 都能用同一套治理代价是多了一层代理、多了一套运维体系复杂度从代码转移到了基础设施。**现实结论**中小团队用 Spring Cloud 依然是最经济的方案大型组织、多语言技术栈才会考虑 Service Mesh。它们不是替代关系更像是你负责业务我负责治理的分工演化。---## 十六、结语把上面这一大圈捋一遍你会发现 Spring Cloud 的组件其实只在回答五个问题1. **服务怎么找到彼此** → 注册中心Nacos / Eureka / Consul2. **找到了怎么调** → OpenFeign LoadBalancer3. **调挂了怎么办** → CircuitBreakerResilience4j / Sentinel4. **入口怎么统一管** → Gateway5. **这么乱怎么管住** → Config、Stream、Tracing、Seata、Security技术本身不复杂复杂的是 **当系统从 1 个进程变成 50 个进程所有原本理所当然的事情都要重新思考一遍。**本地方法调用不会失败网络调用会一个事务能回滚跨服务的事务要设计一个日志文件能看清五十个服务的日志需要 TraceId。Spring Cloud 做的就是把这些问题**一个个变成可以配置、可以治理、可以观测的组件**。所以别被那一堆名词吓到。你只需要记住一句话 **微服务的本质不是拆而是拆完之后还能像一个整体一样工作。**而 Spring Cloud就是那句还能像一个整体的技术翻译。