redis分布式锁及会出现的问题解决_Redis

来源:脚本之家  责任编辑:小易  

从redis获取2113值N,对数值N进行边界检查,自加1,然后N写回5261redis中。 这种应用场景很4102常见,像秒杀,1653全局递增ID、IP访问限制等。以IP访问限制来说,恶意攻击者可能发起无限次访问,并发量比较大,分布式环境下对N的边界检查就不可靠,因为从redis读的N可能已经是脏数据。传统的加锁的做法(如java的synchronized和Lock)也没用,因为这是分布式环境,这个同步问题的救火队员也束手无策。在这危急存亡之秋,分布式锁终于有用武之地了www.zgxue.com防采集请勿采集本网。

一、redis实现分布式锁的主要原理:

Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists。这系列的命令非常有用,这里讲使用SETNX来实现分布式锁。 用SETNX实现分布式锁 利用SETNX非常简单地实现分布式锁。例如:某客户端要

1.加锁

有效的,不过如果使用redis,他内部是单线程实现,大部分操作不加锁,也不会有并发问题,是安全的。

最简单的方法是使用setnx命令。key是锁的唯一标识,按业务来决定命名。比如想要给一种商品的秒杀活动加锁,可以给key命名为 “lock_sale_商品ID” 。而value设置成什么呢?我们可以姑且设置成1。加锁的伪代码如下:

1. 不能重入 2. 没有本地锁,并发性能会比较差,不使用用在并发争锁较多的场景下。本地锁非自旋 3. 未考虑锁等待排序. 这个是redis很难实现的. 可以通过redis的list实现,但缺点是list下每个子节点无超时时间. redis也无法进行模糊查询 key*. 故

setnx(key,1)

Redis的发起者是Salvatore Sanfilippo,最初开发它的目的就是为了解决快速存储和查询社交网站上常见的好友关系数据。 目前Vmware在资助着Redis项目的开发和维护,Redis最新的版本是3.X(本文写作时为version 3.2.5),其中内置支持了很多实用的

当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁;当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败。

比如:秒杀,全局递增ID,楼层生成等等。 大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。 其次Redis提供一些命令SETNX,GETSET,可以方便实现分布

2.解锁

有加锁就得有解锁。当得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行del指令,伪代码如下:

del(key)

释放锁之后,其他线程就可以继续执行setnx命令来获得锁。

3.锁超时

锁超时是什么意思呢?如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程再也别想进来。

所以,setnx的key必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。setnx不支持超时参数,所以需要额外的指令,伪代码如下:

expire(key, 30)

二、加锁的代码

/** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) { Long result = jedis.setnx(lockKey, requestId); if (result == 1) { // 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁 jedis.expire(lockKey, expireTime); }}

上面的代码有一个致命的问题,就是加锁和设置过期时间不是原子操作。

那么会有两种极端情况:

一种是在并发情况下,两个线程同时执行setnx,那么得到的结果都是1,这样两个线程同时拿到了锁。

别一种是如代码注释所示,即执行完setnx,程序崩溃没有执行过期时间,那这把锁就永远不会被释放,造成了死锁。

之所以有人这样实现,是因为低版本的jedis并不支持多参数的set()方法。正确的代码如下:

/** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */public static boolean tryGetDistributedLock(Jedis jedis,String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime); if ("OK".equals(result)) { return true; } return false;}

这个set()方法一共有五个形参:

第一个为key,我们使用key来当锁,因为key是唯一的。

第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。

第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;

第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。

第五个为time,与第四个参数相呼应,代表key的过期时间。

总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

二、解锁的代码

public static void wrongReleaseLock1(Jedis jedis, String lockKey) { jedis.del(lockKey);}

这段代码的问题是容易导致误删,假如某线程成功得到了锁,并且设置的超时时间是30秒。如果某些原因导致线程A执行的很慢很慢,过了30秒都没执行完,这时候锁过期自动释放,线程B得到了锁。

随后,线程A执行完了任务,线程A接着执行del指令来释放锁。但这时候线程B还没执行完,线程A实际上删除的是线程B加的锁。

怎么避免这种情况呢?可以在del释放锁之前做一个判断,验证当前的锁是不是自己加的锁。

至于具体的实现,可以在加锁的时候把当前的线程ID当做value,并在删除之前验证key对应的value是不是自己线程的ID。

