Kafka中时间轮分析与Java实现
在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实现的更多相关文章
- Go语言中时间轮的实现
最近在工作中有一个需求,简单来说就是在短时间内会创建上百万个定时任务,创建的时候会将对应的金额相加,防止超售,需要过半个小时再去核对数据,如果数据对不上就需要将加上的金额再减回去. 这个需求如果用Go ...
- kafka中常用API的简单JAVA代码
通过之前<kafka分布式消息队列介绍以及集群安装>的介绍,对kafka有了初步的了解.本文主要讲述java代码中常用的操作. 准备:增加kafka依赖 <dependency> ...
- [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用
[从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...
- 时间轮算法在Netty和Kafka中的应用,为什么不用Timer、延时线程池?
大家好,我是yes. 最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Ja ...
- kafka时间轮的原理(一)
概述 早就想写关于kafka时间轮的随笔了,奈何时间不够,技术感觉理解不到位,现在把我之前学习到的进行整理一下,以便于以后并不会忘却.kafka时间轮是一个时间延时调度的工具,学习它可以掌握更加灵活先 ...
- Kafka解惑之时间轮 (TimingWheel)
Kafka中存在大量的延迟操作,比如延迟生产.延迟拉取以及延迟删除等.Kafka并没有使用JDK自带的Timer或者DelayQueue来实现延迟的功能,而是基于时间轮自定义了一个用于实现延迟功能的定 ...
- kafka时间轮简易实现(二)
概述 上一篇主要介绍了kafka时间轮源码和原理,这篇主要介绍一下kafka时间轮简单实现和使用kafka时间轮.如果要实现一个时间轮,就要了解他的数据结构和运行原理,上一篇随笔介绍了不同种类的数据结 ...
- 时间轮算法(TimingWheel)是如何实现的?
前言 我在2. SOFAJRaft源码分析-JRaft的定时任务调度器是怎么做的?这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...
- 时间轮机制在Redisson分布式锁中的实际应用以及时间轮源码分析
本篇文章主要基于Redisson中实现的分布式锁机制继续进行展开,分析Redisson中的时间轮机制. 在前面分析的Redisson的分布式锁实现中,有一个Watch Dog机制来对锁键进行续约,代码 ...
随机推荐
- [转] Node.js中package.json中库的版本号详解(^和~区别)
当我们查看package.json中已安装的库的时候,会发现他们的版本号之前都会加一个符号,有的是插入符号(^),有的是波浪符号(~).那么他们到底有什么区别呢?先贴一个例子,对照例子来做解释: bl ...
- Spring boot web程序static资源放在jar外部
spring boot程序的static目录默认在resources/static目录, 打包为jar的时候,会把static目录打包进去,这样会存在一些问题: static文件过多,造成jar包体积 ...
- 分布式系统的CAP理论
一.CAP理论概述 一个分布式系统最多只能同时满足一致性(Consistency).可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项. 二.CAP ...
- hbase0.94.11版本和hbase1.4.9版本的benchamark区别
1.起初使用ycsb对hbase进行benchmark,分别在100%写的情况下检测写性能:在100%读的情况下检测读的性能.实验数据如下: 2.新版本的habse写性能竟然不如老版本.!!!.于是我 ...
- scrapy 日志一般配置
- python2.7安装和uwsgi
python2.7安装和uwsgi tar zxf Python-2.7.13xxxx# 这里,必须用–enable-shared,生成动态库,否则会遇到wsgi不能编译的问题. Bonus: mul ...
- Scala-Unit-2-Scala基础语法1
一.Scala程序的开始->HelloScala 这里的操作如同java的helloworld程序,直接放代码! object HelloScala{ def main(args:Array[S ...
- HDU 4185 Oil Skimming 【最大匹配】
<题目链接> 题目大意: 给你一张图,图中有 '*' , '.' 两点,现在每次覆盖相邻的两个 '#' ,问最多能够覆盖几次. 解题分析: 无向图二分匹配的模板题,每个'#'点与周围四个方 ...
- 你有所不知的<script>元素
向html页面中插入javascript的主要方法,就是使用<script>元素. <script>定义了下列6个属性: async:可选.表示应该立即下载脚本,但不应妨碍页面 ...
- [洛谷P1063][NOIP2006]能量项链
区间DP模板题 题目描述 在Mars星球上,每个Mars人都随身佩带着一串能量项链.在项链上有N颗能量珠.能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数.并且,对于相邻的两颗珠子,前一颗 ...