一、背景

近期在内网上看到一篇文章,文中提到的场景是 系统自动取消 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. UVA11951 Area 题解

    Content 小 S 想买下一块地.他所在的城市可以看成一个 \(n\times m\) 的网格,要购买所处在 \((i,j)\) 的网格需要缴税 \(c_{i,j}\) 元,如果一块地里面有多个网 ...

  2. myeclipse 安装spket

    myeclipse 安装spket为以下步骤 请注意:1.myPlugins为新建的文件夹 2.spket-1.6.23为新建的文件 如上步骤为安装spket,那么如下的步骤为让myeclipse里的 ...

  3. C++ 智能指针(shared_ptr/weak_ptr)原理分析

    其主要的类关系如下所示(省略相关的类模板参数): 图1 从上面的类图可以清楚的看出shared_ptr内部含有一个指向被管理对象(managed object)T的指针以及一个__shared_cou ...

  4. 最强最全面的大数据SQL经典面试题(由31位大佬共同协作完成)

    本套SQL题的答案是由许多小伙伴共同贡献的,1+1的力量是远远大于2的,有不少题目都采用了非常巧妙的解法,也有不少题目有多种解法.本套大数据SQL题不仅题目丰富多样,答案更是精彩绝伦! 注:以下参考答 ...

  5. axios 高级封装

    import axios from 'axios'; import qs from 'qs'; const Unit = { async getApi(ajaxCfg){ let data = awa ...

  6. react中使用Input表单双向绑定方法

    input react 表单 input 密码框在谷歌浏览器下 会有黄色填充 官网的不太用,这个比较好用 type="password" autoComplete="ne ...

  7. JAVAWEB项目处理XSS漏洞攻击处理方案

    对页面传入的参数值进行过滤,过滤方法如下 public static String xssEncode(String s) { if (s == null || s.equals("&quo ...

  8. 【LeetCode】561. Array Partition I 解题报告(Java & Python)

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

  9. 【LeetCode】713. Subarray Product Less Than K 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址: https://leetcode.com/problems/subarray ...

  10. Obfuscated Gradients Give a False Sense of Security: Circumventing Defenses to Adversarial Examples

    目录 概 主要内容 Obfuscated Gradients BPDA 特例 一般情形 EOT Reparameterization 具体的案例 Thermometer encoding Input ...