public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) { // 判断加锁与解锁是不是同一个客户端 if (requestId.equals(jedis.get(lockKey))) { // 若在此时,这把锁突然不是这个客户端的,则会误解锁 jedis.del(lockKey); }}

但是,这样做又隐含了一个新的问题,判断和释放锁是两个独立操作,不是原子性。

解决方案就是使用lua脚本,把它变成原子操作,代码如下:

public class RedisTool { private static final Long RELEASE_SUCCESS = 1L; /** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { 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(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; }}

三、续约问题

上面加锁最后的代码就完美了吗?假想这样一个场景,如果过期时间为30S,A线程超过30S还没执行完,但是自动过期了。这时候B线程就会再拿到锁,造成了同时有两个线程持有锁。这个问题可以归结为”续约“问题,即A没执行完时应该过期时间续约,执行完成才能释放锁。怎么办呢?我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续约”。

其实,后面解锁出现的删除非自己锁,也属于“续约”问题。

四、集群同步延迟问题

用于redis的服务肯定不能是单机,因为单机就不是高可用了,一量挂掉整个分布式锁就没用了。

在集群场景下,如果A在master拿到了锁,在没有把数据同步到slave时,master挂掉了。B再拿锁就会从slave拿锁,而且会拿到。又出现了两个线程同时拿到锁。

基于以上的考虑,Redis 的作者也考虑到这个问题,他提出了一个 RedLock 的算法。

这个算法的意思大概是这样的:假设 Redis 的部署模式是 Redis Cluster,总共有 5 个 Master 节点。

通过以下步骤获取一把锁: 获取当前时间戳,单位是毫秒。 轮流尝试在每个 Master 节点上创建锁,过期时间设置较短,一般就几十毫秒。 尝试在大多数节点上建立一个锁,比如 5 个节点就要求是 3 个节点(n / 2 +1)。 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了。 要是锁建立失败了,那么就依次删除这个锁。 只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。

但是这样的这种算法还是颇具争议的,可能还会存在不少的问题,无法保证加锁的过程一定正确。

这个问题的根本原因就是redis的集群属于AP,分布式锁属于CP,用AP去实现CP是不可能的。

五、Redisson

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。

Redisson通过lua脚本解决了上面的原子性问题,通过“看门狗”解决了续约问题,但是它应该解决不了集群中的同步延迟问题。

总结

redis分布式锁的方案,无论用何种方式实现都会有续约问题与集群同步延迟问题。总的来说,是一个不太靠谱的方案。如果追求高正确率,不能采用这种方案。

但是它也有优点,就是比较简单,在某些非严格要求的场景是可以使用的,比如社交系统一类,交易系统一类不能出现重复交易则不建议用。

到此这篇关于redis分布式锁及会出现的问题解决的文章就介绍到这了,更多相关redis分布式锁内容请搜索真格学网以前的文章或继续浏览下面的相关文章希望大家以后多多支持真格学网! 您可能感兴趣的文章:redis中使用java脚本实现分布式锁Redis实现分布式锁的几种方法总结基于Redis实现分布式锁以及任务队列详解Java如何实现基于Redis的分布式锁浅谈Redis分布式锁的正确实现方式详解使用Redis SETNX 命令实现分布式锁Redis上实现分布式锁以提高性能的方案研究深入理解redis分布式锁和消息队列基于redis分布式锁实现秒杀功能Redis数据库中实现分布式锁的方法

Redis分布式锁的安全性问题2113,在分布式系统专家和Redis的作5261者 antirez 之间4102就发生过一场争论。由于对这个问题一直1653以来比较关注,所以我前些日子仔细阅读了与这场争论相关的资料。这场争论的大概过程是这样的:为了规范各家对基于Redis的分布式锁的实现,Redis的作者提出了一个更安全的实现,叫做 Redlock 内容来自www.zgxue.com请勿采集。


  • 本文相关:
  • 用redis实现微博关注关系
  • redis 集群搭建和简单使用教程
  • 从源码解读redis持久化
  • centos7.5使用mysql_multi方式安装mysql5.7.28多实例(详解)
  • redis 2.8-4.0过期键优化过程全纪录
  • 一次关于redis内存诡异增长的排查过程实战记录
  • windows环境下redis+spring缓存实例讲解
  • centos7 redis主从搭建配置的实现
  • centos7.3安装redis4.0.6详细图文教程
  • redis开启和禁用登陆密码校验的方法
  • 基于Redis 的分布式锁到底安全吗
  • redis 分布式锁为什么比synchronized 快
  • redis分布式锁怎么解决02master宕机后,锁还能正常...
  • 大家所推崇的 Redis 分布式锁,真的可以万无一失吗?
  • java怎么实现redis分布式锁
  • django from django_redis import get_redis_conne...
  • 分布式锁用zookeeper还是redis好
  • redis实现分布式锁必须是单机吗
  • redis 为什么需要分布式锁
  • redis分布式锁是字符串类型还是list
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全mssqlmysqlmariadboracledb2mssql2008mssql2005sqlitepostgresqlmongodbredisaccess数据库文摘数据库其它首页redis中使用java脚本实现分布式锁redis实现分布式锁的几种方法总结基于redis实现分布式锁以及任务队列详解java如何实现基于redis的分布式锁浅谈redis分布式锁的正确实现方式详解使用redis setnx 命令实现分布式锁redis上实现分布式锁以提高性能的方案研究深入理解redis分布式锁和消息队列基于redis分布式锁实现秒杀功能redis数据库中实现分布式锁的方法用redis实现微博关注关系redis 集群搭建和简单使用教程从源码解读redis持久化centos7.5使用mysql_multi方式安装mysql5.7.28多实例(详解)redis 2.8-4.0过期键优化过程全纪录一次关于redis内存诡异增长的排查过程实战记录windows环境下redis+spring缓存实例讲解centos7 redis主从搭建配置的实现centos7.3安装redis4.0.6详细图文教程redis开启和禁用登陆密码校验的方法超强、超详细redis数据库入门教程redis常用命令、常见错误、配置技redis操作命令总结redis中5种数据结构的使用场景介64位windows下安装redis教程redis中使用redis-dump导出、导入redis中统计各种数据大小的方法redis常用命令小结让redis在你的系统中发挥更大作用centos 6.6下redis安装配置记录windows操作系统下redis服务安装图文教程redis数据库的应用场景介绍redis两种持久化方案rdb和aof详解详解redis开启远程登录连接ubuntu下安装redis的2种方法分享redis执行lua脚本的好处与示例代码centos 6.5 64位下安装redis3.0.2的具体步centos6.4 安装redis 教程详解windows下使用redis requirepass认证不起redis启动流程介绍
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved