在Kafka中应用了大量的延迟操作但在Kafka中 并没用使用JDK自带的Timer或是DelayQueue用于延迟操作,而是使用自己开发的DelayedOperationPurgatory组件用于管理延迟操作,Kafka这类分布式框架有大量延迟操作并且对性能要求及其高,而java.util.Timer与java.util.concurrent.DelayQueue的插入和删除时间复杂度都为对数阶O(log n)并不能满足Kafka性能要求,所以Kafka实现了基于时间轮的定时任务组件,该时间轮定时任务实现的插入与删除(开始定时器与暂停定时器)的时间复杂度都为常数阶O(1)

  时间轮的应用并不少见,在Netty、akka、Quartz、Zookeeper等高性能组件中都存在时间轮定时器的踪影;

时间轮数据结构

时间轮名词解释:

  时间格:环形结构中用于存放延迟任务的区块;

  指针(CurrentTime):指向当前操作的时间格,代表当前时间

  格数(ticksPerWheel):为时间轮中时间格的个数

  间隔(tickDuration):每个时间格之间的间隔

  总间隔(interval):当前时间轮总间隔,也就是等于ticksPerWheel*tickDuration

  TimingWheel并非简单的环形时间轮,而是多层级时间轮,每个时间轮由多个时间格组成,每个时间格为一个时间间隔,底层的时间格跨度较小,然后随着延迟任务延迟时间的长短逐层变大;如上图,底下的时间轮每个时间格为1ms,整个时间轮为10ms,而上面一层的时间轮中时间格为10ms,整个时间轮为100ms;

  时间轮添加上级时间轮的规则为:当前currentTime为上级时间轮的startMs,当前interval为上级时间轮的tickDuration,每层ticksPerWheel相同;简单点说就是上层时间轮跨度为当前的M倍,时间格为当前的N倍;

Kafka中时间轮的实现

  Kafka中时间轮时间类为TimingWheel,该类结构为存储定时任务的环形队列,内部使用数组实现,数组是用于存放TimerTaskList对象,TimerTaskList环形双向链表,链表项TimerTaskEntry封装了定时任务TimerTask,TimerTaskList与TimerTaskEntry中均有超时时间字段,TimerTask中delayMs字段用于记录任务延迟时间;该三个类为Kafka时间轮实现的核心;

  TimingWheel:表示一个时间轮,通常会有多层时间轮也就存在多个TimingWheel对象;

  TimerTaskList:为数组对象用于存放延迟任务,一个TimerTaskList就代表一个时间格,一个时间格中能保存的任务到期时间只可在[t~t+10ms]区间(t为时间格到期时间,10ms时间格间格),每个时间格有个过期时间,时间格过期后时间格中的任务将向前移动存入前面时间格中;

  TimerTask:表示延迟任务;

  SystemTimer:kafka实现的定时器,内部封装了TimningWheel用于执行、管理定时任务;

  下面通过一个示例来介绍kafka时间轮的工作过程:

  时间轮初始化:初始时间轮中的格数、间隔、指针的初始化时间,创建时间格所对应的buckets数组,计算总间隔interval;

  添加延迟任务:判断该任务是否已被取消、是否已经过期如已过期则把任务放入线程池中执行、根据时间轮总间隔与当前时间判断任务是否可存入当前层级时间轮否则添加上层时间轮并再次尝试往时间轮中添加该任务;

  时间轮降级:有一个定时任务再300ms后将执行,现层级时间轮每层有10个时间格,顶层时间轮的时间格间隔为1ms,整个时间轮为10ms,无法存下该任务。这时创建第二层时间轮,时间格间隔为10ms,整个时间轮为100ms,还是无法存该任务。接着创建第三层时间轮,时间格间隔为100ms,整个时间轮为1000ms,此时任务存入第三层时间轮的第三个时间格中;过了段时间,TimerTaskList到期(时间格)可该任务还有90ms,还无法执行。此时将再次把定时任务添加到时间轮中,顶层时间轮还是无法满足存入条件,往第二层时间轮添加,这时定时任务存入第二层时间轮第九个时间格当中;任务在时间轮中如此反复,直到任务过期时将放入线程池中执行;

关键实现方法

 public boolean add(TaskEntry e) {
synchronized (this) {
long expiration = e.getExpirationMs();
if(expiration<(currentTime+tickDuration)){
//当前任务过期时间
LOGGER.info("当前任务已过期");
return false;
}else if(expiration<(currentTime+interval)) {
//查找时间格的位置,过期时间/时间格%时间轮大小
long virtualId = expiration / tickDuration;
TaskEntryList taskEntryList = buckets.get((int) (virtualId % ticksPerWheel));
taskEntryList.add(e);
//设置EntryList过期时间
if(taskEntryList.setTime(virtualId * tickDuration)) {
listDelayQueue.offer(taskEntryList); }
return true;
}else{
if(overflowWheel==null){
// 添加上级timingWheel
addOverflowWheel();
}
return overflowWheel.add(e); }
}
} /**
*时间表针移动
* @param timeMS
*/
public void advanceClock(long timeMS){
if(timeMS>=(currentTime+tickDuration)){
currentTime=timeMS-(timeMS%tickDuration);
}
if (overflowWheel != null) overflowWheel.advanceClock(currentTime);
} /**
* 添加定时任务
* @param taskEntry
*/
public void add(TaskEntry taskEntry) {
if (!timingWheel.add(taskEntry)) {
System.out.println(String.format("任务已过期,开始执行 %s",taskEntry.getTimerTask()));
taskExecutor.execute(taskEntry.getTimerTask());
}
}

