Redisson
是一个在Redis
的基础上实现的驻内存数据网格。它不仅提供了一系列的分布式的常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
官网地址: https://redisson.org
GitHub
地址: https://github.com/redisson/redisson
分布式锁
分布式锁应用场景:
提升处理效率:
- 避免重复任务的执行,减少系统资源的浪费(例如幂等场景)。
保障数据一致性:
- 在多个微服务并发访问时,避免出现访问数据不一致的情况,造成数据丢失更新等情况。
以下是不同客户端并发访问时的场景:

分布式锁原理:

可重入实现:
Redisson
实现可重入采用Hash
的结构,在Key
的位置记录锁的名称。
Field
的位置记录线程标识,Value
的位置则记录锁的重入次数。加锁时,如果线程标识是自己,则锁的重入次数加 1,并重置锁的有效期。
释放锁时,重入次数减 1,并判断是否为 0,如果为 0 直接删除,否则重置锁的有效期。

重试与WatchDog
流程:

可重入锁(Reentrant Lock)
可重入就是说同一个线程可以多次拿到同一把锁,不会把自己锁死。
这在方法嵌套调用的时候特别有用,比如方法A调用方法B,两个方法都要加锁,用可重入锁就不会出问题。
这种锁适合什么场景呢?
- 就是普通的分布式锁需求,保证同一时间只有一个节点在执行某段代码。
比如防止重复下单、避免重复处理同一个任务之类的。
RLock lock = redissonClient.getLock("myLock");
lock.lock();
try {
// 处理业务
// 如果这里又调用了需要加锁的方法,不会死锁
lock.lock();
try {
// 嵌套的业务处理
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
公平锁(Fair Lock)
普通锁谁抢到算谁的,可能会出现有些请求一直抢不到锁的情况。
公平锁就不一样了,严格按照申请的顺序来分配锁,不会有人饿死。
什么时候用?
- 主要是对顺序有要求的场景。
- 比如订单处理,你总不能让后提交的订单先处理吧,或者一些排队系统,得保证公平性。
不过公平锁的性能会差一些,毕竟要维护一个队列。
- 所以如果对顺序没那么严格的要求,还是用普通锁比较好。
RLock fairLock = redissonClient.getFairLock("fairLock");
fairLock.lock();
try {
// 业务处理
} finally {
fairLock.unlock();
}
联锁(MultiLock)
一次锁多个资源。
模拟个转账的场景:
从账户A转钱到账户B,你得同时锁住这两个账户吧?
如果分开锁,可能会出现死锁,线程1锁了A等B,线程2锁了B等A,谁都动不了。
联锁的策略是要么全部锁成功,要么全部失败,不会出现锁了一半的情况。
- 这样就避免了死锁问题。
除了转账,还有什么场景会用到?
- 比如库存扣减时需要同时锁定多个商品、或者需要同时更新多个相关的数据表等等。
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);
multiLock.lock();
try {
// 这里可以安全地操作多个资源
} finally {
multiLock.unlock();
}
红锁(RedLock)
在多个独立的Redis实例上同时加锁,只有大多数实例都加锁成功了,才算获取锁成功。
- 这样即使某个Redis实例挂了,锁依然有效。
什么时候用红锁?
- 主要是对可靠性要求特别高的场景。
- 比如金融系统的关键操作,不能因为Redis挂了就出问题。
不过代价也很明显,你得准备多个Redis实例,成本和复杂度都上去了。
所以大部分情况下,普通的分布式锁就够用了,除非你的系统对可靠性要求极高。
RLock lock1 = redissonClient.getLock("lock");
RLock lock2 = redissonClient2.getLock("lock");
RLock lock3 = redissonClient3.getLock("lock");
RLock redLock = redissonClient.getRedLock(lock1, lock2, lock3);
redLock.lock();
try {
// 业务处理
} finally {
redLock.unlock();
}
读写锁(ReadWrite Lock)
多个线程可以同时拿读锁,大家一起读没问题。
但是写锁是排他的,写的时候别人不能读,读的时候也不能写。
- 这在读多写少的场景下能大大提升性能。
典型场景就是缓存更新。
大部分时候都是在读缓存,偶尔需要更新一下。
用读写锁的话,读操作可以并发进行,只有更新的时候才需要独占。
还有配置文件读取、商品信息查询这些场景,都很适合用读写锁。
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
// 读操作
RLock readLock = rwLock.readLock();
readLock.lock();
try {
// 读取数据
} finally {
readLock.unlock();
}
// 写操作
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
// 写入数据
} finally {
writeLock.unlock();
}
信号量(Semaphore)
信号量其实并不是严格意义上的锁,但经常和锁一起用,主要用来限流。
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
semaphore.trySetPermits(3); // 设置3个许可证
semaphore.acquire(); // 拿一个许可证
try {
// 处理业务
} finally {
semaphore.release(); // 还回去
}