【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.hqwc.cn/news/160504.html

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

相关文章

九、W5100S/W5500+RP2040树莓派Pico<SNTP 获取网络时间>

文章目录 1 前言2 协议简介2.1 什么是SNTP2.2 SNTP的优点2.3 SNTP原理2.4 应用场景 3 WIZnet以太网芯片4 SNTP网络设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 随着科技的不断进步和应用需求的不断变…

虚拟dom及diff算法之 —— h函数和diff函数

新虚拟dom和老虚拟dom进行diff算法&#xff08;精细化比较&#xff09;&#xff0c;算出如何最小量更新&#xff0c;最后反映到真实dom上 diff是发生在虚拟dom上的 模板编译 虚拟dom如何产生 - 渲染函数&#xff08;h函数&#xff09; h函数产生虚拟节点&#xff08;vnode&a…

Linux: MV指令(覆盖替换重命名)

MV指令 mv [-bfiuv] [–help] [–version] [-S <附加字尾>] [-V <方法>] [源文件或目录] [目标文件或目录]-b 若需覆盖文件&#xff0c;则覆盖前先行备份-f 若目标文件或目录与现有的文件或目录重复&#xff0c;则直接覆盖现有的文件或目录-i 覆盖前先行询问用户–…

【LeetCode刷题-队列】--2073.买票需要的时间

2073.买票需要的时间 方法一&#xff1a;使用队列 class Solution {public int timeRequiredToBuy(int[] tickets, int k) {Queue<TicketBuyer> queue new LinkedList<>();for(int i 0;i<tickets.length;i){TicketBuyer buyer new TicketBuyer();buyer.inde…

快速了解推荐引擎检索技术

目录 一、推荐引擎和其检索技术 二、推荐引擎的整体架构和工作过程 &#xff08;一&#xff09;用户画像 &#xff08;二&#xff09;文章画像 &#xff08;三&#xff09;推荐算法召回 三、基于内容的召回 &#xff08;一&#xff09;召回算法 &#xff08;二&#xf…

linux基础学习

迷茫不可怕&#xff0c;说明你还在向前走 失败不可怕&#xff0c;只要你还能爬起来 ​​​​​​​ 目录 ​编辑 为什么要学习linux操作系统&#xff1f; linux系统的安装 FinalShell 虚拟机快照 第二章 基本知识 linux目录结构 linux路径的描述方式 linux命令入门…

【C语言进阶】之动态内存管理笔试题及柔性数组

【C语言进阶】之动态内存管理笔试题 1.动态内存管理笔试题汇总1.1第一道题1.2第二道题1.3第三道题1.4第四道题 2.C/C内存管理3.柔性数组3.1什么是柔性数组3.2柔性数组的使用3.2柔性数组的优点 &#x1f4c3;博客主页&#xff1a; 小镇敲码人 &#x1f680; 欢迎关注&#xff1a…

【已解决】设置SSH主机:VS Code-正在本地下载 VS Code 服务器

问题描述 很简单&#xff0c;就是我电脑强制重启之后用vscode再去连服务器&#xff0c;发现连不上了 解决办法 如上图&#xff0c;点击重试按钮&#xff0c;下面的这些东西就可以复制粘贴了 ctrf查找commit&#xff0c;这个时候就能找到一串d037ac076cee195194f93ce6fe2bdfe296…

Spring Security OAuth 2.0 资源服务器— JWT

目录 一、JWT的最小依赖 二、JWT的最基本配置 1、指定授权服务器 2、初始预期&#xff08;Startup Expectations&#xff09; 3、运行时预期&#xff08;Runtime Expectations&#xff09; 三、JWT认证是如何工作的 四、直接指定授权服务器 JWK Set Uri 五、提供 audie…

中国联通携手华为助力长城精工启动商用5G-A柔性产线

[中国&#xff0c;河北&#xff0c;2023年11月3日] 近日&#xff0c;中国联通携手华为助力精诚工科汽车系统有限公司保定自动化技术分公司&#xff08;简称长城精工自动化&#xff09;启动5G-A超高可靠性超低时延柔性产线的商用阶段。 在河北保定精工自动化工厂&#xff0c;5G…

Java医院HIS系统源码

Java医院HIS系统源码 项目描述 该项目是用springbootlayuishiro写的医院管理系统&#xff0c;该系统的业务比较复杂&#xff0c;数据库一共有36张表。项目的视频业务参考文档&#xff0c;都在百度云盘中。可以先看看视频和参考文档。 运行环境 jdk8mysqlIntelliJ IDEAmaven…

STM32中微秒延时的实现方式

STM32中微秒延时的实现方式 0.前言一、裸机实现方式二、FreeRTOS实现方式三、定时器实现&#xff08;通用&#xff09;4、总结 0.前言 最近在STM32驱动移植过程中需要用到微秒延时来实现一些外设的时序&#xff0c;由于网上找到的驱动方法良莠不齐&#xff0c;笔者在实现时序过…