Redisson秒杀系统中的分布式锁应用

Redisson 的分布式锁在秒杀系统中扮演核心角色,能够有效解决超卖、库存扣减冲突等问题。以下从架构设计、实现方案到优化策略详细说明:

一、秒杀系统的核心挑战

  1. 超卖问题:库存扣减时出现并发冲突(如库存 1 件,却被 10 个请求同时扣减)。
  2. 性能瓶颈 :单机锁无法跨节点,Redis 原生操作(如 WATCH)性能不足。
  3. 公平性:先到先得,避免线程饥饿(如黄牛党刷票)。
  4. 防刷机制:限制单个用户短时间内的请求次数。

二、Redisson 分布式锁方案

1. 基础实现:可重入锁(RLock)

java 复制代码
// 获取商品库存锁
RLock stockLock = redisson.getLock("stock:" + productId);

try {
    // 加锁(带超时,防止死锁)
    stockLock.lock(10, TimeUnit.SECONDS);
    
    // 检查库存
    int stock = getStockFromDB(productId);
    if (stock > 0) {
        // 扣减库存
        boolean success = decreaseStock(productId);
        if (success) {
            // 生成订单
            createOrder(userId, productId);
        }
    }
} finally {
    // 释放锁
    stockLock.unlock();
}

2. 公平锁(RFairLock)实现先到先得

java 复制代码
// 使用公平锁保证请求按顺序处理
RFairLock fairLock = redisson.getFairLock("stock:" + productId);

try {
    fairLock.lock(10, TimeUnit.SECONDS);
    // 库存扣减逻辑
} finally {
    fairLock.unlock();
}

3. 联锁(MultiLock)实现多资源锁定

若秒杀涉及多个资源(如库存 + 优惠券):

java 复制代码
RLock stockLock = redisson.getLock("stock:" + productId);
RLock couponLock = redisson.getLock("coupon:" + couponId);
RMultiLock multiLock = new RedissonMultiLock(stockLock, couponLock);

try {
    multiLock.lock(10, TimeUnit.SECONDS);
    // 同时操作库存和优惠券
} finally {
    multiLock.unlock();
}

三、高性能优化策略

1. 本地锁降级(减少 Redis 调用)

java 复制代码
// 本地缓存商品是否还有库存(减少 Redis 访问)
private static final ConcurrentHashMap<String, Boolean> stockCache = new ConcurrentHashMap<>();

public void seckill(String productId, String userId) {
    // 先查本地缓存(快速失败)
    if (stockCache.getOrDefault(productId, true) == false) {
        return; // 库存已售罄
    }
    
    RLock stockLock = redisson.getLock("stock:" + productId);
    try {
        // 尝试快速加锁(不阻塞)
        if (!stockLock.tryLock(0, 10, TimeUnit.SECONDS)) {
            return; // 锁被占用,直接返回
        }
        
        // 检查库存
        int stock = getStockFromDB(productId);
        if (stock <= 0) {
            stockCache.put(productId, false); // 更新本地缓存
            return;
        }
        
        // 扣减库存
        boolean success = decreaseStock(productId);
        if (success) {
            // 库存充足,更新本地缓存
            stockCache.put(productId, stock > 1);
            createOrder(userId, productId);
        }
    } finally {
        stockLock.unlock();
    }
}

2. 分段锁(提升并发度)

将库存分为多个段,每个段独立加锁:

java 复制代码
// 将库存分为16段(可根据实际情况调整)
private static final int SEGMENT_COUNT = 16;

public void seckillWithSegment(String productId, String userId) {
    // 根据用户ID哈希决定使用哪个分段锁
    int segment = userId.hashCode() % SEGMENT_COUNT;
    RLock segmentLock = redisson.getLock("stock:" + productId + ":segment:" + segment);
    
    try {
        segmentLock.lock(10, TimeUnit.SECONDS);
        // 扣减对应分段的库存
        decreaseSegmentStock(productId, segment);
    } finally {
        segmentLock.unlock();
    }
}

3. 读写锁(适用于读多写少场景)

若查询库存频繁,扣减库存较少:

java 复制代码
RReadWriteLock rwLock = redisson.getReadWriteLock("stock:" + productId);

// 查询库存(读锁)
public int getStock() {
    RLock readLock = rwLock.readLock();
    readLock.lock();
    try {
        return getStockFromDB(productId);
    } finally {
        readLock.unlock();
    }
}

