一、背景

近期在内网上看到一篇文章,文中提到的场景是 系统自动取消 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. Python基础入门(7)- Python异常处理机制

    1.初识异常 1.1.什么是异常与异常处理 异常就是错误 异常会导致程序崩溃并停止运行 能监控并捕获异常,将异常部位的程序进行修理使得程序继续正常运行 1.2.异常的语法 1 # coding:utf ...

  2. java 8 启动脚本优化 3

    #!/bin/bash #链接文件 source /etc/profile #java虚拟机启动参数 #通过http://xxfox.perfma.com/jvm/check来检查参数的合理性 #各参 ...

  3. react组件配置样式hover效果的实现

    需求 react 自定义一个组件,组件内部样式可以灵活配置 问题 一般样式都可以通过属性传入,比如:颜色,字号等,但是对于一些有hover效果的地方,属性传入后,按照平时css的使用方式不太容易实现 ...

  4. 【九度OJ】题目1191:矩阵最大值 解题报告

    [九度OJ]题目1191:矩阵最大值 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1191 题目描述: 编写一个程序输入一个mXn的 ...

  5. 【LeetCode】372. Super Pow 解题报告(Python)

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

  6. 【JAVA今法修真】 第七章 洞天风云起,索引混乱平

    您好,我是南橘,万法仙门的掌门,刚刚从九州世界穿越到地球,因为时空乱流的影响导致我的法力全失,现在不得不通过这个平台向广大修真天才们借去力量.你们的每一个点赞,每一个关注都是让我回到九州世界的助力,兄 ...

  7. java 语言基础作业

    1.动手动脑 仔细阅读示例: EnumTest.java,运行它,分析运行结果? 程序运行结果: 实验结论:枚举类型是引用类型!枚举不属于原始数据类型,它的每个具体值都引用一个特定的对象.相同的值则引 ...

  8. CS5211|CS5211参数|eDP转LVDS转换器芯片

    CS5211概述 CS5211是一个eDP到LVDS转换器,配置灵活,适用于低成本显示系统.CS5211与eDP 1.2兼容,支持1车道和2车道模式,每车道速度为1.62Gbps和2.7Gbps.CS ...

  9. tcache BUUCTF gyctf_2020_signin

    Ubuntu18.04的题 用到了两个特性: 一个是 calloc 的特点:不会分配 tcache chunk 中的 chunk 另一个是 tcache 的特点:在分配 fastbin 中的 chun ...

  10. JDK httpClient 详解(源码级分析)——概览及架构篇

    1. 前言 2018年9月,伴随着java 11的发布,内置的httpclient正式登上了历史的舞台.此前,JDK内置的http工具URLConnection性能羸弱,操作繁琐,饱受诟病,也因此令如 ...