前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring boot redis分布式锁

spring boot redis分布式锁

作者头像
企鹅号小编
发布2018-01-09 09:37:29
2K0
发布2018-01-09 09:37:29
举报
文章被收录于专栏:存储存储

随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁。分布式锁的实现有很多种,比如基于数据库、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)

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

如有侵权,请联系 cloudcommunity@tencent.com 删除。

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

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档