Spring Cloud
微服务基础与核心概念
分布式与微服务的区别?
分布式是一种架构思想(把系统拆分到多台机器);微服务是分布式架构的一种具体实现方案:解耦,一个服务只负责单一业务。微服务一定是分布式,但分布式不一定是微服务。
微服务 vs 单体架构
单体架构:所有功能(用户、订单、支付等)打包在一个部署单元中运行。
- 痛点:代码耦合度极高;只能整体扩容,资源利用率低;一个内存泄漏可能导致整个服务崩溃。
微服务架构:按业务边界拆分为多个独立进程,通过网络通信。
- 优势:独立部署(发版快)、按需扩容(例如大促时只给订单服务加机器)、技术异构(不同服务可选不同语言)、故障隔离。
- 劣势:网络延迟与不可靠性、分布式事务一致性问题、链路排查困难、运维成本剧增。
Boot vs Cloud vs Alibaba
| 技术栈 | 核心定位 | 解决的问题 |
|---|---|---|
| Spring Boot | 快速构建单个后端应用 | 通过“约定大于配置”消除繁琐的 XML,帮你快速启动一个独立的 Java Web 服务。 |
| Spring Cloud | 微服务全局治理“全家桶” | 定义了服务发现、网关、负载均衡等一整套标准和接口,它依赖 Spring Boot。 |
| Spring Cloud Alibaba | 阿里开源的微服务实现 | 因为 Netflix 早期组件(Eureka、Hystrix 等)停更,阿里基于国情推出了性能更好、功能更全的落地组件(Nacos、Sentinel),完美接入 Spring Cloud 标准。 |
微服务通信:RPC 与 HTTP 调用
HTTP(如 Spring Cloud OpenFeign):基于应用层 HTTP 协议,数据格式多为 JSON。优势是跨语言支持极好、灵活、简单;劣势是 HTTP 报文头较重,解析开销大。
RPC(如 Dubbo、gRPC):远程过程调用,底层多基于 TCP 和自定义二进制序列化协议。优势是传输效率极高、延迟低,调用起来像本地方法一样;劣势是强绑定特定框架,跨语言相对麻烦。
如何保证通信
微服务引入了网络这个不稳定因素,所有的组件都是为了解决“网络不可靠”和“节点动态变化”的问题:
- 服务注册与发现:微服务节点的 IP 是动态分配的。服务启动时把自己的地址写进注册中心(注册),调用方去注册中心查目标地址(发现),而不是把 IP 写死在代码里。
- 服务雪崩:A 调用 B,B 调用 C。如果 C 宕机,B 因为等待 C 导致线程池阻塞耗尽,A 接着被 B 拖垮,最终整个微服务集群瘫痪。
- 应对雪崩:
- 限流:限制进入系统的并发请求数,把多余的请求拒之门外,保护系统不被打满。
- 熔断:当下游服务持续报错时,直接“拉闸”切断调用,不再浪费时间等待。
- 降级:发生熔断或资源不足时,执行备用逻辑(如返回默认值或缓存数据),保证核心业务运转。
注册中心Nacos、Eureka
Nacos是 Spring Cloud Alibaba 生态中最核心的组件之一,它一个人干了原来 Spring Cloud Netflix 体系里两个人的活:Eureka(注册中心) + Spring Cloud Config(配置中心)。
Nacos 中的服务和实例有什么区别?
服务是业务能力的名称,实例是这个服务实际运行的进程。
比如写好了一个库存服务,服务名叫 stock-service,然后在三台机器上分别启动:
1 | 服务名:stock-service |
Nacos 注册与发现的完整过程
服务注册:服务提供者(Provider)在启动时,通过 REST 请求将自己的元数据(IP、端口、服务名等)注册到 Nacos 服务端。
服务发现:服务消费者(Consumer)向 Nacos 获取服务实例列表,并且缓存在本地内存中。即使 Nacos 全面宕机,短时间内各个微服务仍然可以根据本地缓存的 IP 列表互相通信,只是无法感知到新机器的上下线。
实时更新机制(Pull + Push 结合):
- Consumer 会开启定时任务(定时 Pull)去同步最新的实例列表。
- 当 Provider 发生上下线时,Nacos 服务端会通过 UDP 协议主动推送(Push)变更信息给 Consumer,这比单纯靠拉取的延迟要低得多。
注册中心怎么判断服务是否存活?
临时实例 vs 永久实例与健康检查
| 维度 | 临时实例 (Ephemeral) - 默认 | 永久实例 (Persistent) |
|---|---|---|
| 适用场景 | 普通的无状态微服务(如订单服务、网关) | 必须强一致性的有状态服务(如数据库) |
| 健康检查机制 | 客户端主动上报(心跳)。默认每 5 秒发一次心跳。 | 服务端主动探测。Nacos 建立 TCP/HTTP 连接去探测。 |
| 宕机处理逻辑 | 15秒无心跳:标记为不健康。 30秒无心跳:直接从列表中剔除。 | 探测失败:标记为不健康,但绝对不会剔除,直到手动干预。 |
| 对应的 CAP 理论 | AP(高可用性),基于 Distro 协议 | CP(强一致性),基于 Raft 协议 |
CAP:一致性,可用性,分区容错性
当网络分区发生时,CAP只能三选二

