在系统中,当存在多个进程和线程可以改变某个共享数据时,就容易出现并发问题导致共享数据的不一致性。即多个进程同时获取到了对数据的操作权限并对数据进行了更新,很典型的场景就是在线销售系统在售卖热销商品时遇到多个并发请求在同一时间提交订单的情况则极有可能造成商品超卖的现象。只要访问流量不错的系统都有可能遭遇并发请求造成数据库中数据重复写入的情况。

针对程序块被多个进程并发执行问题的解决方案是确保同一个时刻同一个程序块只能有一个进程可执行,其他进程等待当前进程执行完成才能获取程序块的执行权对数据进行更新,以此类推将并发执行变为串行顺序执行。为了让获取执行权的进程不被其他干扰,就需要设置一个所有进程都能读取到的标记,当标记不存在时可以设置该标记,其余后续进程发现已经有标记了则等待拥有标记的进程结束执行程序块取消标记后再去尝试设置标记。这个标记可以理解为锁,设置标记的过程就是我们通常说的加锁。

用redis 的 setnx、expire 方法做分布式锁

  • setnx()

setnx 的含义就是 SET if Not Exists,其主要有两个参数 setnx(key, value)。该方法是原子的,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0。

  • expire()

expire 设置过期时间,要注意的是 setnx 命令不能设置 key 的超时时间,只能通过 expire() 来对 key 设置。

  • 具体步骤

1、setnx(lockKey, 1) 如果返回 0,则说明占位失败;如果返回 1,则说明占位成功

2、expire() 命令对 lockKey 设置超时时间,为的是避免死锁问题。

3、执行完业务代码后,可以通过 delete 命令删除 key。

这个方案其实是可以解决日常工作中的需求的,但从技术方案的探讨上来说,可能还有一些可以完善的地方。比如,如果在第一步 setnx 执行成功后,在 expire() 命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题,所以如果要对其进行完善的话,可以使用 redis 的 setnx()、get() 和 getset() 方法来实现分布式锁。

用 redis 的 setnx()、get()、getset()方法做分布式锁

这个方案的背景主要是在 setnx() 和 expire() 的方案上针对可能存在的死锁问题,做了一些优化。

  • getset()

这个命令主要有两个参数 getset(key,newValue)。该方法是原子的,对 key 设置 newValue 这个值,并且返回 key 原来的旧值。假设 key 原来是不存在的,那么多次执行这个命令,会出现下边的效果:

  1. getset(key, "value1") 返回 null 此时 key 的值会被设置为 value1

  2. getset(key, "value2") 返回 value1 此时 key 的值会被设置为 value2

  3. 依次类推!

  • 使用步骤

  1. setnx(lockKey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁,转到步骤 2。

  2. get(lockKey) 获取值,值是当前lockKey的过期时间用oldExpireTime代表 ,并将这个 oldExpireTime与当前的系统时间进行比较,如果早于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向 步骤3,否则等待指定时间后返回步骤2重新开始判定。

  3. 计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockKey, newExpireTime) 会返回当前 lockKey 之前设置的旧值currentExpireTime。

  4. 判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前进程getset 设置锁成功,获取到了锁。如果不相等,说明这个锁已经被别的进程获取走了,那么当前请求可以根据具体需求逻辑直接返回失败,或者返回步骤2继续重试。

  5. 在获取到锁之后,当前进程可以开始自己的业务处理,当处理完毕后,比较当前理时间和对锁设置的超时时间,如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,锁可能已由其他进程获得,这时执行 delete释放锁的操作会导致把其他进程已获得的锁释放掉。

下面是用PHP代码实现的Redis分布式锁,关于Redis部分使用的是伪代码,请根据自己的情况用Redis连接对象替代其中的伪代码。

