热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Redis分布式锁的正确实现集群版

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:1、互斥性。在任意时刻,只有一个客户端能持有锁。2、不会发生死锁。即使有

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1、互斥性。在任意时刻,只有一个客户端能持有锁。
2、不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3、具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
4、解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

package com.hz.tgb.data.redis.lock;import cn.hutool.core.util.IdUtil;
import com.hz.tgb.entity.Book;
import com.hz.tgb.spring.SpringUtils;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.TimeoutUtils;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Supplier;/*** Redis分布式锁 - 集群版** @author hezhao on 2019.11.13*/
@Component
public class RedisClusterLockUtil {/*为了确保分布式锁可用&#xff0c;我们至少要确保锁的实现同时满足以下四个条件&#xff1a;1、互斥性。在任意时刻&#xff0c;只有一个客户端能持有锁。2、不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁&#xff0c;也能保证后续其他客户端能加锁。3、具有容错性。只要大部分的Redis节点正常运行&#xff0c;客户端就可以加锁和解锁。4、解铃还须系铃人。加锁和解锁必须是同一个客户端&#xff0c;客户端自己不能把别人加的锁给解了。*/private static final Logger logger &#61; LoggerFactory.getLogger(RedisLockUtil.class);private static RedisTemplate cacheTemplate;/** OK: Redis操作是否成功 */private static final String REDIS_OK &#61; "OK";/** CONN_NOT_FOUND: Redis链接类型不匹配 */private static final String REDIS_CONN_NOT_FOUND &#61; "CONN_NOT_FOUND";/** 解锁是否成功 */private static final Long RELEASE_SUCCESS &#61; 1L;/** 解锁Lua脚本 */private static final String UNLOCK_LUA_SCRIPT &#61; "if redis.call(&#39;get&#39;, KEYS[1]) &#61;&#61; ARGV[1] then return redis.call(&#39;del&#39;, KEYS[1]) else return 0 end";/*** The number of nanoseconds for which it is faster to spin* rather than to use timed park. A rough estimate suffices* to improve responsiveness with very short timeouts.*/private static final long spinForTimeoutThreshold &#61; 1000000L;/*** 加锁* &#64;param lockKey 锁键* &#64;param requestId 请求唯一标识* &#64;param expireTime 缓存过期时间* &#64;param unit 时间单位* &#64;return true: 加锁成功, false: 加锁失败*/&#64;SuppressWarnings("all")public static boolean lock(String lockKey, String requestId, long expireTime, TimeUnit unit) {// 加锁和设置过期时间必须是原子操作&#xff0c;否则在高并发情况下或者Redis突然崩溃会导致数据错误。try {// 以毫秒作为过期时间long millisecond &#61; TimeoutUtils.toMillis(expireTime, unit);String result &#61; execute(connection -> {Object nativeConnection &#61; connection.getNativeConnection();RedisSerializer keySerializer &#61; (RedisSerializer) getRedisTemplate().getKeySerializer();RedisSerializer valueSerializer &#61; (RedisSerializer) getRedisTemplate().getValueSerializer();// springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包// lettuce连接包下序列化键值&#xff0c;否知无法用默认的ByteArrayCodec解析byte[] keyByte &#61; keySerializer.serialize(lockKey);byte[] valueByte &#61; valueSerializer.serialize(requestId);//lettuce连接包&#xff0c;单机模式&#xff0c;ex为秒&#xff0c;px为毫秒if (nativeConnection instanceof RedisAsyncCommands) {RedisAsyncCommands commands &#61; (RedisAsyncCommands)nativeConnection;// 同步方法执行、setnx禁止异步return commands.getStatefulConnection().sync().set(keyByte, valueByte, SetArgs.Builder.nx().px(millisecond));} else if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {// lettuce连接包&#xff0c;集群模式&#xff0c;ex为秒&#xff0c;px为毫秒RedisAdvancedClusterAsyncCommands clusterAsyncCommands &#61; (RedisAdvancedClusterAsyncCommands) nativeConnection;return clusterAsyncCommands.getStatefulConnection().sync().set(keyByte, valueByte, SetArgs.Builder.nx().px(millisecond));}return REDIS_CONN_NOT_FOUND;});// 如果链接类型匹配不上&#xff0c;使用默认加锁方法if (Objects.equals(result, REDIS_CONN_NOT_FOUND)) {return getRedisTemplate().opsForValue().setIfAbsent(lockKey, requestId)&& getRedisTemplate().expire(lockKey, expireTime, unit);}return REDIS_OK.equals(result);} catch (Exception e) {logger.error("RedisLockUtil lock 加锁失败", e);}return false;}/*** 解锁* &#64;param lockKey 锁键* &#64;param requestId 请求唯一标识* &#64;return true: 解锁成功, false: 解锁失败*/&#64;SuppressWarnings("all")public static boolean unLock(String lockKey, String requestId) {try {// 使用Lua脚本实现解锁的原子性&#xff0c;如果requestId相等则解锁Object result &#61; execute(connection -> {Object nativeConnection &#61; connection.getNativeConnection();RedisSerializer keySerializer &#61; (RedisSerializer) getRedisTemplate().getKeySerializer();RedisSerializer valueSerializer &#61; (RedisSerializer) getRedisTemplate().getValueSerializer();// springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包// lettuce连接包下序列化键值&#xff0c;否知无法用默认的ByteArrayCodec解析byte[] keyByte &#61; keySerializer.serialize(lockKey);byte[] valueByte &#61; valueSerializer.serialize(requestId);//lettuce连接包&#xff0c;单机模式if (nativeConnection instanceof RedisAsyncCommands) {RedisAsyncCommands commands &#61; (RedisAsyncCommands)nativeConnection;// 同步方法执行、setnx禁止异步byte[][] keys &#61; {keyByte};byte[][] values &#61; {valueByte};return commands.getStatefulConnection().sync().eval(UNLOCK_LUA_SCRIPT, ScriptOutputType.INTEGER, keys , values);} else if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {// lettuce连接包&#xff0c;集群模式RedisAdvancedClusterAsyncCommands clusterAsyncCommands &#61; (RedisAdvancedClusterAsyncCommands) nativeConnection;byte[][] keys &#61; {keyByte};byte[][] values &#61; {valueByte};return clusterAsyncCommands.getStatefulConnection().sync().eval(UNLOCK_LUA_SCRIPT, ScriptOutputType.INTEGER, keys , values);}return REDIS_CONN_NOT_FOUND;});// 如果链接类型匹配不上&#xff0c;使用默认解锁方法if (Objects.equals(result, REDIS_CONN_NOT_FOUND)) {return getRedisTemplate().delete(lockKey);}return Objects.equals(RELEASE_SUCCESS, result);} catch (Exception e) {logger.error("RedisLockUtil unLock 解锁失败", e);}return false;}/*** 阻塞锁&#xff0c;拿到锁后执行业务逻辑。注意&#xff1a;超时返回null&#xff0c;程序会继续往下执行* &#64;param callback 业务处理逻辑&#xff0c;入参默认为NULL* &#64;param lockKey 锁键* &#64;param timeout 超时时长, 缓存过期时间默认等于超时时长* &#64;param unit 时间单位* &#64;return R*/public static R tryLock(Supplier callback, String lockKey, long timeout, TimeUnit unit) {return tryLock(callback, lockKey, IdUtil.fastSimpleUUID(), timeout, timeout, unit, TimeOutProcess.DEFAULT);}/*** 阻塞锁&#xff0c;拿到锁后执行业务逻辑。注意&#xff1a;超时会抛出异常* &#64;param callback 业务处理逻辑&#xff0c;入参默认为NULL* &#64;param lockKey 锁键* &#64;param timeout 超时时长, 缓存过期时间默认等于超时时长* &#64;param unit 时间单位* &#64;return R*/public static R tryLockTimeout(Supplier callback, String lockKey, long timeout, TimeUnit unit) {return tryLock(callback, lockKey, IdUtil.fastSimpleUUID(), timeout, timeout, unit, TimeOutProcess.THROW_EXCEPTION);}/*** 阻塞锁&#xff0c;拿到锁后执行业务逻辑。注意&#xff1a;超时会给予补偿&#xff0c;即处理正常逻辑* &#64;param callback 业务处理逻辑&#xff0c;入参默认为NULL* &#64;param lockKey 锁键* &#64;param timeout 超时时长, 缓存过期时间默认等于超时时长* &#64;param unit 时间单位* &#64;return R*/public static R tryLockCompensate(Supplier callback, String lockKey, long timeout, TimeUnit unit) {return tryLock(callback, lockKey, IdUtil.fastSimpleUUID(), timeout, timeout, unit, TimeOutProcess.CARRY_ON);}/*** 阻塞锁&#xff0c;拿到锁后执行业务逻辑* &#64;param callback 业务处理逻辑* &#64;param lockKey 锁键* &#64;param requestId 请求唯一标识* &#64;param timeout 超时时长* &#64;param expireTime 缓存过期时间* &#64;param unit 时间单位* &#64;param timeoutProceed 超时处理逻辑* &#64;return R*/public static R tryLock(Supplier callback, String lockKey, String requestId,long timeout, long expireTime, TimeUnit unit, TimeOutProcess timeoutProceed) {boolean lockFlag &#61; false;try {lockFlag &#61; tryLock(lockKey, requestId, timeout, expireTime, unit);if(lockFlag){return callback.get();}} finally {if (lockFlag){unLock(lockKey, requestId);}}if (timeoutProceed &#61;&#61; null) {return null;}if (timeoutProceed &#61;&#61; TimeOutProcess.THROW_EXCEPTION) {throw new RedisLockTimeOutException();}if (timeoutProceed &#61;&#61; TimeOutProcess.CARRY_ON) {return callback.get();}return null;}/*** 阻塞锁* &#64;param lockKey 锁键* &#64;param requestId 请求唯一标识* &#64;param timeout 超时时长, 缓存过期时间默认等于超时时长* &#64;param unit 时间单位* &#64;return true: 加锁成功, false: 加锁失败*/public static boolean tryLock(String lockKey, String requestId, long timeout, TimeUnit unit) {return tryLock(lockKey, requestId, timeout, timeout, unit);}/*** 阻塞锁* &#64;param lockKey 锁键* &#64;param requestId 请求唯一标识* &#64;param timeout 超时时长* &#64;param expireTime 缓存过期时间* &#64;param unit 时间单位* &#64;return true: 加锁成功, false: 加锁失败*/public static boolean tryLock(String lockKey, String requestId, long timeout, long expireTime, TimeUnit unit) {long nanosTimeout &#61; unit.toNanos(timeout);if (nanosTimeout <&#61; 0L) {return false;}final long deadline &#61; System.nanoTime() &#43; nanosTimeout;for (;;) {// 获取到锁if (lock(lockKey, requestId, expireTime, unit)) {return true;}// 判断是否需要继续阻塞&#xff0c; 如果已超时则返回falsenanosTimeout &#61; deadline - System.nanoTime();if (nanosTimeout <&#61; 0L) {return false;}// 休眠1毫秒if (nanosTimeout > spinForTimeoutThreshold) {LockSupport.parkNanos(spinForTimeoutThreshold);}}}public static T execute(RedisCallback action) {return getRedisTemplate().execute(action);}public static RedisTemplate getRedisTemplate() {if (cacheTemplate &#61;&#61; null) {cacheTemplate &#61; SpringUtils.getBean("redisTemplate", RedisTemplate.class);}return cacheTemplate;}public static void main(String[] args) {Book param &#61; new Book();param.setBookId(1234);param.setName("西游记");Boolean flag &#61; tryLock(() -> {int bookId &#61; param.getBookId();System.out.println(bookId);// TODO ...return true;}, "BOOK-" &#43; param.getBookId(), 3, TimeUnit.SECONDS);System.out.println(flag);}/*** 超时处理逻辑*/public enum TimeOutProcess {/** 默认&#xff0c;超时返回null&#xff0c;程序会继续往下执行 */DEFAULT,/** 超时会抛出异常 */THROW_EXCEPTION,/** 超时会给予补偿&#xff0c;即处理正常逻辑 */CARRY_ON,}}


推荐阅读
  • RabbitMq之发布确认高级部分1.为什么会需要发布确认高级部分?在生产环境中由于一些不明原因,导致rabbitmq重启,在RabbitMQ重启期间生产者消息投递失败,导致消息丢 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • python3 nmap函数简介及使用方法
    本文介绍了python3 nmap函数的简介及使用方法,python-nmap是一个使用nmap进行端口扫描的python库,它可以生成nmap扫描报告,并帮助系统管理员进行自动化扫描任务和生成报告。同时,它也支持nmap脚本输出。文章详细介绍了python-nmap的几个py文件的功能和用途,包括__init__.py、nmap.py和test.py。__init__.py主要导入基本信息,nmap.py用于调用nmap的功能进行扫描,test.py用于测试是否可以利用nmap的扫描功能。 ... [详细]
  • Java如何导入和导出Excel文件的方法和步骤详解
    本文详细介绍了在SpringBoot中使用Java导入和导出Excel文件的方法和步骤,包括添加操作Excel的依赖、自定义注解等。文章还提供了示例代码,并将代码上传至GitHub供访问。 ... [详细]
  • 本文介绍了使用readlink命令获取文件的完整路径的简单方法,并提供了一个示例命令来打印文件的完整路径。共有28种解决方案可供选择。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 基于分布式锁的防止重复请求解决方案
    一、前言关于重复请求,指的是我们服务端接收到很短的时间内的多个相同内容的重复请求。而这样的重复请求如果是幂等的(每次请求的结果都相同,如查 ... [详细]
author-avatar
在刀尖上起舞66_596
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有