TimeWheel算法介绍及在应用上的探索
2024.09.17 01:32浏览量:32简介:TimeWheel算法介绍及在应用上的探索
文心大模型4.5及X1 正式发布
百度智能云千帆全面支持文心大模型4.5 API调用,文心大模型X1即将上线
本文从追溯时间轮算法的出现,介绍了时间轮算法未出现前,基于队列的定时任务实现,以及基于队列的定时任务实现所存在的缺陷。接着我们介绍了时间轮算法的算法思想及其数据结构,详细阐述了三种时间轮模型的数据结构和优劣性。
再次,我们介绍时间轮算法在 Dubbo 框架中的应用,并给出了它在 Dubbo 中的主要实现方式。
最后,我们以项目中的某个服务架构优化出发,介绍了目前设计中存在的缺陷,并借助来自中间件团队的,包含时间轮算法实现的延迟 MQ,给出了优化设计的方法。
1.1 时间轮算法的出现
在计算程序中,定时器用于指定一个具体的时间点去执行某一个既定的任务。而时间轮算法就是这样一种能够实现延迟功能(定时器)的巧妙算法。时间轮算法首ci出现在 1997 年 George Varghese 和 Anthony Lauck 发表于 IEEE 期刊,名为“Hashed and Hierarchical Timing Wheels: Efficient Data Structures for Implementing a Timer Facility”的论文上。此文章指出,实现操作系统定时器模块的常规算法需要 O(n)的时间复杂度启动和维护计时器,对于更大问题规模 (n),这样的时间开销是巨大的,文中提出并证明了,通过一种环状桶的数据结构,可以做到使用 O(1)的时间复杂度,就可以启动,停止和维护计时器,并介绍了对时间间隔划分的处理,第一种方式是将所有的计时器时间间隔进行散列(Hash),这些时间间隔被散列到时间轮上特定的槽位中(Slot),第二种方式是利用多粒度定时轮组成具有层级结构的组合,以扩展更大的时间范围。这两种结构将在第二章中详细介绍。
1.2 基于队列的定时任务执行模型
在计算机的世界中,只有待解决的问题大规模化以后,算法的价值才能够得到最大化的体现。在介绍时间轮算法之前,我们有必要介绍另一种定时任务的实现,即基于队列的定时任务。队列这种数据结构无论是在操作系统中还是各编程语言如 Java 中都被大量使用,本文不再展开赘述。
下面从线程模型、定时任务种类和任务队列的数据结构三个方面展开详细介绍:
(1)线程模型
用户线程:负责定时任务的注册;轮询线程:负责从任务队列中扫描出符合执行条件的任务,例如任务的待执行时间已经到达,轮询线程将从队列中取出该任务,并交由异步线程池处理该任务。异步线程池:专门负责任务的执行。
(2)定时任务
定时任务主要分为一次性执行的定时任务(Dubbo 中超时判断)以及重复执行的定时任务,这两种定时任务都很好理解,一次性执行的定时任务在规定的未来某一时刻或距离现在的一段固定时长后执行,分别对应绝对值和相对值的概念。
而重复执行的定时任务是在一次性执行任务的基础上多次重复执行,这意味着,在上述线程协调工作中,当重复执行任务执行完成一次后,将被重新放回任务队列中。
(3)任务队列数据结构
从最简单的数据结构出发,假设我们选用最基本的队列,或者考虑到增减任务的方便,选择双向链表做为任务队列,为任务队列中的每个任务提供一个时间戳字段,这种实现的策略会产生哪些问题?
最大的问题是在查询上,假设任务队列中存在一些任务,那么为了找出达到规定时刻的待执行任务,轮询线程需要扫描全部任务,此种数据结构的时间复杂度为 O(n),而且存在大量的空轮询,即大部分的任务都没有达到执行时间,这种效率几乎是不可接受的。
为了提升查询效率,可以尝试从数据结构出发,利用有序队列,在计算机的算法中,有序性可以显著提高遍历的效率,这样一来,定时任务队列轮询线程从头向尾遍历时,在发现任意任务未达到规定执行时间戳后,就可以停止遍历。
但是维护有序性也需要付出代价,普通任务队列入队一个任务的时间复杂度仅仅是 O(1),而有序任务队列入队一个任务的时间复杂度为 O(nlogn)。其次,我们可以借鉴分治的思想,将任务队列分成 n 份,利用多线程遍历,在线程完全并发执行的情况下,问题规模简化到原来的 1/n。但是多线程也会 CPU 执行效率降低。
综上分析,定时任务框架需要具有如下要素:
严格高效的数据结构,并不能基于简单的队列结构来存储任务,否则轮询的执行效率永远无法提高。
简单的并发模型:CPU 的线程非常宝贵,不应占用过多线程资源。
时间轮算法解决了上述基于队列的定时任务执行模型的缺陷,因此时间轮算法思想在后面互联网技术发展中得到了大量应用,我们熟悉的 Linux Crontab,以及 Java 开发中常用的 Dubbo、Netty、Quartz、Akka、ZooKeeper、Kafka 等,几乎所有的时间任务调度都采用了时间轮算法的思想。
值得一提的是,在 Dubbo 中,为了增强系统容错,很多地方需要用到只需一次执行的任务调度,比如消费者需要知道各个 RPC 调用是否超时,而在 Dubbo 最开始的实现中,是采用将所有的返回结果(defaultFuture),都放入一个集合中,并通过一个定时任务,间隔扫描所有的 future,逐个判断是否超时。这样逻辑简单,但是浪费性能,后面 Dubbo 借鉴了 Netty,引入了时间轮。
发表评论
登录后可评论,请前往 登录 或 注册