准备 Java 微服务面试,最忌讳的是拿到一堆面试题就开始背,背完就忘,面试时遇到一个场景题就不知道怎么组织答案。微服务是一个覆盖分布式通信、注册中心、配置中心、网关、分布式事务、熔断限流、容器化部署等多个技术域的体系,靠死记硬背无法应付面试中“追问”环节。
这篇内容按 3 天节奏梳理微服务面试的核心模块,每个模块都按“核心概念 + 高频面试题 + 回答思路 + 常见坑”组织。第一天打基础,第二天抓方案设计,第三天巩固排查和实战题。每部分都可以直接拿去复习和自测。
1. 面试官问微服务,真正想考察的四个方向
1.1 微服务是什么,以及为什么需要它
微服务是一种将单一应用拆分为一组小服务的架构风格。每个服务围绕业务能力构建,可以独立开发、独立部署、独立扩展,服务之间通过轻量级机制通信,常见的是 HTTP REST 或消息队列。
面试时不能只背定义,要能说清楚拆分前后发生了什么变化。单体应用在规模不大时开发效率其实很高,团队、代码、数据库、部署都在一起,问题出现在规模变大之后:代码冲突变多、局部流量影响整体、技术栈绑定单一、发布验证成本变高。微服务正是为了应对这些规模化问题而出现,不是因为它更简单,而是因为它把复杂度做了重新分配。
可以这样作答:
单体应用把功能模块放在同一个进程里,共享同一个数据库,部署时打成一个包。微服务把“按技术层划分”改成“按业务能力划分”,每个服务包含自己的接口、业务逻辑和持久化,服务之间通过网络调用。它解决的是独立演进的问题,但同时也带来了服务发现、分布式事务、链路追踪这些新问题。
1.2 微服务和分布式的关系
这道题很常见,但很多人讲不清楚。分布式是一个更宽泛的概念,指的是多个节点通过网络协作完成任务;微服务是一种具体的架构风格,它是分布式系统的一种落地形态。
举例来说,一个系统用 Nginx 做负载均衡,后端挂了三台相同服务,这是分布式部署,但还不是微服务。只有当系统按业务边界拆分成订单服务、用户服务、库存服务,并且这些服务独立部署、独立演进时,才算真正使用了微服务架构。
回答时可以补一句:分布式关注的是“多节点如何协同”,微服务关注的是“如何按业务拆分并管理这些服务”。如果项目里所有服务代码放在一个仓库、依赖一个数据库、一起发布,那只能说做了分布式部署,不是微服务。
1.3 微服务带来的挑战与解决方案
面试官问完微服务解决了什么问题,通常紧接着就会问带来了什么问题。这一轮要体现思考深度。
微服务的核心挑战集中在六个方面:
- 服务发现与注册:服务地址动态变化,客户端怎么找到服务。
- 配置管理:几十个服务各自有环境配置,怎么统一管理和动态刷新。
- 网关路由与鉴权:统一入口怎么做路由、限流和认证。
- 服务间调用与容错:依赖的服务挂掉或变慢,怎么避免雪崩。
- 分布式事务:跨服务的数据一致性如何保障。
- 可观测性:调用链跨了多个进程,如何排查问题。
回答时建议用“问题 + 方案 + 你在项目里怎么落地”的结构,不要只罗列名词。例如:
服务发现这块,我们用的是 Nacos,服务启动时把实例 IP 和端口注册上去,消费者从 Nacos 拉取服务列表;为了防止拿到过期地址,客户端做了本地缓存,同时服务端配合心跳检查和健康检查剔除异常实例。
1.4 如何回答“你们项目为什么要用微服务”
这道题一定要结合自己的项目来答,不能背标准答案。推荐的结构是:
- 项目规模:团队人数、模块数量、迭代频率。
- 遇到的单体痛点:例如某个模块发布影响全站、数据库连接不够、代码合并频繁冲突。
- 拆分的依据:按业务域拆,订单、用户、商品分开。
- 拆分后的效果:独立发布、独立扩容、故障隔离。
- 付出了什么代价:部署复杂、排查链路变长、事务一致性问题变多。
如果项目本身规模不大,直接说“我们项目并发不高、团队小,所以没有拆微服务,而是采用模块化单体”也是完全正确的答案。面试官真正想听的是你有没有判断能力,而不是你有没有用过微服务。
2. 第 1 天:服务注册发现、网关与配置中心的高频题
2.1 Nacos 和 Eureka、Consul 如何选型
这个对比题几乎是微服务面试必考。不能只说“Nacos 比较新,功能全”,要对比核心机制和适用场景。
| 对比项 | Eureka | Consul | Nacos |
|---|---|---|---|
| 服务注册发现 | 支持 | 支持 | 支持 |
| 配置管理 | 不支持 | 不支持原生 KV 配置 | 支持,配置中心是核心能力 |
| 一致性协议 | AP | CP,基于 Raft | 注册中心 AP/CP 可切换,配置 CP |
| 健康检查 | 客户端心跳 | 多种检查方式 | 心跳 + 主动探测 |
| 运维成本 | 较低,社区维护放缓 | 需要独立集群维护 | 集成度高,国内使用广泛 |
| Spring Cloud 集成 | 已停更维护 | 集成成本较高 | 适配较好,社区活跃 |
选型思路可以这样回答:如果项目只做注册发现,选 Eureka 或 Nacos 都够用;如果既需要注册发现又需要配置管理,Nacos 能减少一套组件;如果团队已经有 Consul 且运行稳定,没必要为了追新替换它。
有一个细节值得补充:Eureka 2.0 并没有被大规模推广,所以现在新项目用 Nacos 的更多。Nacos 的 AP/CP 切换机制要了解,默认在临时实例场景下是 AP,在需要强一致的配置发布场景下使用 CP 模式。实际使用中不要让一个集群同时承担所有职责,至少要把注册中心和配置中心的使用场景分开理解。
2.2 Nacos 临时实例与持久化实例的区别
这道题是细节题,很多人只背了概念,没有理解背后的设计逻辑。
临时实例:
- 默认注册方式。
- 客户端与 Nacos 保持心跳,心跳超时后服务端直接剔除实例。
- 适合常规微服务场景,因为实例状态变化频繁,需要快速感知。
持久化实例:
- 不依赖临时心跳,由服务端主动探测健康状态。
- 实例数据会持久化到数据库。
- 适合服务提供方数量固定、需要用数据库判断服务状态的场景。
回答时要补一句:临时实例用的是 AP 思路,牺牲强一致换可用性;持久化实例更接近 CP,用持久化换可恢复性。项目中我们默认用临时实例,只有在需要保留服务历史状态或依赖数据库做服务治理时才考虑持久化实例。
2.3 网关为什么要单独一层
网关在微服务中的定位是所有外部请求的统一入口,负责路由转发、鉴权、限流、白名单、日志记录、请求聚合等功能。
面试时先答职责,再答为什么不能在每个服务里做。举个例子,如果鉴权逻辑散落在每个服务里,新增一个服务时就要重写一遍过滤器逻辑,而且策略不统一时,有的服务放行有的服务拦截,安全边界就破了。网关把横切关注点收敛到一个独立的组件里,这是它存在的核心价值。
Spring Cloud Gateway 是基于 WebFlux 和 Reactor 实现的响应式网关,底层 Netty 处理请求,非阻塞模型适合 IO 密集型场景。Zuul 1.x 是 Servlet 阻塞模型,性能上限较低,不推荐在新项目中使用。
需要重点理解 Gateway 的执行流程:
客户端 -> Gateway Handler Mapping -> Gateway Web Handler -> 过滤器链 -> 代理服务过滤器分为 global filters 和 gateway filters,常见用途包括 StripPrefix 去掉路由前缀、RequestRateLimiter 做限流、自定义 GlobalFilter 做统一鉴权。
回答时给一个简化配置片段:
spring: application: name: gateway-server cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path=/api/order/** filters: - StripPrefix=1这里lb://是重点,它表示要使用负载均衡能力从注册中心获取服务实例,而StripPrefix=1表示把请求路径的第一段前缀去掉。如果忘了去掉前缀,服务端接收到的路径会多一层/api,很容易出现 404。
2.4 动态刷新配置的原理
配置中心不是简单的“把配置挪到远端”,更关键的是“配置变更后如何让客户端感知并生效”。
Nacos 动态刷新的原理可以概括为:客户端启动时建立长轮询请求,服务端配置发生变化时,通过 HTTP 请求通知客户端更新本地缓存。长轮询相比纯轮询能减少无效请求,相比 WebSocket 又减少了连接维护成本。
Spring Cloud 中刷新配置最常见的方式:
@RefreshScope标记的 Bean 会在配置刷新时重新创建。- Spring Cloud Bus + MQ 广播配置变更事件,让多个实例同时刷新。
实际项目要注意:不是所有配置都适合动态刷新。数据库连接池、线程池这类创建开销大的资源,动态刷新可能带来连接重建和性能抖动。推荐的做法是启动时加载核心资源配置,运行时只刷新不太敏感的开关配置。
2.5 一个容易忽略的坑:服务启动成功但注册不上
面试即使不直接考,也可能通过场景题引出。常见原因包括:
- 没有引入注册中心客户端依赖。
spring.cloud.nacos.discovery.server-addr配置写错。- 服务配置了
spring.cloud.nacos.discovery.enabled=false。 - 网络不通或防火墙拦截 8848 端口。
- 服务启动时注册中心使用 namespace 隔离,填错了命名空间导致在控制台看不到。
排查路径:
# 检查注册中心是否健康 curl http://localhost:8848/nacos/v1/console/health/readiness# 查看服务日志中是否有注册成功关键字 tail -f logs/xxx.log | grep "register"如果日志没有明显报错,优先检查 namespace 和 group 是否和服务端一致。Nacos 控制台默认是 public 命名空间,如果客户端配置了namespace=dev,而控制台没有切换到 dev,是看不到服务的,这不代表注册失败。
3. 第 2 天:分布式事务、调用链与可靠性设计的答题要点
3.1 分布式事务有哪些方案
跨服务的数据一致性是微服务面试的重头戏。先记住一个判断标准:分布式事务不是银弹,方案选型取决于业务一致性要求和团队运维能力。
主流方案:
| 方案 | 核心思想 | 一致性强度 | 适用场景 |
|---|---|---|---|
| 2PC(两阶段提交) | 准备 + 提交/回滚,引入协调者 | 强一致性 | 对一致性要求极高的少数据量场景 |
| TCC | Try、Confirm、Cancel 三段补偿 | 最终一致 | 需要极强业务补偿逻辑 |
| 本地消息表 | 本地事务 + 消息表 + 定时任务投递 | 最终一致 | 小团队低成本实现可靠消息 |
| 事务消息 | 消息事务保证本地操作与发消息原子性 | 最终一致 | 使用 RocketMQ 时常用方案 |
| SAGA | 长事务拆分,失败时反向补偿 | 最终一致 | 业务流程长、跨越多个服务 |
面试模拟一个场景:用户下单后,扣库存、扣余额、生成订单分属三个服务,要求回答怎么做。
推荐回答结构:
先分析业务能否接受最终一致。如果可以,优先选择事务消息或本地消息表。例如扣库存成功后发送“库存已扣减”消息,订单服务消费消息生成订单,如果生成失败,通过人工或定时任务兜底回补。如果业务要求高一致性,例如用户支付金额不能短暂不一致,需要引入 TCC,但 TCC 的代码复杂度和运维成本会明显上升。
补充一个 TCC 常见坑:Cancel 阶段必须做幂等,因为网络超时会导致协调者重试 Cancel;Confirm 和 Cancel 操作不能依赖上下文之外的状态查询,最好把事务上下文通过参数传递。
Seata 是常用的分布式事务框架,项目中用 AT 模式时要注意:全局锁和分支事务会带来数据库资源占用,高并发场景反而更容易产生锁等待超时。回答时可以主动提到这一点,比直接说“我们项目用了 Seata”更有说服力。
3.2 幂等性怎么保证,常见的重试场景有哪些
面试官经常把幂等性和分布式事务放在一起问。幂等是指同一个请求执行多次和执行一次的结果相同。产生重复请求的场景包括:
- 网络超时后客户端重试。
- MQ 重投机制。
- 用户重复点击提交按钮。
- 定时任务重复调度。
常见实现方式:
- 唯一索引:数据库表中对业务单号加唯一约束,重复插入直接冲突。
- 状态机:订单从“待支付”到“已支付”只允许单向流转,重复支付回调直接忽略。
- Token 机制:前端提交时携带后端生成的一次性 token,后端通过 Redis 判断 token 是否已消费。
- 分布式锁:用 Redis SETNX 对业务键加锁,锁存在时拒绝重复请求。
回答时要强调:没有通用的幂等方案,幂等键必须和业务语义对应。例如支付回调幂等,应该用支付流水号作为幂等键,而不是用户 ID。
3.3 熔断、降级和限流的关系
这是微服务可靠性三剑客,很多人分不清楚。可以用一句话概括:
- 熔断:下游故障时,上游快速失败,不再继续调用。
- 降级:系统资源不足时,牺牲非核心功能,保证核心功能。
- 限流:控制进入系统的请求速率,防止被流量打垮。
三者经常配合使用,但触发条件和处理方式不同。
| 机制 | 触发条件 | 处理方式 | 示例 |
|---|---|---|---|
| 熔断 | 下游错误比例或耗时超阈值 | 快速失败,进入 Open 状态 | 下单服务调用库存服务连续失败,直接返回默认结果 |
| 降级 | 资源不足、依赖不可用 | 返回降级页面或兜底数据 | 广告服务不可用,返回空列表 |
| 限流 | QPS 超过阈值 | 丢弃请求或排队 | 接口每秒最多 1000 次,超出直接 503 |
回答 Sentinel 或 Hystrix 时,要说明核心参数:熔断的阈值、时间窗口、最小请求数;限流的 QPS 阈值和排队超时时间。Sentinel 相比 Hystrix 在 Dashboard、规则动态下发、熔断策略灵活性上更有优势,是目前更常用的选择。
3.4 调用链追踪的底层原理
微服务排查问题难,难在请求跨了多个服务,日志分散在不同节点。链路追踪就是为了解决这个问题。
核心思想是 traceId 和 spanId:
- 请求入口生成全局唯一 traceId。
- 每次服务间调用生成子 spanId。
- 所有日志带上 traceId 上下文,汇入统一日志平台后按 traceId 聚合。
技术选型常见的是 Spring Cloud Sleuth + Zipkin,或者 SkyWalking。这里不需要太深入,但要能说清楚上下文传递的机制。
在 Spring Cloud 中,Feign 或 RestTemplate 发起远程调用时,拦截器会把 traceId 从当前线程上下文中取出,放入 HTTP Header 随请求传递。因此要注意:如果调用线程切换,必须手动传递上下文,否则 traceId 会断。
以 Feign 为例,Lo4j2 + traceId 传递需要保证 MDC 中的 traceId 在异步任务中也存在。面试时可以主动指出:使用线程池异步调用时,MDC 上下文默认不传递,需要自定义 TaskDecorator 或手动 put。这一点非常加分。
@Component public class MdcTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { Map<String, String> contextMap = MDC.getCopyOfContextMap(); return () -> { if (contextMap != null) { MDC.setContextMap(contextMap); } try { runnable.run(); } finally { MDC.clear(); } }; } }代码解释了异步线程池应用中 traceId 丢失的最常见修复方式,完全可以直接用在项目落地里。
3.5 面试高频追问:如果服务间调用超时,怎么处理
这个问题考察综合能力,可从以下层次回答:
- 设置超时时间:Feign/RestTemplate 都要显式配置连接超时和读取超时,不要依赖默认值。
- 超时后快速失败:让上游线程立刻释放,避免线程池堆积。
- 触发熔断:超时比例达到阈值后,进入半开试探,恢复后才放量。
- 数据补偿:如果超时后部分步骤已成功,需要通过消息或定时任务对齐最终状态。
- 异步化优化:非核心链路改为 MQ 异步通知,降低同步等待时间和失败影响面。
还要提到排查顺序:先确认是服务端处理慢还是网络问题;通过压力测试确认服务端阈值的合理值;再根据容量评估设置超时时间,而不是拍脑袋随意填一个 5000ms。
4. 第 3 天:动手准备可演示的微服务项目与环境排错
4.1 本地最小可运行的微服务架构
面试时能说清楚自己实际跑通过的项目,效果远好于背概念。本地搭建一个最小微服务项目,需要准备:
- JDK 8 或 JDK 11,对应 Spring Boot 2.x;如果使用 Spring Boot 3.x,需要 JDK 17。
- Maven 或 Gradle,用于依赖管理。
- Nacos 服务端,作为注册中心和配置中心。
- 一个 Spring Cloud Gateway 或简单直连调用。
- 两个业务服务:例如 order-service 和 user-service,通过 OpenFeign 调用。
以 Maven 为依赖管理工具,父 POM 中需要引入 Spring Cloud 版本和 Spring Cloud Alibaba 版本。Spring Cloud 与 Spring Boot 版本强绑定,必须确认兼容性,常见组合:
| Spring Cloud | Spring Boot | Spring Cloud Alibaba |
|---|---|---|
| 2021.0.x | 2.6.x | 2021.0.5.0 左右 |
| 2022.0.x | 3.0.x | 2022.0.0.0 左右 |
| 2023.0.x | 3.2.x | 2023.0.1.0 左右 |
版本号迭代快,上述只作参考,落地前要去 Maven 仓库或官方文档再次核对。Spring Cloud Alibaba 的版本命名和 Spring Cloud 并不完全一致,直接抄一个组合很容易踩坑。
下面是核心依赖说明。父 POM 中加入:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>服务模块中引入 Nacos 注册发现和配置中心依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>这里有一个反复出现的坑:spring-cloud-starter-alibaba-nacos-discovery必须在依赖中明确加入,不能只引入spring-cloud-starter-alibaba-nacos-config,否则服务无法注册到 Nacos。
4.2 环境变量与 JDK 配置问题
搜索材料很多提到“java 环境变量配置”,这其实不是面试题,而是初学者动手搭建项目前最容易卡住的环节。面试前如果要在自己电脑上演示项目,环境必须提前配好。
Windows 下 JDK 环境变量配置:
JAVA_HOME=C:\Program Files\Java\jdk-17 Path=%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;...验证方式:
java -versionjavac -version如果java -version正常但javac找不到,通常是因为JAVA_HOME没有生效或 Path 中%JAVA_HOME%\bin被其他 JDK 路径覆盖。
Linux 环境推荐使用export或/etc/profile添加,也可以用 SDKMAN 管理 JDK 版本。这里要提醒新手:不要同时安装多个 JDK 再手动来回改配置,很容易出现两个版本混用导致的编译或运行错误。
4.3 服务启动后无法调用的问题排查
本地跑通微服务调用时,最常遇到的错误是java.net.UnknownHostException。这个错误的直接原因一般是:
- 服务名写错。
- 没有通过注册中心发现服务,而直接拼接了主机名。
- 服务没有成功注册。
排查顺序:
- 打开 Nacos 控制台,确认目标服务是否存在。
- 检查调用方是否配置了服务发现依赖。
- 检查服务提供方是否启动了监听端口。
- 看调用方日志里是否有完整的异常堆栈。
另一个高频问题是Connection refused或Read timed out。前者优先关注服务是否启动、端口是否正确;后者要关注网络、服务端处理慢、线程池阻塞。
4.4 内存溢出问题的面试回答思路
热搜词中出现了java: outofmemoryerror: insufficient memory,这类问题在微服务面试中也属于可靠性考察方向,尤其是容器化部署之后更容易出现。
回答思路分两层。第一层是 JVM 层面,先分清是堆溢出、栈溢出还是直接内存溢出:
java.lang.OutOfMemoryError: Java heap space:堆空间不足,常见原因是对象堆积、内存泄漏。java.lang.OutOfMemoryError: Metaspace:元空间溢出,常见原因是动态生成类未回收。java.lang.OutOfMemoryError: Direct buffer memory:直接内存溢出,常见原因是 NIO 分配过多。java.lang.StackOverflowError:不属于 OOM,是栈深度超限,常见原因是递归无出口。
第二层是容器部署层面。容器中 JVM 默认内存可能没有感知容器限制,导致进程被 Cgroup 杀掉。现在通用做法是使用-XX:MaxRAMPercentage让 JVM 根据容器内存自动分配,例如:
java -XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -jar app.jar回答问题时要体现排查流程:先通过监控看内存趋势,再导出堆快照分析大对象和引用链,最后定位泄漏源。
4.5 3 天复习计划
给一个可执行的复习计划,比“背八股文”更有效。
| 阶段 | 学习内容 | 产出 |
|---|---|---|
| 第 1 天上午 | 服务注册发现、Nacos 原理、服务拆分原则 | 画一张服务调用拓扑图 |
| 第 1 天下午 | 网关路由、过滤器链、配置中心动态刷新 | 本地跑通一个 Gateway 转发请求 |
| 第 2 天上午 | 分布式事务方案、幂等方案、分布式锁 | 写出一个下单场景的事务方案设计 |
| 第 2 天下午 | 熔断、限流、降级、链路追踪 | 本地验证 Feign 超时和重试配置 |
| 第 3 天上午 | Feign、RestTemplate、线程池隔离、内存排错 | 用 jstack 分析一次线程阻塞案例 |
| 第 3 天下午 | 综合项目串讲 + 场景题模拟 | 用 1 分钟讲清项目架构,用 5 分钟画架构图 |
4.6 面试中画微服务架构图的方法
热词中多次出现“微服务架构图”。面试能画出清晰架构图,是沟通能力的重要体现。不需要画得多精美,但结构必须正确。
推荐结构:
客户端 -> Nginx(可选,做负载均衡) -> 网关 -> 认证服务 -> 订单服务(注册到 Nacos,配置在 Nacos,调用商品服务) -> 用户服务 -> 消息服务 公共组件:Nacos、Sentinel、Zipkin/SkyWalking、Redis、MQ画图时顺手标注调用链路:请求从哪里进,哪些调用是同步,哪些是异步,数据库和缓存分别由哪些服务访问。面试官通过这张图基本能判断出你有没有真正做过微服务项目。
5. 常见考点补充:Feign、消息队列和分布式锁
5.1 OpenFeign 怎么用,有哪些坑
OpenFeign 是 Spring Cloud 中声明式 HTTP 客户端。核心用法是定义接口并加上@FeignClient(name = "order-service"),调用方直接注入接口调用,底层由动态代理生成实现。
常见的两个坑:
- 超时配置不生效。需要确认是否同时配置了
connectTimeout和readTimeout,并且 Ribbon 或 LoadBalancer 的配置不能和 Feign 冲突。 @FeignClient接口上无法使用@GetMapping等 Spring MVC 注解?实际上可以,但要注意@RequestParam需要显式声明 value,否则参数名在编译后丢失,导致传参为 null。
示例配置:
feign: client: config: default: connectTimeout: 2000 readTimeout: 3000 order-service: connectTimeout: 1000 readTimeout: 2000面试中能说出服务级配置优先级,比只写一个 default 要好得多。
5.2 消息队列在微服务里的作用
微服务里消息队列主要解决解耦、异步、削峰三个问题。
解耦:订单服务创建订单后,通过 MQ 通知积分服务、短信服务、库存服务,不需要在订单服务代码里调一堆接口。异步:把非核心同步操作异步化,让核心链路返回更快。削峰:高流量瞬间通过消息暂存,由下游按消费能力处理。
回答时一定补一句:引入 MQ 同样会增加问题。消息可能丢失、重复消费、消费顺序无法保证、消息积压后系统负荷会平移给下游。所以使用 MQ 时至少要考虑:
- 生产者确认机制。
- 消费者手动 ACK。
- 消费幂等。
- 死信队列和告警。
5.3 Redis 分布式锁的正确实现
分布式锁经常和幂等、扣减库存场景绑定出现。现在不推荐直接写SETNX + expire两步操作,因为无法保证原子性。推荐使用 Redisson,也可以使用SET key value NX EX timeout一步完成加锁和过期时间设置。
示例:
Boolean locked = stringRedisTemplate.opsForValue() .setIfAbsent("lock:order:" + orderId, "1", Duration.ofSeconds(30)); if (Boolean.TRUE.equals(locked)) { try { // 处理业务 } finally { stringRedisTemplate.delete("lock:order:" + orderId); } }但要注意释放锁时先比对 value 再删除,避免误删他人锁。这也是面试官最常追问的细节:
String value = stringRedisTemplate.opsForValue().get(key); if ("当前线程唯一标识".equals(value)) { stringRedisTemplate.delete(key); }从工程角度看,高并发场景更推荐 Redisson 的看门狗机制,它会自动续期,避免业务还没执行完锁就过期了。分布式锁不能只靠 Redis,还要考虑主从切换丢锁问题,面试中能把 RedLock 的争论提出来说明自己有深度,但不要过度推销某个方案。
6. 项目代码正确性之外,还要准备哪些随手的工程亮点
微服务面试不只是背题。面试官往往会根据你的项目经历继续追问。准备几个能随时讲的工程实践点,效果会好很多。
- 配置外置化:不同环境使用
bootstrap.yml配置 Nacos 地址,业务配置统一放到 Nacos,本地不保存环境相关密钥。 - 日志规范:所有请求打上 traceId,入口输出请求参数,出口输出响应状态和耗时。
- 统一异常处理:使用
@RestControllerAdvice统一封装错误码,避免服务间抛出的异常格式不一致。 - 接口幂等设计:写操作接口统一要求前端传
requestId,后端通过 Redis 做重复提交校验。 - 发布策略:服务分批滚动发布,先灰度一台,确认无异常后放量。
回答任何项目题都可以套用这个原则:先说明业务场景和问题,再说技术方案,最后说上线后如何验证和兜底。不要只讲功能,要讲清楚技术选型的理由和代价。
7. 快速自查清单:面试前把这几项过一遍
面试前一天,对照清单做一轮自查,比临时背题更靠谱。
| 检查项 | 具体要求 |
|---|---|
| 服务拆分边界 | 能说清某个服务为什么拆出来,划分依据是什么 |
| 注册中心机制 | 能画出服务注册、发现、心跳剔除的时序图 |
| 网关过滤器 | 能说出自定义 GlobalFilter 的逻辑和放置顺序 |
| 分布式事务 | 能针对自己的业务场景给出选型对比,而不是只背方案名 |
| 幂等设计 | 能回答重复请求场景下具体怎么防重 |
| 超时与重试 | 知道 Feign/Nacos/Gateway 各自超时配置项 |
| 限流与熔断 | 能说出核心参数含义,例如 QPS、熔断时间窗 |
| 链路追踪 | 能说出 traceId 传递机制,以及异步场景丢失的处理 |
| 项目亮点 | 能稳定输出 3 个自己真正做过的优化点 |
| 动手能力 | 能在 30 分钟内本地启动一个最小微服务 Demo,并完成一次跨服务调用 |
微服务面试的本质,是检验你是否具备“拆服务、管服务、调服务、查服务”的能力。3 天时间足够把高频考点系统过一遍,但真正拉开差距的,是面对一个没有标准答案的场景题时,你能不能给出有取舍、有依据的方案。文章里的知识点可以作为骨架,但最终要结合自己的项目经历组织答案。