组建了一个每日学习群 互相督促 已经开始一段时间了,有想法的可以 加微信 yuyezhiji 麻烦备注原因
————————————————————————————————————————————————————————————————————————————————————————
1 不要去 开课吧 问原因就 自己百度去
2 上节回顾/本节重点/上节思考题
2.1 上节讲了加锁/解锁
2.2 本节讲解
1. watch dog
2 阿里规约关于分布式锁 为什么要那么写
2.3 上节 思考题解答
1 需要设置10s之后还拿不到锁就直接返回
RLock lock = client.getLock(lockKey);
lock.tryLock(10000, TimeUnit.MILLISECONDS);
2 如果获取到锁 10s之内 没有主动释放锁 那么就自动释放锁
RLock lock = client.getLock(lockKey);
lock.lock(lockKey,10000);//设置时间的时候 watch dog不生效
3 思考
4 应用场景
5 使用watch dog的步骤
5.1 RedissonLock 类 tryAcquireAsync 方法
5.1.1
// 如果加锁成功 就会开启这个(ttlRemaining 为null 是加锁成功,
// 如果不为空 代表这个锁还需要多少ms 存活时间 为什么请看上节 )
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId);
}
}
5.1.2 方法执行之后 参数
RedissonLock.EXPIRATION_RENEWAL_MAP
key 例子 0dd5078f-f36a-4eaa-bf45-43b50871de62:lockKeyForWatchDog
value RedissonLock.ExpirationEntry
5.2 renewExpiration
5.2.1 知识点
// 使用的时间轮 进行调度处理
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {\
public void run(Timeout timeout) throws Exception {\
//本地存了一个map 根据entryName 找到对应的value\
RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());\
if (ent != null) {\
//value有值 threadIds 本来是一个数组 因为本应用场景 指挥放一个threadId (抢到锁的线程Id)\
Long threadId = ent.getFirstThreadId();\
if (threadId != null) {\
//时间续约的lua方法\
RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);\
future.onComplete((res, e) -> {\
if (e != null) {\
//如果有 error 直接打印error 日志\
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);\
} else {\
if (res) {\
//如果加锁成功 lua返回1 代表true 就重新调用 RedissonLock.this.renewExpiration();\
RedissonLock.this.renewExpiration();\
}\
\
}\
});\
}\
}\
}\
//开启调度任务 30s/3 = 10s 调用一次\
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
5.2.2 watch dog 具体执行的lua
#。如果锁存在 就重新续约到30s 存在时间 返回1 true,否则 返回0 也就是false
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then\
redis.call('pexpire', KEYS[1], ARGV[1]); \
return 1;
end;
return 0;
5.2.3 带入参数的样子
#。如果锁存在 就重新续约到30s 存在时间 返回1 true,否则 返回0 也就是false
if (redis.call('hexists', lockKeyForWatchDog, 9aa6461a-1e1b-4806-9343-56b39e03bddd:1) == 1) then\
redis.call('pexpire', lockKeyForWatchDog, 30000); \
return 1; end; \
return 0;
5.2.4 参数
KEYS[1] lockKeyForWatchDog
ARGV[1] 30000
ARGV[2] 9aa6461a-1e1b-4806-9343-56b39e03bddd:1
5.2.5 处理
如果有 error 直接打印error 日志
如果加锁成功 lua返回1 代表true 就重新调用RedissonLock.this.renewExpiration();
6 基本概念
7 前面思考和应用场景解答 (在此之前 请再次思考前面的问题 )
7.1 如果想改变 watch dog 调度时间 改变哪个参数
从本节 知道 internalLockLeaseTime 默认为30s
this.internalLockLeaseTime / 3L 为调度时间
那么想改变watch dog 调度时间 就需要改变internalLockLeaseTime
是从哪设置的呢
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
可以看到30000是直接代码里写的,只有一个 setLockWatchdogTimeout 可以改变
往回debug 可以看到 其实最开始是
Redisson.create(Config config); 中的config
->
Redisson redisson = new Redisson(config);
->
把config 值重新赋值
总结:
直接改变config中的
config.setLockWatchdogTimeout(xxx)
只能统一改变
7.2 阿里规约关于 分布式锁的解读
阿里规约中 怎么规定的:
9.【强制】在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没\
有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。\
说明一:在 lock 方法与 try 代码块之间的方法调用抛出异常,无法解锁,造成其它线程无法成功获取锁。\
说明二:如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中,unlock 对未加锁的对\
象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出 IllegalMonitorStateException 异常。\
说明三:在 Lock 对象的 lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。\
正例:\
Lock lock = new XxxLock();
// ...
lock.lock();
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
为什么这么写?
Lock lock = new XxxLock();
//try 外面获取锁 但是try后面到try中间不允许有任务业务代码
// 防止 因为因为a业务代码跑出异常 导致锁解不开
lock.lock();
//a()
try {
doSomething();
doOthers();
} finally {
//这块上节说了 要加一个判断
// if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); }
lock.unlock();
}
7.3 如果先lock 获取锁 然后trylock 有什么效果
这个其实没有意义 就是一个key 加了2次锁 注意要解开2次锁
7.4 如果想实现执行调度任务,但是可能某个条件 需要通知调度任务怎么办
1. 开一个调度线程 多线程那一套
2 学完本节 多一种 用时间轮 每次调度之后 根据情况决定是否继续运行 自由度更高
那这块有问题了 redission 我知道用了时间轮 做调度
但是我们在实际开发的时候 怎么用呢
请看 WheelTimerApplicationTests 类 wheelTimerTest 方法
HashedWheelTimer timer = new HashedWheelTimer(new DefaultThreadFactory("redisson-timer"), (long)500, TimeUnit.MILLISECONDS, 1024, false);
timer.newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
System.out.println("1111");
}
}, 1000, TimeUnit.MILLISECONDS);
timer.newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
System.out.println("222");
}
}, 1000, TimeUnit.MILLISECONDS);
Thread.sleep(10000);
8 总结:
9 下节预知
- 调用unlock 主动释放锁 2 宕机自动释放锁
之后添加的额外内容:(先记下 定期添加) 1 时间轮(单机/分布式 和 对应的应用场景) 2 redission 面试题解析
10 目标:
- 随着学习 把一些坑解决 形成一个 redission的基础组件
代码地址: gitee.com/gf-8/yuye-p…
项目: yuye-test-redission
类地址: 对应的test 包之下Test类
11 思考题
如果分布式锁 锁了10000个 key 每锁一个key就是开一个线程么