Spring-data-redis 设置分布式锁
2020-02-12 10:54
417 查看
Spring-data-redis 设置分布式锁
分布式锁设置不当会导致的问题:
- 一个持有锁的进程已经崩溃,其他等待锁的进程始终无法获得锁,导致死锁现象。
- 持有锁的进程因为操作时间太长,锁被自动释放,但进程本身却不知道这一点,甚至会错误的释放了其他进程持有的锁。
项目中运用"spring-data-redis"或者"spring-boot-starter-data-redis",通常是通过RedisTemplate类操作Redis。这里介绍通过redis设置分布式锁的具体过程。
- 获取分布式锁
private RedisTemplate<String, Object> redisTemplate; /** * 尝试在一定等待时间内获取带过期时间的全局锁 * * @param mutexKey 锁key * @param mutexVal 锁val,建议UUID * @param ttl 锁过期时间 * @param timeUnit 锁过期时间单位 * @param waitMilSec 等待毫秒,若 <=0,则一直阻塞等待获得锁 * @return 是否获取到全局锁 */ private boolean getLockWithExpire(String mutexKey, String mutexVal, long ttl, TimeUnit timeUnit, long waitMilSec) { //获取带有过期时间的全局锁 RedisSerializer<String> serializer = redisTemplate.getStringSerializer(); long start = System.currentTimeMillis(); long now = start; long end = waitMilSec > 0 ? now + waitMilSec : Long.MAX_VALUE; Boolean isSet = false; //循环SetNX,直到指定时间或者设置成功 while (now < end) { isSet = redisTemplate.execute( redisConn -> redisConn.set(serializer.serialize(mutexKey), serializer.serialize(mutexVal), Expiration.from(ttl, timeUnit), RedisStringCommands.SetOption.SET_IF_ABSENT), true); if (Boolean.valueOf(true).equals(isSet)) { //设置成功 break; } try { Thread.sleep(5); } catch (InterruptedException e) { throw new RuntimeException(e); } now = System.currentTimeMillis(); } return Boolean.valueOf(true).equals(isSet); }
关键是运用redis的setNX命令,不同的客户端(Jedis/Lettuce)或有不同的api,spring对其做了统一的封装,并屏蔽了获取连接、释放连接、异常处理的具体细节,就像spring封装其它持久层框架一样。目前看来,RedisTemplate能够实现大部分操作redis的需求。
Redis 2.6.12版本后,Set命令增加了多个可选参数:
set key value [expiration EX seconds|PX milliseconds] [NX|XX]
expiration:过期时间,EX 秒、PX毫秒;
NX:Not Exist;
XX:Exist;
例如,在key=name不存在时,设置value=Tom,同时过期时间为60秒:
set name Tom EX 60 NX
通过这个命令,避免了SetNx+Expire实现分布式锁时出现的bug。
2. 释放分布式锁
/** * 释放全局锁 * * @param mutexKey 锁key * @param mutexVal 锁val * @return 当前线程是否成功释放全局锁 */ private boolean releaseLock(String mutexKey, String mutexVal) { String lua = "if redis.call('get',KEYS[1]) == ARGV[1] " + "then return redis.call('del',KEYS[1]) " + "else return 0 end"; RedisSerializer<String> serializer = redisTemplate.getStringSerializer(); Object res = redisTemplate.execute(redisConn -> redisConn.eval(serializer.serialize(lua), ReturnType.BOOLEAN, 1, serializer.serialize(mutexKey), serializer.serialize(mutexVal)), true); return Boolean.valueOf(true).equals(res); }
释放锁需要两步:先检查当前进程是否持有锁,再执行删除。目前只能通过lua脚本实现操作的ACID。
3. Redis官方推荐的分布式锁
以上能在单节点Redis实现简单的分布式锁,在多节点Redis、高并发情况下,会暴露很多问题。Redis官方推荐采用RedLock。the Redlock algorithm
代码仓库
https://gitee.com/thanksm/redis_learn/tree/master/redis_common
- 点赞
- 收藏
- 分享
- 文章举报
相关文章推荐
- spring-data-redis 设置过期时间
- spring-data-redis 使用pipeline批量设置过期时间的bug
- spring-data-redis 自定义注解扩展实现时效设置
- spring-data-redis实现分布式锁
- spring-data-redis RedisTemplate 操作redis时发现存储在redis中的key不是设置的string值,前面还多出了许多类似\xac\xed\x00\x05t\x00这
- spring-data-redis 使用pipeline批量设置过期时间的bug
- 基于spring-data-redis的分布式锁
- java之redis篇(spring-data-redis整合)(很好)
- Spring Data Redis入门示例:字符串操作(六)
- 聊聊spring-boot-starter-data-redis的配置变更
- Spring-Data-Redis之RedisTemplate的使用
- redis结合springboot设置不同缓存失效配置
- Spring Data Redis(Redis Transactions)
- Spring Data Redis 入门的几个坑
- 实现简单秒杀抢购,使用SpringBoot整合Spring-data-redis 、 rabbitMQ消息队列、redis缓存
- 使用spring-session-data-redis来进行session共享
- spring-data-redis操作redis集群
- Spring Data操作Redis时,发现key值出现 \xac\xed\x00\x05t\x00\tb
- Spring Data Redis 使用redis的一些方法点
- spring boot redis多数据源设置