专栏首页存储spring boot redis分布式锁

spring boot redis分布式锁

随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁。分布式锁的实现有很多种,比如基于数据库、zookeeper等,本文主要介绍使用Redis做分布式锁的方式,并封装成spring boot starter,方便使用

一.Redis分布式锁的实现以及存在的问题

锁是针对某个资源,保证其访问的互斥性,在实际使用当中,这个资源一般是一个字符串。使用Redis实现锁,主要是将资源放到Redis当中,利用其原子性,当其他线程访问时,如果Redis中已经存在这个资源,就不允许之后的一些操作。spring boot使用Redis的操作主要是通过RedisTemplate来实现,一般步骤如下:

将锁资源放入Redis(注意是当key不存在时才能放成功,所以使用setIfAbsent方法):

设置过期时间

释放锁

一般情况下,这样的实现就能够满足锁的需求了,但是如果在调用setIfAbsent方法之后线程挂掉了,即没有给锁定的资源设置过期时间,默认是永不过期,那么这个锁就会一直存在。所以需要保证设置锁及其过期时间两个操作的原子性,spring data的RedisTemplate当中并没有这样的方法。但是在jedis当中是有这种原子操作的方法的,需要通过RedisTemplate的execute方法获取到jedis里操作命令的对象,代码如下:

注意:Redis从2.6.12版本开始set命令支持NX、PX这些参数来达到setnx、setex、psetex命令的效果,文档参见:http://doc.redisfans.com/string/set.html

NX:表示只有当锁定资源不存在的时候才能SET成功。利用Redis的原子性,保证了只有第一个请求的线程才能获得锁,而之后的所有线程在锁定资源被释放之前都不能获得锁。

PX:expire表示锁定的资源的自动过期时间,单位是毫秒。具体过期时间根据实际场景而定

这样在获取锁的时候就能够保证设置Redis值和过期时间的原子性,避免前面提到的两次Redis操作期间出现意外而导致的锁不能释放的问题。但是这样还是可能会存在一个问题,考虑如下的场景顺序:

线程T1获取锁

线程T1执行业务操作,由于某些原因阻塞了较长时间

锁自动过期,即锁自动释放了

线程T2获取锁

线程T1业务操作完毕,释放锁(其实是释放的线程T2的锁)

按照这样的场景顺序,线程T2的业务操作实际上就没有锁提供保护机制了。所以,每个线程释放锁的时候只能释放自己的锁,即锁必须要有一个拥有者的标记,并且也需要保证释放锁的原子性操作。

因此在获取锁的时候,可以生成一个随机不唯一的串放入当前线程中,然后再放入Redis。释放锁的时候先判断锁对应的值是否与线程中的值相同,相同时才做删除操作。

Redis从2.6.0开始通过内置的Lua解释器,可以使用EVAL命令对Lua脚本进行求值,文档参见:http://doc.redisfans.com/script/eval.html

因此我们可以通过Lua脚本来达到释放锁的原子操作,定义Lua脚本如下:

具体意思可以参考上面提供的文档地址

使用RedisTemplate执行的代码如下:

代码中分为集群模式和单机模式,并且两者的方法、参数都一样,原因是spring封装的执行脚本的方法中(RedisConnection接口继承于RedisScriptingCommands接口的eval方法),集群模式的方法直接抛出了不支持执行脚本的异常(虽然实际是支持的),所以只能拿到Redis的connection来执行脚本,而JedisCluster和Jedis中的方法又没有实现共同的接口,所以只能分开调用。

spring封装的集群模式执行脚本方法源码:

至此,我们就完成了一个相对可靠的Redis分布式锁,但是,在集群模式的极端情况下,还是可能会存在一些问题,比如如下的场景顺序(本文暂时不深入开展):

线程T1获取锁成功

Redis的master节点挂掉,slave自动顶上

线程T2获取锁,会从slave节点上去判断锁是否存在,由于Redis的master slave复制是异步的,所以此时线程T2可能成功获取到锁

为了可以以后扩展为使用其他方式来实现分布式锁,定义了接口和抽象类,所有的源码如下:

二. 基于AOP的Redis分布式锁

在实际的使用过程中,分布式锁可以封装好后使用在方法级别,这样就不用每个地方都去获取锁和释放锁,使用起来更加方便。

首先定义个注解:

装配分布式锁的bean

定义切面(spring boot配置方式)

spring boot starter还需要在resources/META-INF中添加spring.factories文件

这样封装之后,使用spring boot开发的项目,直接依赖这个starter,就可以在方法上加RedisLock注解来实现分布式锁的功能了,当然如果需要自己控制,直接注入分布式锁的bean即可

如果需要使用其他的分布式锁实现,继承AbstractDistributedLock后实现获取锁和释放锁的方法即可

参考资料 :

http://zhangtielei.com/posts/blog-redlock-reasoning.html

http://doc.redisfans.com/index.html

https://www.jianshu.com/p/d72e8526bea1

https://www.jianshu.com/p/8cc44d008177

源码地址 :

https://gitee.com/itopener/springboot(目录:itopener-parent / spring-boot-starters-parent / lock-redis-spring-boot-starter-parent)

本文来自企鹅号 - 愚猿媒体

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Redis安全加固

    Redis 默认情况下,会绑定在 0.0.0.0:6379,这样将会将 Redis 服务暴露到公网上, 如果在没有开启认证的情况下,可以导致任意用户在可以访问目...

    企鹅号小编
  • 使用Redis走进误区,该怎么办?

    首先是一个产品线开发人员搭建起了一套庞大的价格存储系统,底层是关系型数据库,只用来处理一些事务性的操作和存放一些基础数据; 在关系型数据库的上面还有一套Mong...

    企鹅号小编
  • 聊聊Dubbox(一):为何选择

    1. 前言 随着现在互联网行业的发展,越来越多的框架、中间件、容器等开源技术不断地涌现,更好地来服务于业务,解决实现业务的问题。然而面对众多的技术选择,我们要如...

    企鹅号小编
  • 记录一则ORACLE MOVE操作后重建索引过程被强制中断导致的ORA-8104案例

    对部分表进行Move操作之后,未重建对应的索引,会导致ORA-1502 索引不可用。

    Alfred Zhao
  • 如何在Debian上安装MutliCraft

    PS:本文撰写前已查询相关法律,本文内容不违反《互联网文化管理暂行规定》,遵守EULA协议,请勿举报。

    朝朝
  • 【Web技术】400- 浅谈Shadow DOM

    你在实际的开发中很可能遇到过这样的需求:实现一个可以拖拽的滑块,以实现范围选择、音量控制等需求。

    pingan8787
  • GC的前世与今生

      虽然本文是以.NET作为目标来讲述GC,但是GC的概念并非才诞生不久。早在1958年,由鼎鼎大名的图林奖得主John McCarthy所实现的Lisp语言就...

    陈珙
  • 【程序源代码】SpringBoot整合JDBC数据库连接池

    关键字:本篇为SpringBoot整合JDBC数据库教程,内容比较简单,比较适合小白学习。

    程序源代码
  • 影子(Shadow) DOM

    什么是 Shadow DOM? Shadow DOM 是一个革命性的新技术,先来看下他是什么样子的 以<video>标签为例非常适合,例如 <video ...

    dys
  • GC和垃圾回收器其三:JVM调优原则

    性能角度主要瞄准三个方向:内存占用,时延,吞吐。在这些角度之外可能围绕OOM是否合理,GC参数是否合理,或者启动速度方面等。

    春哥大魔王

扫码关注云+社区

领取腾讯云代金券