黑马点评常见面试题
异步秒杀的处理流程是怎样的?如何保证消息不丢失?
- 用户请求到达后,先通过 
Redis预减库存并判断资格(比如一人一单) - 资格校验通过后,将订单信息写入 
Redis Stream - 消费者服务监听 
Stream,读取消息并异步执行:扣减数据库库存、生成订单、发送通知 - 消费完成后发送确认,未确认消息会被重新投递
 
如何保证 “一人一单” 的业务逻辑?防止重复下单的机制有哪些?
核心逻辑:
- 基于用户 
ID和商品ID生成唯一键,存入Redis并设置过期时间 - 下单前检查该键是否存在,存在则拒绝重复下单
 
防重复机制:
- 后端:
- Redis 加锁+ 分布式锁(Redisson)双重校验
 - 数据库唯一索引(
UNIQUE KEY uk_user_goods (user_id, goods_id)),防止最终数据重复 
 
为什么选择 Redis Stream 实现消息队列?相比 RabbitMQ有什么优势?
相比 RabbitMQ 等中间件, Redis Stream 更轻量, , 使用简单, 不用额外部署中间件,运维成本低, 适合中小规模消息场景
Redisson 分布式锁的实现原理是什么?为什么选择可重入锁?
实现原理: 基于  SET NX PX 命令实现,核心是:
- 加锁:向 
Redis写入键值对,设置过期时间 - 解锁:通过 
Lua脚本判断锁的持有者是否为当前线程,若是则删除键 - 续期:内置 
Watch Dog机制,若线程未执行完,每隔1/3过期时间自动延长锁有效期 
选择可重入锁的原因:
秒杀场景中,同一线程可能多次获取锁,可重入锁允许同一线程重复加锁,避免死锁
乐观锁在项目中的具体实现方式?适用场景是什么?
实现方式: 在数据库表中添加 version 字段,更新时通过版本号判断能否更新成功
适用场景:
- 库存扣减(秒杀场景下并发高,但冲突频率较低)
 - 不需要长时间持有锁的场景,避免悲观锁的性能损耗
 
分布式锁的超时时间如何设置?出现死锁如何解决?
超时时间设置:
- 预估业务执行时间,设置超时时间为预估时间的 2-3 倍
 - 结合 
Redisson的Watch Dog机制,自动延长超时时间,避免业务未完成锁提前释放 
死锁解决:
- 根本避免:设置合理超时时间,确保锁能自动释放
 - 排查手段:通过 
Redis命令(如KEYS lock:*)找到长期未释放的锁,手动删除 - 为锁添加唯一标识(如 
UUID),只允许持有者解锁,避免误释放 
如何解决缓存穿透问题?布隆过滤器的实现细节是什么?
解决:
- 对不存在的商品 
ID,在Redis缓存空值,设置短期过期时间 - 引入布隆过滤器,提前拦截不存在的请求
 
布隆过滤器实现:
- 初始化:将所有商品 
ID哈希到一个bitmap中 - 校验:用户请求时,先通过布隆过滤器判断 ID 是否存在,不存在则直接返回,避免访问数据库
 
缓存雪崩的预防措施有哪些?项目中是如何配置缓存过期时间的?
- 缓存过期时间加随机值,避免大量缓存同时失效
 Redis集群部署,避免单点故障- 缓存失效时,返回兜底数据,而非直接访问数据库
 
过期时间配置:
- 热点数据:较长过期时间+ 主动更新
 - 非热点数据:较短过期时间+ 随机偏移,降低雪崩风险
 
缓存击穿的解决方案是什么?热点数据如何特殊处理?
缓存击穿解决:
- 互斥锁:缓存失效时,只允许一个线程查询数据库并重建缓存,其他线程等待重试
 - 热点数据永不过期:结合定时任务后台更新,避免过期瞬间的并发冲击
 
热点数据处理:
- 秒杀商品等热点数据,提前加载到本地缓存和 
Redis,设置永不过期 - 通过 
Redis Cluster分片存储,避免单节点压力过大 - 限制单用户访问频率,防止恶意请求击穿缓存
 
缓存与数据库的数据一致性如何保证?采用哪种更新策略?
策略:先更新数据库,再删除缓存
- 
步骤:更新
MySQL数据 → 删除 Redis 对应缓存 → 后续读请求会从数据库加载新数据并重建缓存 - 
优化:
- 延迟删除:删除缓存时加短暂延迟,避免并发更新时的缓存脏写
 - 最终一致性:通过定时任务对比缓存与数据库数据,修复差异
 
 
Lua 脚本在 Redis 操作中的作用是什么?写过哪些核心的 Lua 脚本?
作用:
- 保证多个 Redis 命令的原子性
 - 减少网络交互,提升性能
 
脚本示例:
库存预减与资格校验:
-- 检查用户是否已下单,未下单则预减库存local userKey = \'order:user:\' .. ARGV[1] .. \':goods:\' .. ARGV[2]local stockKey = \'stock:\' .. ARGV[2]if redis.call(\'EXISTS\', userKey) == 1 then return 0 -- 已下单endlocal stock = redis.call(\'GET\', stockKey)if not stock or tonumber(stock) <= 0 then return -1 -- 库存不足endredis.call(\'DECR\', stockKey)redis.call(\'SET\', userKey, 1, \'EX\', 86400) -- 标记已下单,有效期1天return 1 -- 成功


