专栏首页存储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 条评论
登录 后参与评论

相关文章

  • 实战来了!Spring Boot+Redis 分布式锁模拟抢单!

    > 公众号:[Java小咖秀](https://t.1yb.co/jwkk),网站:[javaxks.com](https://www.javaxks.com)

    Java小咖秀
  • Spring Boot Redis 实现分布式锁,真香!!

    之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis、Zookeeper 中间件,另外还支持 JDBC...

    Java技术栈
  • spring-boot-klock-starter分布式锁项目开源

    spring-boot-klock-starter是一个基于redis的分布式锁spring boot starter组件,使得项目拥有分布式锁能力变得异常简单...

    kl博主
  • Java | 使用 Spring Redis Lock 实现分布式锁

    在我们项目经常遇到并发问题,在单个项目中,使用自带的锁即可完成并发控制,在多个项目中,就需要使用分布式锁来解决。这里讲一下使用 Redis 来做分布式锁实现方案

    双鬼带单
  • 简洁实用的Redis分布式锁用法

    在微服务中很多情况下需要使用到分布式锁功能,而目前比较常见的方案是通过Redis来实现分布式锁,网上关于分布式锁的实现方式有很多,早期主要是基于Redisson...

    用户5927304
  • 简洁实用的Redis分布式锁用法

    在微服务中很多情况下需要使用到分布式锁功能,而目前比较常见的方案是通过Redis来实现分布式锁,网上关于分布式锁的实现方式有很多,早期主要是基于Redisson...

    JAVA葵花宝典
  • Spring Boot中使用Redisson分布式锁

    Spring Boot使用Redisson特别简单,只要引入一个新的jar就可以,redis的配置跟其他的redis客户端可以兼容,可以不用再额外配置

    十毛
  • 什么鬼,面试官竟然让敖丙用Redis实现一个消息队列!!?

    众所周知,redis是一个高性能的分布式key-value存储系统,在NoSQL数据库市场上,redis自己就占据了将近半壁江山,足以见到其强大之处。同时,由于...

    敖丙
  • 面试官竟让我用Redis实现一个消息队列!

    众所周知,redis是一个高性能的分布式key-value存储系统,在NoSQL数据库市场上,redis自己就占据了将近半壁江山,足以见到其强大之处。同时,由...

    数据和云
  • 如何优雅的实现分布式锁

    今天我们来说说第三种,使用 Spring Integration 实现,也是我个人比较推荐的一种。

    JAVA日知录
  • SpringBoot电商项目实战 — Redis实现分布式锁

    最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实...

    攻城狮的那点事
  • SpringBoot-Redis 实现分布式锁

    大多数互联网系统都是分布式部署的,分布式部署确实能带来性能和效率上的提升,但为此,我们就需要多解决一个分布式环境下,数据一致性的问题。

    周三不加班
  • JAVA高并发 Redis+Lua限流实战

    但是我们也知道,限流器在每次请求令牌和放入令牌操作中,存在一个协同的问题,即获取令牌操作要尽可能保证原子性,否则无法保证限流器是否能正常工作。在RateLimi...

    小东啊
  • 使用 Redisson 实现的 redis 分布式锁在 SpringBoot 中的简单使用

    2. redisson 可以使用 SpringBoot 本身的redis配置, 也可以使用redisson的redis配置, 参考这里

    北漂的我
  • Redis集群实现分布式锁的正确方式

    上文我们介绍的 Redis实现分布式锁的正确方式 是 redis 单机的方式,所以本篇要基于 redis 集群做分布式锁,我们使用 Redisson

    胖虎
  • 一起来学SpringBoot | 第二十七篇:优雅解决分布式限流

    在前面的两篇文章中,介绍了一些限流的类型和策略,本篇从 SpringBoot、 Redis 应用层面来实现分布式的限流....

    battcn
  • 2019年末Java面试解析总结:Java+Redis+数据库+解决方案+分布式..

    2019年快结束了,给大家整理了今年来最经典的面试真题100道,每个题目都有详细的解答,收集了java基础、RabbitMQ,微服务、MySQL数据库、Java...

    用户5546570
  • 这才是实现分布式锁的正确姿势!

    其实Java世界的”半壁江山”——Spring早就提供了分布式锁的实现。早期,分布式锁的相关代码存在于Spring Cloud的子项目Spring Cloud ...

    JAVA葵花宝典
  • SpringCloud微服务实战系列(十三)分布式锁之Redis实现(redisson)

    分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,...

    品茗IT

扫码关注云+社区

领取腾讯云代金券