目录
1简单介绍一下你这个项目的整体架构?
2为什么选择微服务架构?和单体架构相比有什么优缺点?
3 服务是怎么拆分的?拆分的依据是什么?
4各服务之间是怎么通信的?
5Nacos 在你的项目中起什么作用?
6Gateway 网关做了哪些事情?
7 服务之间的负载均衡是怎么做的?
8服务熔断、降级有没有做?
9乘客发单到司机接单,整个流程是怎样的?
10订单状态机是怎么设计的?
11支付流程是怎么实现的?
12行程匹配逻辑是怎么做的?
13并发抢单怎么处理?
14数据库是怎么设计的?
15文件存储用的什么方案?
16有没有用到 Redis?用来做什么?
17项目是怎么部署的?
18Nginx 在你的项目中起什么作用?
19线上出了问题怎么排查?
20用户登录鉴权是怎么做的?
21接口权限怎么控制
22敏感数据怎么保护的?
23 项目中遇到的最大技术难点是什么?
24如果让你重新设计,有哪些地方会改进?
25如果用户量增长 10 倍,系统哪里会成为瓶颈?
26RabbitMQ 死信队列怎么实现的?
27MinIO 为什么不用本地文件存储
28WebSocket 长连接断开怎么解决的?
29 百度 AI OCR 怎么对接的?
30简历问题(百分比是怎么统计的)
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简历问题(百分比是怎么统计的)
1. "降低无效订单率 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 停顿时间下降。
3. "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%。
4. "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.java
Redisson 客户端配置,读取 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 会在下次使用时重新创建,实现配置热更新。