如何利用Redis分布式锁实现控制并发操作_Redis

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

Redis有一系列的命令,特点是2113以5261NX结尾,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists。这系4102列的命1653令非常有用,这里讲使用SETNX来实现分布式锁。用SETNX实现分布式锁利用SETNX非常简单地实现分布式锁。例如:某客户端要获得一个名字foo的锁,客户端使用下面的命令进行获取:SETNX lock.foo <current Unix time + lock timeout + 1> 如返回1,则该客户端获得锁,把lock.foo的键值设置为时间值表示该键已被锁定,该客户端最后可以通过DEL lock.foo来释放该锁。 如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。解决死锁上面的锁定逻辑有一个问题:如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决?我们可以通过锁的键对应的时间戳来判断这种情况是否发生了,如果当前的时间已经大于lock.foo的值,说明该锁已失效,可以被重新使用。发生这种情况时,可不能简单的通过DEL来删除锁,然后再SETNX一次,当多个客户端检测到锁超时后都会尝试去释放它,这里就可能出现一个竞态条件,让我们模拟一下这个场景: C0操作超时了,但它还持有着锁,C1和C2读取lock.foo检查时间戳,先后发现超时了。 C1 发送DEL lock.foo C1 发送SETNX lock.foo 并且成功了。 C2 发送DEL lock.foo C2 发送SETNX lock.foo 并且成功了。这样一来,C1,C2都拿到了锁!问题大了!幸好这种问题是可以避免D,让我们来看看C3这个客户端是怎样做的:C3发送SETNX lock.foo 想要获得锁,由于C0还持有锁,所以Redis返回给C3一个0C3发送GET lock.foo 以检查锁是否超时了,如果没超时,则等待或重试。反之,如果已超时,C3通过下面的操作来尝试获得锁:GETSET lock.foo <current Unix time + lock timeout + 1>通过GETSET,C3拿到的时间戳如果仍然是超时的,那就说明,C3如愿以偿拿到锁了。如果在C3之前,有个叫C4的客户端比C3快一步执行了上面的操作,那么C3拿到的时间戳是个未超时的值,这时,C3没有如期获得锁,需要再次等待或重试。留意一下,尽管C3没拿到锁,但它改写了C4设置的锁的超时值,不过这一点非常微小的误差带来的影响可以忽略不计。注意:为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,这时就不必解锁了。示例伪代码根据上面的代码,我写了一小段Fake代码来描述使用分布式锁的全过程:# get locklock = 0while lock != 1: timestamp = current Unix time + lock timeout + 1 lock = SETNX lock.foo timestamp if lock == 1 or (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)): break; else: sleep(10ms) # do your jobdo_job() # releaseif now() < GET lock.foo: DEL lock.foo是的,要想这段逻辑可以重用,使用python的你马上就想到了Decorator,而用Java的你是不是也想到了那谁?AOP + annotation?行,怎样舒服怎样用吧,别重复代码就行www.zgxue.com防采集请勿采集本网。

redis命令解释

Redis有一系列的命令,特点是以NX结尾,NX是Not eXists的缩写

说道Redis的分布式锁都是通过setNx命令结合getset来实现的,在讲之前我们先了解下setNx和getset的意思,在redis官网是这样解释的

分布式锁的三种实现方式分别是:基于数据库实现分布式锁、基于缓存(Redis等)实现分布式锁、基于Zookeeper实现分布式锁。 一、基于数据库实现分布式锁 1、悲观锁 利用select … where … for update 排他锁。 注意:其他附加功能与实现一基本一致

注:redis的命令都是原子操作

为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作,Redis 集群对节点使用了主从功能: 集群中的每个节点都有 1 个至 N个品(replica), 其中一个品为主节点(master), 而其余的 N-1

SETNX key value

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

将 key 的值设为 value ,当且仅当 key 不存在。

从redis获取值N,对数值N进行边界检查,自加1,然后N写回redis中。 这种应用场景很常见,像秒杀,全局递增ID、IP访问限制等。 以IP访问限制来说,恶意攻击者可能发起无限次访问,并发量比较大,分布式环境下对N的边界检查就不可靠,因为从redis

若给定的 key 已经存在,则 SETNX 不做任何动作。

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

可用版本:

1.0.0+

时间复杂度:

O(1)

返回值:

设置成功,返回 1 。

设置失败,返回 0 。

redis> EXISTS job # job 不存在(integer) 0redis> SETNX job "programmer" # job 设置成功(integer) 1redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败(integer) 0redis> GET job # 没有被覆盖"programmer"

GETSET key value

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

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

可用版本:

1.0.0+

时间复杂度:

O(1)

返回值:

返回给定 key 的旧值。

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

redis> GETSET db mongodb # 没有旧值,返回 nil(nil)redis> GET db"mongodb"redis> GETSET db redis # 返回旧值 mongodb"mongodb"redis> GET db"redis"

代码示例

注意:为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,这时就不必解锁了。

我们看下代码涉及以下几个类,这里有关业务逻辑相关的只定义了方法没有具体实现,关键是学习思路

OrderBiz.java

