同城拼车项目面试问题_01

目录

1简单介绍一下你这个项目的整体架构?

2为什么选择微服务架构?和单体架构相比有什么优缺点?

[3 服务是怎么拆分的?拆分的依据是什么?](#3 服务是怎么拆分的?拆分的依据是什么?)

4各服务之间是怎么通信的?

[5Nacos 在你的项目中起什么作用?](#5Nacos 在你的项目中起什么作用?)

[6Gateway 网关做了哪些事情?](#6Gateway 网关做了哪些事情?)

[7 服务之间的负载均衡是怎么做的?](#7 服务之间的负载均衡是怎么做的?)

8服务熔断、降级有没有做?

9乘客发单到司机接单,整个流程是怎样的?

10订单状态机是怎么设计的?

11支付流程是怎么实现的?

12行程匹配逻辑是怎么做的?

13并发抢单怎么处理?

14数据库是怎么设计的?

15文件存储用的什么方案?

[16有没有用到 Redis?用来做什么?](#16有没有用到 Redis?用来做什么?)

17项目是怎么部署的?

[18Nginx 在你的项目中起什么作用?](#18Nginx 在你的项目中起什么作用?)

19线上出了问题怎么排查?

20用户登录鉴权是怎么做的?

21接口权限怎么控制

22敏感数据怎么保护的?

[23 项目中遇到的最大技术难点是什么?](#23 项目中遇到的最大技术难点是什么?)

24如果让你重新设计,有哪些地方会改进?

[25如果用户量增长 10 倍,系统哪里会成为瓶颈?](#25如果用户量增长 10 倍,系统哪里会成为瓶颈?)

[26RabbitMQ 死信队列怎么实现的?](#26RabbitMQ 死信队列怎么实现的?)

[27MinIO 为什么不用本地文件存储](#27MinIO 为什么不用本地文件存储)

[28WebSocket 长连接断开怎么解决的?](#28WebSocket 长连接断开怎么解决的?)

[29 百度 AI OCR 怎么对接的?](#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。 优化做了两件事:

  1. 引入 Redis 缓存热点业务数据,热点数据直接读取内存,不再访问数据库,数据库 QPS 下降到1100 左右,极大减轻数据库压力;
  2. 对高频 where 条件、关联查询字段建立联合索引,优化慢 SQL。 优化完成之后接口平均响应时间降到 100ms,核心接口可以支撑 5000+QPS。

Q:5000+ QPS 是整体还是单个接口? A:这个是核心接口的峰值吞吐量,不是整个系统全量 QPS,只是压测的那一个热点接口的指标,系统其他接口流量会低很多。


面试官追加高频追问

追问 1:压测过程遇到什么问题?

压测初期并发上来之后,MySQL CPU 直接跑满,响应时间飙升,还偶发超时。一开始只加了缓存,但是没有处理缓存击穿风险;后面增加 Caffeine 本地二级缓存,加上互斥锁防止缓存击穿,压测稳定性提升。

追问 2:为什么不用 Redis 集群做压测?

因为是学校实践项目,测试机器资源有限,压测环境只搭建 Redis 单机;生产环境才会部署集群。

追问 3:压测的时候 JVM 有什么现象?

压测初期 YGC 比较频繁,大量短期对象创建;后面优化代码减少循环内对象创建,同时合理设置堆大小,GC 停顿时间下降。

  1. "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%。

  1. "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 会在下次使用时重新创建,实现配置热更新。

相关推荐
YonyouHRSaaS1 小时前
2026年AI招聘系统怎么选?AI面试功能怎么评估?
人工智能·面试·职场和发展·求职招聘·ai面试
写代码像蔡徐抻1 小时前
三年了,AI为何还没有抢走程序员饭碗?
前端·后端·面试
YHL2 小时前
🚀 SDD:告别「氛围编程」,迎接规范驱动开发新时代
面试
Java成神之路-2 小时前
基于 Spring AI+MCP 协议实现大模型调用本地自定义工具
java·spring·springaialibaba
SQL-First布道者3 小时前
我为什么把 MyBatis 从项目中删了?
java·spring·tomcat·mybatis·mybatis plus·spring jdbc
天衍四九-3 小时前
Agent Skills从入门到工程化(十六):面试中如何讲清楚 Agent Skills?
大数据·数据库·人工智能·python·chatgpt·面试
秋天的一阵风3 小时前
🤔首屏Banner压到40KB,LCP还是4秒?原来一直搞错了最大渲染元素
前端·人工智能·面试
秋天的一阵风3 小时前
💬面试官:Markdown 流式解析如何避免标签截断?「直接重新让 marked 全部渲染」行不行?
前端·面试·ai编程
QCodingDev3 小时前
Spring AI 2.0企业级RAG实战:引用校验、无依据拒答与知识治理怎么做?
java·人工智能·spring·ai