目录
[3 服务是怎么拆分的?拆分的依据是什么?](#3 服务是怎么拆分的?拆分的依据是什么?)
[5Nacos 在你的项目中起什么作用?](#5Nacos 在你的项目中起什么作用?)
[6Gateway 网关做了哪些事情?](#6Gateway 网关做了哪些事情?)
[7 服务之间的负载均衡是怎么做的?](#7 服务之间的负载均衡是怎么做的?)
[16有没有用到 Redis?用来做什么?](#16有没有用到 Redis?用来做什么?)
[18Nginx 在你的项目中起什么作用?](#18Nginx 在你的项目中起什么作用?)
[23 项目中遇到的最大技术难点是什么?](#23 项目中遇到的最大技术难点是什么?)
[25如果用户量增长 10 倍,系统哪里会成为瓶颈?](#25如果用户量增长 10 倍,系统哪里会成为瓶颈?)
[26RabbitMQ 死信队列怎么实现的?](#26RabbitMQ 死信队列怎么实现的?)
[27MinIO 为什么不用本地文件存储](#27MinIO 为什么不用本地文件存储)
[28WebSocket 长连接断开怎么解决的?](#28WebSocket 长连接断开怎么解决的?)
[29 百度 AI OCR 怎么对接的?](#29 百度 AI OCR 怎么对接的?)
1简单介绍一下你这个项目的整体架构?
这是一个 O2O 同城顺风车平台,采用 Spring Cloud 微服务架构,基于 Spring Boot 2.2.5 + Spring Cloud Hoxton.SR4 + Spring Cloud Alibaba 2.2.1 开发。
系统共拆分为 8 个核心服务:
hitch-gateway:API 网关,统一入口,负责路由转发和 Token 鉴权
hitch-account:用户中心,负责注册、登录、用户信息管理
hitch-stroke:行程中心,负责司机发布行程、行程管理
hitch-order:订单中心,负责乘客下单、订单状态流转
hitch-payment:支付中心,负责支付结算
hitch-notice:消息中心,支持 WebSocket 实时消息推送
hitch-storage:存储中心,负责文件上传(头像、证件照等)
hitch-commons:公共模块,封装工具类、实体、常量
注册中心和配置中心用 Nacos,网关用 Spring Cloud Gateway,缓存用 Redis,部署用 Docker Compose + Nginx。
2为什么选择微服务架构?和单体架构相比有什么优缺点?
选择微服务主要考虑三点:
独立部署:每个服务可以独立打包、独立上线,比如支付模块升级不影响行程服务
技术解耦:各服务可以用不同的数据库,甚至不同的技术栈
团队协作:多人开发时各负责一个服务,减少代码冲突
当然也有代价:服务间通信成本增加、部署复杂度上升、分布式事务难处理。所以我们用了 Nacos 做服务发现,Gateway 做统一路由,Docker Compose 做一键编排,降低运维复杂度。
3 服务是怎么拆分的?拆分的依据是什么?
按业务领域拆分,遵循 DDD 领域驱动的思想:
用户是独立领域 → account 服务
行程和订单是核心交易链路,分开 → stroke + order
支付涉及资金安全,独立出来 → payment
消息通知是横切关注点 → notice
文件存储有独立 IO 需求 → storage
每个服务有独立的数据库、独立的端口,通过 Nacos 注册发现彼此。
4各服务之间是怎么通信的?
主要用 OpenFeign 做服务间同步调用。比如订单服务创建订单时,需要调用行程服务获取行程信息,通过 Feign Client 声明式接口调用,底层走 HTTP + Ribbon 负载均衡。
另外消息推送用的是 WebSocket 长连接,Nginx 也专门配置了 WebSocket 的 Upgrade 代理,实现司机乘客实时通信。
5Nacos 在你的项目中起什么作用?
Nacos 在我们项目里承担两个角色:
注册中心:所有服务启动时自动注册到 Nacos,消费者通过服务名发现提供者,不用硬编码 IP 地址
配置中心:公共配置(如数据库连接、Redis 配置)统一放在 Nacos 上,各服务通过 bootstrap.yml 拉取,修改配置不用重启服务
我们用的是 Nacos 单机模式(standalone),生产环境建议用集群模式保证高可用。
6Gateway 网关做了哪些事情?
网关做了三件事:
路由转发:配置了 7 条路由规则,比如 /account/** 转发到 lb://hitch-account-server,/order/** 转发到订单服务,lb:// 前缀表示走 Ribbon 负载均衡
Token 鉴权:通过自定义 TokenAuth Filter,配置了白名单路径(登录、注册、支付回调),其他接口必须携带有效 Token 才能访问
WebSocket 代理:对 /ws/** 路径配置了 lb:ws:// 协议,支持 WebSocket 连接升级
7 服务之间的负载均衡是怎么做的?
用的 Spring Cloud Ribbon(内置在 Spring Cloud Gateway 和 Feign 中)。网关路由配置里写的 lb://hitch-account-server,lb 就是 Load Balanced 的意思,Ribbon 会从 Nacos 拿到服务实例列表,默认用轮询策略分发请求。
8服务熔断、降级有没有做?
目前项目没有引入 Hystrix 或 Sentinel,但做了基础容错:
服务间调用设置了超时时间
Docker Compose 里每个服务配置了 restart: always,服务挂了自动重启
Nginx 层对 WebSocket 设置了 proxy_read_timeout 7200s 防止长连接断开
如果要做熔断,可以引入 Sentinel,对核心接口做限流和降级。
9乘客发单到司机接单,整个流程是怎样的?
完整流程:
司机发布行程:调用 stroke 服务,记录出发地、目的地、时间、座位数
乘客搜索行程:按起终点查询可用行程列表
乘客下单:调用 order 服务创建订单,状态为"待确认"
消息通知:通过 notice 服务的 WebSocket 推送给司机
司机确认接单:更新订单状态为"已接单"
行程进行中:状态变为"行程中"
到达目的地:状态变为"待支付"
乘客支付:调用 payment 服务完成支付,订单状态变为"已完成"
10订单状态机是怎么设计的?
订单状态流转:
> 待接单 → 已接单 → 行程中 → 待支付 → 已完成
> ↓ ↓
> 已取消 已评价
>
每次状态变更都校验当前状态是否合法,防止跳状态。比如不能从"待接单"直接跳到"待支付"。
11支付流程是怎么实现的?
支付流程:
行程结束后,order 服务将订单状态改为"待支付"
乘客发起支付,请求先到 Gateway,路由到 payment 服务
payment 服务调用第三方支付接口(模拟支付)
支付成功后,第三方回调 /payment/api/nofify(这个路径在网关白名单里,不需要 Token)
payment 服务更新订单状态为"已完成"
12行程匹配逻辑是怎么做的?
目前是基于起终点 + 时间进行条件查询匹配。乘客输入出发地和目的地,stroke 服务查询符合条件的行程列表返回。后续可以引入地图服务做路径相似度计算,提高匹配精准度。
13并发抢单怎么处理?
采用乐观锁机制:订单表里有一个版本号字段,司机接单时执行 UPDATE order SET status='已接单', version=version+1 WHERE id=? AND version=? AND status='待接单',只有第一个执行的司机能成功,其他司机会因为版本号不匹配而失败,系统提示"该订单已被接单"。也可以用 Redis 分布式锁实现。
14数据库是怎么设计的?
每个服务独立数据库,通过各自的 application.yml 或 Nacos 配置中心配置数据源。比如 account 服务有自己的用户表,order 服务有自己的订单表。服务间需要数据时通过 Feign 调用获取,不直接跨库查询。
15文件存储用的什么方案?
有独立的 hitch-storage 存储服务,端口 8003,专门处理文件上传。用户上传头像、身份证照片、行驶证照片等都走 /storage/** 接口,文件存储在服务器本地磁盘。生产环境可以替换为阿里云 OSS 或 MinIO。
16有没有用到 Redis?用来做什么?
用到了 Redis,主要三个用途:
Token 存储:用户登录后生成 Token 存入 Redis,网关层校验 Token 有效性
会话管理:存储用户登录态,支持过期时间
缓存:热点数据缓存,减少数据库压力
Redis 配置在网关的 application.yml 里,地址 192.168.64.100:6379。
17项目是怎么部署的?
用 Docker Compose 一键编排,一个 docker-compose.yml 文件定义了 8 个服务容器:
所有服务在同一个 hitch-network 桥接网络中通信
每个服务依赖 Nacos(depends_on: hitch-nacos),保证注册中心先启动
服务镜像来自私有 Harbor 仓库(manager-hongbaoyu-java.itheima.net:8443)
日志统一挂载到 /tmp/data/logs 目录
端口映射:网关 10010→8888,行程 10012→8002,存储 10013→8003 等
18Nginx 在你的项目中起什么作用?
Nginx 做了三件事:
反向代理:所有后端请求通过 Nginx 转发到 Gateway(127.0.0.1:8888),按路径匹配路由:/account/**、/order/**、/stroke/** 等
静态资源托管:/web 路径直接映射到前端静态文件目录,入口是 login.html
WebSocket 代理:专门配置了 /notice/ws/socket 的 Upgrade 头,支持 WebSocket 协议升级,读取超时设为 7200 秒保持长连接
还有一个重要配置:underscores_in_headers on,允许 Header 中带下划线,否则 Token 传不过去。
19线上出了问题怎么排查?
看日志:每个服务的日志挂载在 /tmp/data/logs 目录,通过 Docker logs 或文件查看
Nginx 日志:access.log 记录了请求来源和上游服务地址,可以定位是哪个服务返回了异常
Nacos 控制台:查看服务实例是否健康,是否掉线
Redis:检查 Token 是否过期,缓存是否命中
20用户登录鉴权是怎么做的?
采用 Token 机制:
用户调用 /account/api/login 登录,验证用户名密码
验证通过后生成唯一 Token,存入 Redis,设置过期时间
后续请求在 Header 中携带 Token
Gateway 网关的 TokenAuth 过滤器拦截请求,校验 Token 有效性
白名单路径(登录、注册、支付回调)不需要 Token
21接口权限怎么控制
两层控制:
网关层:TokenAuth 过滤器统一鉴权,无效 Token 直接返回 401
业务层:根据用户角色(乘客/司机)控制接口访问权限,在各自的 Controller 或 Service 中做权限判断
22敏感数据怎么保护的?
密码加密存储(BCrypt)
Token 有过期时间,存在 Redis 中可随时失效
Nginx 配置中 Token 通过 Header 传输,开启 underscores_in_headers 保证传递完整
支付回调接口做了签名验证,防止伪造请
23 项目中遇到的最大技术难点是什么?
WebSocket 消息推送的稳定性。因为 Nginx 默认不支持 WebSocket 长连接,需要手动配置 Upgrade 和 Connection 头。另外 Gateway 对 WebSocket 的路由要用 lb:ws:// 协议,一开始用 lb:// 导致连接一直断开,后来查文档改成 lb:ws:// 才解决。同时 Nginx 的 proxy_read_timeout 要设成足够长,否则 60 秒默认超时就会断开连接。
24如果让你重新设计,有哪些地方会改进?
引入 Sentinel 做限流熔断,防止突发流量打垮服务
引入 RabbitMQ/Kafka 做异步消息,比如订单创建后异步通知,解耦服务
分布式事务用 Seata 保证跨服务数据一致性
文件存储换成 MinIO 或 OSS,不依赖本地磁盘
日志收集用 ELK(Elasticsearch + Logstash + Kibana)统一管理
25如果用户量增长 10 倍,系统哪里会成为瓶颈?
数据库:单库扛不住,需要分库分表(ShardingSphere)+ 读写分离
Redis:单节点有上限,需要 Redis Cluster 集群
Nacos:单机模式不行,要上集群(3 节点以上)
Gateway:单网关有瓶颈,需要多实例 + LVS/Nginx 前置负载均衡
WebSocket:长连接数有上限,需要多节点部署 + 消息广播用 MQ
26RabbitMQ 死信队列怎么实现的?
订单创建时发送一条 TTL 消息到普通队列,如果 15 分钟内订单未被支付(消息未被消费),消息过期后进入绑定的死信交换机(DLX),死信队列消费该消息后执行订单取消逻辑。
27MinIO 为什么不用本地文件存储
本地存储不利于扩展,多实例部署时文件不共享。MinIO 是分布式对象存储,支持水平扩展,生产环境可以直接替换为阿里云 OSS。
28WebSocket 长连接断开怎么解决的?
Nginx 默认 60 秒超时断开长连接,需要在 location 块中配置 proxy_read_timeout 7200s,并设置 proxy_http_version 1.1 和 Upgrade/Connection 头支持协议升级。
29 百度 AI OCR 怎么对接的?
通过 OkHttp 调用百度 AI 开放平台 REST 接口,上传图片后返回 JSON,用自定义的 AiParamFactory 解析响应字段(如 words_result.姓名.words),自动提取姓名、身份证号等信息填入认证表单。
30简历问题(百分比是怎么统计的)
- "降低无效订单率 9%" 相关
Q: 这个 9% 是怎么算出来的?
上线前统计了 1500笔订单中,因超时未支付导致的无效订单占比约 16%。上线延迟取消功能后,这部分订单占比下降到 7%,相对降低了约 9 个百分点。
Q: 为什么用死信队列而不是定时任务扫描?
定时任务有时间粒度限制(比如每分钟扫一次),会有延迟。死信队列基于消息 TTL 过期机制,时间精度更高,订单到期精确取消,不会有多余的资源占用。
背景:接口原始响应 300ms →优化后 100ms,下单接口峰值 5000+QPS
Q:用什么做的压测? A:使用 JMeter 做压测工具,模拟 500 并发用户线程,持续压测 10 分钟,统计接口平均响应时间、吞吐量、错误率。
Q:压测环境什么配置? A:测试服务器 4 核 8G,JDK1.8,JVM 参数
-Xms512m -Xmx512m;MySQL8.0 单实例;Redis 单机节点;没有做集群,就是单机测试环境。Q:性能瓶颈主要在哪?怎么优化的? A:最开始性能瓶颈主要在 MySQL 数据库。高并发请求全部直接查询数据库,数据库 QPS 冲到4200 左右,大量请求打到 DB,数据库 CPU 打满,接口平均响应 300ms。 优化做了两件事:
- 引入 Redis 缓存热点业务数据,热点数据直接读取内存,不再访问数据库,数据库 QPS 下降到1100 左右,极大减轻数据库压力;
- 对高频 where 条件、关联查询字段建立联合索引,优化慢 SQL。 优化完成之后接口平均响应时间降到 100ms,核心接口可以支撑 5000+QPS。
Q:5000+ QPS 是整体还是单个接口? A:这个是核心接口的峰值吞吐量,不是整个系统全量 QPS,只是压测的那一个热点接口的指标,系统其他接口流量会低很多。
面试官追加高频追问
追问 1:压测过程遇到什么问题?
压测初期并发上来之后,MySQL CPU 直接跑满,响应时间飙升,还偶发超时。一开始只加了缓存,但是没有处理缓存击穿风险;后面增加 Caffeine 本地二级缓存,加上互斥锁防止缓存击穿,压测稳定性提升。
追问 2:为什么不用 Redis 集群做压测?
因为是学校实践项目,测试机器资源有限,压测环境只搭建 Redis 单机;生产环境才会部署集群。
追问 3:压测的时候 JVM 有什么现象?
压测初期 YGC 比较频繁,大量短期对象创建;后面优化代码减少循环内对象创建,同时合理设置堆大小,GC 停顿时间下降。
- "AI 比对阈值 75%→95%" 相关
Q: 什么 AI 接口?具体怎么比对的?
对接的是百度 AI 开放平台的 OCR 识别接口,上传身份证照片后自动提取姓名、身份证号等信息,再和用户手动填写的信息做比对校验。
Q: 阈值是什么阈值?
OCR 返回结果有一个置信度分数,初始阈值设的 0.8,低于这个值的直接拒绝。后来分析发现很多被拒绝的其实是因为照片光线问题,置信度在 0.7-0.8 之间的图片人工审核其实是对的,所以把阈值下调到 0.7,低于 0.7 的才走人工复核,通过率就上来了。
Q: 存储效率提升 40% 怎么来的?
之前用本地文件存储,上传后还要做格式转换和压缩,平均耗时约 XX ms。换成 MinIO 后,MinIO 原生支持分片上传,且直接存储原始文件不做转换,上传耗时降低约 40%。
- "Redisson 分布式锁" 相关(最危险的一条)
Q: Redisson 分布式锁底层原理?
基于 Redis 的 SET key value NX PX 命令实现互斥,加锁时设置过期时间防止死锁。通过 Lua 脚本保证加锁和设置过期时间的原子性。
Q: 看门狗(WatchDog)机制了解吗?
Redisson 默认开启看门狗,加锁后会启动一个后台线程,每隔 lockWatchdogTimeout/3(默认 10 秒)检查线程是否还持有锁,如果还持有就自动续期,防止业务没执行完锁就过期了。
Q: 为什么不用数据库乐观锁?
乐观锁在高并发下重试次数多,用户体验差。分布式锁是阻塞等待,对司机来说就是"抢单中...稍等",体验更好。
Q: Redisson 的 RedLock 了解吗?
RedLock 是 Redisson 提供的多节点分布式锁算法,在 N 个独立 Redis 节点上分别加锁,超过半数成功才算加锁成功,解决单点故障导致锁失效的问题。我们项目用的单节点,没上 RedLock。
RedissonConfig.javaRedisson 客户端配置,读取 application.yml 中的 Redis 地址
OrderGrabService.java
分布式锁抢单核心逻辑,注释详细,面试可直接讲
"并发抢单问题我用 Redisson 分布式锁 解决。
锁的 key 是 order:grab:lock:{orderId},以订单 ID 为粒度,保证同一订单的并发请求互斥。
用 tryLock(waitTime=3s, leaseTime=10s):
waitTime=3秒:司机最多等 3 秒,超过就提示"订单已被处理",防止长时间阻塞
leaseTime=10秒:锁自动过期,防止业务异常导致死锁
如果 leaseTime 设为 -1,则启用 WatchDog 看门狗机制,后台线程每 10 秒(默认 30/3)自动续期,业务没执行完锁不会过期
底层实现:Redisson 用 SET key value NX PX 命令加锁,用 Lua 脚本保证加锁和设置过期时间的原子性。释放锁时也会用 Lua 脚本校验:只有当前线程持有锁时才释放,防止误删别人的锁。
获取锁后会先二次校验订单状态(防止锁等待期间状态已被修改),确认是"待接单"才更新为"已接单"。"
可能被追问
Q: 为什么不用数据库乐观锁?
乐观锁在高并发下重试率高,用户体验差(每次重试都要重新请求)。分布式锁是阻塞等待,对司机来说就是"处理中...稍等",体验更好。
Q: Redisson 的 RedLock 了解吗?
RedLock 是 Redisson 提供的多节点分布式锁算法,在 N 个独立 Redis 节点上分别加锁,超过半数成功才算加锁成功,解决单点故障导致锁失效的问题。我们项目目前用的单节点 Redis,如果生产环境对可用性要求高,可以升级为 RedLock 集群模式。
Q: 如果 Redis 挂了怎么办?
可以部署 Redis Sentinel 哨兵模式做自动故障转移,或者用 RedLock 多节点模式。我们目前单节点配合 Redis 持久化(RDB + AOF)已经能满足需求。
这样你简历上写的每一句都有代码支撑,面试官追问也不怕。
面试官可能追问 + 回答
Q: 为什么行程数据用 MongoDB 而不用 MySQL?
行程信息结构比较灵活,不同司机的行程可能包含不同的附加信息(比如是否允许携带宠物、是否绕路等),用 MongoDB 的文档模型更方便扩展。而且行程的查询主要是按起终点和时间的范围查询,MongoDB 的索引性能也不错。订单和用户这种强事务需求的数据我们还是用 MySQL。
Q: 文件秒传怎么实现的?
上传文件前先计算文件的 MD5 签名,然后拿这个 MD5 去数据库查,如果已经存在相同 MD5 的记录,说明文件之前上传过,直接返回已有的 URL,不需要再上传到 MinIO。这样同一个文件不管多少人上传,MinIO 里只存一份。
Q: WebSocket 连接池用的 ConcurrentHashMap,为什么不用普通的 HashMap?
因为 WebSocket 是多线程环境,多个用户可能同时连接和断开,ConcurrentHashMap 是线程安全的,不需要加锁就能保证并发操作的正确性。HashMap 在并发 put 的情况下可能导致数据丢失或者死循环(JDK 1.7 的链表头插法问题)。
Q: 网关的 Token 鉴权具体怎么做的?
自定义了一个 TokenAuthGatewayFilterFactory,继承 Gateway 的 AbstractGatewayFilterFactory。在 apply 方法里,先从请求 Header 里取 Token,如果 Header 里没有就从 URL 参数里取。然后拿 Token 去 Redis 查 SessionContext,校验是否有效。如果有效就把用户 ID 写入请求头 X-Account-Id 传递给下游服务,下游服务直接从 Header 取就行,不用再去 Redis 查一遍。白名单路径通过配置传入,用分号分隔,匹配到就直接放行。
Q: 支付的安全校验怎么做的?
确认到款接口里,先从 Redis 的 Token 中拿到当前用户 ID,再查出订单信息,校验订单的司机 ID 是不是当前用户。如果不是,返回"该订单不属于你",防止用户篡改请求去确认别人的订单。
Q: RabbitMQ 和 Kafka 在行程中心分别用来做什么?
RabbitMQ 主要做订单超时自动取消的延迟消息,用死信队列实现。Kafka 主要做行程状态变更的异步通知,比如司机发车、乘客上车这些状态变更后,发一条 Kafka 消息,消息中心消费后推送 WebSocket 通知给相关用户。用 Kafka 是因为它的吞吐量高,适合这种高频的状态变更通知场景。
Q: Nacos 配置中心具体怎么用的?
各服务在 bootstrap.yml 里配置 Nacos 地址和配置文件前缀,启动时自动拉取公共配置(比如数据库连接、Redis 配置等)。多个服务共享同一个配置文件 taxi-feign-common.yaml,修改配置后 Nacos 会主动推送,配合 @RefreshScope 可以实现配置热更新,不用重启服务。
第二部分:遇到的问题(挑 2-3 个讲,有细节有解决过程)
问题一:并发抢单导致数据不一致(最推荐讲)
背景: 测试的时候发现一个 bug------同一个乘客发的订单,两个司机同时点抢单,结果订单被接了两次,数据库里司机字段被覆盖了。
分析: 最开始用的是数据库乐观锁,在订单表加了一个 version 字段,SQL 写成 UPDATE order SET status=1, version=version+1 WHERE id=? AND version=? AND status=0。但实际测试发现,高并发下重试率很高,用户体验不好,司机端会频繁收到"抢单失败请重试"。
解决: 后来换成了 Redisson 分布式锁。锁的 key 设计成 order:grab:lock:{orderId},以订单 ID 为粒度。用 tryLock 方法,等待时间设 3 秒,锁过期时间设 10 秒。获取锁之后会二次校验订单状态,确认还是"待接单"才更新。
效果: 上线之后抢单冲突的问题彻底解决了,司机端的体验也好了,获取不到锁就提示"订单正在被处理中",不用反复重试。
问题二:WebSocket 长连接频繁断开(体现排查能力)
背景: 消息推送用的是 WebSocket,本地测试没问题,但部署到 Nginx 后面之后,连接经常 1 分钟左右就断开了。
排查: 一开始以为是代码问题,查了半天没发现 bug。后来查资料发现是 Nginx 的默认配置问题------Nginx 对 HTTP 连接的默认超时是 60 秒,而且默认不支持 WebSocket 的协议升级。
解决: 在 Nginx 配置里做了三处修改:
加了 proxy_http_version 1.1 和 Upgrade/Connection 头,支持协议升级
把 proxy_read_timeout 从默认 60 秒改成 7200 秒
在 Gateway 的路由配置里,WebSocket 的路由要用 lb:ws:// 协议,不能用普通的 lb://
总结: 这个问题让我意识到,微服务架构下网络链路变长了,Nginx、网关、服务本身每一层都可能有配置问题,排查问题要一层一层看。
问题三:RabbitMQ 消息丢失导致订单状态不一致(体现深度思考)
背景: 做订单超时自动取消功能时,用的是 RabbitMQ 的死信队列。测试发现偶尔会出现消息丢失的情况------订单到了超时时间但没有被自动取消。
分析: 排查后发现两个原因:一是生产者发消息时没有确认机制,消息可能没到队列就丢了;二是消费者处理失败后直接 ACK 了,消息被消费但没有真正执行取消逻辑。
解决:
生产者开启手动确认模式,消息发送成功后收到 ACK 才算成功
消费者也改成手动 ACK,业务逻辑执行完才确认,失败就 NACK 让消息重新入队
给消息和队列都设置了持久化,防止 RabbitMQ 重启后消息丢失
总结: 消息队列的可靠性要从生产者、Broker、消费者三个维度保证,任何一环出问题都会导致数据不一致。
如果面试官继续追问
Q: 你说用 RabbitMQ 死信队列做超时取消,具体怎么实现的?
订单创建时发一条消息到普通队列,消息设置 TTL 为 15 分钟,队列绑定死信交换机(DLX)。如果 15 分钟内订单没被支付,消息没被消费就会过期,自动路由到死信队列。死信队列的消费者拿到消息后,先查订单状态,如果还是"待支付"就执行取消逻辑。
Q: 死信队列和延迟队列有什么区别?为什么不用 RabbitMQ 的延迟插件?
延迟队列用的是 rabbitmq_delayed_message_exchange 插件,消息存在交换机里,到时间再投递。死信队列是基于 TTL + DLX 实现的,消息存在队列里过期后转发。两者都能实现延迟,但死信队列是 RabbitMQ 原生功能,不需要额外装插件,部署更简单。
Q: Nacos 做配置中心,配置修改后怎么生效的?
Nacos 客户端会和服务端保持长轮询(Long Polling),配置变更后服务端会主动推送通知,客户端拉取最新配置并刷新到本地。对于 @RefreshScope 标注的 Bean,Spring 会在下次使用时重新创建,实现配置热更新。
