面试追问地图
| 主问题 | 必讲关键点 | 下一层追问 |
|---|---|---|
| 注册发现 | 注册、心跳、订阅、本地缓存 | 注册中心不可用还能否调用 |
| 配置中心 | 推送/拉取、版本、灰度 | 多实例短时不一致、错误配置回滚 |
| Feign/RPC | 服务发现、序列化、超时 | 重试风暴、幂等、连接池 |
| 负载均衡 | 节点选择、权重、健康状态 | 一致性哈希扩缩容、客户端缓存 |
| Gateway | 路由、认证、限流、灰度 | 网关高可用、是否做业务编排 |
| Sentinel | 限流、熔断、系统保护 | 流控模式、热点参数、规则持久化 |
| 服务雪崩 | 超时、隔离、重试、熔断、降级 | 线程池隔离 vs 信号量隔离 |
| 分布式一致性 | 消息、补偿、幂等 | 重复、乱序、死信、人工对账 |
| 链路追踪 | traceId/spanId、跨进程传播 | 线程池上下文丢失、MDC 串联、头部 vs 尾部采样 |
微服务题不能只列组件名,要沿一次请求讲清注册、选址、调用、超时和失败处理。
一、Spring Cloud 基础
微服务的服务间通信方式?
难度 🟡
快答
| 方式 | 协议 | 同步/异步 | 场景 |
|---|---|---|---|
| Feign | HTTP/REST | 同步 | 简单调用 |
| gRPC | HTTP/2 | 同步 | 高性能 |
| 消息队列 | MQ | 异步 | 解耦 |
二、微服务核心组件
SpringCloud vs SpringBoot 区别?
- SpringBoot:构建单个 Spring 应用的框架
- SpringCloud:构建分布式微服务架构的工具集(注册中心、负载均衡、熔断器等)
核心微服务组件
| 组件 | 解决问题 | SpringCloud 实现 | Alibaba 实现 |
|---|---|---|---|
| 注册中心 | 服务发现与状态维护 | Eureka | Nacos |
| 负载均衡 | 服务发现 + 请求分发 | Ribbon | Nacos 服务端均衡 |
| 服务通信 | 服务间消息通信 | Feign/RestTemplate | Feign/Dubbo |
| 配置中心 | 集中管理配置 | Config | Nacos 内置 |
| 服务网关 | API 路由 | Gateway | Gateway |
| 服务保护 | 防止雪崩 | Hystrix | Sentinel |
| 链路追踪 | 调用链路可视化 | Sleuth/Zipkin | Sleuth/Zipkin |
负载均衡算法
- 轮询:按顺序分发(简单/加权)
- 随机:随机选择(简单/加权)
- 一致性哈希:同 IP/同参数路由到同一服务器,扩缩容只影响相邻区段;哈希环与虚拟节点原理见分布式系统
- 最小活跃数:分发到当前请求最少的服务器
服务熔断 vs 服务降级
| 熔断 | 降级 | |
|---|---|---|
| 目的 | 保护链路,防止雪崩 | 保护核心服务 |
| 触发条件 | 下游服务超时/不可用 | 系统负载过高 |
| 行为 | 停止调用,快速返回错误 | 非核心功能不处理或简化处理 |
| 比喻 | 保险丝 | 丢卒保车 |
Hystrix 熔断默认配置:10 秒内请求 >= 20 次且错误率 > 50% 时触发。Hystrix 自 2018 年起已进入维护模式、不再新增特性,新项目应选 Sentinel 或 resilience4j;这里列出是因为存量系统和面试历史考点仍常涉及。
熔断三状态(Closed/Open/Half-Open)流转与限流算法原理见系统设计。
三、注册中心与配置中心
服务注册与发现的基本流程?
难度 🟡
服务启动 → 注册实例和元数据 → 定期心跳
调用方 → 拉取/订阅实例列表 → 本地负载均衡 → 发起调用注册中心需要处理:
- 实例上下线。
- 健康检查和心跳超时。
- 客户端缓存。
- 网络分区时的可用性与一致性取舍。
Nacos 临时实例和持久实例有什么区别?
- 临时实例:依赖客户端心跳,心跳超时后可被删除,适合可弹性扩缩的服务实例。
- 持久实例:服务端主动探测,实例异常后保留注册信息,适合需要保留元数据的场景。
回答时应结合实际使用版本和部署模式,不要只背 AP/CP 标签。
配置中心如何保证变更安全?
- 配置按环境和命名空间隔离。
- 变更需要权限、审计和版本记录。
- 客户端监听变更并刷新允许动态更新的 Bean。
- 高风险配置先灰度,再逐步扩大范围。
- 配置解析失败时保留旧值或快速回滚。
四、网关与服务调用
API 网关的职责有哪些?
- 路由和协议适配。
- 统一认证、鉴权和签名校验。
- 限流、黑白名单和防刷。
- 请求日志、Trace ID 和指标。
- 灰度路由和流量切分。
网关不适合承载复杂业务编排,否则会形成新的单体和性能瓶颈。
OpenFeign 调用需要注意什么?
- 明确连接超时和读取超时。
- 只对适合的错误进行有限重试。
- 写请求要保证幂等。
- 配置熔断和降级,避免故障扩散。
- 记录调用方、目标服务、耗时和错误码。
客户端负载均衡和服务端负载均衡的区别?
- 客户端负载均衡:调用方获取实例列表并选择节点,少一次代理转发,但客户端逻辑更复杂。
- 服务端负载均衡:由网关或代理选择后端,治理集中,但增加一跳和中心组件压力。
五、Sentinel 与流量治理
限流、熔断和系统保护有什么区别?
| 能力 | 判断依据 | 目的 |
|---|---|---|
| 限流 | QPS、并发线程数 | 控制进入系统的流量 |
| 熔断 | 慢调用、异常比例或异常数 | 隔离不稳定依赖 |
| 系统保护 | 系统负载、CPU、入口 QPS | 保护整体系统容量 |
Sentinel 常见流控模式?
- 直接:当前资源达到阈值后限流。
- 关联:关联资源过载时限制当前资源。
- 链路:只限制特定调用入口。
流控效果可选择快速失败、预热或匀速排队。方案需要结合业务是否允许等待和请求是否可重试。
如何防止服务雪崩?
难度 🔴
需要组合治理:
- 超时:尽快释放阻塞资源。
- 重试:限制次数并使用退避。
- 熔断:故障依赖快速失败。
- 降级:保留核心功能。
- 隔离:不同依赖使用独立线程池、连接池或信号量。
- 限流:控制入口流量。
- 缓存和兜底:在可接受一致性范围内返回可用结果。
六、微服务一致性与可观测性
微服务如何保证数据一致性?
根据业务选择:
- 强一致:本地事务或尽量收敛到单服务。
- 最终一致:事务消息、本地消息表、补偿任务。
- 长事务:Saga 或 TCC,但开发和治理成本较高。
所有异步方案都要考虑幂等、重复消息、乱序、补偿和人工兜底。
通用概念:补偿与重试都建立在幂等之上——不幂等的接口不能安全重试。
如何设计跨服务 Trace?
频次 ★★★ · 难度 🟡
是什么:一次请求跨多个服务,链路追踪用两个 ID 把散落各处的日志和耗时串成一棵树——traceId 全局唯一、入口生成后整条链路不变;spanId 标识链路中的一段工作单元,记录 parentSpanId 构成父子树。W3C Trace Context 标准头:traceparent: 00-<traceId 32hex>-<spanId 16hex>-<flags>(老系统常见 Zipkin B3 头 X-B3-TraceId 等)。
生成与跨进程传播:
- 生成:入口服务发现请求头没有 trace 上下文就生成一个 128 位随机 traceId——只要求唯一不要求有序,随机数即可,不需要像分布式 ID 那样全局协调(见分布式系统分布式 ID 一节的对比)
- HTTP/RPC:出站拦截器(Feign/RestTemplate interceptor、RPC attachment)把上下文注入协议头;进站过滤器解出并放入线程上下文,同时派生新 spanId、当前 span 变 parent
- MQ:没有”请求头”就塞消息属性(Kafka headers、RocketMQ userProperty),消费端取出接上——载体换了,“上下文随调用走”的模式不变
- 异步/线程池:上下文存在 ThreadLocal 里,跨线程会丢,要装饰 Runnable 在提交时捕获、执行时回放(与 TransmittableThreadLocal 是同一问题,见并发编程ThreadLocal 一节)
与 MDC 日志串联:MDC(Mapped Diagnostic Context)是 logback/log4j2 提供的 ThreadLocal Map。进站时把 traceId 放进 MDC,日志 pattern 写 %X{traceId},所有业务日志自动携带——ELK 里按 traceId 一搜,整条链路的日志聚齐。这是”追踪系统看耗时瀑布”和”日志系统看业务细节”的连接点:
// 进站 Filter:有上下文就接续,没有就新开;写入 MDC 供日志输出
String traceId = parseOrNewTraceId(request.getHeader("traceparent"));
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
MDC.clear(); // 线程池线程复用,不清理会串到下一个请求的日志里
}采样率取舍:全量上报时每个请求产生 N 个 span,存储成本随流量线性涨。
| 策略 | 做法 | 代价 |
|---|---|---|
| 头部采样 | 入口按比例决定(如 1%),决定随上下文传播,整链要采都采 | 便宜,但出错的那条可能恰好没采 |
| 尾部采样 | 链路结束后看有无错误/慢再决定保留 | 不漏关键链路,但收集端要先缓存全量 |
| 工程折中 | 低基线采样 + 错误/慢请求强制采样 | 主流做法 |
常见追问:
- traceId 到线程池里丢了怎么排查/解决?→ ThreadLocal 不跨线程;用 TTL 或 micrometer context-propagation 装饰执行器,提交时捕获、执行时回放
- 采样 1% 漏掉报错链路怎么办?→ 头部采样的固有缺陷,用”错误强制采样”或尾部采样补
- Trace、Metrics、Log 什么关系?→ 可观测性三支柱:Metrics 发现问题(告警)、Trace 定位到哪个服务哪一跳、Log 看服务内部细节;traceId 是三者的关联键
通用概念:链路追踪本质是跨进程的上下文传递——进程内靠 ThreadLocal,跨线程靠捕获/回放,跨进程靠协议头捎带。与 Spring 事务传播行为(线程内的上下文传递策略,见Spring)是同一模式在不同作用域的形态。
微服务一定比单体好吗?
不一定。微服务适合边界清晰、团队较大、需要独立扩缩容和发布的系统,但会增加:
- 网络调用和故障模式。
- 数据一致性难度。
- 部署、监控和运维成本。
- 跨团队协作成本。
拆分应围绕业务边界和组织能力,而不是只按代码层或数据库表拆分。