Redis分布式锁
Redis之分布式锁的实现方案 - 如何优雅地实现分布式锁(JAVA)
文章目录
关键词
分布式锁
: 是控制分布式系统之间同步访问共享资源的一种方式。spring-data-redis
: Spring针对redis的封装, 配置简单, 提供了与Redis存储交互的抽象封装, 十分优雅, 也极具扩展性, 推荐读一读源码Lua
: Lua 是一种轻量小巧的脚本语言, 可在redis执行.
前言
本文阐述了Redis分布式锁的一种简单JAVA实现及优化进阶, 实现了自动解锁、自定义异常、重试、注解锁等功能, 尝试用更优雅简洁的代码完成分布式锁.
需求
- 互斥性: 在分布式系统环境下, 一个锁只能被一个线程持有.
- 高可用: 不会发生死锁、即使客户端崩溃也可超时释放锁.
- 非阻塞: 获取锁失败即返回.
方案
Redis具有极高的性能, 且其命令对分布式锁支持友好, 借助SET
命令即可实现加锁处理.
SET
EX
seconds – Set the specified expire time, in seconds.PX
milliseconds – Set the specified expire time, in milliseconds.NX
– Only set the key if it does not already exist.XX
– Only set the key if it already exist.
实现
简单实现
做法为set if not exist(如果不存在则赋值), redis命令为原子操作, 所以单独使用set
命令时不用担心并发导致异常.
具体代码实现如下: (spring-data-redis:2.1.6
)
依赖引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
配置RedisTemplate
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisSerializer keySerializer = new StringRedisSerializer();
RedisSerializer<?> serializer = new StringRedisSerializer();
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
简单的分布式锁实现
/**
* try lock
* @author piaoruiqing
*
* @param key lock key
* @param value value
* @param timeout timeout
* @param unit time unit
* @return
*/
public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
}
以上代码即完成了一个简单的分布式锁功能:
其中redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
即为执行redis命令:
redis> set dlock:test-try-lock a EX 10 NX
OK
redis> set dlock:test-try-lock a EX 10 NX
null
早期版本spring-data-redis
分布式锁实现及注意事项
方法
Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);
是在2.1版本中新增的, 早期版本中setIfAbsent无法同时指定过期时间, 若先使用setIfAbsent
再设置key的过期时间, 会存在产生死锁的风险, 故旧版本中需要使用另外的写法进行实现. 以spring-data-redis:1.8.20
为例
/**
* try lock
* @author piaoruiqing
*
* @param key lock key
* @param value value
* @param timeout timeout
* @param unit time unit
* @return
*/
public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
return redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
JedisCommands commands = (JedisCommands)connection.getNativeConnection();
String result = commands.set(key, value, "NX", "PX", unit.toMillis(timeout));
return "OK".equals(result);
}
});
}
spring-data-redis:1.8.20</