zoukankan      html  css  js  c++  java
  • Redis分布式锁

    1、synchronized处理并发的缺点?

    (1)速度比较慢,无法做到细粒度的控制。

    (2)只适合单机的情况,不适合集群。

    2、分布式锁的实现方案

    分布式锁一般有三种实现方式:

    (1). 数据库乐观锁;

    (2) 基于Redis的分布式锁;

    (3). 基于ZooKeeper的分布式锁

    3、分布式锁的保障条件

    为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

    (1)互斥性。在任意时刻,只有一个客户端能持有锁。

    (2)不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

    (3)具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

    (4)解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

    4、基于Redis的分布式锁实现(商品秒杀场景的解决方案)

           Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系,redis的SETNX命令可以方便的实现分布式锁。

    1)setNX(SET if Not eXists)将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。

         SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

    返回值:

      设置成功,返回 1 。返回1,则该客户端获得锁,把lock.foo的键值设置为时间值表示该键已被锁定,该客户端最后可以通过DEL lock.foo来释放该锁
      设置失败,返回 0 。返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。
    2)GETSET(先get再set,get旧值,set新值)

          将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

      当 key 存在但不是字符串类型时,返回一个错误。

    返回值:

      返回给定 key 的旧值。
      当 key 没有旧值时,也即是, key 不存在时,返回 nil 。

     描述:

      set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。

      其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。

    其实setnx本身没有设置过期时间的功能,但是我们设置的value值默认包含了过期时间:即当前时间+过期时间,比如当前时间9:42+过期时间10分钟,那么后面进程访问的时候,比如时间是10点钟,那么值钱的时间肯定过期了。

    面试题:redis分布式锁如何实现的:

    主要利用setnx和getset命令来实现的。

    setnx主要是利用保证一个客户端可以获取到该锁即保证互斥性。

    我们会在setnx设置key的value值设置一个时间,这个时间是我们可以设置为当前时间+过期时间,即总共线程可以保留该锁的时间。

    如果该时间过期,则另外有线程访问的时候,则利用getset方法来进行获取原来的时间和设置新的value值,判断下是否可以获取该锁。如果获取了该锁则下一个线程进来的时候就不可以获取该锁了。这样避免了死锁同时也可以防止多个线程同时获取锁。

    主要redis的SETNX和GETSET这两个命令。

    主要有两步操作:加锁和解锁

    初步计划在此段代码添加加锁和解锁的功能:

     redis的分布式锁代码:

    加锁和解锁是在一个类中:

    加锁:

     解锁:

    处理并发的加锁和解锁后的业务代码:

     问题:

    1、为什么不直接使用expire设置超时时间,而将时间的毫秒数其作为value放在redis中?

    Redis Expire 命令用于设置 key 的过期时间。key 过期后将不再可用。

    Expire   KEY_NAME   TIME_IN_SECONDS  例如:EXPIRE runooobkey 60,设置60s后过期。

    答案:假如在setnx后,redis崩溃了,expire就没有执行,结果就是死锁了,锁永远不会超时。
    2、为什么前面的锁已经超时了,还要用getSet去设置新的时间戳的时间获取旧的值,然后和外面的判断超时时间的时间戳比较呢?

      因为是分布式的环境下,可以在前一个锁失效的时候,有两个进程进入到锁超时的判断。如:

    C0超时了,还持有锁,C1/C2同时请求进入了方法里面

    C1/C2获取到了C0的超时时间

    C1使用getSet方法

    C2也执行了getSet方法

    假如我们不加 oldValueStr.equals(currentValueStr) 的判断,将会C1/C2都将获得锁,加了之后,能保证C1和C2只能一个能获得锁,一个只能继续等待。

    注意:这里可能导致超时时间不是其原本的超时时间,C1的超时时间可能被C2覆盖了,但是他们相差的毫秒及其小,这里忽略了。

  • 相关阅读:
    面试常见问题汇总
    java static变量及函数
    java自定义注解及其信息提取
    testNG 注释实例
    让我欲罢不能的node.js
    利用html 5 websocket做个山寨版web聊天室(手写C#服务器)
    html5 Web Workers
    html5 postMessage解决跨域、跨窗口消息传递
    C# socket编程实践——支持广播的简单socket服务器
    简单理解Socket
  • 原文地址:https://www.cnblogs.com/fengli9998/p/8877122.html
Copyright © 2011-2022 走看看