Renren框架DistributeLock排他锁实现详解
Renren框架DistributeLock排他锁实现详解
Renren框架的DistributeLock是基于Redis实现的分布式锁工具,其核心是通过原子操作保证同一时间只有一个客户端可以获取锁。以下从源码分析、实现原理到使用场景进行详细说明。
一、核心实现原理
DistributeLock主要基于Redis的以下特性:
- 原子操作:使用
SET key value NX PX timeout
命令实现原子性加锁 - 过期机制:设置锁的过期时间,防止死锁
- Lua脚本:通过Lua脚本保证解锁操作的原子性
核心源码分析
// 获取锁的核心方法
public boolean tryLock(String key, String requestId, long waitTime, long leaseTime) {long start = System.currentTimeMillis();try {// 循环尝试获取锁,直到超过等待时间while (true) {// 使用RedisTemplate执行SET命令Boolean success = redisTemplate.opsForValue().setIfAbsent(key, requestId, leaseTime, TimeUnit.MILLISECONDS);if (success != null && success) {return true; // 获取锁成功}// 计算剩余等待时间long elapsed = System.currentTimeMillis() - start;if (elapsed >= waitTime) {return false; // 等待超时}// 短暂休眠,避免频繁重试Thread.sleep(100);}} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}
}// 释放锁的核心方法(使用Lua脚本保证原子性)
public void unlock(String key, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +" return redis.call('del', KEYS[1]) " +"else " +" return 0 " +"end";redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),Collections.singletonList(key), requestId);
}
二、使用场景示例
1. 商品库存扣减
@Service
public class ProductService {@Autowiredprivate DistributeLock distributeLock;public void deductStock(String productId, int quantity) {String lockKey = "stock:" + productId;String requestId = UUID.randomUUID().toString();try {// 尝试获取锁,最多等待2秒,锁持有10秒后自动释放boolean locked = distributeLock.tryLock(lockKey, requestId, 2000, 10000);if (!locked) {throw new BusinessException("扣减库存失败,请稍后重试");}// 获取锁成功,执行库存扣减Product product = productDao.getById(productId);if (product.getStock() < quantity) {throw new BusinessException("库存不足");}product.setStock(product.getStock() - quantity);productDao.update(product);} finally {// 释放锁distributeLock.unlock(lockKey, requestId);}}
}
2. 订单创建防重
@Service
public class OrderService {@Autowiredprivate DistributeLock distributeLock;public String createOrder(OrderDTO orderDTO) {// 使用订单号或用户ID作为锁的KeyString lockKey = "order:" + orderDTO.getUserId();String requestId = UUID.randomUUID().toString();try {// 尝试获取锁,等待500毫秒boolean locked = distributeLock.tryLock(lockKey, requestId, 500, 5000);if (!locked) {throw new BusinessException("操作太频繁,请稍后再试");}// 检查订单是否已存在Order existingOrder = orderDao.findByUserIdAndStatus(orderDTO.getUserId(), OrderStatus.CREATED);if (existingOrder != null) {return existingOrder.getOrderNo();}// 创建新订单Order newOrder = new Order();// 设置订单属性...orderDao.save(newOrder);return newOrder.getOrderNo();} finally {distributeLock.unlock(lockKey, requestId);}}
}
三、参数配置说明
参数 | 说明 |
---|---|
key | 锁的唯一标识,建议使用业务相关前缀(如stock:product:123 ) |
requestId | 唯一标识请求,用于确保释放锁的安全性(必须与加锁时一致) |
waitTime | 等待锁的最大时间(毫秒),超过则返回失败 |
leaseTime | 锁的自动释放时间(毫秒),防止死锁 |
四、高级特性
1. 可重入锁支持
Renren框架的DistributeLock默认不支持可重入,如需可重入功能,可自定义实现:
public class ReentrantDistributeLock {private final DistributeLock delegate;private final ThreadLocal<Map<String, Integer>> lockCount = ThreadLocal.withInitial(HashMap::new);public boolean tryLock(String key, String requestId, long waitTime, long leaseTime) {Map<String, Integer> countMap = lockCount.get();if (countMap.containsKey(key)) {countMap.put(key, countMap.get(key) + 1);return true; // 已持有锁,直接返回成功}boolean locked = delegate.tryLock(key, requestId, waitTime, leaseTime);if (locked) {countMap.put(key, 1);}return locked;}public void unlock(String key, String requestId) {Map<String, Integer> countMap = lockCount.get();Integer count = countMap.get(key);if (count == null) {return; // 未持有锁,直接返回}if (count > 1) {countMap.put(key, count - 1); // 减少重入次数} else {countMap.remove(key);delegate.unlock(key, requestId); // 真正释放锁}}
}
2. 看门狗机制(自动续期)
public boolean tryLockWithWatchdog(String key, String requestId, long waitTime) {long start = System.currentTimeMillis();try {while (true) {// 设置较短的初始租期Boolean success = redisTemplate.opsForValue().setIfAbsent(key, requestId, 30, TimeUnit.SECONDS);if (success != null && success) {// 启动看门狗线程,定期续期startWatchdog(key, requestId);return true;}if (System.currentTimeMillis() - start > waitTime) {return false;}Thread.sleep(100);}} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}
}private void startWatchdog(String key, String requestId) {ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();// 每10秒续期一次(租期30秒)executor.scheduleAtFixedRate(() -> {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +" return redis.call('pexpire', KEYS[1], ARGV[2]) " +"else " +" return 0 " +"end";redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),Collections.singletonList(key), requestId, 30000L // 续期30秒);}, 10, 10, TimeUnit.SECONDS);
}
五、常见问题与解决方案
1. 锁超时问题
- 问题:业务执行时间超过leaseTime导致锁提前释放
- 解决方案:
// 方案1:合理估算业务耗时,设置足够长的leaseTime long leaseTime = estimateBusinessTime() * 1.5; // 留出50%的缓冲时间// 方案2:使用看门狗机制自动续期 distributeLock.tryLockWithWatchdog(lockKey, requestId, 5000);
2. 锁释放失败
- 问题:unlock操作失败导致锁无法释放
- 解决方案:
// 方案1:确保unlock在finally块中执行 try {if (distributeLock.tryLock(lockKey, requestId, 1000, 30000)) {// 业务逻辑} } finally {distributeLock.unlock(lockKey, requestId); }// 方案2:使用带返回值的unlock方法,检查释放结果 boolean unlocked = distributeLock.unlock(lockKey, requestId); if (!unlocked) {log.warn("锁释放失败: {}", lockKey); }
3. 锁竞争激烈
- 问题:大量请求竞争同一把锁导致性能下降
- 解决方案:
// 方案1:分段锁设计 int segment = userId % 10; // 将用户分为10段 String lockKey = "user:segment:" + segment;// 方案2:重试机制+指数退避 int maxRetries = 3; for (int i = 0; i < maxRetries; i++) {if (distributeLock.tryLock(lockKey, requestId, 500, 5000)) {try {// 业务逻辑break;} finally {distributeLock.unlock(lockKey, requestId);}}// 指数退避:每次重试等待时间加倍Thread.sleep(100 * (i + 1)); }
六、性能优化建议
-
减少锁持有时间:
// 错误示例:锁内包含IO操作 distributeLock.tryLock(lockKey, ...); try {data = remoteService.getData(); // 远程调用processData(data); } finally {distributeLock.unlock(lockKey, ...); }// 正确示例:仅锁定关键代码 data = remoteService.getData(); // 远程调用 distributeLock.tryLock(lockKey, ...); try {processData(data); // 只锁定数据处理部分 } finally {distributeLock.unlock(lockKey, ...); }
-
使用读写锁(如果适用):
// 自定义读写锁实现 public class ReadWriteDistributeLock {private final DistributeLock readLock;private final DistributeLock writeLock;public ReadWriteDistributeLock(String baseKey) {this.readLock = new DistributeLock(baseKey + ":read");this.writeLock = new DistributeLock(baseKey + ":write");}// 实现读写锁逻辑... }
七、总结
Renren框架的DistributeLock提供了简单易用的分布式锁实现,适用于大多数分布式场景。使用时需注意:
- 正确设置参数:合理设置waitTime和leaseTime
- 确保锁释放:在finally块中调用unlock
- 处理异常情况:考虑锁超时、释放失败等场景
- 优化锁粒度:避免锁范围过大导致性能问题
通过合理使用DistributeLock,可以有效解决分布式环境下的资源竞争问题,保障业务数据的一致性和正确性。