- 浅谈分布式锁和以Redisson做为解决方案
- 1.为什么需要锁,Java锁的种类有哪些?
- 2.本地锁和分布式锁
- 3.传统的Redis分布式锁
- 4.什么是Redisson
- 5.引入Maven依赖和配置
- 6.引入Redisson的优势
- 7.锁续期,看门狗WatchDog机制
在多线程竞争的情况下,保证数据的一致性,避免资源冲突。
不同维度分类:https://www.jianshu.com/p/b8008eeac3e8
今天的主角,可重入锁(所有的锁都应该设计为可重入锁,避免嵌套调用后死锁问题):https://segmentfault.com/a/1190000039266220
2.本地锁和分布式锁本地锁:
假设分布式节点是8个,则会有8个本地锁,最多有8个线程同时抢占成功,打到数据库的请求最多为8;
其实本地锁在分布式系统中也能降低请求量,但是如果想要最多只有一个线程访问,则必须要分布式锁。
public Map> getCatalogJsonFromDbWithLocalLock() { //如果缓存中有就用缓存的 Map > catalogJson = (Map >) cache.get("catalogJson"); if (cache.get("catalogJson") == null) { //调用业务... //返回数据又放入缓存... } //只要是同一把锁,就能锁住这个锁的所有线程 //synchronized (this):SpringBoot所有的组件在容器中都是单例的。 //本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有,必须使用分布式锁 synchronized (this) { //得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询 return getDataFromDb(); } }
分布式锁:
官网:http://www.redis.cn/documentation.html
注意:http://www.redis.cn/commands/set.html
演进阶段一:只使用setnx指令,比如1万线程过来获取锁,确实只有一个setnx成功,但是如果在删除锁的时候出现宕机,则造成死锁。
演进阶段二:setnx过后,设置过期时间
演进阶段三:使用 setnxex,原子性 *** 作,在设置锁的时候,一步到位
演进阶段四:为了避免删除别人的锁,需要给每个锁一个唯一的uuid,在删除锁前,判断一下是不是自己的锁。
最终,演进阶段五:在完成了加锁时需要设置过期时间,删锁需要加入判断唯一性和原子性之外,还可能有什么新的问题?
public Map4.什么是Redisson> getCatalogJsonFromDbWithRedisLock() { //1、占分布式锁。去redis占坑;设置过期时间必须和加锁是同步的,保证原子性(避免死锁) String uuid = UUID.randomUUID().toString(); Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS); if (Boolean.TRUE.equals(lock)) { System.out.println("获取分布式锁成功..."); Map > dataFromDb; try { //加锁成功...执行业务 dataFromDb = getDataFromDb(); } finally { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; //删除锁 stringRedisTemplate.execute(new DefaultRedisscript (script, Long.class), Arrays.asList("lock"), uuid); } //先去redis查询下保证当前的锁是自己的 //获取值对比,对比成功删除=原子性 lua脚本解锁 // String lockValue = stringRedisTemplate.opsForValue().get("lock"); // if (uuid.equals(lockValue)) { // //删除我自己的锁 // stringRedisTemplate.delete("lock"); // } return dataFromDb; } else { System.out.println("获取分布式锁失败...等待重试..."); //加锁失败...重试机制 //休眠一百毫秒 try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //自旋的方式 return getCatalogJsonFromDbWithRedisLock(); } }
官网:https://redisson.org/
Github地址:https://github.com/redisson/redisson
使用文档:https://github.com/redisson/redisson/wiki/Table-of-Content
https://redis.io/topics/distlock
推荐网站:https://www.bookstack.cn/
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。
Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
5.引入Maven依赖和配置org.redisson redisson3.16.4
编写RedissonClient 客户端的config文件,链接redis
com/xunqi/gulimall/product/config/MyRedissonConfig.java:19
// 默认连接地址 127.0.0.1:6379 RedissonClient redisson = Redisson.create(); Config config = new Config(); config.useSingleServer().setAddress("redis//127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config);6.引入Redisson的优势
public Map> getCatalogJsonFromDbWithRedissonLock() { //占分布式锁。去redis占坑 //(锁的粒度,越细越快:具体缓存的是某个数据,11号商品) product-11-lock RLock catalogJsonLock = redissonClient.getLock("catalogJson-lock"); Map > dataFromDb; try { catalogJsonLock.lock(); //加锁成功...执行业务 dataFromDb = getDataFromDb(); } finally { //业务结束,释放锁 catalogJsonLock.unlock(); } return dataFromDb; }
-
Redisson本身就是redis官方提供的,非常安全,没有侵入性;
-
编写的代码非常简洁易懂,便于使用;
-
Api不需要记,上手很快无缝切换,和 Java里 JUC自带的Lock用起来一样。
-
且具备各种锁的分布式解决方案,不然如要用其他种类锁则手动写太麻烦;
-
解决了上述redis使用setnx的一切缺点,并且引入了新机制,锁的自动续期;
7.锁续期,看门狗WatchDog机制部分源码和原理:https://www.jianshu.com/p/47fd7f86c848
假设解锁代码没运行,Redisson会不会死锁?
@ResponseBody @GetMapping(value = "/hello") public String hello() { //1、获取一把锁,只要锁的名字一样,就是同一把锁 RLock myLock = redisson.getLock("my-lock"); //2、加锁 //阻塞式等待。默认加的锁都是30s myLock.lock(); try { System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId()); try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception ex) { ex.printStackTrace(); } finally { //3、解锁 假设解锁代码没有运行,Redisson会不会出现死锁 System.out.println("释放锁..." + Thread.currentThread().getId()); myLock.unlock(); } return "helloWorld!"; }
Redisson 解决了两个问题:
1)、锁的自动续期,如果业务超长,运行期间自动锁上新的30s。不用担心业务时间长,锁自动过期被删掉 2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题
如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。
为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)