您的位置:首页 > 数据库 > Redis

Spring-data-redis 设置分布式锁

2020-02-12 10:54 417 查看

Spring-data-redis 设置分布式锁

分布式锁设置不当会导致的问题:

  • 一个持有锁的进程已经崩溃,其他等待锁的进程始终无法获得锁,导致死锁现象。
  • 持有锁的进程因为操作时间太长,锁被自动释放,但进程本身却不知道这一点,甚至会错误的释放了其他进程持有的锁。

项目中运用"spring-data-redis"或者"spring-boot-starter-data-redis",通常是通过RedisTemplate类操作Redis。这里介绍通过redis设置分布式锁的具体过程。

  1. 获取分布式锁
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

  • 点赞
  • 收藏
  • 分享
  • 文章举报
thanksm1 发布了5 篇原创文章 · 获赞 1 · 访问量 683 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: