一、背景

近期在内网上看到一篇文章,文中提到的场景是 系统自动取消 15分钟内 未支付的订单。对于数据量比较少并且实时性要求不那么高的场景,一种简单的方式是轮询数据库,比如每秒轮询一下数据库中所有的数据,处理所有到期的数据。但是如果需要处理的数据量较大,高达百万甚至千万时,这时候如果还轮询数据库就不妥了。

文中给出了这类问题的解决思路,使用延迟队列:Redis ZSort、RabbitMQ 死信队列、Netty 时间轮

二、延迟队列-时间轮

上图几个名词的解释:

  • tickMs: 时间轮由多个时间格组成,每个时间格就是 tickMs,它代表当前时间轮的基本时间跨度。
  • wheelSize: 代表每一层时间轮的格数
  • interval: 当前时间轮的总体时间跨度,interval=tickMs × wheelSize
  • startMs: 构造当层时间轮时候的当前时间,第一层的时间轮的 startMs 是 TimeUnit.NANOSECONDS.toMillis(nanoseconds()),上层时间轮的 startMs 为下层时间轮的 currentTime。
  • currentTime: 表示时间轮当前所处的时间,currentTime 是 tickMs 的整数倍(通过 currentTime=startMs - (startMs % tickMs 来保正 currentTime 一定是 tickMs 的整数倍),这个运算类比钟表中分钟里 65 秒分钟指针指向的还是 1 分钟)。 currentTime 可以将整个时间轮划分为到期部分和未到期部分,currentTime 当前指向的时间格也属于到期部分,表示刚好到期,需要处理此时间格所对应的 TimerTaskList 的所有任务。

时间轮工作原理:

如上图所示,时间轮是一个存储延迟消息的环形队列,每个元素对应一个延迟任务列表,这个列表是一个双向环形链表,链表中每一项都代表一个需要执行的延迟任务。时间轮会有表盘指针,表示时间轮当前所指时间,随着时间推移,该指针会不断前进,并处理对应位置上的延迟任务列表。

三、Netty 时间轮源码分析

3.1.主要的成员类:

  • HashedWheelTimer:调度器,服务启动 Worker 线程,投递新的 延迟任务。
  • Worker:工作线程,循环执行,每次sheep(tickMs),根据指针的位置,遍历对应的 延迟任务列表。
  • HashedWheelBucket:如上图所示,表盘中的每一个格子,记录着 延迟任务 列表的head、tail。
  • HashedWheelTimeout:对应延迟任务列表中的一个元素,当任务过期时,执行TimerTask中的run方法。
  • TimerTask:接口类,投递的延迟任务 需要实践接口中的 run 方法

3.2.主要的类方法:

  • HashedWheelTimer#newTimeout:投递新的延迟任务 到 timeouts 队列中(Netty 中使用的是 PlatformDependent 队列结构)
  • Worker#waitForNextTick: 等待下个ticks,线程sleep(tick)
  • Worker#transferTimeoutsToBuckets: 从 timeouts 队列中消费 100000 个 延迟任务,分配到不同的 HashedWheelBucket 的链表中。
  • HashedWheelBucket#expireTimeouts:执行到期的任务,然后从队列中剔除已经过期的任务。

四、Rust实现HashedWheelTimer

Rust 实现的 HashedWheelTimer,相比 Netty 只是语言上的区别,算法和数据结构上是一致。源码地址:https://github.com/ZuoFuhong/hashed_wheel_timer

过程中遇到的一些难点:

1.BucketTimeout 双向链表的实现比较费劲,在单线程下使用的 Rc + RefCell 的组合。

2.投递 延迟任务,使用标准库的 mpsc 队列,进行线程间的传递:

3.延迟任务的 run 方法,想过传递闭包,也想过用泛型,这些后续再实现:

4.为什么将WheelTimeout转换成 BucketTimeout:

因为 BucketTimeout  的内部元素 是一个 Rc + RefCell 的组合,不能进行线程传递。如果要支持线程间传递,就要加一个 Arc + Mutex,这样感觉好啰嗦,又得不停的加锁,所以做了一个转换。

5.为什么把 Worker#start 放在 WheelTimer 中执行启动,而观察Netty 是在 WheelTimer#new_timeout 中启动的:

如果不提前启动 Worker#start,使用WheelTimer#clone 后 sender 元素还是 None。

