zoukankan      html  css  js  c++  java
  • 利用redis实现分布式锁知识点总结及相关改进

    利用redis实现分布式锁知识点总结及相关改进

    先上原文,本文只为总结及对相关内容的质疑并提出若干意见,原文内容更详细https://www.cnblogs.com/linjiqin/p/8003838.html

    @frameStart@

    @frameTitle@最新修改@frameTitle@

    老版问题:lock时如果只用jedis.set(String key, String value, String nxxx, String expx, int time)方法存在若干问题:1.不支持重入锁,2.且超时时间的设置也是一个问题

    解决方案:1. 锁的结构用hash,因为string形式无法支持可重入;

                     2.  使用了hash就不能再用jedis.set(String key, String value, String nxxx, String expx, int time)这个方法了,所以取锁也要使用eval表达式。

    hash数据结构:

    1 bizKey:{   // 某个业务锁
    2   clientId:“”,// 业务id,唯一;
    3       state:1, //取锁次数,用来实现可重入
    4       // 超时时间直接用expire来表示,用字段的话不安全。
    5 }
    View Code

    伪代码如下:

       加锁基本思路: 判断锁是否存在,如果不存在,直接加锁;如果存在,则判断client是否与当前业务id一致,如果一致,加锁成功,state+1,否则提示加锁失败

       去锁基本思路:判断锁是否存在,如果不存在,直接返回成功;如果client不一致,返回失败;now = state-1,如果now=0,直接删掉锁。返回成功。

      最后,关于超时时间的问题:如果不设超时的话,业务异常导致来不及解锁时会导致数据一直存在。如果设的话,设的太小了,可能业务处理时间太长,别的线程在锁过期后会取到锁,此方式最不可取。如果设的时间太长,与不设同义。

                 方案:?

    关于锁超时的俩种言论,参考一下:

    一种:

    使用 WATCH/MULTI/EXEC ,watch能保证 MULTI 和 EXEC 之间的命令只有在watch的值没有变化才执行成功,见官方文档https://redis.io/topics/trans...

    所以一般这个锁的值要在整个系统保持唯一。

    另一种:

    锁超时被释放不是很正常吗。。 因为我没有用过redis,所以对于楼上说的超时如何避免我不知道。。

    但是从分布式系统的角度来分析这个问题,锁其实就等于租约,谁从redis得到了租约,谁就是集群的leader,执行一些follower不能做的操作,

    但是呢,leader总是会由于种种问题(网络、gc)无法及时续租,导致超时,这时候另外一个follower进而得到锁,导致集群双主。

    所以问题的性质就变成了分布式情况下如何避免多主(或脑裂)。常用的做法就是fencing机制,如kafka的epoch。楼主可以试下

    @frameEnd@

     1.redis基础知识点:

      1.1 多线程环境下,多条命令不具备原子性,尽量使用单命令或eval表达式;

      1.2 redis天然单线程,化异步为同步,处理效率极高;且一般内网操作,速度比较快;支持多机部署,用来实现锁机制十分合适;

    2. 针对锁的基本特性,利用redis的特点解决之
      2.1互斥性:set一个key NX 观看返回值即可
      2.2 防死锁:设置key的过期时间即可
      2.3 容错性:使用Redisson实现分布式锁
      2.4 排他性:set key value时,value设置一个独一无二的值如uuid
      针对以上几个特性,
      **获取锁**:
        使用jedis.set(key, value_uuid, NX, PX, 10000);即可实现取锁;
      **释放锁**:
        使用eval表达式即可:
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
      **存在问题**

        问题1:锁超时时间大小不好设置,因为业务处理的时间大小不确定,可能会很长,长到超时时间满足不了。

        问题2:关于拿锁是重试还是排队拿锁?可以自己参考AQS类,锁的本质就是一个状态的获得,AQS类是JVM中state变量的获取,而基于redis的分布式锁则是内存中变量的获取,redis已经天然的变异步为同步了,所以只能排队拿锁,如果恰好每次重试的过程中都是被其它客户端持有锁的,极有可能造成线程长时间拿不到锁(即使你忙循环调用redis,redis也不会单独优先处理你的)

      2.5 可重入:不支持,重置有效期是错误的方式;另外value_uuid被设定成了固定值(用来排他),无法像AQS的state字段那样增长或者减少。

      2.6 总结:排他和可重入似乎不能同时实现。而且实现了可重入,不支持排他,这个锁机制也是不可用的。所以,放弃可重入

  • 相关阅读:
    ArrayList和Vector的比较
    ExtJs与jQuery的比较
    列表类型的内建函数
    序列类型函数
    SQL函数
    HTTP状态码
    序列切片
    数值运算
    数值类型转换
    Python中is和==的区别
  • 原文地址:https://www.cnblogs.com/po-shi/p/10370486.html
Copyright © 2011-2022 走看看