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

RedisLockRegistry源码redis分布式锁

redis实现分布式锁,实现了Lock接口,和ReentrantLock意向,有可重入,阻塞等功能使用依赖

redis实现分布式锁,实现了Lock接口,和ReentrantLock意向,有可重入,阻塞等功能


使用

依赖

org.springframework.bootspring-boot-starter-data-redisredis.clientsjedisio.lettucelettuce-coreredis.clientsjedisorg.springframework.integrationspring-integration-redis

配置类

@Configuration
public class RedisConfig {&#64;Beanpublic RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory ) {//设置序列化Jackson2JsonRedisSerializer jackson2JsonRedisSerializer &#61; new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om &#61; new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置redisTemplateRedisTemplate redisTemplate &#61; new RedisTemplate<>();redisTemplate.setConnectionFactory(jedisConnectionFactory);RedisSerializer stringSerializer &#61; new StringRedisSerializer();redisTemplate.setKeySerializer(stringSerializer); // key序列化redisTemplate.setValueSerializer(stringSerializer); // value序列化redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化redisTemplate.setHashValueSerializer(stringSerializer); // Hash value序列化redisTemplate.afterPropertiesSet();return redisTemplate;}&#64;Beanpublic RedisLockRegistry redisLockRegistry(JedisConnectionFactory jedisConnectionFactory) {return new RedisLockRegistry(jedisConnectionFactory, "REDIS_LOCK");}}

举例

&#64;RestController
public class RedisController {&#64;Autowiredprivate RedisLockRegistry redisLockRegistry;&#64;Autowiredprivate UserService userService;//http://localhost:9000/redisLock?id&#61;1&#64;RequestMapping("/redisLock")public String redisLock(Integer id){//redis的key冒号&#xff1a;连接//registryKey和lockKey自动冒号连接&#xff0c;最终key为REDIS_LOCK:USER_ID:1&#xff0c;值为uuidLock lock &#61; redisLockRegistry.obtain("USER_ID:" &#43; id);for (int i &#61; 0; i <3; i&#43;&#43;) {new Thread(() -> {lock.lock();System.out.println(Thread.currentThread().getName() &#43; " begin " &#43; new Date());userService.update();System.out.println(Thread.currentThread().getName() &#43; " end " &#43; new Date());lock.unlock();}).start();}return "ok";}}

Thread-14 begin Fri Jul 19 17:04:30 CST 2019
Thread-14 end Fri Jul 19 17:04:31 CST 2019
Thread-15 begin Fri Jul 19 17:04:31 CST 2019
Thread-15 end Fri Jul 19 17:04:32 CST 2019
Thread-16 begin Fri Jul 19 17:04:32 CST 2019
Thread-16 end Fri Jul 19 17:04:33 CST 2019


源码分析

在这里插入图片描述
ExpirableLockRegistry接口&#xff0c;添加一个过期释放锁的方法

public interface ExpirableLockRegistry extends LockRegistry {/*** Remove locks last acquired more than &#39;age&#39; ago that are not currently locked.* &#64;param age the time since the lock was last obtained.* &#64;throws IllegalStateException if the registry configuration does not support this feature.*/void expireUnusedOlderThan(long age);}

LockRegistry接口&#xff0c;只有一个获取锁的方法

&#64;FunctionalInterface
public interface LockRegistry {/*** Obtains the lock associated with the parameter object.* &#64;param lockKey The object with which the lock is associated.* &#64;return The associated lock.*/Lock obtain(Object lockKey);}

RedisLockRegistry构造器

private static final long DEFAULT_EXPIRE_AFTER &#61; 60000L;private final String registryKey;private final StringRedisTemplate redisTemplate;private final RedisScript obtainLockScript;private final long expireAfter;private static final String OBTAIN_LOCK_SCRIPT &#61;"local lockClientId &#61; redis.call(&#39;GET&#39;, KEYS[1])\n" &#43;"if lockClientId &#61;&#61; ARGV[1] then\n" &#43;" redis.call(&#39;PEXPIRE&#39;, KEYS[1], ARGV[2])\n" &#43;" return true\n" &#43;"elseif not lockClientId then\n" &#43;" redis.call(&#39;SET&#39;, KEYS[1], ARGV[1], &#39;PX&#39;, ARGV[2])\n" &#43;" return true\n" &#43;"end\n" &#43;"return false";/*** Constructs a lock registry with the default (60 second) lock expiration.* &#64;param connectionFactory The connection factory.* &#64;param registryKey The key prefix for locks.*/public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey) {this(connectionFactory, registryKey, DEFAULT_EXPIRE_AFTER);}/*** Constructs a lock registry with the supplied lock expiration.* &#64;param connectionFactory The connection factory.* &#64;param registryKey The key prefix for locks.* &#64;param expireAfter The expiration in milliseconds.*/public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey, long expireAfter) {Assert.notNull(connectionFactory, "&#39;connectionFactory&#39; cannot be null");Assert.notNull(registryKey, "&#39;registryKey&#39; cannot be null");this.redisTemplate &#61; new StringRedisTemplate(connectionFactory);this.obtainLockScript &#61; new DefaultRedisScript<>(OBTAIN_LOCK_SCRIPT, Boolean.class);this.registryKey &#61; registryKey;this.expireAfter &#61; expireAfter;}

获取锁

private final Map locks &#61; new ConcurrentHashMap<>();&#64;Overridepublic Lock obtain(Object lockKey) {Assert.isInstanceOf(String.class, lockKey);String path &#61; (String) lockKey;return this.locks.computeIfAbsent(path, RedisLock::new);}

Map

default V computeIfAbsent(K key,Function mappingFunction) {Objects.requireNonNull(mappingFunction);V v;if ((v &#61; get(key)) &#61;&#61; null) {V newValue;if ((newValue &#61; mappingFunction.apply(key)) !&#61; null) {put(key, newValue);return newValue;}}return v;}default V putIfAbsent(K key, V value) {V v &#61; get(key);if (v &#61;&#61; null) {v &#61; put(key, value);}return v;}

每个lockKey创建一个锁&#xff0c;缓存起来
computeIfAbsent和putIfAbsent的区别是&#xff0c;前者是一个函数式接口&#xff0c;创建对象&#xff0c;作为缓存的值&#xff0c;后者是直接传进来值


上锁

&#64;Overridepublic void lock() {this.localLock.lock();while (true) {try {while (!obtainLock()) {Thread.sleep(100); //NOSONAR}break;}catch (InterruptedException e) {/** This method must be uninterruptible so catch and ignore* interrupts and only break out of the while loop when* we get the lock.*/}catch (Exception e) {this.localLock.unlock();rethrowAsLockException(e);}}}private final String clientId &#61; UUID.randomUUID().toString();private boolean obtainLock() {boolean success &#61; RedisLockRegistry.this.redisTemplate.execute(RedisLockRegistry.this.obtainLockScript,Collections.singletonList(this.lockKey), RedisLockRegistry.this.clientId,String.valueOf(RedisLockRegistry.this.expireAfter));if (success) {this.lockedAt &#61; System.currentTimeMillis();}return success;}

先用ReentrantLock加锁&#xff0c;再用redis调用lua脚本&#xff0c;

private static final String OBTAIN_LOCK_SCRIPT &#61;"local lockClientId &#61; redis.call(&#39;GET&#39;, KEYS[1])\n" &#43;"if lockClientId &#61;&#61; ARGV[1] then\n" &#43;" redis.call(&#39;PEXPIRE&#39;, KEYS[1], ARGV[2])\n" &#43;" return true\n" &#43;"elseif not lockClientId then\n" &#43;" redis.call(&#39;SET&#39;, KEYS[1], ARGV[1], &#39;PX&#39;, ARGV[2])\n" &#43;" return true\n" &#43;"end\n" &#43;"return false";

如果lockKey没有值&#xff0c;设置值&#xff0c;过期时间60秒。否则是线程重入锁&#xff0c;刷新过期时间60秒
redis加锁成功后&#xff0c;每个线程保存加锁时间

如果加锁失败&#xff0c;100毫秒重试&#xff0c;一直循环到获取锁&#xff0c;所以锁是可重入的。


释放锁

RedisLockRegistry.RedisLock

&#64;Overridepublic void unlock() {if (!this.localLock.isHeldByCurrentThread()) {throw new IllegalStateException("You do not own lock at " &#43; this.lockKey);}if (this.localLock.getHoldCount() > 1) {this.localLock.unlock();return;}try {if (Thread.currentThread().isInterrupted()) {RedisLockRegistry.this.executor.execute(this::removeLockKey);}else {removeLockKey();}if (logger.isDebugEnabled()) {logger.debug("Released lock; " &#43; this);}}catch (Exception e) {ReflectionUtils.rethrowRuntimeException(e);}finally {this.localLock.unlock();}}private void removeLockKey() {if (RedisUtils.isUnlinkAvailable(RedisLockRegistry.this.redisTemplate)) {RedisLockRegistry.this.redisTemplate.unlink(this.lockKey);}else {RedisLockRegistry.this.redisTemplate.delete(this.lockKey);}}

ReentrantLock保存了上锁的线程&#xff0c;和线程的重入次数
如果是重入锁&#xff0c;计数器减一&#xff0c;即aqs的state减一
否则redis删除key&#xff0c;然后释放ReentrantLock锁。


推荐阅读
  • 本文介绍了如何清除Eclipse中SVN用户的设置。首先需要查看使用的SVN接口,然后根据接口类型找到相应的目录并删除相关文件。最后使用SVN更新或提交来应用更改。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文讨论了在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下。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
author-avatar
美丽女人一起来_381
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有