栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

Redisson & RedLock分布式锁

Redisson & RedLock分布式锁

前言

本文章旨在经验分享,欢迎各位大佬指正

应用场景:

1、秒杀
2、抢优惠券
3、接口幂等性校验

总结:抢资源

1、单机锁 --> 到分布式锁

synchronized ()就不再这里做阐述了

1.1、基于opsForValue().setIfAbsent()实现

格式:setnx key value
将key的值设为value,当且仅当key不存在
若给定的key已存在,则setnx不做任何操作
setnx是【set if not exists】(若果不存在,则set)的简写
可用版本 >= 1.0.0

String lockKey = "lockKey";
Boolean result = redisTemplate. opsForValue().setIfAbsent(lockKey, "hello");
if(!result){
return "something exception";
}
// 大量业务代码
...
redisTemplate. delete(lockKey);

return "program End ...";

存在的问题,如果业务代码抛异常了,他还能delete吗?
删除不了,就死锁了

1.1.1 改进:加入try … finally. 无论如何,它最后都会delete
try{
    String lockKey = "lockKey";
    Boolean result = redisTemplate. opsForValue().setIfAbsent(lockKey, "hello");
    if(!result){
    return "something exception";
    }
    // 大量业务代码
    ...
    }
finally{ 
    redisTemplate. delete(lockKey);
}
return "program End ...";

继续看看现在存在的问题 ?
释放锁的问题解决了,但是执行到业务代码宕机了怎么办?finally就没用了,又会出现死锁问题。因为redis里的key始终存在。

1.1.2 改进,设置key的过期时间
try{
    String lockKey = "lockKey";
    Boolean result = redisTemplate. opsForValue().setIfAbsent(lockKey, "hello");
    // 加超时时间
    redisTemplate.expire(lockKey,10,TimeUnit.SECONDS);
    if(!result){
    return "something exception";
    }
    // 大量业务代码
    ...
    }
finally{ 
    redisTemplate. delete(lockKey);
}
return "program End ...";

但是我们这么写,就咩问题了吗?

Boolean result = redisTemplate. opsForValue().setIfAbsent(lockKey, "hello");
    // 会出现的问题
redisTemplate.expire(lockKey,10,TimeUnit.SECONDS);

如果这个key刚刚执行完setIfAbsent(), 结果程序挂了,还没到expire(), 那怎么办??这两行代码有原子性的问题

1.1.3 改进,解决原子性的问题

还别说,redis原生就有这两个合二为一的,在底层,redis会帮我们保证原子性

// Boolean result = redisTemplate. opsForValue().setIfAbsent(lockKey, "hello");
// redisTemplate.expire(lockKey,10,TimeUnit.SECONDS);
redisTemplate. opsForValue().setIfAbsent(lockKey, "hello",10,TimeUnit.SECONDS);

这么写的问题是什么呢??

如果放到超高并发场景那就很可怕了,就不小问题了。
假设过来个请求执行了15秒,结果到10秒这个锁就被清理调了,外面又不断有新的请求进来了… …

如此恶性循环下去会有怎样的后果??高并发不断有请求进来... ... 这样会导致什么问题呢?可能这把锁会永久失效!

1.1.4 针对高并发的问题,该怎么解决??

本质在于我自己的加的锁,结果会被别的线程给删掉,对不对。

接下来我们能想到的办法就是给这个锁加一个唯一标志。把id加到锁里边去。

String uniqueId = UUID.randomUUID.toString() + threadId;

当我们要解锁的时候判断一下,加锁的id是否是我本人加的

String uniqueId = UUID.randomUUID.toString() + threadId;
try{
    String lockKey = "lockKey";
    Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey,uniqueId, "hello",10,TimeUnit.SECONDS);
    
    if(!result){
    return "something exception";
    }
    // 大量业务代码
    ...
    }
finally{ 
    if(uniqueId.equals(redisTemplate.opsForValue().get(lockKey))){
    	redisTemplate. delete(lockKey);
    }
}
return "program End ...";

当执行到这儿,还是有点问题哈,如果程序执行15秒才结束,但是10秒锁才释放。还是会有并发的问题。

时间不是解决问题的方案,还有没有别的思路?
在redis的处理方式上确实有实现方法:在此现况的情景下,通过定时器去扫,分线程每过10秒扫一次,判断一下主线程是否持有锁,key是否还存在,如果存在,把超时时间再设置长一点,跟token续期类似。

2、Redisson

现在市面上都有很多开源框架,来帮我们实现这些问题。而且人家都已经封装好了。


    org.redisson
    redisson
    3.6.5

// 初始化客户端
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    // 注入到Spring中
    @Bean
    public Redisson redisson(){
        // 此为单机模式
        Config  config = new Config();
        // 当然,这儿有很多模式可选择,主从、集群、复制、哨兵 等等 ... ...
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);    
    }
}
// 业务端
@Autowire
private Redisson redisson;

RLock redissonLock = redisson.getLock(lockkey);
try{
    // 执行加锁的操作
    redissonLock.lock()  // 这行代码可以看成 setIfAbsent(lockKey,uniqueId, "hello",10,TimeUnit.SECONDS);
    // 业务代码
    ... ...
    }
finally{
    redissonLock.unlock();
}

以下为源码底层加锁逻辑:lua脚本实现

Redisson就不会有问题了吗??

如果redis是做的主从、哨兵,那么依然有问题

主节点写入了key,他是异步的再把数据同步到slave,只要主节点的key写成功了,马上返回给客户端,告诉客户端你这把锁加成功了,key设置成功,客户端线程1开始做业务逻辑了,这个时候,redis主节点才会往从节点去同步。

也就是说:同步在后

在Redis的master节点上拿到了锁;但是这个加锁的key还没有同步到slave节点;master故障,发生故障转移,slave节点升级为master节点;导致锁丢失。

假设,他刚准备同步给从节点,然后主节点挂了,怎么办?假设要同步的这个从节点被选举为master,高并发场景下新的线程(线程3)又进来,他请求的时候发现新的master他没有key,对于这种问题怎么解决呢?

3、Redis主从架构锁失效问题?

其实用redis去解决相对麻烦,当然也是有方法的。但是redis我们都已经做到极致了此时可以换种思路,比如用zk,他也是key-value结构,不过是树形的,CAP中满足CP,他不会马上告诉你成功(C:表示一致性),牺牲时间做同步。所以不存在这种问题。

怎么选择redis或者zookeeper,对并发要求比较高还是要选择redis(单机可支持10W的QPS),锁失效也只有手动对数据进行补偿,出错的概率是比较小。

4、RedLock实现

redis官方推荐使用 底层实现手段跟zk很类似

4.1、RedLock 简介

在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段。实现高效的分布式锁有三个属性需要考虑:

安全属性:互斥,不管什么时候,只有一个客户端持有锁效率属性A:不会死锁效率属性B:容错,只要大多数redis节点能够正常工作,客户端端都能获取和释放锁。

Redlock是redis官方提出的实现分布式锁管理器的算法。这个算法会比一般的普通方法更加安全可靠。关于这个算法的讨论可以看下官方文档。

超过半数redis节点(没有任何的依赖关系,就是单独节点)加锁成功才算加锁成功

4.2、RedLock的弊端?

性能,原来只要主节点写成功了就行了,现在要很多台机器都加锁成功。都不能100%解决锁失效问题

详细实现可参考 方志朋大佬好文:RedLock的实现

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/711595.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号