一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
本人已参加[新人创作礼] 活动,一起开启掘金创作之路
1 前言
研究源码 是为了解决实际问题 - 雨夜
zl(沈阳)-皓哥 是我目前遇到的最好的老大
先看 思考和应用场景的问题 思考下,然后再继续往下看
如果可以的话 点个赞/评论下 哈哈 好有点动力 有问题 也请留言
2 上节回顾/本节重点/上节思考题
2.1 上节讲了watch dog 的逻辑
本节属于 继 《redission 分布式锁 可重入锁(二)》 的一个加餐
2.2 本节讲解
1. redission 时间轮
2 时间轮 应用场景以及扩展
2.3 上节 思考题解答
1 如果分布式锁 锁了10000个 key 每锁一个key就是开一个线程么
不是 是有一个时间轮
3 本节开始 试试带着问题 聊,哪种好 可以评论说下
假设是一个面试,聊到了redission 然后聊到了时间轮
4 面试现场
面试官:看你简历里说自己熟悉分布式锁 redission,你可以说说watch dog的实现么
我: watch dog 就是在lockKey加锁之后 为了保证请求没结束,锁不释放的保证,会有一个定时的调度 是30s/3 = 10s一次把lockKey存在的时间重新设置为30s
这个参数可以修改,但是一般不修改(是改的config 因为他有一个继承的old 继承的事,所以可以 改参数)
面试官: 你说 有一个定时的调度,他是怎么实现的
我: 是有一个时间轮,我们是在lockKey加锁成功之后(lua 返回为null 代表加锁成功) 我会把他放到一个timeouts 数组中,然后后面会有一个定时调度 定期把近期的timeout 放到时间轮(这是为了 防止 时间轮数据太多,可能是10000圈之后 才能调度到,我没必要提前放到时间轮中),然后就是调度一次,根据返回的结果决定是否继续执行下一次的调度
面试官:很好,那有可能任务有撤销的时候 ,怎么办的
我: 和timeouts平齐有一个存放的是放弃的任务,会有一个定期的执行poll 元素((通过 processCancelledTasks 方法)是在Worker run方法中执行的 在时间),然后把timeouts 对应的元素进行remove,这是在放入时间轮之前的
元素已经在时间轮中了,在时间轮处理之前会把cancelledTimeouts 的排除一下 在执行之后会有一个检查元素是否存在于业务的map中(和Eureka的双层缓存 一样)
面试官: 为什么用时间轮啊,没有别的方式了么?
我: 有啊,调度的还是有ScheduledThreadPoolExecutor 和 Timer 这两个类
额外还有 Netty的IdleStateHandler 也可以实现调度的作用
面试官: 这几种有什么区别?为什么选择时间轮?
我: 1 Timer 性能最差,功能性也比ScheduledThreadPoolExecutor 差,不考虑
2 ScheduledThreadPoolExecutor 是线程的调度,比时间轮的方式对cpu 消耗更大,但是准确,里面有2个方法,一个是以开始时间为基准,一定时间之后 执行,一个是以结束时间为基准,一定时间之后执行,一般应用场景偏向于第二种。
3 时间轮和上面2种比较 性能更优,但是时间轮因为是分组,所以时间精准度上不能保证准时执行
4 Netty的IdleStateHandler 是Netty自身的,实时性能保证,IdleStateHandler 更是一个针对心跳的优雅的设计 dubbo 就是默认是时间轮实现心跳,而如果dubbo-netty4 就改为使用Netty的IdleStateHandler
面试官:时间轮这么好,是不是只要涉及调度 定时的都可以用时间轮啊,为什么?
我:不行啊,要根据实际情况 对实时性要求 框已有框架进行分析 选择合适的方式
面试官: 很好,但是你上面说了4种关于调度的,除了理论,你在实际场景中使用过么?
我: 测试过,也使用过,主要用的是时间轮/ScheduledThreadPoolExecutor,Netty的IdleStateHandler
1.时间轮用于 redission分布式锁的 watch dog的定时调度
2 ScheduledThreadPoolExecutor 在数据一致性 因为需要精准执行 所以使用
3 而在租户/服务治理 心跳/链接 用的是Netty IdleStateHandler
5 代码 这里因为代码太多,只是把具体的类拿过来
5.1 java Timer
Timer javaTimer = new Timer();
javaTimer.schedule(new java.util.TimerTask() {
@Override
public void run() {
System.out.println("111");
}
}, 0, 1000);
5.2 时间轮
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");
renewal();
}
}, 1000, TimeUnit.MILLISECONDS);
5.3 ScheduledExecutorService
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(
1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-test-%d").daemon(true).build()
);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("111");
}
},
1000,
1000,
TimeUnit.MILLISECONDS
);
代码都有,代码项目路径在结尾
8 应用场景扩展 (之后如果遇到会加入)
8.1 我们在做自动上下架的时候 会把消息放到延时队列MQ中,依靠延时队列 实现自动上下集
但是有一个问题,可能我定时2年以后上架,我不能完全依赖于延时队列,我们有一个开关job.enable=true/false,如果是true 会job调度把最近一周的需要上下架的数据 放到mq中,模仿的就是时间轮的 timeouts,然后也涉及 取消定时上下架的事,模仿的是 cancelledTimeouts 一边是定时调度,一边是 执行的时候也会验证是否已经取消 定时上下架操作了
8.2 服务治理平台的时候 有服务器心跳的概念 和 Controller 节点的概念,心跳就是模仿的 dubbo,如果是默认实现 就使用时间轮。 如果是有netty 就使用的是Netty IdleStateHandler
9 下节预知
-
调用unlock 主动释放锁 2 宕机自动释放锁
之后添加的额外内容:(先记下 定期添加) 1 redission 面试题解析
10 目标:
- 随着学习 把一些坑解决 形成一个 redission的基础组件
代码地址: gitee.com/gf-8/yuye-p…
项目: yuye-test-redission
类地址: 对应的test 包之下 WheelTimerApplicationTests 类
11 思考题
既然理论/网上都说timer 性能低,如果你的话 怎么验证timer 性能差/其他问题? 怎么能 证明你的理论?欢迎评论区交流