【Java】三种方案实现 Redis 分布式锁

序言

setnx、Redisson、RedLock 都可以实现分布式锁,从易到难得排序为:setnx < Redisson < RedLock。一般情况下,直接使用 Redisson 就可以啦,有很多逻辑框架的作者都已经考虑到了。

方案一:setnx

1.1、简单实现

下面的锁实现可以用在测试或者简单场景,但是它存在以下问题,使其不适合用在正式环境。

  1. 锁可能被误删: 在解锁操作中,如果一个线程的锁已经因为超时而被自动释放,然后又被其他线程获取到,这时原线程再来解锁就会误删其他线程的锁。
  2. **临界区代码不安全:**线程 A 还没有执行完临界区代码,锁就过期释放掉了。线程 B 此时又能获取到锁,进入临界区代码,导致了临界区代码非串行执行,带来了线程不安全的问题。
public class RedisLock {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 加锁*/private boolean tryLock(String key) {Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}/*** 解锁*/private void unlock(String key) {redisTemplate.delete(key);}
}

1.2、使用 lua 脚本加锁、解锁

lua 脚本是原子的,不管写多少 lua 脚本代码,redis 都是通过一条命令去执行的。

下述代码使用了 lua 脚本进行加锁/解锁,保证了加锁和解锁的时候都是原子性的,是一种相对较好的 Redis 分布式锁的实现方式。

它支持获得锁的线程才能释放锁,如果线程 1 因为锁过期而丢掉了锁,然后线程 2 拿到了锁。此时线程 1 的业务代码执行完以后,也无法释放掉线程 2 的锁,解决了误删除的问题。

public class RedisLock {private final StringRedisTemplate redisTemplate;public RedisDistributedLock(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public boolean tryLock(String lockKey, String lockValue, long expireTimeInSeconds) {try {//加锁成功返回 true,加锁失败返回 fasle。效果等同于 redisTemplate.opsForValue().setIfAbsentString luaScript = "if redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) then return 1 else return 0 end";RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue, String.valueOf(expireTimeInSeconds));return result != null && result == 1;} catch (Exception e) {// Handle exceptionsreturn false;}}public void unlock(String lockKey, String lockValue) {try {//拿到锁的线程才可以释放锁,lockValue 可以设置为 uuid。String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);} catch (Exception e) {// Handle exceptions}}
}

方案二:Redisson

Redisson 是一个基于 Java 的客服端,通过 Redisson 我们可以快速安全的实现分布式锁。Redisson 框架具有可重入锁的支持、分布式锁的实现、锁的自动续期、红锁支持等多种特点,给我们开发过程中带来了极大的便利。

@Component
public class RedisLock {@Resourceprivate RedissonClient redissonClient;/*** lock(), 拿不到lock就不罢休,不然线程就一直block*/public RLock lock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);lock.lock();return lock;}/*** leaseTime为加锁时间,单位为秒*/public RLock lock(String lockKey, long leaseTime) {RLock lock = redissonClient.getLock(lockKey);lock.lock(leaseTime, TimeUnit.SECONDS);return null;}/*** timeout为加锁时间,时间单位由unit确定*/public RLock lock(String lockKey, TimeUnit unit, long timeout) {RLock lock = redissonClient.getLock(lockKey);lock.lock(timeout, unit);return lock;}/*** @param lockKey   锁 key* @param unit      单位* @param waitTime  等待时间* @param leaseTime 锁有效时间* @return 加锁成功? true:成功 false: 失败*/public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {RLock lock = redissonClient.getLock(lockKey);try {return lock.tryLock(waitTime, leaseTime, unit);} catch (InterruptedException e) {return false;}}/*** unlock*/public void unlock(String lockKey) {RLock lock = redissonClient.getLock(lockKey);if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}}/*** unlock* @param lock 锁*/public void unlock(RLock lock) {lock.unlock();}
}

方案三:RedLock

RedLock 又叫做红锁,是 Redis 官方提出的一种分布式锁的算法,红锁的提出是为了解决集群部署中 Redis 锁相关的问题。

