Redis分布式锁对消息保证原子性
近段时间做APP的活动预热业务和活动抽奖业务时,涉及Redis对List类型的活动消息数据的插入和取出时,发现如果不保证数据的原子性,会导致数据紊乱,导致用户对抽奖的效果不佳(粘性用户的抽奖效果无法保证,奖品被粘性不高的用户获取,违背活动的意图)
解决办法
redi分布式锁
分布式锁特点
- 互斥性:同一时间只能有一个节点获取到锁,其他节点需要等待获取到锁的节点释放锁才能获取到锁,一般是通过阻塞和自旋两种方式
- 安全性:只能释放自己的锁不能误删别人的锁
- 死锁:在节点宕机时最容易出现锁没有释放是问题,然后出现死锁,所以要做锁的过期
- 容错:当redis宕机,客户端仍然可以释放锁
- 可重入:获取锁失败可以尝试获取锁
我主要是解决当活动开始时,大量的用户在redis缓存中取出抽奖消息进行比对,因为没有加锁,比对后没有中奖的用户将令牌从左侧插入,导致令牌桶顺序淆乱,从而影响用户用户的抽奖体验。
纯分布式锁方式:
public class RedissonManager {
private static Config config = new Config();
//声明redisso对象
private static Redisson redisson = null;
//实例化redisson
static{
config.useClusterServers()
// 集群状态扫描间隔时间,单位是毫秒
.setScanInterval(2000)
//cluster方式至少6个节点(3主3从,3主做sharding,3从用来保证主宕机后可以高可用)
.addNodeAddress("redis://127.0.0.1:6379" )
.addNodeAddress("redis://127.0.0.1:6380")
.addNodeAddress("redis://127.0.0.1:6381")
.addNodeAddress("redis://127.0.0.1:6382")
.addNodeAddress("redis://127.0.0.1:6383")
.addNodeAddress("redis://127.0.0.1:6384");
//得到redisson对象
redisson = (Redisson) Redisson.create(config);
}
//获取redisson对象的方法
public static Redisson getRedisson(){
return redisson;
}
}
// 获取锁
RLock rLock = redissonClient.getLock("lock_stock");
// 加锁并设置有效期
rLock.lock(""RDL",200")
//释放锁
rLock.unlock("RDL");
redisson官方文档对分布式锁的解释:
- Redisson加锁自动过期时间30秒,监控锁的看门狗发现业务没有执行完,会自动进行锁的续期,这样的好处是防止在程序执行期间锁自动过期被删除的问题
- 当业务完成不再给锁续期,即使没有手动释放锁,锁的过期时间到了也会在自动释放锁
红锁
Redis常用的方式有单节点、主从模式、哨兵模式、集群模式,在后三种模式中可能会出现 ,异步数据丢失,脑裂问题,Redis官方提供了解决方RedLock,RedLock是基于redis实现的分布式
- 红锁的特点
- 容错性:只要大多数的节点的redis实例正常运行就能对外提供服务,释放锁
- 互斥性:只要有一个客户端能获取锁,即使发生客户端宕机,也不会发生死锁
基于Redis的Redisson红锁RedissonRedLock对象实现RedLock介绍的加锁算法,该对象可以用来将多个RLock对象关联成一个红锁,每个RLock对象实例可以来自不同发Redisson实例
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRed;ock lock = new RedissonRedLock(lock1,lock2,lock3);
// 同时加锁lock1,lock2,lock3
// 红锁在部分节点成功就算成功
(1)
lock.lock();
...
lock.unloxk();
(2)通过leaseTime的参数来指定加锁时间,超过时间自动解锁
lock.lock(10,TimeUnit.SECNDES);
// 为加锁等待100秒针,并在加锁成功10秒自动解开
boolean res = lock.trylock(100,10,TimeUnit.SECNDES);
...
lock.unlock();