目前业内常用的分布式锁的实现方式主要有以下几种:
本文主要尝试使用SpringBoot集成的RedisTemplate来实现分布式锁的功能,在分布式高并发的业务场景下,我们不得不要考虑分布式锁的实现需要具备的几点要求:
在任意时刻,只有一个线程能够获得锁
一个线程获得锁后,不会一直持有不释放,导致其他线程无法获得锁而影响业务
试想如果加锁的线程还没有执行完业务,被另一线程解锁,那分布式锁必定是无法解决问题的
在使用集群的情况下,如果增加、删除节点,要尽量避免key的miss,如果命中率太低,势必会在某些极端情况下影响到业务,这里可以考虑一致性哈希等算法,一般由运维同学来实施;如果连接Redis时,发生jedis的超时异常,业务该如何处理,这个就要就事论事了,本文不就这点展开讨论
加锁的方法我们要考虑这么几个问题:
来看一下代码:
/**
*
* @param key
* @param requestId
* @param expireTime
* @return
*/
public static boolean getLock(String key, String requestId, String expireTime) {
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/getLock.lua")));
Object result = redisTemplate.execute(redisScript,argsSerializer,resultSerializer,Collections.singletonList(key),requestId,expireTime);
if(EXEC_RESULT.equals(result)) {
return true;
}
return false;
}
我们这里引入了lua脚本,主要的好处是Redis可以通过eval命令保证代码执行的原子性,脚本内容如下:
getLock.lua:
if redis.call('setNx',KEYS[1],ARGV[1]) then
if redis.call('get',KEYS[1])==ARGV[1] then
return redis.call('expire',KEYS[1],ARGV[2])
else
return 0
end
end
解锁的时候我们仍然需要考虑,解锁的线程就是加锁的线程,而且解锁的操作执行命令的原子性:
/**
*
* @param key
* @param requestId
* @return
*/
public static boolean releaseLock(String key, String requestId) {
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/releaseLock.lua")));
Object result = redisTemplate.execute(redisScript,argsSerializer,resultSerializer,Collections.singletonList(key),requestId);
if(EXEC_RESULT.equals(result)) {
return true;
}
return false;
}
releaseLock.lua:
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end
我们这里还是使用lua脚本来实现,不喜欢使用脚本的同学,可以考虑使用RedisScript的另一个方法:
redisScript.setScriptText();
最后,还要强调一下,RedisTemplate使用过程中是有些坑的,比如序列化问题以及脚本的初始化问题,执行命令返回值的类型问题,都需要通过认真的调试完成:
public class RedisUtils {
private static RedisTemplate redisTemplate;
private static DefaultRedisScript redisScript;
private static RedisSerializer argsSerializer;
private static RedisSerializer resultSerializer;
private static final Long EXEC_RESULT = 1L;
@PostConstruct
public void init() {
redisScript = new DefaultRedisScript();
redisScript.setResultType(String.class);
argsSerializer = new StringRedisSerializer();
resultSerializer = new StringRedisSerializer();
}
@Autowired
public RedisUtils(RedisTemplate redisTemplate) {
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
this.redisTemplate = redisTemplate;
}
}
参考资料:
https://www.cnblogs.com/linjiqin/p/8003838.html
https://www.jianshu.com/p/9e7b5c5c9b7b