本轮 SpringCloud微服务架构面试题库(带答案)
【面试题】1. Eureka自我保护机制什么情况下会触发?生产环境要不要关闭?
【标准答案】Eureka自我保护机制在短时间内大量服务实例心跳丢失(超过85%阈值)时触发。触发后,Eureka不再剔除任何服务,保护注册表不被清空。
生产环境不建议关闭。原因有三:
- 防止网络分区误杀:网络抖动时,若关闭自我保护,Eureka会大量剔除正常服务,导致服务雪崩。
- 保护优先级高于准确:宁可保留已下线的"僵尸服务",也不能把正常服务误剔除(AP模型的典型体现)。
- 快速恢复:网络恢复后,心跳恢复正常,自我保护自动解除。
如果担心僵尸服务问题,可以配合服务健康检查和客户端重试机制,而不是关闭自我保护。
【面试题】2. Ribbon如何自定义负载均衡策略?举例说明。
【标准答案】自定义负载均衡策略有两种方式:
方式一:配置文件指定
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule方式二:代码自定义IRule实现类
public class MyWeightedRule extends AbstractLoadBalancerRule {
@Override
public Server choose(ILoadBalancer lb, Object key) {
// 自定义逻辑:如按响应时间加权、按区域优先等
return server;
}
}实际场景举例:
- 按版本灰度发布:根据服务metadata中的version字段,90%流量走v1,10%走v2。
- 按响应时间动态权重:响应快的实例权重更高,慢的降权,实现自适应负载均衡。
- 同区域优先:优先调用同可用区的实例,降低跨区域延迟。
【面试题】3. OpenFeign超时和重试怎么配置?有什么坑?
【标准答案】OpenFeign超时配置分两层:
feign:
client:
config:
default:
connectTimeout: 5000 # 连接超时
readTimeout: 10000 # 读取超时
retryer:
period: 100 # 重试间隔
maxPeriod: 1000 # 最大重试间隔
maxAttempts: 3 # 最大重试次数(包含首次)常见坑点:
- 默认重试策略是Retryer.NEVER_RETRY:如果不显式配置,失败后不会重试。
- Ribbon也有超时配置:如果Ribbon的timeout < Feign的readTimeout,请求会被Ribbon提前掐断。
- 重试可能造成幂等性问题:POST/PUT接口默认重试可能导致重复提交,建议接口设计为幂等,或对非幂等接口关闭重试。
- 超时过长拖垮系统:如果下游服务响应慢,Feign一直等待,会占用连接池,建议设置合理超时并配合熔断。
【面试题】4. Sentinel的限流算法有哪些?各自优缺点?
【标准答案】Sentinel支持三种主流限流算法:
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 令牌桶 | 固定速率生成令牌,请求获取令牌后执行 | 允许一定程度的突发流量(桶内有令牌即可) | 需要预分配令牌空间 |
| 漏桶 | 请求先进队列,固定速率流出 | 输出平滑,保护下游 | 无法应对突发流量,队列可能溢出 |
| 滑动窗口 | 将时间窗口分成多个格子,统计每个格子的请求数 | 统计精准,适合QPS限流 | 内存占用略高 |
实际选型建议:
- API网关限流:用滑动窗口,精准控制每秒请求数。
- 数据库保护:用令牌桶,允许短时突发,但长期平滑。
- 外部调用保护:用漏桶,固定速率调用,避免压垮下游。
【面试题】5. Spring Cloud Gateway如何处理跨域?
【标准答案】Gateway处理跨域有两种方式:
方式一:全局配置
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://example.com"
allowedMethods: "*"
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600方式二:路由级别配置
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
metadata:
cors:
allowedOrigins: "'https://example.com'"注意事项:
- 不要配置
allowedOrigins: "*"同时allowCredentials: true,浏览器会报错,必须明确指定域名。 - 生产环境限制allowedOrigins,只允许受信任的前端域名。
- 预检请求(OPTIONS)单独处理:Gateway会自动响应OPTIONS请求,无需转发到后端。
【面试题】6. Nacos配置热更新的原理是什么?如何实现配置实时推送?
【标准答案】Nacos配置热更新原理:
核心机制:长轮询 + 监听器
- 客户端启动时注册监听器:
configService.addListener("app.yml", "DEFAULT_GROUP", new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
// 配置变更回调,更新本地配置
}
});长轮询机制:
- 客户端向Nacos发起长轮询请求(默认30秒超时)
- 服务端收到请求后,检查配置版本号
- 若版本号一致(无变更),挂起请求
- 若版本号不一致(有变更),立即返回新配置
- 超时未变更,返回304,客户端重新发起长轮询
实时性保证:
- 配置变更后,Nacos主动推送变更到所有监听的客户端
- 推送失败时,客户端下次长轮询时拉取最新配置
与Spring Cloud集成:
@RefreshScope注解标记的Bean会在配置变更时自动刷新- 配置变更后,Spring Cloud Context发布
RefreshEvent,触发Bean重建
【面试题】7. Sleuth如何实现TraceId在微服务间的传递?
【标准答案】Sleuth通过HTTP Header传递实现TraceId跨服务传递:
核心流程:
请求到达网关/首个服务:
- Sleuth生成TraceId(如:
5e6a3d1f2b7c4a8b)和SpanId - 将TraceId写入HTTP Header:
X-B3-TraceId
- Sleuth生成TraceId(如:
服务A调用服务B:
- RestTemplate/Feign拦截器自动从当前上下文读取TraceId
- 将Header透传:
X-B3-TraceId、X-B3-SpanId、X-B3-ParentSpanId
服务B接收请求:
- Sleuth Filter从Header提取TraceId
- 创建新的Span,继承相同的TraceId
- 继续向下游服务传递
关键Header:
X-B3-TraceId:全局唯一,贯穿整个调用链X-B3-SpanId:每个服务的唯一Span标识X-B3-ParentSpanId:父Span的ID
异步调用场景:
- 使用
@NewSpan手动创建Span - 使用
Sleuth提供的Runnable/Callable包装器,自动传递上下文
【面试题】8. Seata AT模式如何保证分布式事务一致性?有什么缺点?
【标准答案】Seata AT模式原理:
核心机制:两阶段提交 + 自动补偿
第一阶段(执行业务SQL):
- 解析业务SQL,记录before image(修改前的数据快照)
- 执行业务SQL
- 记录after image(修改后的数据快照)
- 生成行锁(防止脏写)
- 将before/after image和行锁写入
undo_log表
第二阶段(提交/回滚):
- 提交:异步删除undo_log,释放行锁
- 回滚:根据undo_log反向生成SQL,恢复数据
优点:
- 业务无侵入,自动补偿
- 性能较好,本地事务先提交,不阻塞其他事务
缺点:
- 脏读风险:一阶段提交后,其他事务可能读到未最终确认的数据(隔离级别问题)
- 全局锁竞争:高并发下,多个全局事务竞争同一行数据的锁,性能下降
- undo_log表膨胀:事务量大时,undo_log表增长快,需定期清理
- 不支持复杂SQL:如存储过程、多表关联更新等
适用场景: 大部分业务场景,尤其是对隔离性要求不高的场景。
【面试题】9. Redisson的RedLock(红锁)是什么?解决了什么问题?
【标准答案】RedLock是Redis官方提出的分布式锁高可用方案:
解决的问题:主从架构下的锁丢失
传统Redis主从架构:
- 客户端A在Master上加锁成功
- Master宕机,锁未同步到Slave
- Slave升级为Master
- 客户端B也加锁成功 → 锁失效,两个客户端同时持有锁
RedLock原理:
- 获取当前时间戳
- 依次向N个(通常5个)独立的Redis节点申请加锁
- 计算加锁成功耗时,若超过锁过期时间,则加锁失败
- 若超过半数(N/2+1)节点加锁成功,且总耗时小于锁过期时间,则加锁成功
- 释放锁时,向所有节点发送解锁请求
代码示例:
RedissonClient client1 = Redisson.create(config1);
RedissonClient client2 = Redisson.create(config2);
RedissonClient client3 = Redisson.create(config3);
RLock lock1 = client1.getLock("myLock");
RLock lock2 = client2.getLock("myLock");
RLock lock3 = client3.getLock("myLock");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
try {
// 业务逻辑
} finally {
redLock.unlock();
}争议点: RedLock在时钟漂移、网络分区等极端场景下仍有问题,实际生产中更多采用单节点Redis + 主从切换+锁续期的方案。
【面试题】10. JWT Token过期后如何实现无感刷新?
【标准答案】JWT无感刷新方案:
方案一:双Token机制(Access Token + Refresh Token)
登录时返回两个Token:
- Access Token:有效期短(如15分钟)
- Refresh Token:有效期长(如7天)
请求拦截器处理:
// 请求拦截器
axios.interceptors.request.use(config => {
const accessToken = localStorage.getItem('accessToken');
config.headers.Authorization = `Bearer ${accessToken}`;
return config;
});
// 响应拦截器
axios.interceptors.response.use(
response => response,
async error => {
if (error.response.status === 401) {
const refreshToken = localStorage.getItem('refreshToken');
const res = await axios.post('/auth/refresh', { refreshToken });
localStorage.setItem('accessToken', res.data.accessToken);
// 重新发起原请求
return axios(error.config);
}
return Promise.reject(error);
}
);- 服务端刷新逻辑:
- 校验Refresh Token有效性
- 若有效,生成新的Access Token返回
- 若无效,返回401,前端跳转登录页
方案二:Token快过期时主动刷新
- 前端检查Token过期时间,剩余时间小于阈值时主动刷新
- 避免请求时突然401,体验更好
安全注意事项:
- Refresh Token存储在HttpOnly Cookie中,防止XSS攻击
- Refresh Token使用后立即失效,生成新的(防止重放攻击)
- 敏感操作(如修改密码)要求重新登录,不能用Refresh Token