文章首发地址:Solinx

http://www.solinx.co/archives/989

Kafka中时间轮分析与Java实现的更多相关文章

  1. Go语言中时间轮的实现

    最近在工作中有一个需求,简单来说就是在短时间内会创建上百万个定时任务,创建的时候会将对应的金额相加,防止超售,需要过半个小时再去核对数据,如果数据对不上就需要将加上的金额再减回去. 这个需求如果用Go ...

  2. kafka中常用API的简单JAVA代码

    通过之前<kafka分布式消息队列介绍以及集群安装>的介绍,对kafka有了初步的了解.本文主要讲述java代码中常用的操作. 准备:增加kafka依赖 <dependency> ...

  3. [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用

    [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...

  4. 时间轮算法在Netty和Kafka中的应用,为什么不用Timer、延时线程池?

    大家好,我是yes. 最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Ja ...

  5. kafka时间轮的原理(一)

    概述 早就想写关于kafka时间轮的随笔了,奈何时间不够,技术感觉理解不到位,现在把我之前学习到的进行整理一下,以便于以后并不会忘却.kafka时间轮是一个时间延时调度的工具,学习它可以掌握更加灵活先 ...

  6. Kafka解惑之时间轮 (TimingWheel)

    Kafka中存在大量的延迟操作,比如延迟生产.延迟拉取以及延迟删除等.Kafka并没有使用JDK自带的Timer或者DelayQueue来实现延迟的功能,而是基于时间轮自定义了一个用于实现延迟功能的定 ...

  7. kafka时间轮简易实现(二)

    概述 上一篇主要介绍了kafka时间轮源码和原理,这篇主要介绍一下kafka时间轮简单实现和使用kafka时间轮.如果要实现一个时间轮,就要了解他的数据结构和运行原理,上一篇随笔介绍了不同种类的数据结 ...

  8. 时间轮算法(TimingWheel)是如何实现的?

    前言 我在2. SOFAJRaft源码分析-JRaft的定时任务调度器是怎么做的?这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...

  9. 时间轮机制在Redisson分布式锁中的实际应用以及时间轮源码分析

    本篇文章主要基于Redisson中实现的分布式锁机制继续进行展开,分析Redisson中的时间轮机制. 在前面分析的Redisson的分布式锁实现中,有一个Watch Dog机制来对锁键进行续约,代码 ...

随机推荐

  1. .net remoting(1)简单例子

    1.例子(程序间的通讯) class Program { static void Main(string[] args) { HttpChannel _channel = ); ChannelServ ...

  2. [转] HTML5+规范:device(管理设备信息)

    http://blog.csdn.net/qq_27626333/article/details/51815310 Device模块管理设备信息,用于获取手机设备的相关信息,如IMEI.IMSI.型号 ...

  3. Git Flow,Git团队协作最佳实践

    规范的Git使用 Git是一个很好的版本管理工具,不过相比于传统的版本管理工具,学习成本比较高, 实际开发中,如果团队成员比较多,开发迭代频繁,对Git的应用比较混乱,会产生很多不必要的冲突或者代码丢 ...

  4. 【Android】spannableStringBuilder

    EditText: 通常用于显示文字,但有时候也需要在文字中夹杂一些图片,比如QQ中就可以使用表情图片,又比如需要的文字高亮显示等等,如何在android中也做到这样呢? 记得android中有个an ...

  5. 【Android】Android 多个APK数据共享

    Android给每个APK进程分配一个单独的用户空间,其manifest中的userid就是对应一个Linux用户(Android 系统是基于Linux)的.所以不同APK(用户)间互相访问数据默认是 ...

  6. spring cloud (一、服务注册demo_eureka)

    首先我的博客记理论知识很少,大家对spring boot.spring cloud  .分布式 .微服务什么的一点概念都没有的还请先去百度看看理论,知道了是做什么用的,然后再写下demo ,这样学起来 ...

  7. Python上下文管理器 with

    对于系统资源的操作,如:文件操作.数据库操作等,我们往往打开文件.连接数据库后忘了将其close掉,这时就可能会引发异常,因此我们常用的做法是: # coding:utf-8 f = open(&qu ...

  8. git合并

    git 里合并了两个分支以后,是不是两个分支的内容就完全一样了? 不是.看合并到哪个分支,这个分支有两个分支所有的内容.另外一个分支不变. 合并操作( merge )对当前所在分支产生影响. 合并分支 ...

  9. 笔记-JS高级程序设计-BOM篇

    BOM提供了很多对象,用于访问浏览器的功能.这些功能与任何网页无关. 1BOM的核心对象是window,它代表浏览器的一个实例,它是通过JS访问浏览器窗口的一个借口,同时又是ECMAScript规定的 ...

  10. 机器学习实战笔记-k-近邻算法

    机器学习实战笔记-k-近邻算法 目录 1. k-近邻算法概述 2. 示例:使用k-近邻算法改进约会网站的配对效果 3. 示例:手写识别系统 4. 小结 本章介绍了<机器学习实战>这本书中的 ...