如何使用 Redis 快速实现分布式锁?

本文我们来讨论如何使用 Redis 快速实现分布式锁。

分布式锁有很多种解决方案,前面简单介绍过,Redis 可以通过 set key 方式来实现分布式锁,但实际情况要更加复杂,比如如何确保临界资源的串行执行,如何及时释放,都是需要额外考虑的。

本文要讲的是一个完备的分布式锁应该具备哪些特性,以及如何使用 Redis 来一步步优化实现。
分布式锁需要具有哪些特点
先来看一下,一个完备的分布式锁,需要支持哪些特性?

图片1(2).png

一般来说,生产环境可用的分布式锁需要满足以下几点:

  • 互斥性,互斥是锁的基本特征,同一时刻只能有一个线程持有锁,执行临界操作;
  • 超时释放,超时释放是锁的另一个必备特性,可以对比 MySQL InnoDB 引擎中的 innodb_lock_wait_timeout 配置,通过超时释放,防止不必要的线程等待和资源浪费;
  • 可重入性,在分布式环境下,同一个节点上的同一个线程如果获取了锁之后,再次请求还是可以成功;
  • 高性能和高可用,加锁和解锁的开销要尽可能的小,同时也需要保证高可用,防止分布式锁失效;
  • 支持阻塞和非阻塞性,对比 Java 语言中的 wait() 和 notify() 等操作,这个一般是在业务代码中实现,比如在获取锁时通过 while(true) 或者轮询来实现阻塞操作。

可以看到,实现一个相对完备的分布式锁,并不是锁住资源就可以了,还需要满足一些额外的特性,否则会在业务开发中出现各种各样的问题。

下面我们以 Redis 实现分布式锁为例,看一下如何优化分布式锁的具体实现。

使用 setnx 实现分布式锁

Redis 支持 setnx 指令,只在 key 不存在的情况下,将 key 的值设置为 value,若 key 已经存在,则 setnx 命令不做任何动作。使用 setnx 实现分布式锁的方案,获取锁的方法很简单,只要以该锁为 key,设置一个随机的值即可。如果 setnx 返回 1,则说明该进程获得锁;如果 setnx 返回 0,则说明其他进程已经获得了锁,进程不能进入临界区;如果需要阻塞当前进程,可以在一个循环中不断尝试 setnx 操作。

