黑马点评面试题整理

张开发
2026/6/14 10:19:38 15 分钟阅读
黑马点评面试题整理
黑马点评面试题整理整理时间2026-03-31 23:55:17这份文档汇总了前面整理过的几类高频面试题按项目亮点拆分方便后续复习。1. 登录认证Token 拦截器 ThreadLocal1.1 面试官会问什么你这套登录流程从登录到鉴权完整说一遍。Token 登录和 Session 登录有什么区别为什么要用拦截器为什么不用过滤器ThreadLocal 为什么能保存用户信息为什么请求结束后一定要清理 ThreadLocal如果异步线程里要拿用户信息怎么办Token 放在哪里传给前端前端怎么携带如果 token 过期了怎么办如何实现退出登录登录和权限控制有什么区别1.2 标准答法1. 登录流程怎么说用户登录成功后服务端会生成 token 返回给前端。前端后续请求会把 token 放在请求头里。服务端拦截器先解析 token校验通过后把用户信息保存到 ThreadLocal这样 Controller 和 Service 层都能直接拿到当前用户。请求结束后再清理 ThreadLocal避免线程复用导致用户信息串用。2. Token 和 Session 的区别Session 是服务端保存会话状态客户端只保存 sessionId。Token 方案一般由客户端保存凭证请求时携带服务端再解析。 Session 方案实现简单但分布式场景要考虑 session 共享Token 方案更适合分布式但主动失效和续期管理更复杂。3. 为什么用拦截器因为拦截器更贴近 Spring MVC 的请求处理流程适合做统一鉴权、用户上下文注入这类和业务接口强相关的逻辑。相比过滤器拦截器更方便拿 Spring 容器里的 Bean也更适合权限相关处理。4. ThreadLocal 为什么能保存用户信息因为 ThreadLocal 是线程隔离的每个线程都有自己的副本。一次 HTTP 请求通常由一个线程处理所以把当前用户放进 ThreadLocal 后这条请求链路上的代码都能拿到当前用户信息。5. 为什么一定要 remove因为线程池会复用线程。如果请求结束不清理下一次请求复用了同一个线程就可能读到上一个用户的数据既有安全问题也有内存泄漏风险。6. 异步线程里怎么办默认拿不到因为 ThreadLocal 不能跨线程。常见做法是显式传参或者在特殊场景下使用支持上下文传递的方案。但面试里更稳的回答是异步场景优先显式传用户 id不依赖 ThreadLocal。1.3 回答时要避开的坑如果项目里不是 JWT而是 Redis Token就不要硬说 JWT。不要说 ThreadLocal 能跨线程。不要漏掉remove()。2. Redis 缓存穿透、雪崩、击穿2.1 面试官会问什么Redis 里你缓存了什么数据商品缓存的 key 怎么设计缓存穿透、雪崩、击穿分别是什么这三者的区别是什么缓存穿透怎么解决为什么缓存空值能解决穿透副作用是什么布隆过滤器你了解吗缓存雪崩怎么解决为什么给过期时间加随机值可以缓解雪崩缓存击穿怎么解决互斥锁和逻辑过期有什么区别为什么更新数据库后删除缓存为什么不是先删缓存再更新数据库如果删缓存失败怎么办热点缓存重建时多个线程同时进来怎么办2.2 标准答法1. 缓存了什么主要缓存高频访问、读多写少的数据比如商品或店铺详情、分类信息、登录验证码、登录用户信息以及秒杀场景下的库存和资格标记。2. key 怎么设计一般是业务前缀加主键比如cache:shop:1或cache:product:1001。这样命名清晰也方便分类管理和失效控制。3. 穿透、雪崩、击穿怎么解释缓存穿透查一个根本不存在的数据每次都打到数据库。缓存雪崩大量 key 同时失效或者 Redis 整体不可用导致大量请求一起打到数据库。缓存击穿某个热点 key 失效瞬间大量并发请求同时打到数据库。4. 穿透怎么解决最常见的是缓存空值。如果数据库查不到也往 Redis 写一个空值并设置较短 TTL。这样后续同样请求就不会每次都打数据库。恶意请求比较多时还可以加布隆过滤器。5. 雪崩怎么解决常见做法是过期时间加随机值避免同一时刻集中失效热点数据做永不过期或逻辑过期Redis 做高可用服务层做限流、降级6. 击穿怎么解决热点 key 过期后通常用互斥锁控制只有一个线程去重建缓存其他线程等待或重试。 如果业务更强调响应速度也可以用逻辑过期让请求先返回旧值再后台异步刷新。7. 为什么先更新数据库再删除缓存因为如果先删缓存再更新数据库在数据库还没更新完成时其他线程可能已经读到旧数据并重新写回缓存从而造成脏数据。先更新数据库再删缓存可以降低这个问题发生的概率。2.3 回答时要避开的坑如果项目里没明确缓存订单详情不要硬说“订单也做了完整缓存”。不要把穿透、击穿、雪崩混为一谈。不要把“更新数据库后删除缓存”说成绝对强一致它本质上是最终一致。3. Lua 脚本一人一单与库存不超卖3.1 面试官会问什么为什么要用 Lua 脚本Lua 脚本为什么能保证原子性脚本里具体做了哪几步一人一单在 Redis 里怎么判断库存不超卖怎么保证如果不用 Lua用普通 Redis 命令会有什么问题为什么判断库存和扣减库存必须放到一个原子操作里用户重复请求多次怎么避免重复下单Redis 扣库存成功了但数据库落单失败怎么办Redis 和数据库库存怎么保证最终一致3.2 标准答法1. 为什么要用 Lua因为秒杀场景下并发很高“判断库存”“判断是否已下单”“扣减库存”“记录购买资格”如果拆成多条 Redis 命令就会出现并发竞争导致超卖或重复下单。Lua 可以把这些操作放到 Redis 中一次执行保证原子性。2. 为什么有原子性因为 Redis 执行 Lua 脚本时是单线程串行执行的脚本执行过程中不会被其他命令打断所以脚本里的多个操作对外表现为一个整体。3. 脚本里通常做什么判断库存是否充足判断用户是否已下单如果不满足条件直接返回失败如果满足条件扣减库存记录用户已购买标记返回成功4. 一人一单怎么做一般会用Set记录某个商品已经下单的用户 id。脚本先判断当前用户是否已经在集合里如果在就说明已经买过直接返回失败。5. 不超卖怎么做在 Lua 脚本里先判断库存是否大于 0只有大于 0 才执行扣减而且判断和扣减在同一个原子操作里完成所以不会出现多个请求都判断成功然后把库存扣成负数。6. Redis 成功、数据库失败怎么办这说明前置校验成功了但最终订单没落库会造成 Redis 和数据库短暂不一致。生产里通常要结合消息队列、补偿、重试或定时对账来恢复一致性。面试里不要说成“完全不会出错”而是说“这是典型最终一致性问题需要补偿处理”。3.3 回答时要避开的坑不要说 Lua 能解决所有一致性问题它主要解决 Redis 内部原子校验。不要忽略数据库层的唯一约束或幂等兜底。不要把最终一致说成强一致。4. Kafka 秒杀异步化削峰填谷4.1 面试官会问什么为什么秒杀场景要异步化什么叫削峰填谷为什么先 Redis 校验再发 KafkaKafka 在整个链路里处于什么位置Kafka 消息里放哪些字段订单 id 是什么时候生成的为什么消费者要做幂等如何避免重复订单Redis 扣库存成功但消息发送失败怎么办消息发送成功但消费失败怎么办Redis 和数据库库存怎么保证最终一致Kafka 能不能解决数据库写入瓶颈Redis Lua 已经够快了为什么还要 Kafka异步方案的优点和缺点是什么如果消费者挂了会怎么样4.2 标准答法1. 为什么要异步化因为秒杀请求在极短时间内会爆发式涌入如果同步完成库存校验、订单创建和数据库写入数据库会很快成为瓶颈。异步化可以把高并发请求先拦在前面后续订单创建交给消息队列慢慢消费从而提升系统稳定性。2. 什么是削峰填谷就是把瞬时洪峰请求转成后端可控速度的处理流量。前端瞬间进来的大量请求不会直接压到数据库而是先进入 Kafka 排队由消费者按系统可承受速度消费。3. 为什么先 Redis 再 Kafka因为 Redis 适合做高并发下的快速资格校验比如库存是否充足、是否一人一单。 如果不先过滤而是把所有请求都打进 Kafka就会把大量无效请求也塞进队列浪费消费资源。4. Kafka 的作用Kafka 主要承担异步解耦和流量缓冲。前面 Redis 做资格校验后面 Kafka 把通过校验的请求转成消息异步交给消费者创建订单、更新数据库库存。5. 消费者为什么要做幂等因为 Kafka 可能出现重复投递或重复消费如果不做幂等就可能生成重复订单。常见做法是用订单 id、业务流水号或user_id product_id联合唯一约束做兜底。6. Redis 成功但消息发送失败怎么办这会造成资格已经占用但后续订单流程没继续。一般要做补偿比如回补 Redis 库存、删除用户资格标记或者通过事务消息、Outbox 模式保证消息最终能发送成功。7. 消费成功但数据库失败怎么办这属于最终一致性问题。通常要通过消费者重试、补偿消息、失败记录和定时对账来恢复一致。真正落订单和扣数据库库存时最好放在一个本地事务里。8. Kafka 能不能解决数据库瓶颈Kafka 不能提升数据库物理写入上限但它能把瞬时流量摊平让数据库以可承受速度处理请求。所以它解决的是流量洪峰问题不是数据库极限问题。4.3 回答时要避开的坑不要说“请求返回成功就一定下单成功”异步场景通常只是拿到资格。不要忽略消费者幂等。不要把 Kafka 说成能直接解决所有一致性问题。5. 乐观锁支付与关单的并发冲突5.1 面试官会问什么为什么支付和关单会并发冲突这种冲突会导致什么状态混乱为什么要用乐观锁而不是悲观锁乐观锁的核心思想是什么版本号机制怎么实现为什么版本号能避免状态覆盖如果更新失败了说明什么乐观锁的优缺点是什么为什么订单状态流转一定要设计清晰支付回调和关单同时到来时最终会怎样5.2 标准答法1. 为什么会冲突因为支付和超时关单都可能在同一时刻修改同一笔订单状态。支付想把订单从待支付改成已支付关单想把订单从待支付改成已取消如果没有并发控制就会互相覆盖。2. 乐观锁怎么做通常是在订单表中增加version字段。更新时带条件where id ? and version ?如果更新成功就把 version 加一如果更新条数为 0说明这条订单已经被别的线程更新过了这次操作直接失败。3. 为什么它能防止状态混乱因为同一时刻只有一个线程能基于旧版本更新成功。支付线程和关单线程就算都拿到了旧版本也只有一个能真正提交成功另一个会因为版本不匹配而失败从而避免后写覆盖前写。4. 为什么不用悲观锁因为这个场景虽然可能冲突但不是像秒杀库存那样的超高频热点写入。乐观锁不需要长时间持有数据库锁性能影响更小更适合这类订单状态更新场景。5. 更新失败后怎么办更新失败一般说明订单状态已经被其他线程改过了。这个时候应该重新查询订单当前状态再根据业务规则决定后续处理而不是无脑重试。5.3 回答时要避开的坑不要说乐观锁失败就是 bug它本来就是正常并发结果。不要只讲版本号不讲订单状态机。不要把乐观锁说成适用于所有高并发场景。6. Spring Task超时未支付订单自动取消6.1 面试官会问什么为什么要定时扫描超时未支付订单什么叫超时未支付订单Spring Task 是怎么实现定时任务的扫描频率一般怎么定SQL 一般怎么查超时订单扫描到后会做哪些操作为什么扫描到后还要再次校验状态定时关单会不会和支付产生并发冲突如何避免把已支付订单取消掉集群环境下 Spring Task 会不会重复执行怎么避免多个实例重复扫单为什么不用延迟队列而用 Spring Task6.2 标准答法1. 为什么要扫超时订单因为订单创建后通常会有支付有效期如果用户一直不支付这笔订单不能无限占用库存或优惠资格所以要自动关单释放资源。2. 超时订单怎么定义就是订单创建时间已经超过支付时限但状态仍然是待支付的订单。比如支付时限 15 分钟那么创建时间早于当前时间 15 分钟前且状态仍然是待支付的订单就属于超时未支付订单。3. Spring Task 怎么做一般通过Scheduled注解配置定时任务按固定时间间隔扫描数据库把满足条件的订单查出来然后逐笔处理关单逻辑。4. 为什么扫描到后还要再校验状态因为扫描和真正更新之间可能有时间差。在这段时间内用户可能已经支付成功。所以不能“查出来是待支付就直接改”更新时必须带条件比如只允许当前状态仍为待支付的订单被更新为已取消。5. 如何避免取消已支付订单更新时要带状态条件例如update order set statusCANCELLED where id? and statusUNPAID这样如果支付已经成功状态就不是待支付了关单更新会失败。6. 集群环境会不会重复执行会。如果多个应用实例都跑同一个 Spring Task就可能重复扫描和处理同一批订单。所以集群部署时通常要加分布式锁或者交给统一调度平台处理。7. 为什么不用延迟队列Spring Task 的优点是实现简单、落地快适合中小规模项目。 延迟队列更精准数据库扫描压力更小但实现和维护成本更高。如果订单量不大Spring Task 已经够用。6.3 回答时要避开的坑不要说“查到后直接关单”一定要强调更新时再校验状态。不要忽略集群场景下的重复执行问题。不要把 Spring Task 说成最优解它更多是简单可落地方案。7. 高频追问速记7.1 你最该优先背的缓存题穿透、雪崩、击穿分别是什么它们分别怎么解决为什么更新数据库后删除缓存为什么先删缓存再更新数据库不合适Redis 热点缓存击穿时怎么做互斥重建7.2 你最该优先背的秒杀题为什么要用 Lua 脚本为什么 Lua 有原子性一人一单怎么做不超卖怎么做Redis 成功、数据库失败怎么办Kafka 为什么能削峰填谷Redis Lua 够快了为什么还要 Kafka异步方案为什么通常是最终一致7.3 你最该优先背的订单状态题支付和关单为什么会冲突乐观锁怎么解决状态覆盖为什么扫描超时订单后还要再判断一次状态定时关单为什么可能和支付并发集群环境下 Spring Task 怎么避免重复执行8. 复习建议第一轮先背定义和主流程确保能说完整登录链路缓存三大问题Lua 秒杀校验Kafka 异步化乐观锁防状态覆盖Spring Task 扫超时订单第二轮重点背“为什么这样做”为什么更新数据库后删除缓存为什么先 Redis 再 Kafka为什么支付和关单要用乐观锁为什么扫到订单后还要再判断状态第三轮补边界场景Redis 成功但数据库失败Kafka 消息丢失或重复消费ThreadLocal 不清理会怎样Spring Task 集群重复执行怎么办

更多文章