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 的分布式锁与其他特性(如布隆过滤器、限流器)结合,可构建高性能、高可用的秒杀系统。

相关推荐
倔强青铜三6 分钟前
苦练Python第2天:安装 Python 与设置环境
前端·后端·python
Kookoos18 分钟前
ABP VNext + .NET Minimal API:极简微服务快速开发
后端·微服务·架构·.net·abp vnext
倔强青铜三23 分钟前
苦练Python第1天:为何要在2025年学习Python
前端·后端·python
LjQ20401 小时前
Java的一课一得
java·开发语言·后端·web
求知摆渡2 小时前
共享代码不是共享风险——公共库解耦的三种进化路径
java·后端·架构
brzhang2 小时前
前端死在了 Python 朋友的嘴里?他用 Python 写了个交互式数据看板,着实秀了我一把,没碰一行 JavaScript
前端·后端·架构
该用户已不存在3 小时前
不知道这些工具,难怪的你的Python开发那么慢丨Python 开发必备的6大工具
前端·后端·python
Xy9103 小时前
开发者视角:App Trace 一键拉起(Deep Linking)技术详解
java·前端·后端
嘻嘻哈哈开森3 小时前
技术分享:深入了解 PlantUML
后端·面试·架构
vvw&3 小时前
Linux 中的 .bashrc 是什么?配置详解
linux·运维·服务器·chrome·后端·ubuntu·centos