Redisson 分布式锁
Redisson 是Redis官方推荐的一个企业级开源Redis Client。提供了分布式锁的支持。
1 基本用法
maven依赖:
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.24.3</version>
</dependency>
public void buy(Integer num) throws InterruptedException {String name = Thread.currentThread().getName();RLock lock = redissonClient.getLock(goods.getName());// 尝试获取锁 2 为获取锁最长等待时间,30为持有锁最长时间boolean isLocked = lock.tryLock(2, 30, TimeUnit.SECONDS);if (isLocked) {if (goods.getCount() >= num) {System.out.println(name + ",购买:" + num);Thread.sleep(4000);goods.setCount(goods.getCount() - num);System.out.println(goods);} else {System.out.println(name + "商品库存不足");}} else {System.out.println("获取锁失败");}if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}}
1.1 锁类型
Redisson 提供了多种分布式锁实现,有以下主要的锁类型:
- 可重入锁。支持锁重入,同一个线程可以多次获得同一把锁。
- 公平锁,按照请求的顺序获取锁,避免线程饥饿问题。
- 读写锁,读写分离的锁。
- 信号量,限制同时访问资源的线程数量。
- 联锁,将多个锁对象关联为一个锁,所有锁都成功获取才算成功,否则失败。用于对多资源原子性加锁。
- 红锁,通过在多个独立的Redis节点上获取锁来提高可靠性。有以下特性:a) 多数派原则:在N个节点上获取锁,成功获取(N/2 + 1)个才算成功。b)高可用性:容忍部分节点故障。
红锁的使用场景有:
- 高可用性要求:金融交易、支付系统等。
- 关键业务:不能容忍锁失败的业务场景。
2 Redisson 分布式锁原理
Redisson 使用Lua脚本来保证加锁操作的原子性。
采用了Redis的Hash结构存储锁信息。key 是锁名称,entry-name是客户端ID+线程ID。entry-value 是锁重入次数。通过判断key是否存在来确定是否获得锁。
- 当锁被其他客户端持有时,当前客户端的操作:a)获取锁的剩余生存时间。b)通过自旋等方式等待。c)通过Redis的pub/sub订阅解锁消息通道。
- 解锁通知机制:a)锁释放时会通过pub/sub发布消息到订阅频道。b)等待中的客户端收到通知后会立即尝试获取锁,避免无效轮询。
2.1 缺陷
Redisson 虽然提供了丰富的锁类型和强大的功能,但在实际使用中仍然存在一些缺陷和潜在问题。
1 主从架构下锁失效问题
当主节点解锁后未同步到从节点就宕机,从节点提升为主节点后,可能导致多个客户端同时持有锁。
解决:使用红锁或使用Zookeeper替代(会牺牲性能)。
2 看门狗机制导致的死锁问题
看门狗机制会定期(默认10s)续期锁(重置为30s),如果程序异常终止或网络问题导致锁未正确释放,锁会被无限续期。
解决:a)合理设置锁超时时间,避免使用默认的看门狗机制。b)确保锁释放操作放在finally块。
finally {// 正确释放锁的方式if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}
}
3 性能问题
每次获取锁都需要Redis网络通信。高并发下Redis可能成为瓶颈,锁竞争激烈时性能下降明显。
3 实战
通过分层命名设计和锁粒度控制实现锁的精准控制,同时避免命名冲突。
分层命名设计:[环境]:[模块]:[业务类型]:[操作类型]:[资源标识]
环境:prod/dev等
模块:order/goods等
业务类型:/check/deduct等
操作类型:lock/read/write
资源标识:id
1 、资源标识细化。
单资源锁:针对单一资源。
多资源锁:针对多个资源。
通配符锁:针对一类资源。需谨慎使用以避免锁范围过大。
2、操作类型区分。
写操作:使用write或deduct等明确标识。
读操作:使用read标识,并结合读锁实现读写分离,提升并发性能。
3.1 读写锁分离
统一资源锁,例如对商品库存的修改、查询及其他有关库存的业务使用同一个锁名,这样虽然能绝对互斥,但是并发性能差。
读写锁分离,多个读操作可并行,写操作独占资源,读写互斥。
通过一个资源注册中心类来规范锁的命名。
Redission的获取锁及释放锁操作,可以提取为一个注解,通过aop来对使用改注解的方法执行对应操作。但是这样锁的粒度是整个方法了。