if(setnx(key,value)==1){try{//业务处理}finally{//释放锁del(key)}
}

释放锁时只要删除对应的 key 就可以,为了防止系统业务进程出现异常导致锁无法释放,使用 Java 中的 try-catch-finally 来完成锁的释放。

对比一下上面说的分布式锁特性,使用这种方式实现分布式锁的问题很明显:不支持超时释放锁,如果进程在加锁后宕机,则会导致锁无法删除,其他进程无法获得锁。

使用 setnx 和 expire 实现

在分布式锁的实现中,依赖业务线程进行锁的释放,如果进程宕机,那么就会出现死锁。Redis 在设置一个 key 时,支持设置过期时间,利用这一点,可以在缓存中实现锁的超时释放,解决死锁问题。

在使用 setnx 获取锁之后,通过 expire 给锁加一个过期时间,利用 Redis 的缓存失效策略,进行锁的超时清除。

伪代码如下:

if(setnx(key,value)==1){expire(key,expireTime)try{//业务处理}finally{//释放锁del(key)}
}

通过设置过期时间,避免了占锁到释放锁的过程发生异常而导致锁无法释放的问题,但是在 Redis 中,setnx 和 expire 这两条命令不具备原子性。如果一个线程在执行完 setnx 之后突然崩溃,导致锁没有设置过期时间,那么这个锁就会一直存在,无法被其他线程获取。

使用 set 扩展命令实现

为了解决这个问题,在 Redis 2.8 版本中,扩展了 set 命令,支持 set 和 expire 指令组合的原子操作,解决了加锁过程中失败的问题。

set 扩展参数的语法如下:

redis> SET key value expireTime nx

nx 表示仅在键不存在时设置,这样可以在同一时间内完成设置值和设置过期时间这两个操作,防止设置过期时间异常导致的死锁。那么这种方式还存在问题吗?

使用 setex 方式看起来解决了锁超时的问题,但在实际业务中,如果对超时时间设置不合理,存在这样一种可能:在加锁和释放锁之间的业务逻辑执行的太长,以至于超出了锁的超时限制,缓存将对应 key 删除,其他线程可以获取锁,出现对加锁资源的并发操作。

我们来模拟下这种情况:

  • 客户端 A 获取锁的时候设置了 key 的过期时间为 2 秒,客户端 A 在获取到锁之后,业务逻辑方法执行了 3 秒;
  • 客户端 A 获取的锁被 Redis 过期机制自动释放,客户端 B 请求锁成功,出现并发执行;
  • 客户端 A 执行完业务逻辑后,释放锁,删除对应的 key;
  • 对应锁已经被客户端 B 获取到了,客户端A释放的锁实际是客户端B持有的锁。

可以看到,第一个线程的逻辑还没执行完,第二个线程也成功获得了锁,加锁的代码或者资源并没有得到严格的串行操作,同时由于叠加了删除和释放锁操作,导致了加锁的混乱。

如何避免这个问题呢?首先,基于 Redis 的分布式锁一般是用于耗时比较短的瞬时性任务,业务上超时的可能性较小;其次,在获取锁时,可以设置 value 为一个随机数,在释放锁时进行读取和对比,确保释放的是当前线程持有的锁,一般是通过 Redis 结合 Lua 脚本的方案实现;最后,需要添加完备的日志,记录上下游数据链路,当出现超时,则需要检查对应的问题数据,并且进行人工修复。

分布式锁的高可用

上面分布式锁的实现方案中,都是针对单节点 Redis 而言的,在生产环境中,为了保证高可用,避免单点故障,通常会使用 Redis 集群。

集群下分布式锁存在哪些问题

集群环境下,Redis 通过主从复制来实现数据同步,Redis 的主从复制(Replication)是异步的,所以单节点下可用的方案在集群的环境中可能会出现问题,在故障转移(Failover) 过程中丧失锁的安全性。

由于 Redis 集群数据同步是异步的,假设 Master 节点获取到锁后在未完成数据同步的情况下,发生节点崩溃,此时在其他节点依然可以获取到锁,出现多个客户端同时获取到锁的情况。

我们模拟下这个场景,按照下面的顺序执行:

  • 客户端 A 从 Master 节点获取锁;
  • Master 节点宕机,主从复制过程中,对应锁的 key 还没有同步到 Slave 节点上;
  • Slave 升级为 Master 节点,于是集群丢失了锁数据;
  • 其他客户端请求新的 Master 节点,获取到了对应同一个资源的锁;
  • 出现多个客户端同时持有同一个资源的锁,不满足锁的互斥性。

可以看到,单实例场景和集群环境实现分布式锁是不同的,关于集群下如何实现分布式锁,Redis 的作者 Antirez(Salvatore Sanfilippo)提出了 Redlock 算法,我们一起看一下。

Redlock 算法的流程

Redlock 算法是在单 Redis 节点基础上引入的高可用模式,Redlock 基于 N 个完全独立的 Redis 节点,一般是大于 3 的奇数个(通常情况下 N 可以设置为 5),可以基本保证集群内各个节点不会同时宕机。

假设当前集群有 5 个节点,运行 Redlock 算法的客户端依次执行下面各个步骤,来完成获取锁的操作:

  • 客户端记录当前系统时间,以毫秒为单位;
  • 依次尝试从 5 个 Redis 实例中,使用相同的 key 获取锁,当向 Redis 请求获取锁时,客户端应该设置一个网络连接和响应超时时间,超时时间应该小于锁的失效时间,避免因为网络故障出现的问题;
  • 客户端使用当前时间减去开始获取锁时间就得到了获取锁使用的时间,当且仅当从半数以上的 Redis 节点获取到锁,并且当使用的时间小于锁失效时间时,锁才算获取成功;
  • 如果获取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间,减少超时的几率;
  • 如果获取锁失败,客户端应该在所有的 Redis 实例上进行解锁,即使是上一步操作请求失败的节点,防止因为服务端响应消息丢失,但是实际数据添加成功导致的不一致。

在 Redis 官方推荐的 Java 客户端 Redisson 中,内置了对 RedLock 的实现。下面是官方网站的链接,感兴趣的同学可以去了解一下:
redis-distlock
redisson-wiki

分布式系统设计是实现复杂性和收益的平衡,考虑到集群环境下的一致性问题,也要避免过度设计。在实际业务中,一般使用基于单点的 Redis 实现分布式锁就可以,出现数据不一致,通过人工手段去回补。

总结

今天分享了如何使用 Redis 来逐步优化分布式锁实现的相关内容,包括一个完备的分布式锁应该支持哪些特性,使用 Redis 实现分布式锁的几种不同方式,最后简单介绍了一下 Redis 集群下的 RedLock 算法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/220220.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用Flask搭建简单的web模型部署服务

目录结构如下: 分类模型web部署 classification.py import os import cv2 import numpy as np import onnxruntime from flask import Flask, render_template, request, jsonifyapp Flask(__name__)onnx_session onnxruntime.InferenceSession("mobilen…

Tomcat部署Activiti官方 流程设计器【数据库更换为Mysql !!!】

一、官网下载activiti6 解压后结构如下: database: 存放数据库对象相关脚本,包含不同的数据库脚本 libs: 包含activiti开发过程中需要用到的jar包和源码,不建议通过jar包直接引用,建议通过maven进行管理 wars&am…

大模型应用_FastGPT

1 功能 整体功能,想解决什么问题 官方说明:FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!个人体会…

ubuntu将本机的wifi网络通过网线分享给另一台机器(用于没有有线网络,重装系统后无wifi驱动或者另一台设备没有wifi网卡)

1.将两台机器通过网线连接 2.在pci ethernet中设置选择另一台机器的mac address,ipv4中选择share to other computer,另一台机器上设置为动态ip,连接上之后另一台机器即可上网。

大数据机器学习深度解读DBSCAN聚类算法:技术与实战全解析

大数据机器学习深度解读DBSCAN聚类算法:技术与实战全解析 一、简介 在机器学习的众多子领域中,聚类算法一直占据着不可忽视的地位。它们无需预先标注的数据,就能将数据集分组,组内元素相似度高,组间差异大。这种无监…

Github 2023-12-14开源项目日报 Top10

根据Github Trendings的统计,今日(2023-12-14统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量非开发语言项目5TypeScript项目2JavaScript项目1Jupyter Notebook项目1PHP项目1 基于项目的学习 创建周期&a…

Python进阶(一)

1.Python中一切皆对象 1.1 Python中一切皆对象 JAVA中有class和object这两个概念,object只是class的一个实例。 而在Python中面向对象更加的彻底,class和函数都是对象。代码也是对象,模块也是对象。 函数和类也是对象,对象有四…

AUTOSAR_SWS_LogAndTrace文档中文翻译

1 Introduction and functional overview 本规范规定了AUTOSAR自适应平台日志和跟踪的功能。 日志和跟踪为AA提供接口,以便将日志信息转发到通信总线、控制台或文件系统。 提供的每个日志记录信息都有自己的严重性级别。对于每个严重级别,都提供了一个单…

bugku--source

dirsearch扫一下 题目提示源代码(source) 也就是源代码泄露,然后发现有.git 猜到是git泄露 拼接后发现有文件 但是点开啥也没有 kali里面下载下来 wegt -r 下载网站的所有内容 ls 查看目录 cd 进入到目录里面 gie reflog 引用日志使用…

如何用python编写抢票软件,python爬虫小程序抢购

大家好,小编来为大家解答以下问题,python小程序抢购脚本怎么写,如何用python编写抢票软件,现在让我们一起来看看吧! 大家好,小编来为大家解答以下问题,python小程序抢购脚本怎么写,如…

【洛谷算法题】P1422-小玉家的电费【入门2分支结构】

👨‍💻博客主页:花无缺 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P1422-小玉家的电费【入门2分支结构】🌏题目描述🌏输入格…

diag_service的GLINK_IST是怎么来的

背景 平台:SA8155,QA 1.2.1 8155上集成了很多IP核,其中有不少的IP本质上是arm M核或者R核,这些模块在开发或者使用过程中也是需要监控和诊断的,但是他们并没有外部的调试接口,高通设计了整套诊断框架通过APSS&#x…

OpenHarmony应用开发——实现Toast提示功能-鸿蒙物联网应用开发-HarmonyOs应用开发

一、前言 本文我们将实现Toast样式的功能,以便于和用户进行简单、基本的信息交互。需要注意的是,本专栏(OpenHarmony应用开发)不阐述UI设计内容,而主要介绍大家开发中常遇到、常使用的功能问题,以及在物联网…

基于Dockerfile创建LNMP

实验组件 172.111.0.10:nginx docker-nginx 172.111.0.20:mysql docker-mysql 172.111.0.30:php docker-php 实验步骤 1.建立nginx-lnmp镜像及容器 cd /opt mkdir nginx cd nginx/ --上传nginx-1.22.0.tar.gz和wordpress-6.4.2-zh_C…

Android13适配所有文件管理权限

Android13适配所有文件管理权限 前言: 很早之前在Android11上面就适配过所有文件管理权限,这次是海外版升级到Android13,由于选择相册用的是第三方库,组内的同事没有上架Google的经验直接就提交代码,虽然功能没有问题…

自动化补丁管理软件

什么是自动化补丁管理 自动补丁管理(或自动补丁)是指整个补丁管理过程的自动化,从扫描网络中的所有系统到检测缺失的补丁,在一组测试系统上测试补丁,将它们部署到所需的系统,并提供定期更新和补丁部署状态…

国产数据库适配-达梦(DM)

1、通用性 达梦数据库管理系统兼容多种硬件体系,可运行于X86、X64、SPARC、POWER等硬件体系之上。DM各种平台上的数据存储结构和消息通信结构完全一致,使得DM各种组件在不同的硬件平台上具有一致的使用特性。 达梦数据库管理系统产品实现了平台无关性&…

【算法与数据结构】37、LeetCode解数独

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:本题也是一道困难题,难点在于如何构建数独棋盘,如何检查棋盘的合法性&#xff…

H5开发App应用程序的常见问题以及解决方案

Hello大家好,我是咕噜铁蛋,天冷记得添衣,ok话说回来H5开发成为了一种流行的方式来构建跨平台的移动应用程序。然而,在H5开发App应用程序的过程中,我们常常会遇到一些问题,这些问题可能涉及性能、兼容性、用…

人工智能_机器学习065_SVM支持向量机KKT条件_深度理解KKT条件下的损失函数求解过程_公式详细推导---人工智能工作笔记0105

之前我们已经说了KKT条件,其实就是用来解决 如何实现对,不等式条件下的,目标函数的求解问题,之前我们说的拉格朗日乘数法,是用来对 等式条件下的目标函数进行求解. KKT条件是这样做的,添加了一个阿尔法平方对吧,这个阿尔法平方肯定是大于0的,那么 可以结合下面的文章去看,也…