/*** 获取Redis分布式锁** @param $lockKey* @return bool*/
function getRedisDistributedLock(string $lockKey) : bool
{$lockTimeout = 2000;// 锁的超时时间2000毫秒$now = intval(microtime(true) * 1000);$lockExpireTime = $now + $lockTimeout;$lockResult = Redis::setnx($lockKey, $lockExpireTime);if ($lockResult) {// 当前进程设置锁成功return true;} else {$oldLockExpireTime = Redis::get($lockKey);if ($now > $oldLockExpireTime && $oldLockExpireTime == Redis::getset($lockKey, $lockExpireTime)) {return true;}}return false;
}
/*** 串行执行程序** @param string $lockKey Key for lock* @param Closure $closure 获得锁后进程要执行的闭包* @return mixed*/
function serialProcessing(string $lockKey, Closure $closure)
{if (getRedisDistributedLock($lockKey)) {$result = $closure();$now = intval(microtime(true) * 1000);if ($now < Redis::get($lockKey)) {Redis::del($lockKey);   }} else {// 延迟200毫秒再执行usleep(200 * 1000);return serialProcessing($lockKey, $closure);}return $result;
}

上面 serialProcessing方法里当前进程设置锁成功,获取了代码块的执行权后就会执行闭包参数 $closure里的代码块,通过传递闭包给方法,让我们可以在项目任何需要确保程序串行执行的地方使用 serialProcessing方法给程序加分布式锁解决并发请求的问题。

上面代码实现用面向过程的方式是为了能简单明了的描述怎么设置分布式锁,读者可以针对自己的情况执行设计实现代码。针对于大型系统使用集群Redis的情况,设置分布式锁的步骤更复杂,有兴趣的可以查看 Redlock算法和 redissonredis分布式锁组件。

用redis实现分布式锁相关推荐

  1. 基于 Redis 实现分布式锁思考

    以下文章来源方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/xuan_lu/article/details/111600302 分布式锁 基于redis实 ...

  2. Redis实现分布式锁的深入探究

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 一.分布式锁简介 锁 是一种用来解决多个执行线程 访问共享资源 错 ...

  3. nx set 怎么实现的原子性_基于Redis的分布式锁实现

    前言 本篇文章主要介绍基于Redis的分布式锁实现到底是怎么一回事,其中参考了许多大佬写的文章,算是对分布式锁做一个总结 分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问 ...

  4. Zookeeper和Redis实现分布式锁,附我的可靠性分析

    作者:今天你敲代码了吗 链接:https://www.jianshu.com/p/b6953745e341 在分布式系统中,为保证同一时间只有一个客户端可以对共享资源进行操作,需要对共享资源加锁来实现 ...

  5. Redis——由分布式锁造成的重大事故

    作者:浪漫先生 原文:juejin.im/post/6854573212831842311 前言 基于Redis使用分布式锁在当今已经不是什么新鲜事了.本篇文章主要是基于我们实际项目中因为redis分 ...

  6. 基于Redis的分布式锁和Redlock算法

    来自:后端技术指南针 1 前言 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手 ...

  7. 《Redis官方文档》用Redis构建分布式锁

    <Redis官方文档>用Redis构建分布式锁 用Redis构建分布式锁 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现 ...

  8. 《Redis官方文档》用Redis构建分布式锁(悲观锁)

    2019独角兽企业重金招聘Python工程师标准>>> **用Redis构建分布式锁 ** 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章 ...

  9. redis 实现分布式锁

    为什么80%的码农都做不了架构师?>>>    redis 实现分布式锁 伪代码 lock(){if(jedis.setNx("key",timestamp)){ ...

  10. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

最新文章

  1. 深入分析jquery解析json数据
  2. tomcat安装apr优化
  3. python第五单元答案_中国大学MOOC第五单元测试答案_数据结构与算法Python版慕课答案在哪里可以看...
  4. Java之——利用Comparator接口对多个排序条件进行处理
  5. MySQL 中的 FOUND_ROWS() 与 ROW_COUNT() 函数
  6. 算法导论之字符串匹配
  7. html 简单 在线编辑器 ie ff,一款垃圾中的极品HTML编辑器(兼容IE OR FF)
  8. 什么是 SAP UI5 的 Element binding
  9. docker 报错 Container is not running
  10. threejs- z-fighting 问题
  11. 人脸识别 | 你的论文离CVPR , 还有多远?
  12. 深入浅出redux知识
  13. python 分类变量xgboost_用于可解释机器学习的四个Python库
  14. jeecg框架MybatisPlus出现查询条件重复现象
  15. 网站被劫持的方式,和检测方法、网站被劫持、检测方法有哪些
  16. opencv 判断点在多边形内外
  17. subst 不小心创建出来虚拟磁盘 在磁盘管理中删不掉 使用subst命令删除 完美解决
  18. 解密TLS协议全记录之Openssl的使用与Nginx Server的配置
  19. 解决加载高德地图闪白框及logo问题
  20. 把阿拉伯数字翻译成中文大写数字

热门文章

  1. 最好的计划是略有闲余的计划,用于缓冲必然出现的错误与突发事件(转)
  2. C# 调用其他的动态库开发应注意的问题
  3. TI DSP simulator 种类选择
  4. 区块链制作的比特币的缺陷
  5. Spring Boot应用的打包和部署
  6. android.graphics包中的一些类的使用
  7. weblogic启动脚本
  8. 【Arduino】开发入门教程【六】数据类型转换函数
  9. Android文本框实现搜索和清空效果
  10. 六安瓜片在51CTO【礼树迎蛇 红满社区】