Redisson 的分布式锁在秒杀系统中扮演核心角色,能够有效解决超卖、库存扣减冲突等问题。以下从架构设计、实现方案到优化策略详细说明:
一、秒杀系统的核心挑战
- 超卖问题:库存扣减时出现并发冲突(如库存 1 件,却被 10 个请求同时扣减)。
- 性能瓶颈 :单机锁无法跨节点,Redis 原生操作(如
WATCH
)性能不足。 - 公平性:先到先得,避免线程饥饿(如黄牛党刷票)。
- 防刷机制:限制单个用户短时间内的请求次数。
二、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();
}
七、总结与最佳实践
- 锁粒度控制:优先使用分段锁或读写锁,减少锁竞争。
- 快速失败:通过布隆过滤器和本地缓存过滤无效请求。
- 异步化处理:预扣库存后,通过队列异步落库,提升吞吐量。
- 防刷限流:结合分布式限流器和令牌桶算法,防止恶意请求。
- 异常处理:设置合理的锁超时时间,实现幂等性保障。
通过 Redisson 的分布式锁与其他特性(如布隆过滤器、限流器)结合,可构建高性能、高可用的秒杀系统。