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分布式锁》

 

 redis的分布式锁代码:

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

加锁:

《Redis分布式锁》

 

 解锁:

《Redis分布式锁》

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

 

《Redis分布式锁》

 问题:

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

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

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

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

《Redis分布式锁》

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

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

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

C1使用getSet方法

C2也执行了getSet方法

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

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

    原文作者:小虾米的java梦
    原文地址: https://www.cnblogs.com/fengli9998/p/8877122.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