时间轮在Kafka的实践

本文深入探讨了时间轮在Kafka中的应用,特别是在实现延迟操作如延迟生产、延迟拉取等方面的角色。时间轮通过高效的数据结构降低延迟任务的处理复杂度,避免了全量扫描,提升了系统性能。文章介绍了时间轮的数据结构、任务存放、时间轮的升降级机制,并通过代码分析解释了任务添加和驱动时间轮滚动的核心流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

桔妹导读:时间轮是一个应用场景很广的组件,在很多高性能中间件中都有它的身影,如Netty、Quartz、Akka,当然也包括Kafka,本文主要介绍时间轮在kafka的应用和实战,从核心源码和设计的角度对时间轮进行深入的讲解 。

1. 

引子

从2个面试题说起,第一个问题:如果一台机器上有10w个定时任务,如何做到高效触发?

具体场景是:

有一个APP实时消息通道系统,对每个用户会维护一个APP到服务器的TCP连接,用来实时收发消息,对这个TCP连接,有这样一个需求:“如果连续30s没有请求包(例如登录,消息,keepalive包),服务端就要将这个用户的状态置为离线”。

 其中,单机TCP同时在线量约在10w级别,keepalive请求包较分散大概30s一次,吞吐量约在3000qps。

怎么做?

常用方案使用time定时任务,每秒扫描一次所有连接的集合Map<uid, last_packet_time>,把连接时间(每次有新的请求更新对应连接的连接时间)比当前时间的差值大30s的连接找出来处理。

另一种方案,使用环形队列法:

三个重要的数据结构:

  1. 30s超时,就创建一个index从0到30的环形队列(本质是个数组)

  2. 环上每一个slot是一个Set<uid>,任务集合

  3. 同时还有一个Map<uid, index>,记录uid落在环上的哪个slot里

这样当有某用户uid有请求包到达时:

  1. 从Map结构中,查找出这个uid存储在哪一个slot里

  2. 从这个slot的Set结构中,删除这个uid

  3. 将uid重新加入到新的slot中,具体是哪一个slot呢 => Current Index指针所指向的上一个slot,因为这个slot,会被timer在30s之后扫描到

  4. 更新Map,这个uid对应slot的index值

哪些元素会被超时掉呢?

Current Index每秒种移动一个slot,这个slot对应的Set<uid>中所有uid都应该被集体超时!如果最近30s有请求包来到,一定被放到Current Index的前一个slot了,Current Index所在的slot对应Set中所有元素,都是最近30s没有请求包来到的。

所以,当没有超时时,Current Index扫到的每一个slot的Set中应该都没有元素。

两种方案对比:

方案一每次都要轮询所有数据,而方案二使用环形队列只需要轮询这一刻需要过期的数据,如果没有数据过期则没有数据要处理,并且是批量超时,并且由于是环形结构更加节约空间,这很适合高性能场景。

第二个问题:在开发过程中有延迟一定时间的任务要执行,怎么做?

如果不重复造轮子的话,我们的选择当然是延迟队列或者Timer。

延迟队列和在Timer中增 加延时任务采用数组表示的最小堆的数据结构实现,每次放入新元素和移除队首元素时间复杂度为O(nlog(n))。

2. 

时间轮

方案二所采用的环形队列,就是时间轮的底层数据结构,它能够让需要处理的数据(任务的抽象)集中,在Kafka中存在大量的延迟操作,比如延迟生产、延迟拉取以及延迟删除等。Kafka并没有使用JDK自带的Timer或者DelayQueue来实现延迟的功能,而是基于时间轮自定义了一个用于实现延迟功能的定时器(SystemTimer)。JDK的Timer和DelayQueue插入和删除操作的平均时间复杂度为O(nlog(n)),并不能满足Kafka的高性能要求,而基于时间轮可以将插入和删除操作的时间复杂度都降为O(1)。时间轮的应用并非Kafka独有,其

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值