高频面试题:设计秒杀系统,用Redis+Lua解决超卖

高频面试题:设计秒杀系统,用Redis+Lua解决超卖

      • [**1. 问题背景**](#1. 问题背景)
      • [**2. 解决方案:Redis + Lua**](#2. 解决方案:Redis + Lua)
        • [**为什么选择Redis + Lua?**](#为什么选择Redis + Lua?)
        • **核心代码逻辑**
        • [**Java调用示例(Spring Boot)**](#Java调用示例(Spring Boot))
      • [**3. 方案优势**](#3. 方案优势)
      • [**4. 面试回答话术**](#4. 面试回答话术)
      • [**5. 可能的追问与应对**](#5. 可能的追问与应对)
        • [**Q1: 如果Redis宕机了怎么办?**](#Q1: 如果Redis宕机了怎么办?)
        • [**Q2: 如何防止恶意请求刷库存?**](#Q2: 如何防止恶意请求刷库存?)
        • [**Q3: Lua脚本的缺点?**](#Q3: Lua脚本的缺点?)
      • [**6. 最终总结**](#6. 最终总结)

在秒杀或高并发库存扣减场景中,使用 Redis + Lua 解决超卖问题是一个经典的方案。以下是详细的实现逻辑和面试回答话术,突出技术深度和项目实践:


1. 问题背景

超卖原因

高并发下,多个请求同时查询库存并扣减,导致库存扣减顺序混乱,最终库存为负数(如:剩余1件商品,但多个请求同时判定有库存,最终卖出多件)。


2. 解决方案:Redis + Lua

为什么选择Redis + Lua?
  • 原子性:Lua脚本在Redis中单线程执行,保证多个操作的原子性。
  • 高性能 :Redis内存操作+Lua脚本避免网络往返(减少GET+DECR的多次交互)。
  • 避免分布式锁 :比SETNX等方案更轻量,无锁竞争开销。
核心代码逻辑
lua 复制代码
-- KEYS[1]: 库存key(如 "seckill:stock:123")
-- ARGV[1]: 扣减数量(通常为1)
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock <= 0 then
    return 0  -- 库存不足
end
if stock >= tonumber(ARGV[1]) then
    redis.call('DECRBY', KEYS[1], ARGV[1])
    return 1  -- 扣减成功
else
    return 0  -- 库存不足
end
Java调用示例(Spring Boot)
java 复制代码
@RestController
public class SeckillController {
    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String STOCK_KEY = "seckill:stock:123";
    private static final String LUA_SCRIPT =
        "local stock = tonumber(redis.call('GET', KEYS[1]))\n" +
        "if stock <= 0 then return 0 end\n" +
        "if stock >= tonumber(ARGV[1]) then\n" +
        "    redis.call('DECRBY', KEYS[1], ARGV[1])\n" +
        "    return 1\n" +
        "else return 0 end";

    @PostMapping("/seckill")
    public String seckill() {
        // 1. 加载Lua脚本
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(LUA_SCRIPT);
        script.setResultType(Long.class);

        // 2. 执行脚本(KEYS和ARGV列表)
        Long result = redisTemplate.execute(
            script,
            Collections.singletonList(STOCK_KEY),
            "1"  // ARGV[1]: 扣减数量
        );

        // 3. 处理结果
        if (result == 1) {
            // 扣减成功,创建订单(异步或MQ)
            return "秒杀成功";
        } else {
            return "库存不足";
        }
    }
}

3. 方案优势

  1. 原子性 :脚本内的GETDECRBY是一个不可分割的操作。
  2. 高性能:单次网络开销,Redis单线程执行无竞争。
  3. 可扩展
    • 结合Redis集群 (通过{hash_tag}保证同一个库存key落到同一节点)。
    • 配合MQ异步处理订单,进一步削峰。

4. 面试回答话术

问题"请说明你在项目中如何解决超卖问题?"

回答模板

"在我们公司的秒杀系统中,我通过Redis + Lua脚本 实现了库存扣减的原子性操作。

核心思路是将库存预加载到Redis中,通过Lua脚本保证查询库存扣减库存的原子性执行。

当用户请求到达时,先执行Lua脚本快速判定库存是否充足,若充足则扣减并进入订单创建流程,否则直接返回失败。

为了进一步提升性能,我们还将订单创建通过MQ异步处理,最终实现了每秒数万级别的并发扣减,且无超卖问题。

此外,我们还通过压测验证了方案的可靠性,比如模拟网络延迟和Redis故障场景下的降级策略。"


5. 可能的追问与应对

Q1: 如果Redis宕机了怎么办?

  • 降级方案:开启本地库存缓存(如Guava Cache),设置短有效期,允许少量超卖后人工修复。
  • 持久化:Redis开启AOF持久化,故障恢复后从磁盘恢复数据。
Q2: 如何防止恶意请求刷库存?

  • 限流:网关层对用户ID/IP限流(如令牌桶)。
  • 验证:前置验证码/风控系统。
Q3: Lua脚本的缺点?

  • 调试困难:需通过Redis日志排查逻辑错误。
  • 复杂度:长脚本会阻塞Redis,建议拆分简单操作。

6. 最终总结

  • 核心:Lua脚本是Redis中实现复杂原子操作的终极武器。
  • 扩展 :可结合分布式ID生成 (订单号)、异步削峰 (MQ)、库存预热(提前加载)构建完整秒杀系统。

通过这个案例,面试官能清晰看到你对高并发设计Redis深度使用的能力。

相关推荐
在努力的前端小白12 分钟前
Spring Boot 敏感词过滤组件实现:基于DFA算法的高效敏感词检测与替换
java·数据库·spring boot·文本处理·敏感词过滤·dfa算法·组件开发
未来之窗软件服务14 分钟前
自建知识库,向量数据库 (九)之 量化前奏分词服务——仙盟创梦IDE
数据库·仙盟创梦ide·东方仙盟·自建ai·ai分词
冒泡的肥皂3 小时前
MVCC初学demo(一
数据库·后端·mysql
.Shu.5 小时前
Redis Reactor 模型详解【基本架构、事件循环机制、结合源码详细追踪读写请求从客户端连接到命令执行的完整流程】
数据库·redis·架构
薛晓刚7 小时前
当MySQL的int不够用了
数据库
SelectDB技术团队8 小时前
Apache Doris 在菜鸟的大规模湖仓业务场景落地实践
数据库·数据仓库·数据分析·apache doris·菜鸟技术
星空下的曙光8 小时前
mysql 命令语法操作篇 数据库约束有哪些 怎么使用
数据库·mysql
小楓12018 小时前
MySQL數據庫開發教學(一) 基本架構
数据库·后端·mysql
染落林间色8 小时前
达梦数据库-实时主备集群部署详解(附图文)手工搭建一主一备数据守护集群DW
数据库·sql
颜颜yan_8 小时前
企业级时序数据库选型指南:从传统架构向智能时序数据管理的转型之路
数据库·架构·时序数据库