Redis 实现分布式锁有多种方式,在实际业务场景可根据实际情况选择实现方式
简单的例子,基于 spring-data-redis
@Service
public class RedisLockService {
@Autowired private RedisTemplate<String, Long> redisTemplate;
public boolean lock(String key) {
if (redisTemplate.opsForValue().setIfAbsent(key, Thread.currentThread().getId())) {
redisTemplate.expire(key, 10, TimeUnit.SECONDS);
return true;
} else {
return false;
}
}
public boolean unlock(String key) {
Long lock = redisTemplate.opsForValue().get(key);
if (lock != null && Thread.currentThread().getId() == lock) {
redisTemplate.delete(key);
return true;
} else {
return false;
}
}
}
基于 Redis 的 setnx 方式,获取名为 key 的锁,将当前线程 id 作为值,用于解锁判断,设置超时时间以防锁一直没有被释放。
这里只是举个例子,上面的代码有很多东西没有考虑,比如加锁时,设值和设置超时时间不是原子性的,如果设完值后 Redis 挂了,没有设置到超时时间,就无法自动解锁。
那么如何使多条命令有原子性呢,Spring 给出了使用Lua脚本的例子 Spring-data-redis script
@Bean
public RedisScript<Boolean> script() {
ScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua");
return RedisScript.of(scriptSource, Boolean.class);
}
public class Example {
@Autowired
RedisScript<Boolean> script;
public boolean checkAndSet(String expectedValue, String newValue) {
return redisTemplate.execute(script, singletonList("key"), asList(expectedValue, newValue));
}
}
-- checkandset.lua
local current = redis.call('GET', KEYS[1])
if current == ARGV[1]
then redis.call('SET', KEYS[1], ARGV[2])
return true
end
return false
Redis 官方给出了分布式锁的算法,Redlock,实现方式较复杂,
有篇关于 Redlock 是否安全的 文章
Redis 官方推荐实现了 Redlock 的 Java 客户端是 Redisson
Redisson 提供了即开即用的分布式锁服务 Redisson 分布式锁
// 1. Create config object
Config = ...
// 2. Create Redisson instance
RedissonClient redisson = Redisson.create(config);
// 3. Get object you need
RMap<MyKey, MyValue> map = redisson.getMap("myMap");
RLock lock = redisson.getLock("myLock");
RExecutorService executor = redisson.getExecutorService("myExecutorService");
// over 30 different objects and services ...