以上,真的不是换个语言那么容易的事,现在回过头发现 java 和 golang 这类有 GC 的语言,真实太爽了,啥都不用管。

五、总结思考

作为一个服务端开发人员,在面试过程中,或多或少会被人问到 “大流量、高并发” 的问题。想要解决这类问题,前提是要能遇到 大规模的用户量,如果没有真实的用户量,可以模拟用户量。当有了 大规模的用户量时,进行验证时,很多问题就会冒出来。这一点在工作中,越发感受深刻:

  • 1.系统的承载能力,需要通过 “模拟压测” 的方式去预估用户量。不能等到真正有了用户量的时候,用实践去评估。
  • 2.任何一个小问题,在用户量较小的时候,不会出现。一旦用户量上来了,就会成为大问题。
  • 3.系统是不断迭代的,要结合背景 因时制宜,早期为了适应市场,快速开发,不要过度的去追求设计上的完美。当下满足诉求即可,后续在迭代中逐步加强。

Rust 实现Netty HashedWheelTimer时间轮的更多相关文章

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

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

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

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

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

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

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

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

  5. Redis之时间轮机制(五)

    一.什么是时间轮 时间轮这个技术其实出来很久了,在kafka.zookeeper等技术中都有时间轮使用的方式. 时间轮是一种高效利用线程资源进行批量化调度的一种调度模型.把大批量的调度任务全部绑定到同 ...

  6. SpringBoot定时任务 - 经典定时任务设计:时间轮(Timing Wheel)案例和原理

    Timer和ScheduledExecutorService是JDK内置的定时任务方案,而业内还有一个经典的定时任务的设计叫时间轮(Timing Wheel), Netty内部基于时间轮实现了一个Ha ...

  7. 延时任务-基于netty时间轮算法实现

    一.时间轮算法简介 为了大家能够理解下文中的代码,我们先来简单了解一下netty时间轮算法的核心原理 时间轮算法名副其实,时间轮就是一个环形的数据结构,类似于表盘,将时间轮分成多个bucket(比如: ...

  8. 使用netty HashedWheelTimer构建简单延迟队列

    背景 最近项目中有个业务,需要对用户新增任务到期后进行业务处理.使用定时任务定时扫描过期时间,浪费资源,且不实时.只能使用延时队列处理. DelayQueue 第一想到的是java自带的延时队列del ...

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

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

随机推荐

  1. CSAcademy Prefix Suffix Counting 题解

    CSAcademy Prefix Suffix Counting 题解 目录 CSAcademy Prefix Suffix Counting 题解 题意 思路 做法 程序 题意 给你两个数字\(N\ ...

  2. 分布式系统一致性算法(Paxos)

    CAP理论    一致性(Consistency)    可用性(Availability)    分区容错性(网络分区)Partition toleranceCAP理论的特点,就是CAP只能满足其中 ...

  3. [C# Expression] 之动态创建表达式

    上一篇中说到了 Expression 的一些概念性东西,其实也是为了这一篇做知识准备.为了实现 EFCore 的多条件.连表查询,简化查询代码编写,也就有了这篇文章.   在一些管理后台中,对数据进行 ...

  4. c++模板移除引用

    背景 一个函数内部需将函数内的一个变量转为其参数的类型, 函数的参数是引用 本文要演示的环境需要c++11支持(使用了新的关键字 using) 例如 get_value 内部将dobuble类型数据转 ...

  5. c++11之 algorithm 算法库新增 minmax_element同时计算最大值和最小值

    0.时刻提醒自己 Note: vector的释放 1. minmax_element 功能 寻找范围 [first, last) 中最小和最大的元素. 2. 头文件 #include <algo ...

  6. 【LeetCode】912. Sort an Array 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 库函数排序 桶排序 红黑树排序 归并排序 快速排序 ...

  7. 【LeetCode】1014. Capacity To Ship Packages Within D Days 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  8. 【LeetCode】75. Sort Colors 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 计数排序 双指针 日期 题目地址:https://l ...

  9. A. Watchmen(Codeforces 650A)

    A. Watchmen time limit per test 3 seconds memory limit per test 256 megabytes input standard input o ...

  10. PAT甲组 1010 Radix (二分)

    1010 Radix (25分) Given a pair of positive integers, for example, \(6\) and \(110\), can this equatio ...