基于 Redis 实现分布式锁的全过程

前言

这一篇文章拖了有点久,虽然在项目中使用分布式锁的频率比较高,但整理成文章发布出来还是花了一点时间。在一些移动端、用户量大的互联网项目中,经常会使用到 Redis 分布式锁作为控制访问高并发的工具。

一、关于分布式锁

总结:分布式锁是一种在分布式系统中用于控制并发访问的机制。

在分布式系统中,多个客户端同时对一个资源进行操作时,容易影响数据的一致性。分布式锁的主要作用就是确保同一时刻只有一个客户端能够对某个资源进行操作,以避免数据不一致的问题。

主要应用场景:

  • 数据库并发控制:在分布式数据库中,多个线程同时对某张表进行操作时,可能会出现并发冲突问题,使用分布式锁可以确保同一时刻只有一个线程能够对该表进行操作,避免并发冲突。
  • 分布式缓存:在分布式缓存中,如果多个线程同时对某个缓存进行操作,可能会出现缓存数据不一致的问题。使用分布式锁可以确保同一时刻只有一个线程能够对该缓存进行操作,保证缓存数据的一致性。
  • 分布式任务调度:在分布式任务调度中,多个线程同时执行某个任务,可能出现任务被重复执行的问题,使用分布式锁可以确保同一时刻只有一个线程能够执行该任务,避免任务被重复执行。

目前主流的分布式锁实现方案是基于 Redis 来实现的,今天要分享的有 2 种实现: 基于 RedLock 红锁和基于 setIfAbsent() 方法


二、RedLock 红锁(不推荐)

RedLock 对于多节点(集群)的分布式锁算法使用了多个实例来存储锁信息,这种方式可以提高获取锁的速度和成功率,从而可以有效地防止单点故障;

但由于 RedLock 的实现比较复杂,且容易因为配置不正确而导致锁无法获取。此外,如果 Redis 服务宕机,也会导致锁无法正常使用。

RedLock 简单图示

RedLock 会对集群的每个节点进行加锁,如果大多数(N/2+1)加锁成功了,则认为获取锁成功。这个过程中可能会因为网络问题,或节点超时的问题,影响加锁的性能,故而在最新的 Redisson 版本中中已经正式宣布废弃 RedLock。

以下是一个简易的 demo 实现:

包括两部分:暴露给业务系统逻辑层使用的静态方法、锁的底层实现。思路用代码和注释说得比较清楚了,大家可以看一下:

    @Overridepublic String set(String key, String value, RedisSetArgs redisSetArgs) {//这里引入的是 Jedis 的客户端,后来被抛弃了,Redis 推荐的是 Redission try (Jedis jedis = getJedis()) {SetParams setParams = new SetParams();if (redisSetArgs.isNx()) {setParams.nx();} else if (redisSetArgs.isXx()) {setParams.xx();}if (Objects.nonNull(redisSetArgs.getEx())) {setParams.ex(redisSetArgs.getEx());} else if (Objects.nonNull(redisSetArgs.getPx())) {setParams.px(redisSetArgs.getPx());}return jedis.set(SafeEncoder.encode(buildKey(key)), SafeEncoder.encode(value), setParams);}}public static boolean unlock(String key, String requestId) {//Lua 脚本Object result = RedisCacheCommonFactory.getRedisCache().loose(LUA_SCRIPT, Collections.singletonList(key), Collections.singletonList(requestId));if (NumberUtils.LONG_ONE.equals(result)) {return true;}return false;}@Overridepublic Object loose(String script, List<String> keys, List<String> args) {try (Jedis jedis = getJedis()) {return jedis.eval(SafeEncoder.encode(script), keys.stream().map(this::buildKey).map(SafeEncoder::encode).collect(Collectors.toList()),args.stream().map(SafeEncoder::encode).collect(Collectors.toList()));}}

三、基于 redisTemplate

以下推荐的是在分布式集群环境中的最佳实践,其实无论是单机还是集群,保证原子性都是第一位的,如果能同时保证性能和高可用,那么就是一个可靠的分布式锁解决方案。

主要思路是:设置锁时,使用 redisTemplate,因为其底层实际包含了 setnx 、expire 的功能,起到了原子操作的效果。给 key 设置随机且唯一的值,并且只有在 key 不存在时才设置成功返回 True,并且设置 key 的过期时间(最好是毫秒级别)。

RedLock 简单图示

基于 setIfAbsent() 方法简单图示

以下同样给出一个简单的示例,包括两部分:暴露给业务系统逻辑层使用的静态方法、锁的底层实现。注释写得比较清楚了:

    public Boolean setIfAbsent(String key, String value, Duration timeout) {return this.redisTemplate.opsForValue().setIfAbsent(key, value, timeout);}public Boolean loose(String unLockKeyCheck) {if (unLockKeyCheck== null) {return Boolean.FALSE;}if (this.unLockRedisResult(redisLockKey, unLockKeyCheck)) {return Boolean.TRUE;}return Boolean.FALSE;}private Boolean unLockRedisResult(String redisLockKey, String value) {//Lua 脚本RedisScript<Long> commonRedisLuaScript = new GeneraCommonRedisLuaScript<>(redisLuaScript, Long.class);Long value = redisTemplate.execute(commonRedisLuaScript, Collections.singletonList(key), value);boolean flag = Objects.NonNull(value) && NumberUtils.Integer_ONE.equals(value)return flag ;}

四、使用示例

下面分别给出两个使用示例分别来介绍怎么在具体的业务场景中去使用的 demo,一般来说针对数据库的并发操作和多线程的并发任务操作,会使用得比较多。

至于为什么不使用分布式锁去保证缓存数据的一致性,其实是有专门的分布式缓存方案的:什么是 Redis 缓存?它解决了什么问题?怎么使用它?-CSDN博客

    @Testpublic void testRedLock(){final String requestId = UUIDUtils.generateUUID();if (RedisRedLock.attemptLock("xxxSys.insert.xxxId(唯一)", requestId, 3L, TimeUnit.SECONDS)) {try {log.info("数据库xxx高并发写入成功!");} catch (Throwable e) {log.error("失败信息: error", e);} finally {RedisRedLock.unlock("xxxSys.insert.xxxId(唯一)", requestId);}}}
    @Testpublic void testDistributedLock(){RedisLock redisLock = RedisLockUtils.getRedisLock("xxxSys.insert.xxxId(唯一)" ,3,1);Assert.hasText(redisLock.getRedisLock(), "操作出错,请检查!");if (redisLock .loose("xxxSys.insert.xxxId(唯一)")){log.info("释放锁成功!");}}

五、文章小结

到这里基于 Redis 实现分布式锁的全过程就分享完了,其实基于 Redis 实现分布式锁还有许多底层和实际应用的情况没有展开来说。目前笔者虽然有较多使用,但还是感到技术的海洋深不见底:学到的越多就感觉到自己的不足越多。

最后,如果文章有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区里交流!

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

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

相关文章

计算机图形学入门05:投影变换

1.投影变换 上一章已经介绍了投影变换&#xff0c;就是将三维图像投影到二维平面上&#xff0c;而投影变换又分为正交投影(Orthographic Projection)和透视投影(Perspective Projection)。如下图&#xff1a; 正交投影 没有近大远小的现象&#xff0c;无论图形与视点距离是远是…

机器学习学习

机器学习类型(按学习方式分):监督学习、半监督学习、无监督学习、强化学习; 通过已知标签训练集训练模型,使用模型及逆行预测、测试; 向量表示法,其中每一维对应一个特征(feature)或者称为属性,记为[x1,x2,...,xn] 特征值、特征、标签,共同完成训练集的数据填充,最…

设计模式基础知识点(七大原则、UML类图)

Java设计模式&#xff08;设计模式七大原则、UML类图&#xff09; 设计模式的目的设计模式七大原则单一职能原则&#xff08;SingleResponsibility&#xff09;接口隔离原则&#xff08;InterfaceSegreation&#xff09;依赖倒转原则&#xff08;DependenceInversion&#xff0…

Python 关于字符串格式化

在Python中&#xff0c;字符串格式化有以下几种方法&#xff1a; 1.可以使用字符串的str.center(width), str.ljust(width), 和 str.rjust(width)方法来实现字符串的居中、左对齐和右对齐操作。 居中对齐&#xff1a; text "Python" centered_text text.center(10…

pytest-sugar插件:对自动化测试用例加入进度条

摘要 在自动化测试过程中&#xff0c;测试进度的可视化对于开发者和测试工程师来说非常重要。本文将介绍如何使用pytest-sugar插件来为pytest测试用例添加进度条&#xff0c;从而提升测试的可读性和用户体验。 1. 引言 自动化测试是软件开发过程中不可或缺的一部分&#xff…

Django Celery技术详解

文章目录 简介安装和配置创建并调度任务启动Celery Worker在视图中调用异步任务拓展功能 简介 Django Celery 是一个为Django应用程序提供异步任务处理能力的强大工具。它通过与消息代理&#xff08;如RabbitMQ、Redis&#xff09;集成&#xff0c;可以轻松地处理需要长时间运…

Windows11 wsl2编译Android14 使用ASfP Debug windows上启动的模拟器

wsl2的安装和配置 安装&#xff1a; 直接百度搜索最新的wsl2安装教程即可&#xff0c;官网&#xff1a;https://learn.microsoft.com/zh-cn/windows/wsl/install 1. 启用适用于 Linux 的 Windows 子系统(以管理员身份打开 PowerShell 并运行) Enable-WindowsOptionalFeature…

C++ | Leetcode C++题解之第125题验证回文串

题目&#xff1a; 题解&#xff1a; class Solution { public:bool isPalindrome(string s) {int n s.size();int left 0, right n - 1;while (left < right) {while (left < right && !isalnum(s[left])) {left;}while (left < right && !isalnu…

fyne apptab布局

fyne apptab布局 AppTabs 容器允许用户在不同的内容面板之间切换。标签要么只是文本&#xff0c;要么是文本和一个图标。建议不要混合一些有图标的标签和一些没有图标的标签。 package mainimport ("fyne.io/fyne/v2/app""fyne.io/fyne/v2/container"//&…

全国产飞腾模块麒麟信安操作系统安全漏洞

1、背景介绍 目前在全国产飞腾模块上部署了麒麟信安操作系统&#xff0c;经第三方机构检测存在以下漏洞 操作系统版本为 内核版本为 openssh版本为 2、openssh CBC模式漏洞解决 首先查看ssh加密信息 nmap --script "ssh2*" 127.0.0.1 | grep -i cbc 可以通过修改/…

Python | Leetcode Python题解之第125题验证回文串

题目&#xff1a; 题解&#xff1a; class Solution:def isPalindrome(self, s: str) -> bool:n len(s)left, right 0, n - 1while left < right:while left < right and not s[left].isalnum():left 1while left < right and not s[right].isalnum():right - …

【linux】运维-基础知识-认知hahoop周边

1. HDFS HDFS&#xff08;Hadoop Distributed File System&#xff09;–Hadoop分布式文件存储系统 源自于Google的GFS论文&#xff0c;HDFS是GFS的克隆版 HDFS是Hadoop中数据存储和管理的基础 他是一个高容错的系统&#xff0c;能够自动解决硬件故障&#xff0c;eg&#xff1a…

进程与线程(四)

进程与线程&#xff08;四&#xff09; 基于System V IPC对象的进程间通信机制SystemV IPC引入查看Linux系统中IPC工具的方式查看所有IPC工具命令&#xff1a;ipcs 查看指定的IPC工具key值获取方法&#xff1a;ftok()函数 消息队列消息队列的特征&#xff1a;消息队列的操作打开…

Jmeter实战教程入门讲解

前言 通过前面对Jmeter元件的讲解&#xff0c;大家应该都知道常用元件的作用和使用了。编写Jmeter脚本前我们需要知道Jmeter元件的执行顺序&#xff0c;可以看看我这篇性能测试学习之路&#xff08;三&#xff09;—初识Jmeter来了解下。下面我将以工作中的一个简单的实例带大…

使用ssh连接ubuntu

一、下载连接工具 常见的连接工具右fianlshell、xshell等等。在本文章中使用的finalshell&#xff0c;工具可以去官网上下载&#xff0c;官网下载。 二、Ubuntu中配置shh 1、使用下面指令更新软件包&#xff08;常用于下载安装或更新软件时使用&#xff0c;更新到最新的安装…

正邦科技(day1)

1&#xff1a;充电桩工作了两个半小时&#xff0c;已用电量13度电&#xff08;一般的话是一个小时7度电&#xff09; 2&#xff1a;火线&#xff08;红色&#xff0c;棕色&#xff09;&#xff0c;零线&#xff08;蓝色&#xff09; 3&#xff1a;充电桩工作了两个半小时&#…

(自适应手机端)响应式服装服饰外贸企业网站模板

(自适应手机端)响应式服装服饰外贸企业网站模板PbootCMS内核开发的网站模板&#xff0c;该模板适用于服装服饰网站、外贸网站等企业&#xff0c;当然其他行业也可以做&#xff0c;只需要把文字图片换成其他行业的即可&#xff1b;自适应手机端&#xff0c;同一个后台&#xff0…

【C++集群聊天服务器(一)】|Linux平台资源受限下boost库和muduo网络库源码编译安装

本人使用的服务器是2G2核 ubuntu22.04 前置工作 muduo库源码github仓库地址&#xff1a; muduo WIndows和Linux平台的boost源码包下载(zip是Windows版&#xff0c;tar.gz是Linux版&#xff0c;你也可以去boost官网下载最新版本) Boost C Libraries 由于muduo网络库是基于boo…

2024年海南省三支一扶报名指南,照片要求

2024年海南省三支一扶报名指南&#xff0c;照片要求 一、考试时间安排&#xff1a; 报名时间&#xff1a;6月1日8:00至6月7日18:00 准考证打印时间&#xff1a;6月17日8:00 考试时间&#xff1a;6月22日 二、招聘人数 海南省计划招募390名高校毕业生

颠仆流离学二叉树2 (Java篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…