/** * 使用redis锁来控制并发抢单 * @author fuyuwei */public class OrderBiz { public int createOrder(){ // 下单之前的参数、合法性校验这里就不在演示 OrderLock<Boolean> orderLock = new RedisOrderLock<Boolean>("pro-12345678901"); boolean isSyn = orderLock.isSyn(new OrderLockBiz<Boolean>(){ @Override public Boolean createOrder() { // 省去创建订单逻辑 return null; } }); if(!isSyn){ BizLogger.info("创建订单失败"); } return 0; }}

OrderLock.java

public interface OrderLock<T> { public boolean isSyn(OrderLockBiz<T> orderBiz);}

OrderLockBiz.java

public interface OrderLockBiz<T> { public T createOrder();}

RedisOrderLock.java

public class RedisOrderLock<T> implements OrderLock<T> { // 锁等待超时,防止线程饥饿,永远没有入锁执行代码的机会 public static final long timeout = 10000;//ms // 锁持有超时,防止线程在入锁以后,无限的执行下去,让锁无法释放 public static final long expireMsecs = 10000;// ms public String lockKey = "orderLockKey"; public Jedis jedis; private static volatile JedisPool jedisPool; public RedisOrderLock(String lockKey) { this.lockKey = lockKey; } /** * 初始化redis * @return */ public Jedis getInstance() { if(jedisPool == null) { synchronized(RedisOrderLock.class) { if(jedisPool == null) { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(100); jedisPool = new JedisPool(config,"localhost",6379, 3000,"test"); } } } return jedisPool.getResource(); } /** * 线程安全的业务逻辑处理 */ @Override public boolean isSyn(OrderLockBiz<T> orderBiz) { jedis = this.getInstance(); try { // 获取到锁 if(acquire(jedis)){ // 执行创建订单逻辑 orderBiz.createOrder(); }else{ BizLogger.info("waiting other thread creating"); } } catch (Exception e) { BizLogger.error(e,"acquire lock failre"); }finally{ // 解锁 this.releaseLock(jedis); } return false; } /** * accqure lock * @param jedis * @return * @throws InterruptedException */ public synchronized boolean acquire(Jedis jedis){ boolean locked = false; while(timeout > 0){ long expires = System.currentTimeMillis() + expireMsecs + 1; // 10秒之后锁到期 String expiresStr = String.valueOf(expires); // 获取到锁 if(jedis.setnx(lockKey, expiresStr) == 1){ locked = true; return locked; } // 没有获取到锁 String oldValue = jedis.get(lockKey); // expireMsecs(10秒)锁的有效期内无法进入if判断,如果锁超时了 if(oldValue != null && Long.parseLong(oldValue) < System.currentTimeMillis()){ // 如果锁超时重新设置 String oldValue_ = jedis.getSet(lockKey, expiresStr); // 值相同说明是同一个线程的操作,获取锁成功 if(Long.valueOf(oldValue_) == Long.valueOf(oldValue)){ locked = true; }else{ // 被其他线程抢先获取锁 locked = false; } } // 锁没有超时,继续等待 return false; } } /** * 释放锁 * @param jedis */ public synchronized void releaseLock(Jedis jedis){ try { long current = System.currentTimeMillis(); // 避免删除非自己获取得到的锁 if (current < Long.valueOf(jedis.get(lockKey))) jedis.del(lockKey); } catch (Exception e) { e.printStackTrace(); }finally{ // 把用完的连接放到连接池汇中供其他线程调用 jedisPool.returnResource(jedis); } }}

以上这篇如何利用Redis分布式锁实现控制并发操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持真格学网。 您可能感兴趣的文章:SpringBoot中使用redis做分布式锁的方法redis分布式锁及会出现的问题解决SpringBoot整合Redis正确的实现分布式锁的示例代码基于redis实现分布式锁的原理与方法SpringBoot使用Redis实现分布式锁

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


  • 本文相关:
  • linux安装配置及使用redis
  • redis分布式锁的正确实现方法总结
  • redis中如何使用lua脚本让你的灵活性提高5个逼格详解
  • redis安装教程图解
  • redis template实现分布式锁的实例代码
  • redis2.8配置文件中文详解
  • nosql和redis简介及redis在windows下的安装和使用教程
  • redis中的数据过期策略详解
  • redis实现排行榜的简单方法
  • 详解redis大幅性能提升之使用管道(pipeline)和批量(batch)操
  • 什么是分布式锁及正确使用redis实现分布式锁
  • 利用redis什么原理实现分布式锁
  • java怎么实现redis分布式锁
  • 大家所推崇的 Redis 分布式锁,真的可以万无一失吗?
  • 如何使用Redis SETNX命令实现分布式锁功能
  • 分布式锁的三种实现方式
  • redis分布式锁怎么解决02master宕机后,锁还能正常...
  • redis实现分布式锁必须是单机吗
  • redis 分布式锁为什么比synchronized 快
  • 分布式锁用zookeeper还是redis好
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全mssqlmysqlmariadboracledb2mssql2008mssql2005sqlitepostgresqlmongodbredisaccess数据库文摘数据库其它首页springboot中使用redis做分布式锁的方法redis分布式锁及会出现的问题解决springboot整合redis正确的实现分布式锁的示例代码基于redis实现分布式锁的原理与方法springboot使用redis实现分布式锁linux安装配置及使用redisredis分布式锁的正确实现方法总结redis中如何使用lua脚本让你的灵活性提高5个逼格详解redis安装教程图解redis template实现分布式锁的实例代码redis2.8配置文件中文详解nosql和redis简介及redis在windows下的安装和使用教程redis中的数据过期策略详解redis实现排行榜的简单方法详解redis大幅性能提升之使用管道(pipeline)和批量(batch)操超强、超详细redis数据库入门教程redis常用命令、常见错误、配置技redis操作命令总结redis中5种数据结构的使用场景介64位windows下安装redis教程redis中使用redis-dump导出、导入redis中统计各种数据大小的方法redis常用命令小结让redis在你的系统中发挥更大作用centos 6.6下redis安装配置记录浅谈分布式锁的几种使用方式(redis、zooredis3.2开启远程访问详细步骤详解redis命令和键_动力节点java学院整理redis中scan命令的深入讲解redis字符串对象实用笔记redis中事件驱动模型示例详解redis如何在项目中合理使用经验分享简单粗暴的redis数据备份和恢复方法redis执行lua脚本的好处与示例代码异步redis队列实现 数据入库的方法
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved