redis 分布式锁的实现
思路: 1)、先判断没有,2)、再给里面放值
1、代码第一阶段;
代码语言:javascript复制 public void hello(){
获取和设置值必须是原子的
String lock = getFromRedis("lock");get("lock")
if(lock == null){
setRedisKey("lock","1");
执行业务
delRedisKey("lock")
return ;
}else{
hello();自旋
}
}
问题:加锁的原子性不能保证 解决:使用redis的setnx 2、代码第二阶段 setnx->set if not exist:原子操作。判断带保存。
代码语言:javascript复制 public void hello(){
1、获取到锁
Integer lock = setnx("lock',"111"); 0代表没有保存数据,说明已经有人占了。1代表占可坑成功
if(lock!=0){
执行业务逻辑
释放锁、删除锁
del("lock")
}else{
等待重试
hello();
}
}
问题:如果由于各种问题(未捕获的异常、断电等)导致锁没释放。其他人永远获取不到锁。 解决:加个过期时间。 3、代码第三阶段
代码语言:javascript复制 public void hello(){
超时和加锁必须原子
Integer lock = setnx("lock',"111");
if(lock!=null){
expire("lock",10s);
执行业务逻辑
释放锁
del("lock')
}else{
hello();
}
}
问题:刚拿到锁,机器炸了,没来得及设置超时。 解决:加锁和加超时也必须是原子的。
4、代码第四阶段:
代码语言:javascript复制 public void hello(){
String result = setnxex("lock","111",10s);
if(result=="ok"){
加锁成功
执行业务逻辑
del("lock")
}else{
hello();
}
}
问题:如果业务逻辑超时,导致锁自动删除,业务执行完又删除一遍锁。至少多个人都获取到了锁。 解决:每个线程加锁设置不同的值 删锁时对比 保证删的是自己加的锁 5、代码第五阶段。
代码语言:javascript复制 public void hello(){
String token = UUID;
String result = setnxex("lock",token,10s);
if(result == "ok"){
执行业务
删锁,保证删除自己的锁
if(get("lock")==token){
del("lock")
}
}else{
hello();
}
}
问题:我们获取锁的时候,锁的值正在给我们返回。锁过期。redis删除了锁。但是我们拿到了值,而且对比成功(此时此刻正好有人又获取)。我们还删除了锁。至少两个线程又进入同一个代码。 原因:删锁不是原子 解决: lua脚本进行删除。
代码语言:javascript复制 String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end";
jedis.eval(script, Collections.singletonList(key), Collections.singletonList(token));
1、分布式锁的核心(保证原子性)
- 加锁。占坑一定要是原子的。(判断如果没有,就给redis中保存值)
- 锁要自动超时。
- 解锁也要原子。
最终的分布式锁的代码:
代码语言:javascript复制 @Lock
public void hello(){
String token = uuid;
String lock = redis.setnxex("lock",token,10s);
if(lock=="ok"){
执行业务逻辑
脚本删除锁
}else{
hello();自旋。
}
}
RedisTemplate和Jedis客户端2选一
代码语言:javascript复制 @Autowired
JedisPool jedisPool;
public void incrDistribute(){
Jedis jedis = jedisPool.getResource();
try {
String token = UUID.randomUUID().toString();
// 三秒过期
String lock = jedis.set("lock", token, SetParams.setParams().ex(3).nx());
if (lock!=null&&lock.equalsIgnoreCase("OK")) {
String num = jedis.get("num");
Integer i = Integer.parseInt(num);
i=i 1;
jedis.set("num", i.toString());
//删除锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList("lock"),Collections.singletonList(token));
logger.info("删除锁成功:{}" Thread.currentThread().getId());
}else {
try {
Thread.sleep(1000);
incrDistribute();
} catch (InterruptedException e) {
logger.error("线程睡眠异常:{}",e);
}
}
} finally {
jedis.close();
}
}
代码语言:javascript复制 @Autowired
StringRedisTemplate redisTemplate;
public void incrDistribute(){
//1、加锁
String token = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", token, 3, TimeUnit.SECONDS);
if (lock) {
ValueOperations<String, String> stringStringValueOperations = redisTemplate.opsForValue();
String num = stringStringValueOperations.get("num");
if (num !=null) {
Integer i = Integer.parseInt(num);
i=i 1;
stringStringValueOperations.set("num", i.toString());
}
//删除锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> script1 = new DefaultRedisScript<Long>(script,Long.class);
redisTemplate.execute(script1, Arrays.asList("lock"),token);
logger.info("删除锁成功:{}" Thread.currentThread().getId());
}else {
incrDistribute();
}
}
问题:如果业务执行超时 锁被自动删除也是个问题 其他线程获取到锁进来了也是个问题 解决: 使用守护线程 守护线程做加时操作 保证过期时间够用 本线程业务执行完成后 守护线程加时操作自然结束 redisson的看门狗监听也可以
redis实现分布式锁有各种问题 建议使用Redisson
锁的更多考虑 1)、自旋。 自旋次数。 自旋超时。 2)、锁设置 锁粒度;细;记录级别; 1)、各自服务各自锁 2)、分析好粒度,不要锁住无关数据。一种数据一种锁,一条数据一个锁。 3)、锁类型
使用jedis springboot整合redis
application.properties
代码语言:javascript复制spring.redis.host=192.168.217.130
spring.redis.jedis.pool.max-active=20
spring.redis.jedis.pool.max-idle=5
pom文件进入jar包
代码语言:javascript复制<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.1</version>
</dependency>
添加配置类:
代码语言:javascript复制package com.xiepanpan.locks.lockstest.config;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author: xiepanpan
* @Date: 2020/2/20
* @Description: jedis 配置类
*/
@Configuration
public class JedisConfig {
@Bean
public JedisPool jedisPoolConfig(RedisProperties properties) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
RedisProperties.Pool pool = properties.getJedis().getPool();
//设置配置
jedisPoolConfig.setMaxIdle(pool.getMaxIdle());
jedisPoolConfig.setMaxTotal(pool.getMaxActive());
JedisPool jedisPool = new JedisPool(jedisPoolConfig,properties.getHost(),properties.getPort());
return jedisPool;
}
}