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

基于Redis实现分布式锁剖析

之前的文章《分布式锁详解-分别利用Zookeeper和数据库实现分布式锁》,由于篇幅太长,又碰上加班时间不够充裕,所以没有把Redis的实

之前的文章《分布式锁详解 - 分别利用Zookeeper和数据库实现分布式锁》,由于篇幅太长,又碰上加班时间不够充裕,所以没有把Redis的实现也顺带进去,特此做一些利用Redis实现分布式锁的分析。

PS:让我做选择的话,分布式锁的选择还是更愿意选择利用Zookeeper去实现的。
原因的话可以参考下面这张图:在这里插入图片描述


一、关于Redis分布式锁的基础知识


1、缓存有效期:

redis中的数据,不一定都是持久化的;为存储元素设置生存时间,当元素过期时,会被自动删除。(主要是为了防止死锁,一般生存时间设定为业务操作时间的两倍基本就可以了)


2、SETNX命令:

SETNX key value,将key的值设为value,当且仅当key不存在。若给定的key已经存在,则 SETNX 不做任何动作。SETNX是 【 SET if Not eXists 】的简写。(SETNX 命令就是用来保证设置失效时间和插入值是原子操作,如果不是原子执行就可能设置完key,在设置过期时间的时候系统挂机,就会导致死锁问题)


3、lua脚本

轻量小巧的脚本语言,用于支持redis操作序列的原子性。



PS:如果对Redis不是很熟悉的话,建议还是要去学习一下的,现在基本大部分的互联网公司都离不开Redis的掌握。此处顺便分享一个Redis的学习路线图给大家。
在这里插入图片描述


二、Redis实现分布式锁

还是和上一篇文章一样,Redis实现抽象锁AbstractLock,然后拓展其抽象方法。
AbstractLock.java

import java.util.concurrent.locks.Lock;/*** @author WangCw* @create 2019-04-22 19:52* @description**/
public abstract class AbstractLock implements Lock {public void getLock(){//尝试获取锁资源if(tryLock()){System.out.println("获取Lock锁的资源 #####");} else {//等待waitLock();//重新获取锁资源(等待后递归获取锁)getLock();}}//获取锁资源public abstract boolean tryLock();//等待public abstract void waitLock();//释放锁public abstract void unLock();}

1、项目引入Redis相关依赖信息:spring-data-redis(Spring整合Redis一些信息)和jedis(Redis客户端)。

2、Jedis连接池的配置:
在这里插入图片描述
3、实现:

class RedisLock extends AbstractLock{//随机值&#xff0c;保证每个线程随机值独立ThreadLocal<String> local &#61; new ThreadLocal<>();//获取锁的方法 ---> 插入一个元素&#xff0c;成功返回true&#xff0c;否则falsepublic boolean tryLock(){//产生随机值String uuid &#61; UUID.randomUUID.toString();local.set(uuid);//获取redis的原始链接Jedis jedis &#61; (Jedis) factory.getConnection().getNativeConnection();//使用setNx命令请求写值&#xff0c;并设置失效时间String ret &#61; jedis.set(KEY, uuid, "NX", "PX", 1000);//返回"OK"意味着加锁成功if("OK".equals(ret)){return true}return false; }// 等待获取锁的方法&#xff0c;一段缓存时间&#xff0c;防止系统频繁去获取锁public void waitLock() throw Exception{Thread.sleep(3000);}//释放锁 ---> 借助lua脚本删除加锁时添加的元素&#xff0c;保证操作原子性public void unLock() throw Exception{//读取lua脚本String script &#61; FileUtils.readFileByLines("C://unlock.lua");//获取redis的原始链接Jedis jedis &#61; (Jedis) factory.getConnection().getNativeConnection();//通过jedis去执行Lua脚本jedis.eval(script, Array.asList(KEY), Arrays.asList(local.get()));}}

其中上面的lua脚本如下即可&#xff1a;

if redis.call("get", KEYS[1]) &#61;&#61; ARGV[1] thenreturn redis.call("del", KEYS[1])
elsereturn 0
end


在这里插入图片描述


Redisson分布式锁

上面实现的Redis分布式存在一个大问题&#xff1a;假设设置失效时间10秒&#xff0c;如果由于某些原因导致10秒还没执行完任务&#xff0c;这时候锁自动失效&#xff0c;导致其他线程也会拿到分布式锁。面对这种情况&#xff0c;就需要通过一个守护线程&#xff0c;定时检测持有锁的线程是否还在正常做业务&#xff0c;如果是就给分布式锁的过期时间进行续命。实现起来相当复杂。这个时候&#xff0c;就可以借助Redisson

Redisson是一个在Redis的基础上实现的&#xff0c;基于Netty封装了一些利用了Redis特性的工具&#xff0c;让我们可以更高效、更简便的使用Redis。

业界大部分基于Redis的分布式锁都是由Redisson实现的&#xff0c;看看他的使用吧&#xff1a;

RLock lock &#61; redisson.getLock("lock-xxxx");
try{加锁方式一&#xff1a;最常见的使用方法&#xff0c;默认30秒过期&#xff0c;watch dog(看门狗)每过10秒检测一次线程情况&#xff0c;进行续锁lock.lock();加锁方式二&#xff1a;支持过期解锁功能,10秒钟以后自动解锁, 无需调用&#96;unlock()&#96;方法手动解锁lock.lock(10, TimeUnit.SECONDS);加锁方式三&#xff1a;尝试加锁&#xff0c;最多等待3秒&#xff0c;上锁以后10秒自动解锁。boolean res &#61; lock.tryLock(3, 10, TimeUnit.SECONDS);if(res){// 加锁成功&#xff0c;do your business}
finally {解锁方式&#xff1a;lock.unlock();
}

是不是非常简单&#xff0c;直接通过引入Redisson&#xff0c;获取到Rlock&#xff0c;剩下的操作就跟我们Java单机锁的使用方式基本相差无几了。

在这里插入图片描述


Redisson的两个特性&#xff1a;

1、加锁机制


  • 线程去获取锁&#xff0c;获取成功: 执行lua脚本&#xff0c;保存数据到redis数据库。
  • 线程去获取锁&#xff0c;获取失败: 一直通过while循环尝试获取锁&#xff0c;获取成功后&#xff0c;执行lua脚本&#xff0c;保存数据到redis数据库。

2、watch dog自动延期机制
当我们使用lock.lock();方法进行加锁时&#xff0c;默认锁的过期时间是30秒。Redisson会启动一个watch dog(看门狗)后台线程&#xff0c;以锁超时时间 / 3 &#61; 30/3&#61;10s作为周期&#xff08;每过10秒检测一次&#xff09;&#xff0c;检测持有锁的线程是否还在执行业务&#xff0c;还在执行&#xff0c;就进行锁的续期。也就是说&#xff0c;如果一个拿到锁的线程一直没有完成业务逻辑&#xff0c;那么看门狗会帮助线程不断的延长锁超时时间&#xff0c;锁不会因为超时而被释放。默认情况下&#xff0c;看门狗的续期时间是30s&#xff0c;也可以通过修改Config.lockWatchdogTimeout来另行指定。


推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • 标题: ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • Java如何导入和导出Excel文件的方法和步骤详解
    本文详细介绍了在SpringBoot中使用Java导入和导出Excel文件的方法和步骤,包括添加操作Excel的依赖、自定义注解等。文章还提供了示例代码,并将代码上传至GitHub供访问。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
author-avatar
奋怒的小超_656
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有