Redis实现分布式锁
Redis是一个开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。它支持数据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超级日志,带有半径查询和流的地理空间索引。Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供了高可用性。
一、分布式锁的实现
分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
可靠性
首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
1、导入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、在Spring boot配置文件中加入配置
spring:
redis:
host: 192.168.34.100 #redis地址
port: 6379
database: 0
lettuce:
pool:
max-active: 8 #最大连接数,负数表示无限制
max-wait: 10000 #最大等待ms,负数表示无限制
max-idle: 8 #最大空闲连接数,负数表示无限制
min-idle: 0 #最小空闲连接数
shutdown-timeout: 100 #shutdown超时
如需解决乱码问题需要在启动类上加入,全局配置
@Autowired
private RedisTemplate redisTemplate;
@Bean
public RedisTemplate<String, Object> stringSerializerRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
return redisTemplate;
}
3、编码(加锁、解锁、锁超时)
要实现加锁可以根据redis中的setnx命令实现,该命令可以确保只有一个key。
@Autowired
private RedisTemplate redisTemplate;
//线程唯一id
String uuid = UUID.randomUUID().toString();
/**
* 加锁,setIfAbsent=setnx命令
* 如果redis已经有该key后返回false,加锁失败
*/
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(String.valueOf(orderRequest.getSchedulerId()), uuid);
/**
*如果加锁失败后进入循环等待
*/
while(!aBoolean){
TimeUnit.SECONDS.sleep(1);
}
uuid : 这里会随机生成一个id,为确保加锁和解锁的一致性,一个线程加锁后必须有他解锁,解锁时利用id做判断。解决误删锁。
锁超时
:如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,该资源将会被永远占用,其他线程将无法访问
- 可以给锁添加失效时间(expire方法)
redisTemplate.expire(String.valueOf(scheduleId).concat(":").concat(seatArrays[i]),20, TimeUnit.SECONDS);
加锁setnx和锁超时expire两个命令未非原子性操作,当执行加锁setnx后,若因网络或客户端问题锁超时expire命令未成功执行时,锁将无法被释放
因此需要调用setIfAbsent()可以同时设置的失效时间,从而解决程序出错二导致的无法释放锁的死锁问题。
redisTemplate.opsForValue().setIfAbsent(String.valueOf(orderRequest.getSchedulerId()),uuid,10,TimeUnit.SECONDS);
解锁使用del命令,删除掉锁。
/**
*判断uuid和锁的值是否一致,
*/
if(uuid.equals(redisTemplate.opsForValue().get(String.valueOf(orderRequest.getSchedulerId())))){
//释放锁
redisTemplate.delete(String.valueOf(orderRequest.getSchedulerId()));
}