分布式锁解决方案-Redission
前言
随着软件系统的不断迭代,目前越来越多的单体应用已经不再满足当前业务的需求,分布式系统越来越普及(一个应用往往部署到很多台不同的机器上)。为了保证在多机部署环境中,不同机器不同的进程之间 业务操作的安全,分布式锁应运而生。
必备条件
为了保证分布式锁的可用,:
互斥性/排他性:任意时刻,只会有一个”人“能持有锁。
不会发生死锁:在”甲“持有锁的期间系统崩溃导致没有主动释放锁,也能保证后续 ”乙“能获取锁。-设置超时时间
加锁和解锁需得同一个”人“:”甲“不能把”乙“加的锁给释放了,隔离性。
容错性:只要大多数Redis节点正常,”我“就能获取或释放锁。
可重入:当前线程持有锁后是否可再次进入
公平性:加锁顺序是否与请求加锁顺序一致,还是随机性
Redission
支持多种模式:单点模式,总从模式,哨兵模式,集群模式。
单点模式举例
// 1.构造redisson实现分布式锁必要的Config
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0);
// 2.构造RedissonClient
RedissonClient redissonClient = Redisson.create(config);
// 3.获取锁对象实例(无法保证是按线程的顺序获取到)
RLock rLock = redissonClient.getLock(lockKey);
try {
/**
*4.尝试获取锁
* waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
*leaseTime 锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
*/
boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);
if (res) {
//成功获得锁,在这里处理业务
}
} catch (Exception e) {
throw new RuntimeException("aquire lock fail");
}finally{
//无论如何, 最后都要解锁
rLock.unlock();
}
Lua脚本 加锁脚本
若锁不存在,则新增锁,并设置锁重入计数为1 设置锁的过期时间
若锁存在,且唯一标识匹配:则表明当前加锁请求为锁重入请求,锁重入计数+1,并再次设置锁过期时间
若锁存在,但唯一标识不匹配:则表明锁是被其他线程占用,当前线程无权解锁其他线程的锁,直接返回锁剩余过期时间
返回nil表明加锁成功,
解锁脚本
若锁不存在,则直接广播解锁消息 返回1
若锁存在,但唯一标识不匹配,表明锁被其他线程占用,返回nil
若锁存在,且标识匹配,则锁重入计数-1
锁重入计数-1后如果为0 表明锁已被释放,直接删除锁 并广播解锁消息,去唤醒哪些争抢过锁而目前处于阻塞状态的线程
锁重入计数-1后还大于- 表明当前线程持有锁还有重入,不能进行删除,可以设置下过期时间
返回1 表明请求触发了解锁;实际上客户端也并不关心解锁请求的返回值。
单机模式如果故障就会导致加锁失败。主从模式的话,加锁就只对一个节点加锁,即使通过 sentinel做了高可用,如果主节点故障 在主从切换时可能导致锁丢失的问题。