spring_cloud_alibaba复习
nacos
nacos是AP还是CP?
1)配置中心模块(Config)
固定 CP(Raft/JRaft),不可切换 所有配置数据,统一走 Raft 强一致。
原因:配置不能出现「节点 A 读到 v1、节点 B 读到 v2」,比如支付费率、开关配置,不一致会引发故障。
特性:
写请求必须经过 Raft Leader;集群存活节点不足半数 → 配置读写直接不可用。
2)服务注册发现模块(Naming)
服务分两类实例,协议完全隔离:
- 临时实例 ephemeral=true(SpringCloud 默认) 采用 Distro 协议 → AP 模型(最终一致性) 任意节点都能处理注册;写入本地内存,异步广播同步其他节点;
集群哪怕只剩一台节点存活,依然支持注册、查询服务列表。 - 持久实例 ephemeral=false(Dubbo 常用) 采用 Raft 协议 → CP 模型(强一致性) 写操作需要集群过半节点确认;节点不足半数,持久实例注册 / 删除操作失败。
划重点:临时 / 持久实例是客户端注册时携带的属性,不是集群全局配置。 同一集群里,可以同时存在 AP 管理的临时微服务 + CP 管理的持久 Dubbo 服务。
临时实例依靠客户端持续发心跳保活,进程宕机、断连,30s 无心跳自动删除。
持久实例不靠心跳剔除;实例断开连接只会标记不健康,不会自动删除,需要主动调用下线接口才移除。
Nacos 配置动态刷新原理
客户端启动时,和 Nacos 服务端建立gRPC 长连接(2.x)
- 客户端订阅自己需要的 DataId
- 服务端配置发生修改,主动推送变更通知
- 客户端收到通知,主动拉取最新配置
两种刷新方式:
@RefreshScope:动态生成代理对象,调用方法时加载新配置;适合 SpringCloud@NacosValue(autoRefreshed=true):nacos 自有注解,字段实时刷新
坑:普通@Value无法自动刷新!
nacos使用的什么通信模型
Nacos2.x:gRPC 长连接双向通信 提供者、消费者均与服务端建立长连接;注册、心跳、变更推送全部走长连接
优势:降低 HTTP 反复创建连接开销,配置、服务变更实时推送
AP 模式 Distro 协议原理
Distro:Nacos 自研最终一致性协议,专门用于服务注册发现
流程:
- 服务注册请求随机分发到集群某一台 Nacos 节点(接收节点)
- 接收节点本地保存实例,异步把注册数据同步转发给集群其他所有节点
- 每个节点只负责管理一部分实例(分片思想)
- 节点启动 / 失联时,执行数据同步校验
特点:
- 写操作异步同步;读操作本地直接返回,不等待集群同步完成
- 只要一台节点存活就能对外提供服务(高可用 AP)
缺陷:短时间集群节点之间数据不一致(最终一致性)
对比 Raft:Raft 写请求必须半数节点确认成功,强一致,牺牲可用性
Raft 协议(CP 持久实例、配置中心底层)
配置中心的数据、持久实例元数据,全部基于 Raft 保证强一致
Raft 集群角色:Leader、Follower、Candidate
- 所有写请求必须转发到 Leader 节点处理
- Leader 写入成功后,同步日志给 Follower,半数 Follower 确认才算提交成功
- Leader 宕机,集群重新选举新 Leader
关键点:CP 模式下,如果集群存活节点不足半数,整个 Nacos 无法提供读写服务
临时实例心跳机制
- 客户端默认 5 秒 发送一次心跳包
- Nacos 服务端 15s 没有收到心跳 → 标记实例为不健康
- 持续 30s 无心跳 → 直接删除实例
设计目的:容忍网络短暂抖动,避免网络闪断误剔除服务
消费者如何感知服务上下线?
消费者启动建立 gRPC 长连接,订阅指定服务名 当服务实例新增 / 下线:
Nacos 服务端主动推送服务列表变更事件给所有订阅的消费者
消费者本地更新缓存服务实例列表,实现实时感知。
配置读写流程
- 客户端请求获取配置,请求转发至 Raft Leader 节点
- Leader 查询 MySQL 持久化数据,并同步到 Follower
- Nacos 内存维护配置缓存,大量读请求走内存,提升性能
配置推送机制
客户端订阅 DataId → 长连接绑定订阅关系
管理员修改配置 → Leader 更新 MySQL + 内存缓存
服务端主动推送变更事件给所有订阅客户端,客户端拉取最新配置
Nacos 集群最少几个节点?
- AP 集群(只做服务注册):3 节点起步
- CP 集群(使用持久实例、依赖 Raft):最少 3 节点(满足多数投票机制,允许挂 1 台)
Nacos 集群会不会脑裂?
Distro (AP):不存在脑裂,不需要投票,节点独立提供服务,只是数据短暂不一致
Raft (CP):不会脑裂。Raft 选举要求获得半数以上投票才能成为 Leader;网络分裂后没有节点满足半数,集群不可写,避免脑裂
Nacos 集群节点之间怎么同步数据?
- AP 临时实例:Distro 异步广播同步实例信息
- CP 持久实例 / 配置:Raft 日志同步
如果 Nacos 全部节点宕机,微服务还能互相调用吗?
消费者本地缓存一份服务实例列表。注册中心宕机,不影响已有服务调用,只是无法感知新服务上线、旧实例下线。属于注册中心设计标准 CAP 取舍。
获取服务实例表流程
服务启动阶段
消费者启动,发起远程请求:订阅服务名(如 service-order) Nacos 服务端返回当前全部实例列表 → 初始化本地缓存。
同时建立长连接,完成订阅绑定。
日常业务调用(99% 运行场景)
前端发起请求 → 调用负载均衡选取实例
直接读取本地内存缓存,完全不访问 Nacos
优点:注册中心压力极小;就算 Nacos 宕机,业务调用照常执行。
服务提供者上下线、实例变动(新增 / 下线 / 不健康)
Nacos 服务端感知实例变化
→ 通过 gRPC 长连接主动推送变更事件给所有订阅该服务的消费者
→ 消费者收到通知,主动发起一次远程请求,拉取全量最新实例 → 更新本地缓存
Nacos2.x 是推送机制;对比 Nacos1.x 老版本:客户端定时轮询拉取(pull 模式)
拉兜底
消费者内部有定时任务兜底拉取(推拉结合模式)
长连接断了收不到推送,定时任务会周期性主动拉取实例列表刷新缓存,防止永久使用陈旧缓存。
也就是 Nacos 是 push 为主,pull 兜底。
nacos集群节点间可以自动发现吗?如何感知节点健康?
默认模式:File 文件寻址(生产最常用) 启动流程:
- Nacos 启动,加载
conf/cluster.conf,文件内填写所有集群节点 IP:8848 ServerMemberManager读取文件,生成一份完整集群成员列表- 所有节点必须保持
cluster.conf内容完全一致!
另外两种寻址模式:
- 环境变量
nacos.member.list,逗号分隔节点地址(容器部署常用) - address-server 地址服务器模式(云环境动态集群,很少用)
关键坑:
集群扩容时必须修改所有节点 cluster.conf;新节点地址写入全部机器配置文件,重启后集群才能识别新节点。 只新增一台机器,其他节点配置不改,老节点不会自动发现新节点!
节点之间如何感知对方是否健康
拿到静态成员列表后,开启后台定时任务 MemberInfoReportTask
- 节点周期性向列表里其他所有 Nacos 节点发送 HTTP 心跳上报(/v1/core/cluster/report 接口)
- 如果对方正常响应 → 标记该节点为健康可用
- 长时间无法响应 → 标记为不健康,暂时排除出可用列表
- 静态列表永远不变;只是区分「配置存在」和「当前是否在线」
划重点: 静态列表 = 预期集群成员;健康列表 = 当前活着的节点。 配置文件不会动态更新成员,只能手动修改。
sentinel
原理
Sentinel 基本概念
资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
Sentinel 功能和设计理念
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状
流量控制有以下几个角度:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程池、系统负载等;
- 控制的效果,例如直接限流、冷启动、排队等。
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
熔断降级
什么是熔断降级
除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
熔断降级设计理念
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix 通过线程池(每一个依赖服务分配独立线程池。调用外部接口走独立线程池执行。)的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。
Sentinel 对这个问题采取了两种手段:
- 通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
- 通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
Sentinel 是如何工作的
Sentinel 的主要工作机制如下:
- 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
- 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
- Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。
Sentinel 工作主流程
在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:
NodeSelectorSlot负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;ClusterBuilderSlot则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;StatisticSlot则用于记录、统计不同纬度的 runtime 指标监控信息;FlowSlot则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;AuthoritySlot则根据配置的黑白名单和调用来源信息,来做黑白名单控制;DegradeSlot则通过统计信息以及预设的规则,来做熔断降级;SystemSlot则通过系统的状态,例如 load1 等,来控制总的入口流量;
总体的框架如下:

Sentinel 将 ProcessorSlot 作为 SPI 接口进行扩展(1.7.2 版本以前 SlotChainBuilder 作为 SPI),使得 Slot Chain 具备了扩展的能力。您可以自行加入自定义的 slot 并编排 slot 间的顺序,从而可以给 Sentinel 添加自定义的功能。

逐个作用:
- NodeSelectorSlot:区分调用链路,构建调用树(链路限流依赖此 Slot)
- ClusterBuilderSlot:集群节点统计
- StatisticSlot【核心】:统计成功、失败、RT、并发数(滑动窗口统计)
- FlowSlot:执行限流判断
- DegradeSlot:熔断降级判断
- SystemSlot:系统保护校验
- AuthoritySlot:黑白名单校验
执行顺序:先统计,后校验规则!
流量控制 FlowRule
限流阈值类型
- QPS:每秒请求数量(最常用)
- 并发线程数:限制同时执行的请求数(控制慢调用堆积)
限流模式
- 直接限流:当前资源达到阈值直接拦截
- 关联限流:关联资源触发阈值,限流当前资源(例如:写数据库压力大,限流查询接口)
- 链路限流:只统计指定入口来源调用该资源的流量
经典坑:高版本 SCA(spring cloud alibaba) 链路限流默认失效!
熔断降级 DegradeRule
熔断目的:下游服务故障时,快速失败,避免雪崩。
三种策略:
- 慢调用比例 设置 RT 阈值;超过 RT 的请求占比到达阈值 → 触发熔断
- 异常比例 单位时间内,抛出异常请求占总请求比例超过阈值,熔断
- 异常数 单位时间内异常总数量超过阈值,触发熔断
熔断三状态流转
关闭 → 打开 → 半开
- 关闭:正常统计指标
- 打开:拒绝所有请求(熔断时长)
- 半开:熔断时间结束,放行少量探测请求;探测成功恢复关闭;失败重回打开
热点参数限流 ParamFlowRule
针对请求参数做限流。场景:防刷。
例:根据用户 ID 限流,同一个用户每秒最多访问 1 次。
支持参数例外项:特定参数不受限流限制。
系统保护规则 SystemRule
不针对单个接口,保护整个服务器。
监控指标:
CPU 使用率、系统负载、入口 QPS、最大并发线程数、平均 RT。
防止服务器资源耗尽。
@SentinelResource 两个兜底方法区别
- blockHandler:处理限流、熔断、权限拦截 → BlockException(Sentinel 框架异常)
- fallback:业务异常(NullPointer、自定义异常)兜底
注意:BlockException 不会进入 fallback
1 | |
信号量隔离原理
所有请求共用业务线程,不创建独立线程池。
优点:开销极低;缺点:无法控制超时,阻塞调用会占用业务线程。
Sentinel 没有线程池隔离方案。
滑动时间窗口 LeapArray(重中之重)
Sentinel 所有指标统计底层实现。
默认窗口总时长:1s;分割成 2 个窗口,每个窗口 500ms。
对比固定窗口:解决临界瞬间流量突增问题。
核心思路:
- 当前时间定位到对应的窗口桶
- 桶过期自动清空废弃数据
- 实时累加 QPS、异常数、RT 等指标
面试官追问:滑动窗口解决什么问题?
答:固定窗口算法存在临界点双倍流量击穿;滑动窗口平滑流量统计。
规则持久化原理
控制台默认规则保存在内存,应用重启规则全部丢失,不能上生产。
持久化方案:本地文件、Nacos、Apollo。
主流方案:Nacos 持久化 工作模式:应用监听 Nacos 配置;规则变更实时推送更新,无需重启服务。
两种模式:
- 推送模式(推荐):控制台修改 → Nacos 保存规则 → 微服务监听拉取
- 拉取模式:应用定时轮询
Sentinel 控制台作用?
控制台只是可视化管理面板! 限流熔断核心逻辑在业务应用本地执行。
规则下发到客户端;所有流量统计、拦截逻辑在应用本地运行;控制台仅展示监控、修改规则。
优势:控制台宕机,不影响已生效限流规则。
Sentinel 能不能实现分布式限流?
原生只支持单机限流。分布式限流需要额外扩展:
方案 1:Redis + Lua 实现分布式计数
方案 2:Sentinel 集群限流模式(ClusterFlowRule)
集群限流性能开销更高,大部分场景优先网关层限流。
Sentinel 集群限流原理?
选择一台 Token Server 作为计数节点;所有节点请求向 Token Server 申请令牌。
缺点:存在单点风险,生产用的不多。
seata
原理
定位:分布式事务框架。
三大核心角色:
- TM TransactionManager:事务发起者;
@GlobalTransactional所在服务,向 TC 申请开启全局事务,生成唯一XID。 - TC TransactionCoordinator:事务协调器(seata-server),保存全局 / 分支事务状态,驱动二阶段提交 / 回滚。
- RM ResourceManager:资源管理器;每个微服务,拦截 SQL、管理 undo_log、向 TC 注册分支事务。
四大模式基础对比
- AT 模式(自动事务,首选) 无代码侵入;基于 JDBC 代理、undo_log 快照;支持 MySQL 等关系型数据库;最终一致性。
- TCC 模式(手动补偿) 代码侵入强;自行实现 Try-Confirm-Cancel;不依赖数据库事务;适合非 DB 资源(Redis、第三方支付)。
- SAGA 模式(长事务) 长流程分布式事务;正向服务、反向补偿;无锁;适合跨多个异构系统,弱一致。
- XA 模式(传统 2PC) 数据库原生支持强一致性;一阶段长期占用数据库锁,性能极差,生产极少使用。
选型背诵:普通微服务数据库事务用 AT;第三方资源 / 金融核心用 TCC;超长业务流程用 SAGA。
AT 模式底层原理
AT 两阶段完整流程
一阶段(执行业务 SQL)
- TM 发起全局事务,TC 生成全局唯一 XID,XID 通过 RPC 透传给所有微服务。
- RM 拦截业务 SQL,查询数据库生成 before-image(修改前快照)。
- 在同一个本地事务内:执行业务 SQL + 写入 undo_log(before+after 镜像)。
- RM 向 TC注册分支事务。
- 提交本地数据库事务,释放本地锁!
- RM 上报分支执行成功状态给 TC。
关键点:一阶段直接提交本地事务,区别 XA,不会长期占用数据库锁
二阶段(分两种情况)
场景 1:全局提交(所有分支成功)
TC 下发 commit 指令;RM异步删除 undo_log,立刻完成,不需要操作业务数据。
场景 2:全局回滚(任意分支异常)
TC 下发 rollback 指令;
- RM 根据 XID+branchId 查询 undo_log;
- 校验 after-image 和 当前数据库数据是否一致(脏写校验);
- 使用 before-image 生成反向 SQL 执行回滚;
- 删除 undo_log,释放全局锁。
什么是seata的undo_log
每个业务库必须创建 undo_log 表;存储数据前后镜像,用于自动回滚。
约束:SQL 表必须存在主键,否则无法定位数据,不能生成回滚 SQL。
全局锁 GlobalLock(解决脏写)
什么是脏写? 事务 T1 一阶段提交数据,但二阶段需要回滚;此时事务 T2 修改了同一行。
如果 T1 直接用快照回滚,会覆盖 T2 提交的数据 → 数据丢失(脏写)。
全局锁规则: RM 一阶段本地事务提交之前,先向 TC 申请该行记录的全局锁;
- 获取成功:提交本地事务,全局锁持续持有到二阶段完成;
- 获取失败:不断重试,超时直接回滚本地事务。
作用:保证同一行数据,同时只能有一个全局事务进行更新,杜绝脏写。
重要区分:
本地锁 = MySQL 行锁;全局锁 = Seata TC 维护的分布式锁。
读写隔离问题
AT 模式默认没有脏写,但存在脏读! 现象:T1 一阶段提交,未完成二阶段;其他事务查询可以读到中间状态。若 T1 后续回滚,读到的数据无效。
解决方案:
查询方法添加注解 @GlobalLock,查询时竞争全局锁;没有拿到锁代表存在未完成全局事务,阻塞等待。
@GlobalTransactional 和 @Transactional 区别?
@Transactional:本地事务,仅当前单个数据库生效;底层 Spring AOP。@GlobalTransactional:分布式全局事务入口,Seata AOP;生成 XID,协调多个微服务分支事务。
搭配使用:每个微服务内部依旧需要
@Transactional。
XID 如何在多个微服务传递?
OpenFeign / Dubbo 拦截器,把 XID 放入请求头;下游 RM 从请求头提取 XID,加入同一全局事务。
坑:自定义拦截器、网关转发时丢失请求头 → 分支事务无法加入全局事务,事务失效。
TC(seata-server)存储模式?
- file:单机内存存储,重启丢失事务信息;只用于测试,禁止生产。
- db(MySQL):生产推荐;全局事务、分支事务、全局锁持久化到数据库,支持集群高可用。
- redis:较少使用。
AT 模式回滚幂等怎么保证?
TC 下发回滚指令可能重试;
RM 执行回滚前先查询 undo_log;
如果 undo_log 已删除 → 代表已经完成回滚,直接返回成功,防止重复回滚。
空回滚、悬挂
- 空回滚:Cancel 请求先到达,此时 Try 还没执行;直接返回成功。
- 悬挂:Cancel 执行完成后,Try 请求才到达;禁止执行 Try。
官方解决方案:TCC Fence事务控制表。
AT 模式相比传统 XA 2PC 优势?
XA 一阶段锁定资源,等待所有参与者响应,长时间占用数据库锁,并发差;
AT 一阶段直接提交本地事务,释放数据库锁;依靠全局锁做并发控制,性能远高于 XA;适合高并发微服务。
二阶段回滚时,after-image 校验失败会发生什么?
代表出现脏写:其他事务修改了本条数据;
Seata无法自动回滚,事务进入未知状态,需要人工介入修复数据。
@GlobalTransactional 失效全部场景
- AOP 代理失效 同类方法内部调用;方法为 private/static;直接 new 对象调用。
- 异常被 try-catch 捕获,没有向外抛出 Seata 无法感知异常,不会触发全局回滚。解决方案:catch 后主动 throw。
- 数据源没有被 DataSourceProxy 代理 无法拦截 SQL,不生成 undo_log,分布式事务完全失效。
- 事务分组配置错误(tx-service-group) 客户端无法找到 TC 服务。生产对接 Nacos 极易踩坑。
- RPC 调用丢失 XID(请求头丢失),下游分支脱离全局事务。
- 微服务缺少 undo_log 数据表。
- Feign 开启重试:分支重复执行,引发数据错乱,建议关闭 Feign 重试。
- 入口方法没有声明
rollbackFor = Exception.class,默认只回滚 RuntimeException。
Seata TC 宕机会怎么样?
- 一阶段执行中 TC 宕机:RM 上报分支状态找不到 TC,本地事务正常提交;全局事务处于悬挂状态。
- Seata 内置定时重试机制:TC 重启后扫描未完成全局事务,继续驱动二阶段提交 / 回滚。
生产必须部署 TC 集群保证高可用。
AT 模式不支持哪些 SQL 操作?
- 不支持 DDL 语句(alter、drop,无法生成 undo 镜像);
- 批量 update/delete 无主键(无法定位行);
- 存储过程;
- 跨多个库的单条 SQL(单 RM 只能管理一个数据源)。
Spring Cloud Gateway
API 网关,基于 Spring5 + WebFlux + Reactor + Netty,异步非阻塞响应式编程
三大核心组件:路由 (Route)、断言 (Predicate)、过滤器 (Filter)
少量线程支撑大量连接。
Route 路由
路由 = 网关最基础单元,由下面组成:
- id:路由唯一标识
- uri:转发目标地址(http://xxx/lb:// 服务名 负载均衡)
- predicates:断言集合,匹配请求
- filters:过滤器集合,请求 / 响应预处理
Predicate 断言(匹配条件)
作用:根据 HTTP 请求信息判断当前请求是否命中这条路由 内置常用断言:
- Path:匹配请求路径(最常用)
- Method:GET/POST
- Header:请求头匹配
- Query:请求参数
- RemoteAddr:客户端 IP
多个断言之间:逻辑与 &&,全部满足才匹配路由
Filter 过滤器
两种分类
- GatewayFilter(路由局部过滤器) 只作用于某一条 Route,路由级别过滤器
- GlobalFilter(全局过滤器) 所有路由全部生效,无需绑定 Route;自定义鉴权、限流、跨域、日志都写这个
过滤器执行顺序:pre前置逻辑 → 转发请求 → post后置逻辑
Filter 执行顺序 order
- order 值越小,优先级越高
- 同一过滤器:先执行 pre,响应回来执行 post
执行时序举例:
1 | |
整体请求流转核心链路
1 | |
核心类记忆:
DispatcherHandler:WebFlux 请求调度核心
RoutePredicateHandlerMapping:路由匹配
FilteringWebHandler:组装过滤器链
WebFlux 异步非阻塞模型
底层 Reactor,基于事件驱动,少量 NIO 线程(默认 Netty 工作线程数 CPU 核数 * 2)
优势:长连接、大量并发连接场景资源占用低
注意:不能在 Filter 中写阻塞代码(RestTemplate、JDBC 同步调用)
阻塞代码会占用有限的 NIO 工作线程,网关直接雪崩!
阻塞场景必须使用响应式客户端 WebClient。
负载均衡:lb:// 原理
URI 配置 lb://user-service
- 自动整合 Spring Cloud LoadBalancer(新版本舍弃 Ribbon)
- Gateway 内置负载均衡过滤器
LoadBalancerClientFilter流程:
解析服务名 → 向 Nacos 获取服务实例列表 → 根据负载均衡策略选出实例 → 替换真实地址转发
坑:不能使用旧 Ribbon 依赖,SpringCloud2020 + 默认移除 Ribbon
CORS 跨域处理原理
浏览器 OPTIONS 预检请求;网关统一配置跨域,可以避免每个微服务单独配置。
两种方式:yaml 全局配置 / 自定义 GlobalFilter 处理 OPTIONS 请求。
经典坑:允许 Credentials 为 true 时,不能配置 * 允许所有 Origin。
一个请求匹配多条路由会发生什么?
只会匹配第一条命中的路由! 路由顺序非常关键;yml 中从上到下依次匹配,匹配成功直接执行,不再遍历后续路由。
实践规范:精确路由放在上方,模糊路由放下方。
GlobalFilter 和 GatewayFilter 区别?
- GatewayFilter:绑定单条路由,粒度小;只对当前 route 生效
- GlobalFilter:全局所有路由生效;鉴权、限流、全局日志统一使用它
Gateway 在启动时,会自动把所有 GlobalFilter 包装成 GatewayFilter 加入每条路由过滤链。
过滤器中 pre 和 post 执行时机?
pre:请求转发给下游服务之前执行(鉴权、参数修改、限流)
post:下游返回响应之后执行(统一封装返回体、添加响应头)
Gateway 如何整合 Sentinel 实现网关限流?两种限流粒度:
路由维度限流:对整个 route 设置 QPS 阈值
API 分组限流:自定义 API 分组,一批接口统一限流
底层依靠 SentinelGatewayFilter;
注意:网关层限流属于入口限流,推荐优先在网关拦截,保护后端微服务集群。
Gateway 支持 websocket 吗?
原生支持。底层依靠 Netty 处理 ws 长连接。
网关如何做请求重试?
内置 RetryGatewayFilter;yml 配置开启重试。
生产注意:GET 安全接口可重试;POST / 写接口谨慎开启,防止重复提交。
如何实现统一鉴权?
自定义 GlobalFilter,order 设置高优先级(负数靠前)
流程:
- 获取 token
- 校验 token 有效性
- 校验失败直接返回 401,不再向下游转发
- 校验通过,把用户信息放入请求头传给微服务
Gateway 过滤器中如果执行阻塞代码会有什么后果?
WebFlux NIO 工作线程数量有限,阻塞代码会占用工作线程无法释放。
大量并发下,工作线程耗尽,新请求全部堆积,网关彻底卡死。
解决方案:同步阻塞逻辑封装成响应式,使用 WebClient;或者隔离线程池。
Gateway 如何实现请求 body 重复读取?
默认只能读取一次 RequestBody! 原因:请求体流只能消费一次,过滤器读取后下游无法获取 body。
解决方案:自定义缓存过滤器 CachedBodyGlobalFilter,把 body 缓存到内存。
鉴权、日志打印请求体几乎必写这个过滤器。
网关全局异常怎么统一处理?
两种方案:
- 实现
ErrorWebExceptionHandler全局异常处理器(推荐) - 使用
Spring Cloud Gateway的fallback熔断降级(整合 Resilience4j/Sentinel)
不能使用 @ControllerAdvice!
重点坑:@ControllerAdvice 基于 Servlet,WebFlux 网关不生效!
网关超时配置分哪两类?
连接超时:和下游建立 TCP 连接超时
响应超时:连接成功后,下游业务处理返回数据超时
注意区分全局超时 和 单路由独立超时配置。
dubbo
Apache Dubbo:高性能、轻量级 Java RPC 远程调用框架。
核心能力:远程通信、服务发现、负载均衡、容错、序列化。
在 SCA 中有两种使用模式:
- Dubbo 原生模式(dubbo:// 协议)
- Spring Cloud 兼容模式(triple 协议,Dubbo3 新特性,支持 HTTP/REST、gRPC 风格)
核心五大核心部件:
- Provider 服务提供者:提供接口实现
- Consumer 服务消费者:调用远程接口
- Registry 注册中心:服务注册与发现(Nacos/Zookeeper)
- Monitor 监控中心:统计调用次数、耗时
- Container 容器:启动容器(Spring 容器)
调用完整流程
- Provider 启动:扫描 @DubboService → 构建服务元信息 → 注册服务元数据到注册中心,开启 Netty 监听端口
- Consumer 启动:@DubboReference,向注册中心订阅目标服务
- 注册中心推送 Provider 实例列表给 Consumer
- Consumer 本地保存服务实例缓存,不每次请求查询注册中心
- 发起调用:接口代理对象 → Filter 过滤器链 → 负载均衡选出节点 → Netty 发送 TCP 请求
- Provider 接收请求:分发到业务线程池执行业务方法
- 结果原路返回给 Consumer
关键点:注册中心只负责地址推送,不参与实际业务调用。注册中心宕机,消费端依靠本地缓存依旧可以正常调用服务!
Dubbo 和 OpenFeign 区别?
- 通信协议 Feign:HTTP 协议(七层),基于 Spring MVC,文本协议;
Dubbo:默认 dubbo:// TCP 二进制协议;Dubbo3 triple 支持 HTTP2。 - IO 模型 Feign:同步阻塞;
Dubbo:Netty NIO 异步非阻塞。 - 性能 Dubbo 二进制序列化、TCP 短连接复用,吞吐量更高、延迟更低,适合内部微服务密集调用;
Feign 适合前后端互通、异构语言调用。 - 功能生态 Dubbo 内置负载均衡、多种容错策略、线程模型精细化隔离;
Feign 能力单一,负载均衡依靠 Spring Cloud LoadBalancer。 - 使用场景 内部服务之间密集 RPC 调用 → Dubbo;
对外 HTTP 接口、跨语言调用 → OpenFeign。
@DubboService、@DubboReference 作用
- @DubboService:标记暴露为 Dubbo 服务,注册到注册中心
- @DubboReference:引用远程服务,生成代理对象发起远程调用
坑:不要和 Spring @Service 混淆;@DubboReference 注入时机容易出现 Bean 循环依赖。
Dubbo 支持哪些协议?
- dubbo://(默认):TCP、Hessian2 二进制,长连接,内部调用首选
- triple://(Dubbo3 主推):HTTP2,兼容 gRPC,支持跨语言
- http://:HTTP 协议,通用性强,性能弱
- rmi://、webservice:// 基本淘汰
支持的序列化方案
- Hessian2(Dubbo2 默认)
- Fastjson2
- Kryo(高性能,推荐生产)
- Java 原生序列化(严禁生产使用,漏洞多、体积大)
安全重点:Dubbo 反序列化漏洞,建议关闭允许陌生序列化类。
负载均衡策略
- Random 随机(默认):加权随机,简单高效
- RoundRobin 加权轮询:按权重依次分发
- LeastActive 最小活跃数:优先调用当前处理请求最少的节点(生产推荐)
- ConsistentHash 一致性哈希:相同参数路由到同一节点,适合会话黏滞
追问:默认 Random,为什么不轮询?加权随机在节点权重变化时更加平滑。
集群容错策略 Cluster
调用失败后的处理策略:
- Failover 失败自动重试【默认】:失败自动切换其他节点;注意幂等!写接口慎用。
- Failfast 快速失败:调用一次,失败直接抛异常(新增 / 修改写接口首选)
- Failsafe 失败安全:失败直接忽略,返回空,日志打印(日志、统计类无关业务接口)
- Failback 失败自动恢复:后台异步重试
- Forking 并行调用多个服务,取最快返回(极少使用,消耗连接)
高频坑:默认 Failover 重试次数默认 2 次,总最多调用 3 次;非幂等接口必须改成 Failfast,防止重复新增!
服务调用超时 timeout
- 优先级:方法级 > 接口级 > 全局默认(默认 1000ms)
- 注意:超时≠异常,只是等待结果时间截止;服务端可能继续执行逻辑,造成脏数据。
连接模型:长连接还是短连接?
Dubbo 协议:Consumer 与 Provider 之间单一长连接复用。
多个请求复用一条 TCP 连接;使用异步 IO。
补充:大量 Consumer 时,Provider 连接数会暴涨,可以配置连接限制。
Dubbo 线程模型
三大线程池分工隔离,互不阻塞
Netty IO 线程(Reactor 线程):负责 TCP 连接、编解码、网络 IO 读写;禁止执行业务代码!
业务线程池:真正执行接口业务逻辑(固定线程池,可配置)
心跳线程:定时发送心跳包维持长连接
致命坑:
如果业务代码阻塞在 IO 线程(过滤器长时间同步阻塞),会导致所有连接收发消息卡死!
SPI 机制
Dubbo SPI:扩展机制,替代 Java 原生 SPI,增强功能:
- 支持按需加载、缓存、依赖注入、包装类(Wrapper)、激活条件 @Activate
Dubbo 几乎所有组件都是 SPI 扩展:负载均衡、容错策略、序列化、过滤器、注册中心。
面试追问:Java SPI 和 Dubbo SPI 区别
- Java SPI 一次性实例化所有实现类;Dubbo SPI 按需懒加载
- Dubbo 支持别名、依赖注入、AOP 包装 Wrapper
- Java SPI 无缓存,性能差
服务暴露过程、服务引用过程
服务暴露
Config → ProxyFactory 创建代理 → Invoker → Filter 链 → Exporter → Netty 服务器启动 → 注册 Registry
服务引用
创建远程 Invoker → 注册中心订阅 → 监听器感知地址变化 → Cluster 集群包装 → 创建消费端代理
消费者本地缓存
消费端订阅成功后,实例列表缓存在内存;注册中心推送变更时更新缓存。
注册中心故障,不影响存量调用;新服务无法发现。
隐式参数 Attachment(上下文传递)
RpcContext 可以设置附件,透传给下游 Provider。
典型场景:传递 traceId、登录用户信息、灰度标记。
坑:Attachment 大小不要过大,占用网络传输;异步调用注意 RpcContext 上下文失效!
异步调用三种方式
- Future 异步调用
- 回调 Callback
- 响应式调用(Dubbo3 支持 Reactor)
Dubbo 重试机制,什么时候会重试?
Failover 模式下:收到网络异常、连接异常、超时异常触发重试; 业务异常(自定义 Exception)不会重试! 举例:代码抛出参数错误,不会切换节点重试。
如何解决重试带来的重复请求?
方案:
- 写接口集群策略改为 Failfast 关闭重试
- 接口设计幂等(唯一业务单号防重复)
- 控制重试次数
Provider 业务线程池打满会发生什么?
新请求无法分配业务线程,返回线程池耗尽异常;IO 线程正常,只是业务处理队列溢出。
可以配置队列长度、拒绝策略。
RpcContext 使用坑
同步调用正常;异步调用、新线程中,上下文会丢失。
原因:RpcContext 基于 ThreadLocal 存储。
怎么实现 Dubbo 灰度发布?
常用两种方案
- 基于参数路由:Attachment 传递灰度标记,自定义 Router 路由组件
- Nacos 元数据标记版本号,版本路由
version/group补充:group 分组、version 版本用于环境隔离、灰度、多版本并存。
group 和 version 区别
version:同一个接口多个实现版本(灰度、升级)
group:区分不同环境、不同业务分组(测试组 / 生产组)
OpenFeign
定位:声明式 HTTP 客户端,简化微服务之间 HTTP 调用;底层封装 RestTemplate / Apache HttpClient / OkHttp。
OpenFeign 是 Spring Cloud 官方增强版本,支持 @RequestMapping、@RequestParam 等 Spring 注解。
整体启动流程
@EnableFeignClients 开启 Feign 扫描
启动时扫描所有标注@FeignClient的接口
通过 FeignClientFactoryBean 创建 Factory
使用 JDK 动态代理生成接口代理对象
发起调用时:代理对象 → 解析注解(路径、参数、请求头)→ 构造 HTTP 请求 → http 客户端发送
核心:基于 JDK 动态代理,不支持 private、static 方法
请求完整调用链路
1 | |
OpenFeign 是什么?
声明式、模板化 HTTP 调用框架。
只需要定义接口,添加注解,不用手动拼接 url、不用发起 http 请求,Feign 自动生成代理实现类发起远程调用。
OpenFeign 整合组件
- 服务发现:配合 Nacos/Eureka
- 负载均衡:SpringCloud LoadBalancer(2020 版本后移除 Ribbon)
- 超时、重试、日志、拦截器全部内置扩展
核心注解
@FeignClient(name = "service-name") 属性:
- name:服务名称(注册中心服务名)
- url:直连地址,调试使用,生产禁用
- fallback:服务降级类(传统)
- fallbackFactory:可以捕获异常,推荐使用
四大核心组件(Feign SPI 扩展)
Encoder:请求编码,Java 对象 → http body(默认 Jackson)
Decoder:响应解码,http 返回体 → Java 对象
Contract:注解解析器
- SpringMvcContract(OpenFeign 默认,支持 Spring 注解)
- Default Contract(原生 Feign 注解)
RequestInterceptor 请求拦截器
最常用:统一传递 token、traceId、请求头
负载均衡如何实现?
当@FeignCliet使用服务名(lb 模式)
拦截器 LoadBalancerFeignClient 生效
内部调用 Spring Cloud LoadBalancer 获取实例
将服务名替换为真实 IP + 端口,发起 http 请求
重要知识点:
新版本 SpringCloud 移除 Ribbon,默认使用内置 LoadBalancer;
超时机制两套配置
OpenFeign 包含两层超时控制,容易混淆
- Feign 自身超时(上层)
1 | |
- LoadBalancer 负载均衡重试的超时(下层) LoadBalancer 也拥有超时、重试配置
执行顺序:先负载均衡选出实例,再执行 feign 调用
经典坑:只改 feign 超时,忘记关闭负载均衡重试,依然出现多次调用。
@FeignClient 接口方法 @PathVariable 必须指定 value?
是的!
OpenFeign 解析注解时,如果不写 @PathVariable("id"),编译后参数名丢失,无法拼接路径,调用报错。
OpenFeign 默认 http 客户端是什么?
默认 JDK 原生 java.net.HttpURLConnection,不支持连接池,性能差。
生产推荐替换:Apache HttpClient5 或者 OkHttp,开启连接池提升吞吐量。
fallback 和 fallbackFactory 区别?
- fallback:只能做降级返回,拿不到原始异常,无法区分失败原因
- fallbackFactory:可以捕获 Throwable 异常,根据不同异常做不同降级逻辑
生产推荐 fallbackFactory
OpenFeign 默认开启重试吗?
原生 Feign 内置重试器(DefaultRetryer); 但是!整合 Spring Cloud LoadBalancer 之后,默认重试被关闭。
注意:不要轻易开启重试,非幂等接口会产生重复创建数据。
Feign 拦截器 RequestInterceptor 使用场景
统一透传请求头:用户 token、traceId、灰度标识。
经典坑:使用多线程异步调用 Feign,ThreadLocal 信息丢失,请求头传不过去!
OpenFeign GET 请求传递 POJO 实体对象会报错?
原因:GET 请求无法携带 requestBody。
解决方案:
- 拆分成多个 @RequestParam
- 改为 POST
- 使用 Feign 自定义拦截器处理
同时配置 LoadBalancer 重试 + Feign 重试会怎样?
两层重试叠加,调用次数指数级增多,极易引发雪崩;
规范:只保留一层重试,建议直接全部关闭重试,业务自行处理异常。
OpenFeign 能不能传递文件?
原生不方便。
需要引入 feign-form 扩展依赖,设置表单编码;
尽量避免跨服务传输大文件,推荐对象存储传递 URL。