比如当线程 A 请求锁成功了,这时候从节点还没有复制锁。此时主节点挂掉了,从节点成为了主节点。线程 B 请求加锁,在原来的从节点(现在是主节点)上加锁成功。这时候就会出现线程安全问题。

下图是红锁的简易思路。红锁认为 (N / 2) + 1 个节点加锁成功后,那么就认为获取到了锁,通过这种算法减少线程安全问题。简单流程为:

  1. 顺序向五个节点请求加锁
  2. 根据一定的超时时间判断是否跳过该节点
  3. (N / 2) + 1 个节点加锁成功并且小于锁的有效期
  4. 认定加锁成功

image-20231104162822749

@Service
public class MyService {private final RedissonClient redissonClient;@Autowiredpublic MyService(RedissonClient redissonClient) {this.redissonClient = redissonClient;}public void doSomething() {RLock lock1 = redissonClient.getLock("lock1");RLock lock2 = redissonClient.getLock("lock2");RLock lock3 = redissonClient.getLock("lock3");RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);redLock.lock();try {// 业务逻辑} finally {redLock.unlock();}}
}

总结

  • 自己玩或者测试的时候使用方案一的简单实现。
  • 单机版 Redis 使用方案二。
  • Redis 集群使用方案三。

最后

我是 xiucai,一位后端开发工程师。

如果你对我感兴趣,请移步我的个人博客,进一步了解。

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于个人博客,未经许可禁止转载💌

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

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

相关文章

深度学习_9_图片分类数据集

散装代码&#xff1a; import matplotlib.pyplot as plt import torch import torchvision from torch.utils import data from torchvision import transforms from d2l import torch as d2ld2l.use_svg_display()# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式…

数据库的事务四大特性(ACID)、详解隔离性以及隔离级别、锁

文章目录 &#x1f389;数据库的事务四大特性&#xff08;ACID&#xff09;以及隔离性一、事务的四大特性✨1、原子性&#xff08;Atomicity&#xff09;&#x1f38a;2、一致性&#xff08;Consistency&#xff09;&#x1f38a;3、隔离性&#xff08;Isolation&#xff09;&a…

leetcode:389. 找不同

一、题目 函数原型&#xff1a;char findTheDifference(char * s, char * t) 二、思路 作者原先的思路是先将两个字符串从小到大排序&#xff0c;然后两个字符串依次比较。若出现字符串t中的元素和字符串s不相等&#xff0c;则说明该元素就是被添加的字母。 但是&#xff0c;该…

【LeetCode】118. 杨辉三角

118. 杨辉三角 难度&#xff1a;简单 题目 给定一个非负整数 *numRows&#xff0c;*生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例…

农业水土环境与面源污染建模及对农业措施响应

目录 ​专题一 农业水土环境建模概述 专题二 ArcGIS入门 专题三 农业水土环境建模流程 专题四 DEM数据制备流程 专题五 土地利用数据制备流程 专题六 土壤数据制备流程 专题七 气象数据制备流程 专题八 农业措施数据制备流程 专题九 参数率定与结果验证 专题十 模型结…

算法随想录算法训练营第四十七天| 647. 回文子串 516.最长回文子序列

647. 回文子串 题目&#xff1a;给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。具有不同开始位置或结束位置的子串&#xff0c;即使是由相同的字…

Python之Excel——复制一个sheet当做模板,生成多个sheet

目录 专栏导读背景思路1、加载模板2、项目文件2、完整版代码:3、视频演示:4、总结:&#x1f44d; 该系列文章专栏&#xff1a;[Python办公自动化专栏] 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍&…

一文弄懂synchronized

简述 synchronized是什么? synchronized 关键字是一种同步锁&#xff0c;它可以保证在一个时刻只有一个线程可以执行某段代码。synchronized 关键字可以用在方法、代码块、静态方法和静态代码块上。 synchronized怎么用&#xff1f; synchronized是Java中用于实现线程同步…

Android和JNI交互 : 常见的图像格式转换 : NV21、RGBA、Bitmap等

1. 前言 最近在使用OpenCV处理图片的时候&#xff0c;经常会遇到需要转换图像的情况&#xff0c;网上相关资料比较少&#xff0c;也不全&#xff0c;有时候得费劲老半天才能搞定。 自己踩了坑后&#xff0c;在这里记录下&#xff0c;都是我在项目中遇到的图像转化操作&#xf…

AI开源 - LangChain UI 之 Flowise

原文&#xff1a;AI开源 - LangChain UI 之 Flowise 一、Flowise 简介 Flowise 是一个为 LangChain 设计的用户界面(UI)&#xff0c;使得使用 LangChain 变得更加容易&#xff08;低代码模式&#xff09;。 通过拖拽可视化的组件&#xff0c;组建工作流&#xff0c;就可以轻…

ScrapeKit库中Swift爬虫程序写一段代码

以下是一个使用ScrapeKit库的Swift爬虫程序&#xff0c;用于爬取网页视频的代码&#xff1a; import ScrapeKit// 创建一个配置对象&#xff0c;用于指定爬虫ip服务器信息 let config Configuration(proxyHost: "duoip", proxyPort: 8000)// 创建一个爬虫对象 let s…

diffusers-Load pipelines,models,and schedulers

https://huggingface.co/docs/diffusers/using-diffusers/loadinghttps://huggingface.co/docs/diffusers/using-diffusers/loading 有一种简便的方法用于推理是至关重要的。扩散系统通常由多个组件组成&#xff0c;如parameterized model、tokenizers和schedulers&#xff0c…

Spring-Spring 之底层架构核心概念解析

BeanDefinition BeanDefinition表示Bean定义&#xff0c;BeanDefinition中存在很多属性用来描述一个Bean的特点。比如&#xff1a; class&#xff0c;表示Bean类型scope&#xff0c;表示Bean作用域&#xff0c;单例或原型等lazyInit&#xff1a;表示Bean是否是懒加载initMeth…

LeetCode 421. 数组中两个数的最大异或值

原题链接&#xff1a;https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/description/?envTypedaily-question&envId2023-11-04 题目分析 异或且时间复杂度在nlogn内第一反应想到字典树&#xff0c;扫一遍存进字典树&#xff0c;然后遍历每个数&…

【Git企业开发】第四节.Git的分支管理策略和bug分支

文章目录 前言一、Git的分支管理策略 1.1 Fast forward 模式和--no-ff 模式 1.2 企业分支管理策略二、bug分支三、删除临时分支四、总结总结 前言 一、Git的分支管理策略 1.1 Fast forward 模式和--no-ff 模式 通常合并分支时&#xff0c;如果可能&#xff0c;Git 会…

AI:51-基于深度学习的电影评价

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌本专栏包含以下学习方向: 机器学习、深度学…

【CSS】div 盒子居中的常用方法

<body><div class"main"><div class"box"></div></div> </body>绝对定位加 margin: auto; &#xff1a; <style>* {padding: 0;margin: 0;}.main {width: 400px;height: 400px;border: 2px solid #000;positio…

C++prime之输入输出文件

作为一种优秀的语言&#xff0c;C必然是能操作文件的&#xff0c;但是我们要知道&#xff0c;C是不直接处理输入输出的&#xff0c;而是通过一族定义在标准库中的类型来处理IO的。 ‘流’和‘缓冲区’ ‘流’和‘缓冲区’ C程序把输入输出看作字节流&#xff0c;并且其只检查…

Microsoft Dynamics 365 CE 扩展定制 - 5. 外部集成

本章内容包括: 使用.NET从其他系统连接到Dynamics 365使用OData(Java)从其他系统连接到Dynamics 365使用外部库从外部源检索数据使用web应用程序连接到Dynamics 365运行Azure计划任务设置Azure Service Bus终结点与Azure Service Bus构建近乎实时的集成使用来自Azure服务总线…

接口测试入门,如何划分接口文档

1.首先最主要的就是要分析接口测试文档&#xff0c;每一个公司的测试文档都是不一样的。具体的就要根据自己公司的接口而定&#xff0c;里面缺少的内容自己需要与开发进行确认。 我认为一针对于测试而言的主要的接口测试文档应该包含的内容分为以下几个方面。 a.具体的一个业…