Redis实现分布式锁
1、使用背景
几乎每个互联网公司中都使用了分布式部署,分布式服务下,就会遇到对同一个资源的并发访问的技术难题,如秒杀、下单减库存等场景。这些场景有一个共同特点就是访问量激增,虽然在系统设计时会通过限流、异步、排队等方式优化,但整体的并发还是平时的数倍以上,为了避免并发问题,防止库存超卖,给用户提供一个良好的购物体验,这些系统中都会用到锁的机制。
如果在分布式场景中,实现不同客户端的线程对代码和资源的同步访问,保证在多线程下处理共享数据的安全性,就需要用到分布式锁技术。
2、分布式锁是什么
分布式锁是什么?分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同系统或同一系统不同主机之间共享了某个资源时,往往需要互斥防止干扰保证一致性。
一个相对安全的分布式锁需要具有以下几个特征:
互斥性。互斥是锁的基本特征,同一时刻锁只能被一个线程持有,执行临界区操作。
超时释放。通过超时释放可以避免死锁,防止不必要的等待和资源浪费。
可重入性。一个线程在持有锁的情况下可以对其再次请求加锁,防止锁在线程执行完临界区操作之前释放。
高性能和高可用。加锁和释放锁的过程性能开销要尽可能的低,同时也要保证高可用,防止分布式锁意外失效。
可以看出实现分布式锁,并不是锁住资源就可以了,还需要满足一些额外的特征,避免出现死锁、锁失效等问题。
3、分布式锁的实现方式
目前实现分布式锁的方式有很多,其中常见的有以下几种:
3.1 Memcached 分布式锁
利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情况下,才能 add 成功,也就意味着线程得到了锁。
3.2 Zookeeper 分布式锁
利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。ZooKeeper 作为一个专门为分布式应用提供方案的框架,它提供了一些非常好的特性,如 ephemeral 类型的 znode 自动删除的功能,同时 ZooKeeper 还提供 watch 机制,可以让分布式锁在客户端用起来就像一个本地的锁一样:加锁失败就阻塞住,直到获取到锁为止。
3.3 Redis 分布式锁
基于 Redis 单机实现的分布式锁,其方式和 Memcached 的实现方式类似,利用 Redis 的 SETNX 命令,此命令同样是原子性操作,只有在 key 不存在的情况下,才能 set 成功。而基于 Redis 多机实现的分布式锁Redlock,是Redis 的作者 antirez 为了规范 Redis 分布式锁的实现,提出的一个更安全有效的实现机制。
4、Redis分布式锁
使用Redis作为分布式锁,本质上要实现的目标就是一个进程在 Redis 里面占据了仅有的一个“茅坑”,当别的进程也想来占坑时,发现已经有人蹲在那里了,就只好放弃或者等待稍后再试。
目前基于 Redis 实现分布式锁主要有两大类,一类是基于单机,另一类是基于 Redis 多机,不管是哪种实现方式,均需要实现加锁、解锁、锁超时这三个分布式锁的核心要素。
4.1 基于 Redis 单机实现的分布式锁
4.1.1 使用 SETNX 指令
最简单的加锁方式就是直接使用 Redis 的 SETNX 指令,该指令只在 key 不存在的情况下,将 key 的值设置为 value,若 key 已经存在,则 SETNX 命令不做任何动作。key 是锁的唯一标识,可以按照业务需要锁定的资源来命名。
比如在某商城的秒杀活动中对某一商品加锁,那么 key 可以设置为 lock_resource_id ,value 可以设置为任意值,在资源使用完成后,使用 DEL 删除该 key 对锁进行释放,整个过程如下:
很显然,这种获取锁的方式很简单,但也存在一个问题,就是我们上面提到的分布式锁三个核心要素之一的锁超时问题,即如果获得锁的进程在业务逻辑处理过程中出现了异常,可能会导致 DEL 指令一直无法执行,导致锁无法释放,该资源将会永远被锁住。
所以,在使用 SETNX 拿到锁以后,必须给 key 设置一个过期时间,以保证即使没有被显式释放,在获取锁达到一定时间后也要自动释放,防止资源被长时间独占。由于 SETNX 不支持设置过期时间,所以需要额外的 EXPIRE 指令,整个过程如下:
这样实现的分布式锁仍然存在一个严重的问题,由于 SETNX 和 EXPIRE 这两个操作是非原子性的, 如果进程在执行 SETNX 和 EXPIRE 之间发生异常,SETNX 执行成功,但 EXPIRE 没有执行,导致这把锁变得“长生不老”,这种情况就可能出现前文提到的锁超时问题,其他进程无法正常获取锁。
4.1.2 使用 SET 扩展指令
为了解决 SETNX 和 EXPIRE 两个操作非原子性的问题,可以使用 Redis 的 SET 指令的扩展参数,使得 SETNX 和 EXPIRE 这两个操作可以原子执行,整个过程如下:
在这个 SET 指令中:
NX 表示只有当 lock_resource_id 对应的 key 值不存在的时候才能 SET 成功。保证了只有第一个请求的客户端才能获得锁,而其它客户端在锁被释放之前都无法获得锁。
EX 10 表示这个锁10秒钟后会自动过期,业务可以根据实际情况设置这个时间的大小。
但是这种方式仍然不能彻底解决分布式锁超时问题:
锁被提前释放。假如线程 A 在加锁和释放锁之间的逻辑执行的时间过长(或者线程 A 执行过程中被堵塞),以至于超出了锁的过期时间后进行了释放,但线程 A 在临界区的逻辑还没有执行完,那么这时候线程 B 就可以提前重新获取这把锁,导致临界区代码不能严格的串行执行。
锁被误删。假如以上情形中的线程A执行完后,它并不知道此时的锁持有者是线程 B,线程A会继续执行 DEL 指令来释放锁,如果线程 B 在临界区的逻辑还没有执行完,线程 A 实际上释放了线程 B 的锁。
为了避免以上情况,建议不要在执行时间过长的场景中使用 Redis 分布式锁,同时一个比较安全的做法是在执行 DEL 释放锁之前对锁进行判断,验证当前锁的持有者是否是自己。
具体实现就是在加锁时将 value 设置为一个唯一的随机数(或者线程 ID ),释放锁时先判断随机数是否一致,然后再执行释放操作,确保不会错误地释放其它线程持有的锁,除非是锁过期了被服务器自动释放,整个过程如下:
但判断 value 和删除 key 是两个独立的操作,并不是原子性的,所以这个地方需要使用 Lua 脚本进行处理,因为 Lua 脚本可以保证连续多个指令的原子性执行。
基于 Redis 单节点的分布式锁基本完成了,但是这并不是一个完美的方案,只是相对完全一点,因为它并没有完全解决当前线程执行超时锁被提前释放后,其它线程乘虚而入的问题。
4.1.3 使用 Redisson 的分布式锁
怎么能解决锁被提前释放这个问题呢?
可以利用锁的可重入特性,让获得锁的线程开启一个定时器的守护线程,每 expireTime/3 执行一次,去检查该线程的锁是否存在,如果存在则对锁的过期时间重新设置为 expireTime,即利用守护线程对锁进行“续命”,防止锁由于过期提前释放。
当然业务要实现这个守护进程的逻辑还是比较复杂的,可能还会出现一些未知的问题。
目前互联网公司在生产环境用的比较广泛的开源框架 Redisson 很好地解决了这个问题,非常的简便易用,且支持 Redis 单实例、Redis M-S、Redis Sentinel、Redis Cluster 等多种部署架构。
感兴趣的朋友可以查阅下官方文档或者源码:https://github.com/redisson/redisson/wiki
其实现原理如图所示(图中以 Redis 集群为例):
4.2 基于 Redis 多机实现的分布式锁 Redlock
以上几种基于 Redis 单机实现的分布式锁其实都存在一个问题,就是加锁时只作用在一个 Redis 节点上,即使 Redis 通过 Sentinel 保证了高可用,但由于 Redis 的复制是异步的,Master 节点获取到锁后在未完成数据同步的情况下发生故障转移,此时其他客户端上的线程依然可以获取到锁,因此会丧失锁的安全性。
整个过程如下:
客户端 A 从 Master 节点获取锁。
Master 节点出现故障,主从复制过程中,锁对应的 key 没有同步到 Slave 节点。
Slave升 级为 Master 节点,但此时的 Master 中没有锁数据。
客户端 B 请求新的 Master 节点,并获取到了对应同一个资源的锁。
出现多个客户端同时持有同一个资源的锁,不满足锁的互斥性。
正因为如此,在 Redis 的分布式环境中,Redis 的作者 antirez 提供了 RedLock 的算法来实现一个分布式锁,该算法大概是这样的:
假设有 N(N>=5)个 Redis 节点,这些节点完全互相独立,不存在主从复制或者其他集群协调机制,确保在这 N 个节点上使用与在 Redis 单实例下相同的方法获取和释放锁。
获取锁的过程,客户端应执行如下操作:
获取当前 Unix 时间,以毫秒为单位。
按顺序依次尝试从5个实例使用相同的 key 和具有唯一性的 value(例如 UUID)获取锁。当向 Redis 请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端 Redis 已经挂掉的情况下,客户端还在一直等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个 Redis 实例请求获取锁。
客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(N/2+1,这里是3个节点)的 Redis 节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
如果取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
如果因为某些原因,获取锁失败(没有在至少N/2+1个 Redis 实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的 Redis 实例上进行解锁(使用 Redis Lua 脚本)。
释放锁的过程相对比较简单:客户端向所有 Redis 节点发起释放锁的操作,包括加锁失败的节点,也需要执行释放锁的操作,antirez 在算法描述中特别强调这一点,这是为什么呢?
原因是可能存在某个节点加锁成功后返回客户端的响应包丢失了,这种情况在异步通信模型中是有可能发生的:客户端向服务器通信是正常的,但反方向却是有问题的。虽然对客户端而言,由于响应超时导致加锁失败,但是对 Redis 节点而言,SET 指令执行成功,意味着加锁成功。因此,释放锁的时候,客户端也应该对当时获取锁失败的那些 Redis 节点同样发起请求。
除此之外,为了避免 Redis 节点发生崩溃重启后造成锁丢失,从而影响锁的安全性,antirez 还提出了延时重启的概念,即一个节点崩溃后不要立即重启,而是等待一段时间后再进行重启,这段时间应该大于锁的有效时间。
关于 Redlock 的更深层次的学习,感兴趣的朋友可以查阅下官方文档,https://redis.io/topics/distlock
5、总结
分布式系统设计是实现复杂性和收益的平衡,既要尽可能地安全可靠,也要避免过度设计。Redlock 确实能够提供更安全的分布式锁,但也是有代价的,需要更多的 Redis 节点。在实际业务中,一般使用基于单点的 Redis 实现分布式锁就可以满足绝大部分的需求,偶尔出现数据不一致的情况,可通过人工介入回补数据进行解决,正所谓“技术不够,人工来凑”!
相关文章:
Redis实现分布式锁
1、使用背景几乎每个互联网公司中都使用了分布式部署,分布式服务下,就会遇到对同一个资源的并发访问的技术难题,如秒杀、下单减库存等场景。这些场景有一个共同特点就是访问量激增,虽然在系统设计时会通过限流、异步、排队等方式优…...
刷题小抄1-2数之和
时间复杂度和空间复杂度 对于一个算法高效性的评估,分为时间复杂度与空间复杂度两种,在算法优化到极致的情况下,只能舍弃时间来换取空间,或者舍弃空间来换取时间,故而两者可以说是互斥关系 时间复杂度衡量的是算法运行的速度,而空间复杂度衡量的是算法运行所需要的额外内存空…...
axicom的测试文档
目录)SQLpython开放性业务题(二选一)完整代码SQL 问题描述 SQL, 请根据前一周各产品的总GMV将其分成五类:GMV Top 20%、20%-40%,40%-60%,60%-80%以及Bottom 20%的产品组,请计算这五…...
基于vue3异步组件、动态组件、vite批量导入实现路由权限动态管理(非addRoute方案)
开发后台管理系统必备的需求:动态菜单权限管理、或者说路由权限动态管理 原理是通过addRoute实现路由权限控制,一般分为两种: 后端生成当前用户相应的路由后由前端(用 Vue Router 提供的API)addRoutes 动态加载路由前…...
带中转hub的卡车无人机车辆路径问题
本文介绍了两类无人机卡车协同配送问题: 第一类是旅行商问题,也即一辆卡车拉着一架无人机服务给定的节点集合第二类是车辆路径问题,这里强制要求了一架卡车只能搭配一架无人机无人机卡车旅行商问题 符号列表: N N N:表示所有节点集合,含起始和终止节点M M M...
前端食堂技术周刊第 72 期:Signals 是前端框架的未来、Chrome Headless、ts-reset、magic-regexp、Bun 新文档
美味值:🌟🌟🌟🌟🌟 口味:草莓番茄 食堂技术周刊仓库地址:https://github.com/Geekhyt/weekly 本期摘要 Signals 是前端框架的未来Chrome Headless 进化成完全体Next.js 13.2Deno…...
mysql中用逗号隔开的字段作查询用(find_in_set的使用)
mysql中用逗号隔开的字段作查询用(find_in_set的使用) 场景说明 在工作中,经常会遇到一对多的关系。想要在mysql中保存这种关系,一般有两种方式,一种是建立一张中间表,这样一条id就会存在多条记录。或者采用第二种方式ÿ…...
Day902.Memory存储引擎 -MySQL实战
Memory存储引擎 Hi,我是阿昌,今天学习记录的是关于Memory存储引擎的内容。 两个 group by 语句都用了 order by null,为什么使用内存临时表得到的语句结果里,0 这个值在最后一行; 而使用磁盘临时表得到的结果里&…...
Linux(Centos)安装RabbitMQ+延时插件+开机自启动
安装目录1:前言1.1 系统环境1.2:安装版本1.3 简介2:安装2.1:安装前准备:2.2:安装Erlang2.3:安装RabbitMQ2.3:延迟依赖插件安装1:前言 1.1 系统环境 操作系统版本&#…...
最近是遇到了CKPT(BLOCKED)
半夜被电话吵醒,数据库不可用了,无法交易。 远程登录查看,这个时候就没有所谓的安全不安全了,都可以远程了。 onstat - 数据库处于CKPT(REQ) CKPT:BLOCKED状态 onstat -l 发现所有的逻辑日志都是U------状态ÿ…...
RabbitMQ死信队列
目录 一、概念 二、出现死信的原因 三、实战 (一)代码架构图 (二)消息被拒 (三)消息TTL过期 (四)队列达到最大长度 一、概念 先从概念解释上搞清楚这个定义,死信&…...
Word控件Spire.Doc 【书签】教程(1):在C#/VB.NET:在 Word 中插入书签
Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下,轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具,专注于创建、编辑、转…...
微服务框架-学习笔记
1 微服务架构介绍 1.1 系统架构演变历史 单体架构垂直应用架构:按照业务线垂直划分分布式架构:抽出业务无关的公共模块SOA架构:面向服务微服务架构:彻底的服务化1.2 微服务架构概览 1.3 微服务架构核心要素 服务治理࿱…...
实验心理学笔记01:引论
原视频链接: https://www.bilibili.com/video/BV1Qt41137Kv 目录 一、实验心理学:定义、内容及简要历史回顾 二、实验心理学和普通心理学、认知心理学的区别 三、实验方法与非实验方法 四、实验范式 五、实验中的各种变量 六、The science of psy…...
预备3-如何学习编程
如何学习编程 我说说曾经学习编程踩得坑 纠结字面上的意思 如纠结一个关键词的名称如何来 为什么叫这个名称... 只是一个简单的名称,该名称代表某一想象/行为,就好比你为啥叫张三, 千万别去深究这些...做笔记的时间比敲代码的时间还多 做笔记的原因是,自己总结归纳所学的知识, …...
操作系统权限提升(十七)之绕过UAC提权-Windows令牌概述和令牌窃取攻击
系列文章 操作系统权限提升(十二)之绕过UAC提权-Windows UAC概述 操作系统权限提升(十三)之绕过UAC提权-MSF和CS绕过UAC提权 操作系统权限提升(十四)之绕过UAC提权-基于白名单AutoElevate绕过UAC提权 操作系统权限提升(十五)之绕过UAC提权-基于白名单DLL劫持绕过UAC提权 操作系…...
【时间之外】系统管人,能行?(冷眼旁观连载之二)
上次写了在用的工具系统和痛点,基本情况都交待清楚了,春节假期很快就过去了。这次继续按照之前观察计划,谈谈对这些工具使用情况的感受,学而时习之,算是抛砖引玉,也算是个人对这项工作的总结和体会。 目录…...
【数据结构必会基础】关于树,你所必须知道的亿些概念
目录 1.什么是树 1.1浅显的理解树 1.2 数据结构中树的概念 2.树的各种结构概念 2.1 节点的度 2.2 根节点/叶节点/分支节点 2.3 父节点/子节点 2.4祖先节点/子孙节点 2.5兄弟节点 2.6树的度 2.7节点的层次 2.8森林 3. 如何用代码表示一棵树 3.1链式结构 3.1.1 树节…...
设计模式的应用(已在大型项目中使用)
说明:开发语言:在本文中,使用的是C# 一、目录 •1 、单例模式 •2 、简单工厂模式 •3 、代理模式 •4 、观察者模式 •5 、外观模式 •6 、享元模式 •7 、命令模式 •8 、状态模式 •9 、发布订阅模式...
Git的相关用法
1.全局设置自己的git提交用户名和邮箱git config --global user.name 张三 git config --global user.email zsgmail.com即所有的提交都会用这个姓名和邮箱。如果不知道自己配置的是什么,可以查询下git config --global user.name git config --global user.email 或…...
Linux服务:Nginx反向代理与负载均衡
目录 一、Nginx反向代理 1、什么是代理 2、实现反向代理实验 ①实验拓扑 ②实验目的 ③实验过程 二、反向代理负载均衡 1、反向代理负载均衡调度算法 ①轮询算法 ②加权轮询算法 ③最小连接数算法 ④ip、url 哈希算法 ⑤响应时间fair算法 2、实现反向代理负载均…...
数据结构与算法——2.算法概述
这篇文章,我们来讲一下算法的概述,大致理解一下什么是算法。 目录 1.定义 2.生活实例 3.算法目标 4.实际案例 4.1案例一 4.2案例二 5.小结 1.定义 官方解释: 算法是指解题方案的准确而完整的描述,是一系列解决问题的清…...
BPMN2.0是什么,BPMN能解决企业流程管理中哪些问题?
一、前言: 在任何行业和企业中,一定存在着各式各样的流程,请假流程、报销流程、入职流程、离职流程、出差流程、合同审批流程、出入库流程等等…… 无论是管理者、技术人员还是业务人员,每天肯定也在使用各种流程,但…...
Java线程池的基本工作原理及案例
一、线程池的优点 线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。 主要特点:线程复用;控制最大并发数;管理线程…...
Stacked hourglass networks for human pose estimation代码学习
Stacked hourglass networks for human pose estimation https://github.com/princeton-vl/pytorch_stacked_hourglass 这是一个用于人体姿态估计的模型,只能检测单个人 作者通过重复的bottom-up(高分辨率->低分辨率)和top-down࿰…...
SpringCloud(五)MQ消息队列
MQ概念常见消息模型helloworld案例实现实现spring AMQP发送消息实现spring AMQP接收消息工作消息队列实现发布订阅模型Fanout Exchange实现DirectExchange实现TopicExchange实现DirectExchange 和FanoutExchange的差异DirectExchange 和TopicExchange的差异基于RabbitListener注…...
SQL语法基础汇总
三年前的存稿 默认端口号 3306 超级用户名 root 登录 mysql -uroot -p / mysql -uroot -proot 退出 exit / quit 服务器版本 SELECT VERSION(); 当前日期 SELECT NOW(); 当前用户 SELECT USER(); 备份 mysqldump -uroot -p 数据库名称 > 保存的路径 还原 create database1-…...
惠普星14Pro电脑开机不了显示错误代码界面怎么办?
惠普星14Pro电脑开机不了显示错误代码界面怎么办?有用户电脑开机之后,进入了一个错误界面,里面有一些错误代码。重启电脑之后依然是无法进入到桌面中,那么这个情况怎么去进行解决呢?我们可以重装一个新系统,…...
顺序表的构造及功能
定义顺序表是一种随机存储都结构,其特点是表中的元素的逻辑顺序与物理顺序相同。假设线性表L存储起始位置为L(A),sizeof(ElemType)是每个数据元素所占的存储空间的大小,则线性表L所对应的顺序存储如下图。顺序表的优缺点优点:随机…...
cesium: 绘制线段(008)
第008个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中绘制线段,左键点击开始绘制,右键点击取消绘制 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共139行)相关API参考:专栏目标示例效果 配置方式 1)…...
深圳市建委网站/企业网站推广的形式有
链接: 1、 RAM、ROM、内存还有硬盘,它们到底有什么区别 2、ROM 一、ROM的定义 ROM :只读内存(Read-Only Memory)简称) ROM是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数…...
现在个人做网站还能盈利吗/青岛seo服务
概述在PHP开发中工作里非常多使用到超时处理到超时的场合,我说几个场景:1. 异步获取数据如果某个后端数据源获取不成功则跳过,不影响整个页面展现2. 为了保证Web服务器不会因为当个页面处理性能差而导致无法访问其他页面,则会对某…...
住房和城乡建设统计网站/长春seo网站排名
中国浙江温州小型皮鞋厂老板,面对皮鞋进水发胖问题,没有任何解决的办法! 本周末写点轻松的好玩的事,所谓轻松的好玩的事,那就是既不怕犯错,又不用背锅的事,完全不用负责任,那就说说t…...
网站开发与设计培训的就业前景/班级优化大师免费下载安装
写作时间:2020-08-15目录:1.问题:2.将二进制表达的负数换成十进制怎么弄?3.总结一下正文:1.问题:比如说。要表达0~255的十进制数,在FPGA使用一个8bit[7:0]的二进制就可以。但是,我们…...
做房产网站在百度推广推广费/关键词挖掘网站
一、Linux 中文件名的命名规范1.严格区分大小写2.可以使用除了“/”以外的任意字符,最长可以达到255个字符,但是不建议使用特殊字符和空格作为用户名。容易造成混淆,可能误将“a b”当成两个文件“a”和“b”3.后缀名,比如“.txt”…...
建设网站怎样赚钱/推广计划怎么做推广是什么
系列目录 一、安装MySql 二、安装并破解Navicat 三、没有my.in配置文件怎么办 四、设置MySql的大小写敏感 五、重置MySql登陆密码 这一篇可以说是我的踩坑的血泪史了。。。 MySql在Windows下默认是大小写不敏感的,当表名中有大写字母的时候,MySql会直接把…...