高频面试题:设计秒杀系统,用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深度使用的能力。

相关推荐
@Turbo@2 分钟前
【QT】在QT6中读取文件的方法
开发语言·数据库·qt
ArabySide12 分钟前
【EF Core】 EF Core 批量操作的进化之路——从传统变更跟踪到无跟踪更新
数据库·.net·efcore
Clownseven1 小时前
Nginx Lua模块(OpenResty)实战:动态化、智能化你的Nginx,实现复杂Web逻辑 (2025)
nginx·lua·openresty
线条12 小时前
Hive SQL 中 BY 系列关键字全解析:从排序、分发到分组的核心用法
数据库·hive·sql
字节源流2 小时前
【MYSQL】索引篇(一)
数据库·mysql
n33(NK)2 小时前
MySQL中count(1)和count(*)的区别及细节
数据库·mysql
heart000_14 小时前
MySQL高级查询技巧:分组、聚合、子查询与分页【MySQL系列】
数据库·mysql
会敲键盘的猕猴桃很大胆4 小时前
Redis实战-基于redis和lua脚本实现分布式锁以及Redission源码解析【万字长文】
java·redis·分布式·spring·lua
凭君语未可5 小时前
MySQL中COUNT(*)、COUNT(1)和COUNT(字段名)的深度剖析与实战应用
数据库·mysql
z人间防沉迷k5 小时前
MySQL事务和索引原理
数据库·笔记·sql·mysql