Redission基础
1.功能
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供
了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁
(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。
支持 Redis 单节点(single)模式、哨兵(sentinel)模式、主从(Master/Slave)模式以及集群
(Redis Cluster)模式
程序接口调用方式采用异步执行和异步流执行两种方式
数据序列化,Redisson 的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在
Redis 里的读取和存储
单个集合数据分片,在集群模式下,Redisson 为单个 Redis 集合类型提供了自动分片的功能
提供多种分布式对象,如:Object Bucket,Bitset,AtomicLong,Bloom Filter 和 HyperLogLog
等
提供丰富的分布式集合,如:Map,Multimap,Set,SortedSet,List,Deque,Queue 等
分布式锁和同步器的实现,可重入锁(Reentrant Lock),公平锁(Fair Lock),联锁
(MultiLock),红锁(Red Lock),信号量(Semaphonre),可过期性信号锁
(PermitExpirableSemaphore)等
提供先进的分布式服务,如分布式远程服务(Remote Service),分布式实时对象(Live
Object)服务,分布式执行服务(Executor Service),分布式调度任务服务(Schedule
Service)和分布式映射归纳服务(MapReduce)
2.RLock原理
// 设置锁定资源名称
RLock disLock = redissonClient.getLock("DISLOCK");
//尝试获取分布式锁
boolean isLock= disLock.tryLock(500, 15000, TimeUnit.MILLISECONDS);
if (isLock) {
try {
//TODO if get lock success, do something;
Thread.sleep(15000);
} catch (Exception e) {
} finally {
// 无论如何, 最后都要解锁
disLock.unlock();
}
}
1.对应redis的hash结构的key:锁名 field:线程标识(如 UUID:threadId)就是UUID+threadId,hash结构的value就是重入值
2.使用 Lua 脚本,发送给redis 保证加锁和解锁的原子性。
SET key value NX PX timeout
NX表示“只有当key不存在时才设置”,用于获取锁。PX设置过期时间,防止死锁。
3.每次重入时,重入次数 +1,释放锁时次数 -1,直到为 0 才真正释放锁
4.非公平锁
2.1 主从模式
存在主从同步延迟导致的锁丢失风险。
例如主节点加锁后宕机,锁未同步到从节点,从节点提升为主节点后锁丢失。
解决方案:
使用 Redlock 算法(Redisson 提供 RedissonRedLock)。
或使用 Redis 集群模式 + 多数派机制来提高一致性。
2.2 集群模式
在集群模式下,锁操作会路由到对应的 slot 上。
Redisson 保证锁操作的原子性,即使在集群环境下也能正常工作。
可通过 RedissonRedLock 在多个独立 Redis 实例上实现更可靠的分布式锁。
3.WatchDog 机制
后台线程,默认有效期是30秒,会每隔10秒检查一下,如果还持有锁key,那么就会不断的延长锁key的生存时间
1.watchDog 只有在未显示指定leaseTime过期时间时才会生效。
2.lockWatchdog Timeout设定的时间不要太小 ,比如我之前设置的是 100毫秒,由于网络直接导致加锁完后,watchdog去延期时,这个key在redis中已经被删除了。
3.watchdog 会每 lockWatchdogTimeout/3时间,去延时。
4.watchdog 通过 类似netty的 Future功能来实现异步延时
5.watchdog 最终还是通过 lua脚本来进行延时
4.公平锁(Fair Lock)
在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。
RLock fairLock = redisson.getFairLock("anyLock");
公平锁通过 Redis 队列维护请求顺序,按 FIFO 原则获取锁。
5.联锁(MultiLock)
以将多个RLock对象关联为一个联锁,每个RLock对象实例可以
来自于不同的Redisson实例。
RLock lock1 = redisson1.getLock("lock1");
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3, 所有的锁都上锁成功才算成功。
lock.lock();
6.红锁(RedLock)
不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,n / 2 + 1,必须在大多数
redis节点上都成功创建锁,才能算这个整体的RedLock加锁成功,避免说仅仅在一个redis实例上
加锁而带来的问题。
-
特性
互斥性:在任何时候,只能有一个客户端能够持有锁;避免死锁:
当客户端拿到锁后,即使发生了网络分区或者客户端宕机,也不会发生死锁;(利用key的存活时间)
容错性:只要多数节点的redis实例正常运行,就能够对外提供服务,加锁或者释放锁; -
加锁步骤
- 获取当前时间戳,单位是毫秒;
- 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒;
- 尝试在大多数节点上建立一个锁,比如 5 个节点就要求是 3 个节点 n / 2 + 1;
- 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了;
- 要是锁建立失败了,那么就依次之前建立过的锁删除;
- 只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。
将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。
RLock lock1 = redisson1.getLock("lock1");
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。
lock.lock();
7.读写锁(ReadWriteLock)
同时还支持自动过期解锁。该对象允许同时有多个读
取锁,但是最多只能有一个写入锁。
RReadWriteLock rwlock = redisson.getLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();
// 支持过期解锁功能
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
8.信号量(Semaphore)
RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//或
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
//或
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//或
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
//或
semaphore.releaseAsync();
9.可过期性信号量(PermitExpirableSemaphore)
在RSemaphore对象的基础上,为每个
信号增加了一个过期时间。每个信号可以通过独立的ID来辨识,释放时只能通过提交这个ID才能释放。
RPermitExpirableSemaphore semaphore =
redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();
// 获取一个信号,有效期只有2秒钟。
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
10.闭锁/倒数闩(CountDownLatch)
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();
// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
11.锁性能优化
设置合理的 leaseTime,避免锁持有时间过长。
使用 tryLock 带超时机制,避免无限等待。
合理使用 WatchDog 机制自动续期,但要确保业务能正常结束。
保证加锁和解锁在同一线程中,避免跨线程操作。
尽量减少锁的持有时间。
避免在锁内执行耗时操作。
根据业务需求选择公平锁或非公平锁。
在高并发场景下,考虑分段锁或使用 Redlock 提高可靠性。
12. 与 Zookeeper相比
- RLock: 性能高(基于内存),依赖主从同步ap,高并发、低一致性要求
- zookeeper: 性能一般(基于 ZAB 协议),强一致性,强一致性要求
13.分段锁Segment Lock
将资源分成多个段(Segment),每个段使用独立的锁,从而减少锁竞争,提高并发性能。
降低锁粒度:将一个大锁拆分为多个小锁
提高并发度:不同段的数据可以并发访问
固定数量的锁,如16个,业务上id会有很多,通过hash将无限的key映射到有限的锁上,如果一个id一个锁,内存很高
@Service
public class ComplexPointsService {
@Autowired
private RedissonClient redissonClient;
/**
* 当需要同时锁定多个用户时使用
*/
public void transferPoints(String fromUserId, String toUserId, int points) {
// 为两个用户分别获取分段锁
int fromSegment = Math.abs(fromUserId.hashCode()) % 32;
int toSegment = Math.abs(toUserId.hashCode()) % 32;
RLock fromLock = redissonClient.getLock("points_lock:segment_" + fromSegment);
RLock toLock = redissonClient.getLock("points_lock:segment_" + toSegment);
// 创建多重锁,确保两个操作原子性
RLock multiLock = redissonClient.getMultiLock(fromLock, toLock);
try {
multiLock.lock(15, TimeUnit.SECONDS);
// 执行转账逻辑
doTransferPoints(fromUserId, toUserId, points);
} finally {
multiLock.unlock();
}
}
private void doTransferPoints(String fromUserId, String toUserId, int points) {
// 扣减发送方积分
String fromUserKey = "user_points:" + fromUserId;
RAtomicLong fromAtomic = redissonClient.getAtomicLong(fromUserKey);
fromAtomic.addAndGet(-points);
// 增加接收方积分
String toUserKey = "user_points:" + toUserId;
RAtomicLong toAtomic = redissonClient.getAtomicLong(toUserKey);
toAtomic.addAndGet(points);
System.out.println("转账完成: " + fromUserId + " -> " + toUserId + ", 积分: " + points);
}
}