Spring框架与微服务面试考题
02 - Spring框架与微服务 · 高频面试考题背诵指南
目录
- Q1:Spring IOC 的工作原理?BeanFactory 和 ApplicationContext 的区别?
- Q2:Spring Bean 的完整生命周期
- Q3:Spring AOP 原理?JDK 动态代理和 CGLIB 代理的区别?
- Q4:Spring 如何解决循环依赖?三级缓存各自的作用?
- Q5:@Autowired 和 @Resource 的区别?
- Q6:@Transactional 失效的场景(至少 5 种)
- Q7:Spring 事务的 7 种传播行为
- Q8:SpringBoot 自动装配原理?@SpringBootApplication 做了什么?
- Q9:MyBatis 的一级缓存和二级缓存?分别有什么坑?
- Q10:MyBatis 的 #{} 和 ${} 的区别?
- Q11:Nacos 注册中心工作原理?和 Eureka 的区别?
- Q12:Sentinel 的限流算法和熔断策略
- Q13:Seata 的 TCC 模式原理?AT 模式和 TCC 的区别?
- Q14:TCC 模式的三大异常场景(空回滚、悬挂、幂等)
- Q15:可靠消息最终一致性(本地消息表方案)
- Q16:2PC / 3PC / TCC / Saga / 可靠消息 五大方案对比
- Q17:Feign 的超时、重试与负载均衡原理
- Q18:Spring Cloud Gateway 核心架构与应用
一、Spring Core
Q1:Spring IOC 的工作原理?BeanFactory 和 ApplicationContext 的区别?
核心考点:控制反转思想、容器启动流程、两种容器的差异。
背诵话术:
IOC(控制反转)的核心思想是:对象的创建和依赖关系的管理不再由业务代码自己控制,而是交给 Spring 容器来负责。我们只需要通过注解或 XML 配置声明 Bean 的定义,容器会在启动时扫描这些定义,完成 Bean 的实例化、属性填充和依赖注入,并将 Bean 存储在内部的Map<String, BeanDefinition>结构中。
BeanFactoryvsApplicationContext:
BeanFactory是最基础的 IOC 容器接口,采用懒加载策略,只有在第一次调用getBean()时才实例化 Bean,功能较为简单。ApplicationContext继承自BeanFactory,是更完整的企业级容器,支持饿加载(容器启动时就实例化所有单例 Bean)、国际化(i18n)、事件发布(ApplicationEvent)、AOP 自动代理等扩展功能。生产项目中一律使用ApplicationContext。
Q2:Spring Bean 的完整生命周期
核心考点:从实例化到销毁的完整 11 个步骤。
背诵话术:
Spring Bean 的完整生命周期如下:
- 实例化(Instantiation):容器通过反射调用构造方法创建 Bean 的原始对象(此时属性还未填充)。
- 属性填充(Populate Properties):将配置文件或注解中声明的属性值和依赖的其他 Bean 注入进来(
@Autowired、@Value在这一步生效)。Aware接口回调:如果 Bean 实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware等接口,容器会依次回调对应的setXxx()方法,让 Bean 能感知到容器的相关信息。BeanPostProcessor前置处理(postProcessBeforeInitialization):对 Bean 进行初始化前的增强处理,Spring AOP 的代理对象就是在 BeanPostProcessor 中创建的。- 初始化(Initialization):依次执行
@PostConstruct注解方法 →InitializingBean.afterPropertiesSet()→ XML 中配置的init-method。BeanPostProcessor后置处理(postProcessAfterInitialization):初始化后的增强,Spring AOP 代理实际上在此阶段生成并替换原始 Bean。- Bean 就绪,放入单例池(singletonObjects):Bean 可以被正常使用。
- 销毁(Destruction):容器关闭时,依次执行
@PreDestroy→DisposableBean.destroy()→ XML 配置的destroy-method。
Q3:Spring AOP 原理?JDK 动态代理和 CGLIB 代理的区别?
核心考点:代理模式,两种代理的底层实现,何时用哪种。
背诵话术:
Spring AOP 的本质是动态代理。Spring 会在 Bean 初始化的
BeanPostProcessor后置处理阶段,判断该 Bean 是否需要被 AOP 切面增强,如果需要,则为该 Bean 创建一个代理对象并返回,后续从容器中获取到的就是这个代理对象。Spring 支持两种代理方式:
JDK 动态代理:
- 要求目标类必须实现至少一个接口。
- 代理对象通过
java.lang.reflect.Proxy.newProxyInstance()创建,代理对象实现了与目标类相同的接口。- 方法调用通过
InvocationHandler.invoke()进行拦截和增强。CGLIB 代理:
- 不要求目标类实现接口,适用于没有接口的普通类。
- 通过字节码技术(ASM)在运行时动态生成目标类的子类作为代理对象,通过重写父类方法来实现拦截。
- 因为是继承方式实现,
final修饰的类和方法无法被代理。选择策略:在 Spring Boot 2.x 中,默认强制使用 CGLIB(
spring.aop.proxy-target-class=true),因为 CGLIB 不需要依赖接口,更通用。
Q4:Spring 如何解决循环依赖?三级缓存各自的作用?
核心考点:三个 Map 的存储内容,解决循环依赖的完整流程,为何需要三级而不是二级。
背诵话术:
Spring 通过三级缓存来解决单例 Bean 之间的循环依赖:
- 一级缓存(
singletonObjects):存储完全初始化好的 Bean,可以直接对外使用。- 二级缓存(
earlySingletonObjects):存储提前暴露的、尚未完全初始化的 Bean 对象(已实例化但属性还未填充)。- 三级缓存(
singletonFactories):存储 Bean 的工厂对象(ObjectFactory),调用工厂的getObject()方法可以得到 Bean 的早期引用(可能是代理对象)。解决循环依赖的流程(A 依赖 B,B 依赖 A):
- 创建 A,A 实例化后,将 A 的
ObjectFactory放入三级缓存。- A 开始填充属性,发现需要注入 B,触发 B 的创建。
- 创建 B,B 实例化后,将 B 的
ObjectFactory放入三级缓存。- B 填充属性时,发现需要注入 A,去三级缓存中找到 A 的工厂,调用
getObject()获取 A 的早期引用,并将其升级到二级缓存,A 的工厂从三级缓存中移除。- B 成功注入了 A 的早期引用,B 完成初始化,进入一级缓存。
- A 继续完成属性填充(注入了完整的 B),A 完成初始化,进入一级缓存。
为什么需要三级而不是二级:三级缓存中存的是
ObjectFactory而不是对象本身,目的是支持 AOP 代理。如果 A 需要被 AOP 代理,则调用ObjectFactory.getObject()时会返回 A 的代理对象而非原始对象,确保 B 注入的是 A 的代理版本。如果只有二级缓存(直接存原始对象),就无法在循环依赖场景下保证注入的是正确的代理对象。
Q5:@Autowired 和 @Resource 的区别?
核心考点:注入方式、来源规范、按类型 vs 按名称。
背诵话术:
@Autowired:Spring 原生注解,优先按类型(byType)注入。如果容器中有多个相同类型的 Bean,则再按字段名(byName)进行匹配,也可以配合@Qualifier指定 Bean 名称。@Resource:JDK 原生注解(javax.annotation),优先按名称(byName)注入。如果找不到匹配名称,再按类型匹配。适合希望减少对 Spring 框架依赖的场景。实际开发中两者差别不大,
@Autowired更常用,但当接口有多个实现类时,@Autowired + @Qualifier("beanName")或@Resource(name="beanName")来明确指定是最清晰的做法。
Q6:@Transactional 失效的场景(至少 5 种)
核心考点:AOP 代理机制导致的各种失效场景。
背诵话术:
@Transactional失效最常见的场景有以下几种:
- 自我调用(同类内部调用):在同一个 Service 类中,方法 A(无事务)直接调用方法 B(有
@Transactional),因为 A 内部调用 B 是通过this.B(),没有走 Spring 的代理对象,事务切面无法被触发,事务完全失效。解决方法:通过ApplicationContext获取自身代理对象再调用,或将 B 方法抽取到另一个 Service 类中。- 方法非 public:Spring 事务的 AOP 拦截器只对
public方法生效,private、protected或包级别方法上的@Transactional会被忽略。- 异常被 catch 吞掉:方法内部用
try-catch捕获了异常,没有在catch中重新抛出,Spring 的事务管理器感知不到异常,事务不会回滚。- 异常类型不匹配:
@Transactional默认只对RuntimeException和Error回滚,如果抛出的是IOException等受检异常,事务不会回滚。需要配置@Transactional(rollbackFor = Exception.class)。- 数据库不支持事务:如使用 MySQL 的
MyISAM存储引擎,本身不支持事务,注解无效。- Bean 未被 Spring 管理:如果类没有被 Spring 扫描到(忘加
@Service),不在容器中,自然没有代理。- 多线程中失效:
@Transactional的事务是绑定在当前线程的Connection上的,如果方法内开启了新线程去执行数据库操作,新线程拿到的是不同的Connection,不在同一个事务中。
Q7:Spring 事务的 7 种传播行为
核心考点:各传播行为对当前事务的处理逻辑,REQUIRED vs REQUIRES_NEW vs NESTED 的区别。
背诵话术:
Spring 事务传播行为定义了”当一个事务方法被另一个事务方法调用时,该如何处理事务”:
传播行为 说明 REQUIRED(默认)有事务则加入,没有则新建。最常用。 REQUIRES_NEW无论如何都新建一个独立事务,挂起当前事务。两个事务互不影响,互不回滚。 NESTED在当前事务内创建一个嵌套事务(保存点 Savepoint),子事务可以独立回滚到保存点,不影响父事务;但父事务回滚会导致子事务也回滚。 SUPPORTS有事务则加入,没有事务则以非事务方式执行。 NOT_SUPPORTED以非事务方式执行,若当前有事务则挂起事务。 MANDATORY必须在事务中执行,若没有活动事务则抛异常。 NEVER以非事务方式执行,若当前有事务则抛异常。 实际项目中,
REQUIRES_NEW常用于”子操作失败不影响主流程”的场景,比如在招行调度中台中,记录操作日志的方法使用REQUIRES_NEW,即使日志写入失败也不影响主业务事务的提交。
二、SpringBoot
Q8:SpringBoot 自动装配原理?@SpringBootApplication 做了什么?
核心考点:组合注解、@EnableAutoConfiguration、spring.factories SPI 机制。
背诵话术:
@SpringBootApplication是一个组合注解,包含了三个核心注解:
@SpringBootConfiguration:标识这是一个 Spring 配置类(本质是@Configuration)。@ComponentScan:开启组件扫描,默认扫描主启动类所在包及其子包下的所有@Component、@Service、@Repository、@Controller等注解。@EnableAutoConfiguration:这是自动装配的核心。
@EnableAutoConfiguration的工作原理:
- 它通过
@Import(AutoConfigurationImportSelector.class)导入一个选择器。- 这个选择器的
selectImports()方法会扫描所有 JAR 包的META-INF/spring.factories文件(JDK SPI 扩展机制),读取org.springframework.boot.autoconfigure.EnableAutoConfiguration键下所有的自动配置类全限定名(通常有 100+ 个)。- 然后通过
@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等条件注解进行过滤,只有当条件满足时(比如类路径下存在某个类),对应的自动配置类才会生效。- 最终将满足条件的配置类注册到 Spring 容器中,完成相关 Bean 的自动创建。
💡 SpringBoot 3.x 版本变更:
从 SpringBoot 3.0 开始,官方不再默认扫描META-INF/spring.factories文件。自动装配的配置文件路径改为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。每个自动配置类的类名单独占一行,不再需要以逗号分隔,面试时需特别注意项目所使用的版本。
Q9:MyBatis 的一级缓存和二级缓存?分别有什么坑?
核心考点:缓存作用域(SqlSession vs Mapper/Namespace),脏数据问题。
背诵话术:
一级缓存(SqlSession 级别):默认开启,生命周期与
SqlSession相同。同一个SqlSession内,相同的查询语句和参数第二次查询时直接从缓存中返回,不访问数据库。坑:在分布式或多
SqlSession场景下,如果 Session A 修改了数据,Session B 的一级缓存中还保存着旧数据,会读取到脏数据。另外,执行insert、update、delete操作后,当前SqlSession的一级缓存会被清空。二级缓存(Mapper/Namespace 级别):需要手动开启(在 mapper.xml 中加
<cache/>),生命周期与SqlSessionFactory相同,可以跨SqlSession共享。坑(非常危险):多表联查时,如果查询涉及表 A 和表 B,但缓存的 namespace 只绑定了 Mapper A,当表 B 数据变更时,Mapper A 的缓存不会失效,会读到脏数据。因此,生产中一般禁用 MyBatis 的二级缓存,而是使用 Redis 等统一的分布式缓存方案来替代。
Q10:MyBatis 的 #{} 和 ${} 的区别?
核心考点:预编译占位符 vs 字符串替换,SQL 注入风险。
背诵话术:
#{}(预编译占位符):MyBatis 会将#{}替换为?,通过 JDBC 的PreparedStatement预编译处理,传入的参数值会作为字符串被转义,完全防止 SQL 注入。这是绝大多数场景下的首选。${}(字符串拼接):直接将参数值替换到 SQL 语句中,不做任何转义,存在严重的 SQL 注入风险。例如:SELECT * FROM user WHERE name = '${name}',如果 name 传入' OR 1=1 --,就会执行SELECT * FROM user WHERE name = '' OR 1=1 --,查出所有用户数据。
${}唯一合理的使用场景是动态传入表名或列名(因为这类内容无法通过?参数传递),但此时必须在代码层面对传入值进行严格的白名单校验,杜绝用户直接控制输入。
三、Spring Cloud 微服务
Q11:Nacos 注册中心工作原理?和 Eureka 的区别?
核心考点:服务注册/发现机制,AP vs CP,Nacos 推送 vs Eureka 拉取。
背诵话术:
Nacos 工作原理:服务启动时,将自身的 IP、端口、服务名等信息向 Nacos Server 进行注册,Nacos 维护一个服务注册表。客户端(调用方)向 Nacos 发起订阅,Nacos 会主动将服务实例列表推送给订阅方,同时客户端也会定期从 Nacos 拉取最新列表作为兜底(推拉结合)。Nacos 通过心跳机制(默认 5s 一次)检测服务健康状态,如果 15s 内未收到心跳,将该实例标记为不健康,30s 后从注册表中剔除。
与 Eureka 的主要区别:
特性 Nacos Eureka 一致性模型 支持 AP(默认)和 CP(可切换) 只支持 AP 通知机制 主动推送 客户端定时拉取(30s/次,更新慢) 健康检测 支持 TCP/HTTP/MySQL 等多种健康检查 仅 HTTP 心跳 配置中心 内置 需配合 Spring Cloud Config 维护状态 阿里开源,活跃维护 Netflix 官方停止维护 在我们 SRM 和招行项目中均使用 Nacos 作为注册和配置中心,主要看中的是其推送机制带来的更快的服务发现响应,以及内置的配置中心能力。
Q12:Sentinel 的限流算法和熔断策略
核心考点:滑动窗口限流,三种熔断策略(慢调用/异常比例/异常数)。
背诵话术:
Sentinel 的限流算法:
- 滑动窗口算法(默认):将 1 秒划分为多个小窗口(默认 10 个,每个 100ms),统计每个小窗口内的请求量,滑动求和判断是否超阈值。比固定窗口算法更平滑,不会出现边界突刺问题。
- 漏桶算法(
RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER):请求以固定速率匀速通过,超出速率的请求排队等待(队列模式),适合流量整形场景。- 令牌桶算法:令牌以固定速率生成,请求消耗令牌,令牌满了停止生成,支持一定程度的流量突发。
Sentinel 的三种熔断策略:
- 慢调用比例:统计窗口期内,响应时间超过阈值(如 500ms)的请求占比超过设定比例(如 50%),触发熔断。熔断后进入半开状态,允许少量探测请求通过。
- 异常比例:统计窗口期内,异常请求占总请求的比例超过阈值,触发熔断。
- 异常数:统计窗口期内,异常请求的绝对数量超过阈值,触发熔断。
在招行调度中台中,我们对所有 Feign 外部 RPC 调用配置了慢调用比例熔断,设定响应超过 2s 且慢调用比例超过 30% 时熔断 30s,降级返回兜底数据,防止因下游服务”假死”拖垮上游。
Q13:Seata 的 TCC 模式原理?AT 模式和 TCC 的区别?
核心考点:两阶段提交,AT 模式的自动回滚(undo_log),TCC 的业务侵入性。
背诵话术:
AT 模式(自动):对业务代码无侵入,由 Seata 自动拦截 SQL,在执行前后生成
undo_log(回滚日志)。第一阶段:直接提交本地事务,释放数据库锁,并将 undo_log 持久化。第二阶段:如果全局事务提交成功,删除 undo_log;如果全局事务回滚,通过 undo_log 自动反向补偿恢复数据。AT 模式使用了全局行锁,高并发下性能有一定损耗。TCC 模式(手动):将业务逻辑拆分为三个方法:
Try:预留资源(如冻结库存、预扣余额),检查业务可行性。Confirm:确认执行,消耗 Try 阶段预留的资源(真正扣减)。全局事务提交时调用。Cancel:取消执行,释放 Try 阶段预留的资源(解冻)。全局事务回滚时调用。核心区别:AT 模式对代码无侵入但有全局锁,适合并发量一般的场景;TCC 模式需要开发者手动实现三个方法,有业务侵入性,但不依赖数据库全局锁,并发性能更高,适合金融级高并发场景。在招行核心调度中台里,关键的资金流转操作使用了 TCC 模式保证分布式事务的强一致性。
Q14:TCC 模式的三大异常场景(空回滚、悬挂、幂等)
核心考点:TCC 的三个难题,各自解决手段。
背诵话术:
TCC 的核心难点不在于写 Try/Confirm/Cancel,而在于处理异常场景:
1. 空回滚(Empty Rollback):
- 场景:Try 阶段由于网络超时,被判定为失败,TC(事务协调器)发起 Cancel。但 Try 请求其实根本没到达过调用方(或到达了但调用方没执行),Cancel 被执行时本地根本没有对应的预留资源。
- 解决:Cancel 操作需要支持空回滚——如果没找到对应的 Try 记录,Cancel 不能抛异常,而是记录一条”已回滚”标记,然后返回成功。
2. 悬挂(Suspend):
- 场景:Cancel 比 Try 先到达。TC 先发起 Cancel(如 Try 超时),Cancel 执行完了,然后之前”堵在半路上的”Try 请求后到达执行了。如果 Try 执行成功并预扣了资源,这笔资源就永久被锁死了(Cancel 已经执行完了,没人来解冻)。
- 解决:Cancel 执行时记录一条”已回滚”标记。Try 执行前先检查该标记,如果发现”已回滚”,则拒绝执行 Try(返回失败),阻止悬挂的发生。
3. 幂等控制:Try / Confirm / Cancel 三个操作都可能因网络重试被多次调用,所以三者都必须是幂等的——重复执行结果一致。通常用业务唯一 ID 做唯一索引防重。
Seata TCC 框架的处理:Seata 的 TCC 模式通过维护一张事务记录表自动处理了空回滚和悬挂——Try 前查是否有 Cancel 记录(防悬挂),Cancel 前查是否有 Try 记录(空回滚则记录标记),Confirm/Cancel 成功后记录状态(防幂等)。
Q15:可靠消息最终一致性(本地消息表方案)
核心考点:本地事务 + 本地消息表 + 定时任务重试,适合高可用场景。
背诵话术:
可靠消息最终一致性是分布式事务中最常用的柔性方案,不需要两阶段提交,适合对实时一致性要求不高、但对可用性要求高的业务场景。
本地消息表方案(RocketMQ 事务消息同理):
- 业务方(生产者):在自己的数据库中,将业务操作和消息记录放在同一个本地事务中完成(如”创建订单” + “写入一条 MQ 消息记录”)。本地事务保证要么都成功要么都失败。
- 消息发送:定时任务轮询本地消息表中状态为”未发送”的记录,将消息投递到 MQ。发送成功后将记录状态更新为”已发送”。
- 消费方:消费消息,正常处理业务逻辑。消费完成后提交 offset。如果消费失败,MQ 重投或死信队列兜底。消费者必须有幂等性防止重复消费。
- 补偿检查:定时任务扫描超时未确认的消息(已发送但未消费或消费失败),进行人工处理或补偿。
在招行调度中台的应用:流程调度中的回调通知场景使用了可靠消息方案(不是金融转账核心场景,那个才用 TCC)。流程执行完成后,在本地事务中同时写入回调消息记录,后台定时任务投递到 Kafka,下游系统幂等消费。这套方案保证了即使回调系统暂时不可用(网络闪断),消息也不会丢失,最大程度保证了流程状态的最终一致性。
Q16:2PC / 3PC / TCC / Saga / 可靠消息 五大方案对比
核心考点:各方案的一致性、性能、业务侵入性的权衡。
背诵话术:
方案 一致性 性能 业务侵入 适用场景 2PC(XA) 强一致 差(全程锁资源) 低(数据库直接支持) 传统单体应用、并发低场景 3PC 强一致(比 2PC 略好) 差 低 基本不用(2PC 的改良版,引入超时机制防阻塞),成本高,生态差 TCC 最终强一致 好(不长期锁资源) 高(需实现 Try/Confirm/Cancel) 金融核心交易、库存扣减 Saga 最终一致 好(无锁) 中(需实现正向+补偿) 长流程业务、微服务编排 可靠消息最终一致 最终一致(可能有短暂窗口) 最好(完全异步) 低(只需发消息+幂等消费) 通知类、回调类、解耦场景
选型要点:核心金融资金用 TCC(强一致 + 高性能),通知回调用可靠消息(高吞吐、低耦合),跨多微服务的复杂长流程可以考虑 Saga(每个参与者独立提交 + 补偿回滚),2PC 基本不用于分布式场景(锁资源太久,性能太差)。
在招行项目中,资金流转使用 TCC 模式(如节点流程执行中的状态扣减、冻结),回调通知下游使用可靠消息最终一致性方案(异步通知 + Kafka 投递 + 幂等消费)。
Q17:Feign 的超时、重试与负载均衡原理?
核心考点:声明式客户端、Feign 的调用执行流程、超时配置冲突、Ribbon / LoadBalancer 负载均衡原理。
背诵话术:
Feign 工作原理:
Feign 是一个声明式的 HTTP 客户端。通过在接口上添加@FeignClient注解,Spring 容器启动时,会为该接口生成 JDK 动态代理对象(对应的 Handler 为FeignInvocationHandler)。当调用接口方法时,代理对象拦截请求,通过SynchronousMethodHandler的executeAndDecode()方法进行处理:将方法参数通过Encoder序列化为 HTTP 请求体,生成一个Request对象,接着经过负载均衡器选择实例,交由底层的Client(如 OkHttp/HttpClient/HttpURLConnection)发送 HTTP 请求,最后通过Decoder反序列化为接口返回值。超时与重试:
Feign 内部默认有重试机制(默认不启用或重试 5 次,但微服务组件一般关闭它),超时默认是 10s(Read)和 20s(Connect)。
在招行分布式环境的超时干预中:
我们一般关闭 Feign 的默认重试,并将超时控制权交给 Ribbon(或 Spring Cloud LoadBalancer),通过如下配置进行精细干预:
1
2
3
4
5 ribbon:
ReadTimeout: 2000 # 业务处理超时时间 2s
ConnectTimeout: 1000 # 连接超时时间 1s
MaxAutoRetries: 0 # 同一实例重试次数,设为 0
MaxAutoRetriesNextServer: 1 # 切换实例重试次数,设为 1对于写操作等非幂等接口,需关闭重试,以防止网络抖动导致的重复提交。
负载均衡机制:
Feign 会集成 Ribbon(或 Spring Cloud LoadBalancer)。Ribbon 从注册中心(如 Nacos)获取服务实例列表,本地维护一个缓存,并在发起请求时通过负载均衡算法(如RoundRobinRule轮询、RandomRule随机、BestAvailableRule最小并发数)计算出一个健康的实例 IP 和端口,最后拦截 Feign 的 Request 并重写 URL 进行请求。
Q18:Spring Cloud Gateway 核心架构与应用?
核心考点:Gateway vs Zuul 1.x、网关三大核心概念(Route, Predicate, Filter)、自定义拦截器鉴权与限流。
背诵话术:
Gateway vs Zuul 1.x:
- Zuul 1.x 基于 Servlet 2.5 构建,是同步阻塞 I/O 架构。每个请求占用一个线程,在高并发场景下极易耗尽线程池。
- Spring Cloud Gateway 基于 Spring WebFlux 和 Netty 构建,是异步非阻塞(Reactive)架构,使用极少量的线程就能处理大量并发请求,吞吐量和并发性能大幅领先。
核心概念:
- Route(路由):网关的基本模块,由 ID、目标 URI、断言集合、过滤器集合组成。
- Predicate(断言):Java 8 的 Predicate 接口,用于匹配请求属性(如 Path 路径、Method 方法、Header 请求头等)。
- Filter(过滤器):GatewayFilter 实例,在发送请求到下游服务之前(pre)或之后(post)修改请求/响应。
自定义 Filter 拦截器实战:
在我们医博系统和 OpenAPI 开放平台中,我们自定义了GlobalFilter和GatewayFilterFactory实现如下功能:
- 统一鉴权:在
pre阶段,从请求头获取 JWT Token,进行合法性校验、解析并将解析出的 userId/role 信息写入ServerHttpRequest的 Header 中传递给下游微服务。若无效,直接将 Http 状态码设为 401 并结束请求。- 分布式限流:使用 Gateway 的
RequestRateLimiter过滤器,结合 Redis 和 Lua 脚本实现令牌桶限流算法。针对不同 API 或不同租户设定不同的 Rate 和 Capacity。- 灰度发布/路由干预:根据请求头中的特定版本号(如
x-version: v2)动态修改ServerWebExchange的路由目标,实现金丝雀/灰度流量分流。








