卡飞资源网

专业编程技术资源共享平台

阿里P8亲授:分布式锁+Redis秒杀系统设计,吞吐量提升10倍!


一、超卖问题的本质:高并发下的原子性缺失

在秒杀场景中,超卖(库存扣减至负数)和超买(用户重复购买)的核心问题源于非原子性操作。例如,当多个线程同时查询库存并执行扣减时,传统数据库的“查询-判断-扣减”三步操作可能被并发请求打乱,导致库存逻辑错误(如剩余库存为1时被多个线程同时判定为可购买)。

案例模拟:某电商平台推出100件商品秒杀,瞬间涌入1万请求。假设库存存储于MySQL,若未加锁,多个线程可能同时查询到剩余库存为1,均执行扣减,最终库存变为-99,导致超卖。


二、Redis分布式锁:秒杀系统的核心防线

1. 分布式锁的核心要求

  • 互斥性:同一时刻仅一个线程持有锁。
  • 防死锁:锁需设置超时时间,避免线程崩溃导致锁永久占用。
  • 原子性:锁的获取和释放必须是原子操作(如Lua脚本实现)。

2. Redis实现分布式锁的三种方案

方案一:SETNX + EXPIRE(基础版)

// 加锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock:product_001", "uuid", 10, TimeUnit.SECONDS);
if (lock) {
    try {
        // 业务逻辑(扣减库存)
    } finally {
        // 释放锁时需校验UUID,防止误删其他线程的锁
        if ("uuid".equals(redisTemplate.opsForValue().get("lock:product_001"))) {
            redisTemplate.delete("lock:product_001");
        }
    }
}

缺陷:SETNX和EXPIRE非原子操作,可能因线程崩溃导致死锁。

方案二:Lua脚本(原子操作版)

-- 扣减库存的Lua脚本
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock > 0 then
    redis.call('DECR', KEYS[1])
    return 1  -- 扣减成功
else
    return 0  -- 库存不足
end

通过Redis单线程特性,保证操作的原子性,避免超卖。

方案三:RedLock(高可用版)

在Redis主从架构下,为避免主节点宕机导致锁失效,使用RedLock算法(需部署多个独立Redis实例):

// Redisson实现RedLock
RLock lock1 = redisson.getLock("lock:product_001");
RLock lock2 = redisson.getLock("lock:product_001");
RLock lock3 = redisson.getLock("lock:product_001");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
try {
    // 扣减库存
} finally {
    redLock.unlock();
}

RedLock通过半数以上节点加锁成功来保证高可用性。


三、实战:Redis秒杀系统设计(吞吐量提升10倍的关键)

1. 架构设计

  • 流量分层:前端用Nginx+Lua限流,拦截90%无效请求。
  • 库存预热:活动开始前将库存加载至Redis(如stock:product_001=100)。
  • 异步下单:扣减库存后发送MQ消息,由消费者异步处理订单和数据库更新,缓解数据库压力。

2. 核心代码实现(Spring Boot + RedisTemplate)

@RestController
public class SeckillController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @PostMapping("/seckill")
    public String seckill(@RequestParam String productId) {
        String stockKey = "stock:" + productId;
        String lockKey = "lock:" + productId;
        String uuid = UUID.randomUUID().toString();

        // 获取分布式锁(设置10秒超时)
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 10, TimeUnit.SECONDS);
        if (locked != null && locked) {
            try {
                // Lua脚本原子扣减库存
                String script = "local stock = tonumber(redis.call('GET', KEYS[1])) " +
                                "if stock > 0 then redis.call('DECR', KEYS[1]) return 1 else return 0 end";
                Long result = redisTemplate.execute(
                    new DefaultRedisScript<>(script, Long.class),
                    Collections.singletonList(stockKey)
                );
                if (result == 1) {
                    // 发送MQ消息,异步创建订单
                    return "秒杀成功!";
                } else {
                    return "库存不足!";
                }
            } finally {
                // 释放锁时校验UUID
                if (uuid.equals(redisTemplate.opsForValue().get(lockKey))) {
                    redisTemplate.delete(lockKey);
                }
            }
        }
        return "系统繁忙,请重试!";
    }
}

四、性能优化:从千级到万级并发的跨越

1. 分段锁:提升并发粒度

将库存拆分为多个Key(如stock:product_001_1到stock:product_001_10),每个Key对应10件库存。线程随机选择分段扣减,并发能力提升10倍。

2. 主从架构下的锁失效问题

Redis主从同步存在延迟,若主节点宕机,可能导致锁丢失。解决方案:

  • 使用RedLock(需部署多实例)。
  • ZooKeeper替代(CP模型,但性能较低)。

3. 熔断降级

通过Sentinel或Hystrix监控系统负载,当库存耗尽或Redis异常时,直接返回“已售罄”,避免无效请求冲击。


五、总结:秒杀系统的核心公式

高性能秒杀 = 流量分层 + 原子操作 + 异步处理 + 熔断降级
通过Redis分布式锁和Lua脚本,结合分段锁与RedLock,可实现万级TPS的秒杀系统。实际落地中需根据业务规模选择方案:小规模场景用Lua脚本+单Redis,大规模高可用场景用RedLock+分段锁。


实战建议:在阿里云等生产环境中,建议使用Tair(阿里云版Redis)或Redisson封装分布式锁,避免重复造轮子。若追求极致性能,可结合本地缓存(如Guava)减少Redis访问频率。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言