十五、Redisson源码解析
十五、Redisson源码解析
1、code
public class WatchDogDemo
{
public static final String LOCKKEY = "AAA";
private static Config config;
private static Redisson redisson;
static {
config = new Config();
config.useSingleServer().setAddress("redis://"+"192.168.111.147"+":6379").setDatabase(0);
redisson = (Redisson)Redisson.create(config);
}
public static void main(String[] args)
{
RLock redissonLock = redisson.getLock(LOCKKEY);
redissonLock.lock();
try
{
System.out.println("1111");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(25); } catch (InterruptedException e) { e.printStackTrace(); }
}catch (Exception e){
e.printStackTrace();
}finally {
redissonLock.unlock();
}
System.out.println(Thread.currentThread().getName() + " main ------ ends.");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
redisson.shutdown();
}
}
2、缓存续命
Redis 分布式锁过期了,但是业务逻辑还没处理完怎么办
- 守护线程“续命”
额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。
Redisson 里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间;
分布式难以避免的,系统时钟影响
如果线程 1 从 3 个实例获取到了锁。但是这 3 个实例中的某个实例的系统时间走的稍微快一点,则它持有的锁会提前过期被释放,当他释放后,此时又有 3 个实例是空闲的,则线程2也可以获取到锁,则可能出现两个线程同时持有锁了。
3、watchdog
在获取锁成功后,给锁加一个 watchdog,watchdog 会起一个定时任务,在锁没有被释放且快要过期的时候会续期
4、源码解析
1、分析1 - 通过redisson新建出来的锁key,默认是30秒
2、分析2
3、分析3
这里面初始化了一个定时器,dely 的时间是 internalLockLeaseTime/3。
在 Redisson 中,internalLockLeaseTime 是 30s,也就是每隔 10s 续期一次,每次 30s。
4、分析4 - watch dog自动延期机制
客户端A加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间,默认每次续命又从30秒新开始
5、分析5
KEYS[1]代表的是你加锁的那个key | RLock redissonLock = redisson.getLock(“lockzzyy”);这里你自己设置了加锁的那个锁key |
---|---|
ARGV[2]代表的是加锁的客户端的ID | |
ARGV[1]就是锁key的默认生存时间 | 默认30秒 |
如何加锁 | 你要加锁的那个锁key不存在的话,你就进行加锁hincrby 7bcf6a9f-e7f7-49b0-9727-141df3b88038:117 1接着会执行 pexpire lockzzyy 30000 |
6、流程解释
- 通过exists判断,如果锁不存在,则设置值和过期时间,加锁成功
- 通过hexists判断,如果锁已存在,并且锁的是当前线程,则证明是重入锁,加锁成功
- 如果锁已存在,但锁的不是当前线程,则证明有其他线程持有锁。返回当前锁的过期时间(代表了lockzzyy这个锁key的剩余生存时间),加锁失败
7、加锁查看
package com.atguigu.redis.test;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class WatchDogDemo
{
public static final String LOCKKEY = "AAA";
private static Config config;
private static Redisson redisson;
static {
config = new Config();
config.useSingleServer().setAddress("redis://"+"192.168.111.147"+":6379").setDatabase(0);
redisson = (Redisson)Redisson.create(config);
}
public static void main(String[] args)
{
RLock redissonLock = redisson.getLock(LOCKKEY);
redissonLock.lock();
try
{
System.out.println("1111");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(25); } catch (InterruptedException e) { e.printStackTrace(); }
}catch (Exception e){
e.printStackTrace();
}finally {
if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {
redissonLock.unlock();
}
}
System.out.println(Thread.currentThread().getName() + " main ------ ends.");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
redisson.shutdown();
}
}
加锁成功后,在redis的内存数据中,就有一条hash结构的数据。
Key为锁的名称;field为随机字符串+线程ID;值为1。见下
如果同一线程多次调用lock方法,值递增1。----------可重入锁见后
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4cqjFHsu-1638776749698)(images/9.Redis分布式锁/image-20210628145110022.png)]
8、可重入锁查看
加大业务逻辑处理时间,看超过10秒钟后,redisson的续命加时
9、解锁
5、常见异常情况
import com.zzyy.study.util.RedisUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.core.io.support.EncodedResource;
/**
* @auther zzyy
* @create 2020-09-15 14:48
*/
@RestController
public class GoodController
{
public static final String REDIS_LOCK_KEY = "lockzzyy";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
@Autowired
private Redisson redisson;
@GetMapping("/buy_goods")
public String buy_Goods() throws IOException
{
RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY);
redissonLock.lock();
try
{
String result = stringRedisTemplate.opsForValue().get("goods:001");
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0)
{
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");
System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口:"+serverPort);
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口:"+serverPort;
}else{
System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口:"+serverPort);
}
return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口:"+serverPort;
}finally {
if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread())
{
redissonLock.unlock();
}
}
}
}
下一篇:Redis的缓存过期淘汰策略
更多内容:
更多内容大家可以关注一下个人博客网,https://blog.xueqimiao.com/,内容更丰富喔。
回复Redis可以获取完整md文档喔,谢谢关注。