浅谈分布式锁和引入Redisson

浅谈分布式锁和引入Redisson,第1张

浅谈分布式锁和引入Redisson

文章目录
  • 浅谈分布式锁和以Redisson做为解决方案
    • 1.为什么需要锁,Java锁的种类有哪些?
    • 2.本地锁和分布式锁
    • 3.传统的Redis分布式锁
    • 4.什么是Redisson
    • 5.引入Maven依赖和配置
    • 6.引入Redisson的优势
    • 7.锁续期,看门狗WatchDog机制

浅谈分布式锁和以Redisson做为解决方案 1.为什么需要锁,Java锁的种类有哪些?

在多线程竞争的情况下,保证数据的一致性,避免资源冲突。

不同维度分类: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();
  }
}

分布式锁:

3.传统的Redis分布式锁

官网:http://www.redis.cn/documentation.html

注意:http://www.redis.cn/commands/set.html

演进阶段一:只使用setnx指令,比如1万线程过来获取锁,确实只有一个setnx成功,但是如果在删除锁的时候出现宕机,则造成死锁。

演进阶段二:setnx过后,设置过期时间

演进阶段三:使用 setnxex,原子性 *** 作,在设置锁的时候,一步到位

演进阶段四:为了避免删除别人的锁,需要给每个锁一个唯一的uuid,在删除锁前,判断一下是不是自己的锁。

最终,演进阶段五:在完成了加锁时需要设置过期时间,删锁需要加入判断唯一性和原子性之外,还可能有什么新的问题?

public Map> 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();
  }
}
4.什么是Redisson

官网: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
redisson
3.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;
}
  1. Redisson本身就是redis官方提供的,非常安全,没有侵入性;

  2. 编写的代码非常简洁易懂,便于使用;

  3. Api不需要记,上手很快无缝切换,和 Java里 JUC自带的Lock用起来一样。

  4. 且具备各种锁的分布式解决方案,不然如要用其他种类锁则手动写太麻烦;

  5. 解决了上述redis使用setnx的一切缺点,并且引入了新机制,锁的自动续期

部分源码和原理:https://www.jianshu.com/p/47fd7f86c848

7.锁续期,看门狗WatchDog机制

假设解锁代码没运行,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来另行指定。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5611941.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存