Nacos 集群
如果只有一台 Nacos,它宕机后:
- 已运行的服务可能依靠本地缓存继续调用。
- 新实例的注册、实例变化的获取等操作会受到影响。
Distro 协议(针对临时实例与非持久化配置):
- AP 协议。
- 每个节点负责一部分数据,并把自己的数据定期异步同步给其他节点。
Raft 协议(针对永久实例与持久化配置):
- 选举 Leader,由 Leader 统一处理写请求并同步给 Follower 节点,确保强一致性(CP)。
Nacos 与 Eureka 的区别
功能定位:Nacos = 注册中心 + 配置中心;Eureka 仅仅是注册中心。
CAP 模型:Eureka 只支持 AP 模式;Nacos 默认 AP(针对临时实例),但也支持 CP(针对永久实例),支持动态切换。
发现机制:Eureka 完全依赖客户端定时拉取(通常有 30 秒延迟);Nacos 采用定时拉取 + 服务端 UDP 主动推送,实例上下线感知极快。
社区状态:Eureka 1.x 已经进入维护期不再更新,Nacos 由阿里开源,社区极其活跃。
配置中心Nacos
为什么需要配置中心?
假设商城有订单、库存、商品等多个服务,每个服务又部署了多个实例。
如果配置都放在各自的 application.yml 中,修改一个共享配置时,就可能需要修改多个应用的配置,并重新发布或重启才能生效,管理起来比较麻烦。
配置中心就是把需要集中管理的配置放到统一的位置,由各个服务读取。
主要解决三个问题:
| 能力 | 作用 |
|---|---|
| 集中管理 | 在统一位置管理多个服务的配置 |
| 环境隔离 | 开发、测试、生产使用各自的配置 |
| 动态更新 | 部分配置修改后,可以在运行期间生效 |
配置中心的动态刷新
Nacos 配置中心是如何做到修改配置后,不重启微服务就能实时生效的?
业务侧用法:在需要动态刷新的类上加上 @RefreshScope 注解:能让刷新发生后,让相关 Bean 在下一次使用时重新创建,从而获取新的配置值。
原理:
① 平时通过代理访问 Bean
Spring 为 @RefreshScope Bean 提供代理。业务调用经过代理,转交给实际的目标对象。
目标对象创建后,会被缓存起来,后续调用继续使用,不是每次请求都创建一个对象。
② 刷新时清除目标对象缓存
刷新触发后,作用域中对应的目标对象缓存会被清除。
③ 下次调用时重新创建
下一次通过代理调用时,发现没有目标对象,就重新创建 Bean,并从更新后的配置环境中注入属性。Spring 官方文档将其描述为具有延迟初始化能力的代理。
配置中心宕机了,应用还能正常运行吗?
配置中心宕机后,已经运行的应用通常可以使用内存中的现有配置继续运行,但无法正常接收配置更新。新启动的应用能否启动,取决于是否存在可用快照、本地默认配置,以及远程配置加载失败时的处理策略。对于缺少必要配置且没有可用备用来源的应用,启动可能失败。
负载均衡Spring Cloud LoadBalancer
什么是负载均衡
负载均衡是将请求分配给多个服务实例,避免请求集中在单个实例上,提高系统的吞吐量和可用性。
常见策略:
- 轮询:按顺序选择实例。
- 随机:随机选择实例。
- 加权策略:根据实例权重分配请求。
在 Spring Cloud 中,常见的客户端负载均衡方案是 Spring Cloud LoadBalancer,默认轮询策略。它可以与服务发现、OpenFeign 配合工作。
@LoadBalanced
它可以让我们通过服务名调用服务,并在发送请求前选择具体实例。
1 | @Bean |
使用这个 Bean 发请求:
1 | String result = restTemplate.getForObject( |
发送请求时,拦截器从 URL 中取出 stock-service,自动去 Nacos 查找同名服务,负载均衡组件从该服务的实例列表中选一个实例,把服务名替换成选中实例的地址,保留原来的路径和参数。
LoadBalancer 和 Nginx 区别
根本区别:Nginx 是服务端(集中式)负载均衡,请求必须先发给 Nginx,由 Nginx 转发,多了一层网络开销。LoadBalancer 是客户端负载均衡,调用方自己从 Nacos 拉取地址列表,在自己的进程内存里执行算法选出一个 IP,直接发起点对点直连。
常见策略:默认是 轮询(RoundRobin) 和 随机(Random)。
负载均衡选中的实例调用失败情况
负载均衡自身只决定这次请求发给谁,是否重试,要看调用客户端、依赖和相关配置,不能认为引入 LoadBalancer 后所有失败请求都会自动换实例重试。
同时超时只说明没有及时收到结果,不代表对方执行失败。
比如:
订单服务选择 A,发送扣库存请求。
A 已经扣减成功。
返回响应时网络异常,订单服务等待超时。
订单服务改为调用 B,再扣一次。
在实际设计时,应该判断当前操作是否适合重试,同时写操作做好幂等,例如重试扣库存都携带同一个业务请求号。
服务调用:OpenFeign
OpenFeign 是什么?
只定义接口 + 加注解,不用手写 RestTemplate 发请求、序列化、解析响应;框架底层用 JDK 动态代理自动生成实现类,帮你完成 HTTP 调用,远程调用像调用本地方法一样。
1 | // 启动类开启 |
底层调用流程
启动扫描:Spring Boot 启动时,@EnableFeignClients 会扫描所有标注了 @FeignClient 注解的接口。
动态代理生成:通过 JDK 动态代理,为这些接口生成代理对象,并注入到 Spring 容器中。
请求模板构造:当业务代码调用这个接口的方法时,会被代理对象的 InvocationHandler 拦截。拦截器会解析方法上的 Spring MVC 注解(如 @GetMapping、@PathVariable),将参数拼接,构造出一个完整的 HTTP 请求模板(RequestTemplate)。
负载与执行:提取 @FeignClient 中的服务名,交由 LoadBalancer 从 Nacos 本地缓存列表中解析出一个具体的 IP:Port,最后底层通过 HTTP 客户端发起真实的请求。
Gateway网关
在没有网关时,前端需要记录成百上千个微服务的不同 IP,且每个微服务都要自己写一套鉴权逻辑,非常混乱。网关的作用就是:统一入口、统一鉴权、统一限流、统一跨域。
Gateway 的底层是基于 Spring 5 的 WebFlux 响应式编程(非阻塞 I/O,性能极高)。它有三个绝对核心的概念:
- Route(路由):网关最基础的构建模块。它包含一个 ID、一个目标 URI(去哪)、一组断言(条件)和一组过滤器(操作)。
- Predicate(断言):判断条件。决定请求是否匹配某个路由。最常用的是
Path路径断言(例如:如果请求路径是/user/**,就匹配这个路由)。 - Filter(过滤器):拦截器。分为路由过滤器(只对特定路由生效)和全局过滤器(对所有路由生效)。可以在请求转发前(Pre)添加请求头,或者在响应返回前(Post)修改响应体。可以通过order配置执行顺序,
order越小,越先执行。
断言判断是否匹配,路由确定转发目标,过滤器执行处理。
Sentinel
Sentinel 是用于流量控制和服务保护的组件,主要提供流量控制、熔断降级、系统保护等能力。
它可以针对资源配置规则,例如限制 QPS,或者在异常比例达到一定阈值时触发熔断。
限流
指标维度:QPS vs 线程数
- QPS 限流:严格限制每秒进入的请求数量,超过就拒绝。
- 线程数限流:当调用下游某个慢接口导致当前服务的线程被大量挂起占用时,限制该接口最多只能占用多少个线程,防止拖垮整个微服务。
限流算法
快速失败(默认):超过阈值直接抛出 FlowException。
Warm Up(预热):基于令牌桶算法。如果系统很久没流量,突然来了一波大流量,直接放行可能会把冷启动的系统(如数据库连接池未初始化)打死。Warm Up 会让通过的流量在设定的时间内缓慢增加,最终达到设定的阈值。
排队等待(匀速排队):基于漏桶算法。在优惠券秒杀抢购等场景中,瞬间涌入的请求远超数据库处理能力。排队等待会把“突刺流量”拉平,让请求以固定的时间间隔通过(例如严格每 100ms 放行一个),超时未通过的请求才被丢弃。
热点参数限流
普通流控是整个接口(资源)维度限流;热点参数限流是【同一个参数值】单独统计 QPS,对热点参数做精细限流,防止单个热点把数据库 / 缓存打崩,典型用来防缓存击穿、秒杀热点商品。
熔断
Sentinel 的熔断机制底层依靠一个状态机来运作:
- Closed(关闭状态):一切正常,请求正常放行。当统计窗口内的“慢调用比例”、“异常比例”或“异常数”达到配置的阈值时,触发熔断。
- Open(熔断状态):断路器打开。此时所有发往该下游的请求都会被直接拦截(Fast-Fail),并触发自定义的
Fallback降级逻辑(比如返回“当前排队人数过多,请稍后再试”)。 - Half-Open(半开状态):经过一个设定的“熔断时间窗口”(如 5 秒)后,Sentinel 不会立刻恢复所有流量,而是进入半开探测状态。它会尝试放行一个或几个探测请求去请求下游:
- 如果探测请求成功,说明下游恢复了,状态机切回 Closed。
- 如果探测请求依然失败,说明下游还没喘过气,状态机切回 Open,重新开始倒计时。
Sentinel 根据什么触发熔断?
| 策略 | 关注什么 | 示例 |
|---|---|---|
| 慢调用比例 | 响应太慢的请求占比 | 超过 500ms 算慢调用,慢调用比例超过 50% |
| 异常比例 | 发生异常的请求占比 | 异常请求比例超过 30% |
| 异常数 | 发生异常的请求数量 | 统计窗口内异常数超过 10 次 |
降级
监控某个资源(接口 / Feign 远程调用)的健康指标,如果下游持续慢、报错多,就打开熔断器,一段时间内不再调用下游,直接走兜底逻辑,防止服务雪崩。
Sentinel规则持久化
Sentinel 默认控制台推送的规则保存在应用内存中,重启后会丢失。可以接入 Nacos 动态数据源,将规则持久化到配置中心,由应用加载并监听更新。要注意,仅接入数据源不会让控制台修改自动写回 Nacos,需要统一规则发布入口为nacos,再让nacos去更新sentinel。
分布式事务与 Seata
微服务中,各服务通常独立管理数据库,本地事务只能保证自身事务范围内的原子性,无法自动覆盖远程服务已经提交的操作。因此,需要通过分布式事务框架或可靠消息、补偿机制,保证跨服务业务最终达到一致状态。
三大组件
| 角色 | 职责 | 对应位置 |
|---|---|---|
| TM,事务管理器 | 发起全局事务,请求提交或回滚 | 订单服务中的事务入口 |
| RM,资源管理器 | 管理分支事务,注册分支、报告状态、执行提交或回滚 | 订单、库存服务中的 Seata 客户端 |
| TC,事务协调器 | 维护事务状态,协调各分支提交或回滚 | 通常独立部署的 Seata Server |
TM 发起,RM 执行分支,TC 统一协调,XID 串联事务。
AT 模式
- 一阶段(Prepare)
RM 拿到 XID,执行业务 SQL,先查询数据前后镜像 写入 undo_log(同本地事务内)
1 | 修改前:库存 100(前镜像) |
执行业务 update,拿数据库本地行锁
向 TC 申请全局锁(全局锁由 TC 维护)
本地事务提交:业务 SQL + undo_log 一起提交,释放数据库本地行锁
重点:一阶段本地事务已经提交,数据已经对其他 SQL 可见,这是 AT 隔离问题根源。 全局锁不锁数据库行,是 TC 内存 / DB 里的逻辑锁,用来防止脏写。
二阶段 Commit TC 收到全局提交决议后通知 RM commit;RM异步删除 undo_log,直接结束,非常快。
二阶段 Rollback TC 通知 RM 回滚;RM 读取 undo_log,校验镜像,反向 SQL 补偿回滚业务数据;回滚成功后删除 undo_log;失败会不断重试,保证最终一致。
两个锁区分:
本地行锁:数据库原生锁,一阶段事务提交就释放
全局锁:TC 维护的逻辑排他锁,一阶段提交前拿到,二阶段完成才释放,用来防止脏写
TCC 模式
TCC 要由业务代码实现三个操作:Try 预留资源、Confirm 确认使用、Cancel 释放资源。虽然有三个方法,但属于两阶段:第一阶段执行 Try,第二阶段执行 Confirm 或 Cancel。
| 操作 | 做什么 | 可用库存 | 冻结库存 |
|---|---|---|---|
| Try | 检查库存,把 2 件转为冻结 | 98 | 2 |
| Confirm | 确认售出,消耗冻结库存 | 98 | 0 |
| Cancel | 取消购买,把冻结库存退回 | 100 | 0 |
相比 AT 依赖 undo_log 自动补偿,TCC 需要业务自己实现三个方法,控制更灵活,但实现更复杂。
Saga 模式
Saga 将长事务拆成多个独立提交的本地事务,失败后通过业务补偿撤销已完成步骤的影响。相比 TCC 的资源预留模式,Saga 更适合长流程,但不保证全局隔离,需要处理幂等、补偿失败和中间状态可见等问题。
不用 Seata,怎么通过消息保证跨服务数据一致?
本地消息表
① 在同一个数据库事务里,创建订单并写入待发送事件
1 | 开启订单库事务 |
因为在同一本地事务中,订单和事件要么都写入,要么都不写入,避免“订单创建了,却没留下发消息的记录”。
② 后台任务发送消息
扫描待发送事件,发送到 MQ。收到发送成功确认后,再标记事件已发送;失败则重试。
如果消息已经发成功,但还没标记就宕机,恢复后可能重复发送,所以不能依赖它保证绝对不重复。
③ 库存服务幂等消费
库存服务根据事件 ID 等做防重复,在自己的本地事务中完成:
- 扣库存。
- 记录处理结果。
- 需要反馈订单时,同时记录结果事件。
④ 根据结果推进订单状态
扣库存成功,订单变为“已确认”;库存不足,订单变为“已取消”。
结果通知也要可靠发送,订单侧也要幂等处理,不能只保证前半段。