// 扣减库存(写锁)
public boolean decreaseStock() {
    RLock writeLock = rwLock.writeLock();
    writeLock.lock();
    try {
        // 扣减逻辑
    } finally {
        writeLock.unlock();
    }
}

四、防刷与限流策略

1. 布隆过滤器过滤无效请求

java 复制代码
// 初始化布隆过滤器,存储已参与秒杀的用户
RBloomFilter<String> userBloomFilter = redisson.getBloomFilter("seckill:users");
userBloomFilter.tryInit(1000000, 0.01); // 预计100万用户,误判率1%

public boolean checkUser(String userId) {
    // 检查用户是否已参与过秒杀
    if (userBloomFilter.contains(userId)) {
        return false; // 已参与,拒绝
    }
    
    // 未参与,记录并放行
    userBloomFilter.add(userId);
    return true;
}

2. 分布式限流器限制请求频率

java 复制代码
// 限制单个用户每秒最多5次请求
RRateLimiter userLimiter = redisson.getRateLimiter("user:" + userId);
userLimiter.trySetRate(RateType.PER_CLIENT, 5, 1, TimeUnit.SECONDS);

public void seckill(String productId, String userId) {
    // 尝试获取令牌
    if (!userLimiter.tryAcquire()) {
        return; // 超出频率限制
    }
    
    // 正常处理秒杀逻辑
}

五、库存预扣与异步扣减

1. 预扣库存 + 异步落库

java 复制代码
// 预扣库存(原子操作)
RAtomicLong stock = redisson.getAtomicLong("stock:" + productId);

public boolean reserveStock() {
    // 原子性扣减库存(大于0时扣减)
    while (true) {
        long current = stock.get();
        if (current <= 0) {
            return false; // 库存不足
        }
        
        if (stock.compareAndSet(current, current - 1)) {
            // 预扣成功,异步落库
            asyncUpdateDB(productId);
            return true;
        }
    }
}

2. 基于 Redis List 的异步队列

java 复制代码
// 将扣减请求放入队列,后台异步处理
RBlockingQueue<SeckillRequest> requestQueue = redisson.getBlockingQueue("seckill:requests");

// 处理请求
public void handleRequest(SeckillRequest request) {
    // 放入队列
    requestQueue.put(request);
}

// 后台消费者(单独线程或服务)
public void startConsumer() {
    while (true) {
        SeckillRequest request = requestQueue.take();
        // 处理库存扣减和订单生成
    }
}

六、异常处理与幂等性保障

1. 超时与重试机制

java 复制代码
RLock lock = redisson.getLock("stock:" + productId);
int retryTimes = 3;

for (int i = 0; i < retryTimes; i++) {
    try {
        // 尝试获取锁(带超时)
        if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
            // 处理业务逻辑
            break;
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        break;
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
    
    // 重试间隔
    Thread.sleep(100);
}

2. 幂等性保障

java 复制代码
// 使用唯一订单号作为锁,保证同一请求不重复处理
RLock orderLock = redisson.getLock("order:" + orderId);

try {
    orderLock.lock(10, TimeUnit.SECONDS);
    
    // 检查订单是否已存在(幂等性检查)
    if (orderExists(orderId)) {
        return getOrderResult(orderId);
    }
    
    // 创建订单
    createOrder(userId, productId);
} finally {
    orderLock.unlock();
}

七、总结与最佳实践

  1. 锁粒度控制:优先使用分段锁或读写锁,减少锁竞争。
  2. 快速失败:通过布隆过滤器和本地缓存过滤无效请求。
  3. 异步化处理:预扣库存后,通过队列异步落库,提升吞吐量。
  4. 防刷限流:结合分布式限流器和令牌桶算法,防止恶意请求。
  5. 异常处理:设置合理的锁超时时间,实现幂等性保障。

通过 Redisson 的分布式锁与其他特性(如布隆过滤器、限流器)结合,可构建高性能、高可用的秒杀系统。

相关推荐
长栎27 分钟前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode31 分钟前
Redis 在生产项目的使用
前端·后端
用户5598224812236 分钟前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode36 分钟前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战38 分钟前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha1 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn1 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425911 小时前
ShardingJDBC
后端
行者全栈架构师1 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
Colin草率地做慢慢地改1 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构