关于时间轮算法的起始

我也认真的看了时间轮算法相关,大致都是如下的一个图





个人认为的问题

大部分文章在解释这个为何用时间轮的时候都再说

假设我们现在有一个很大的数组,专门用于存放延时任务。它的精度达到了毫秒级!那么我们的延迟任务实际上需要将定时的那个时间简单转换为毫秒即可,然后将定时任务存入其中:

比如说当前的时间是 2018/10/24 19:43:45,那么就将任务存入 Task[1540381425000],value 则是定时任务的内容。

假如这个数组长度达到了亿亿级,我们确实可以这么干。 那如果将精度缩减到秒级呢?我们也需要一个百亿级长度的数组。

先不说内存够不够,显然你的定时器要这么大的内存显然很浪费。

为什么要用时间轮实现

  1. 通常用于实现 linux 内核任务、游戏类的 buf 计时。
  2. 单个时间轮局限性:保存的任务数量少,不能超过当前时间轮。
  3. 多层时间轮,典型:日-时-分-秒
  4. 传统 java 实现定时:Timer,只能单线程,会阻塞; Executors.newScheduledThreadPoll, 使用的最小堆来实现,任务还是不能太多,添加时间复杂度为 O(logn)

时间轮定时器最大的优点

  1. 是任务的添加与移除,都是 O(1)级的复杂度;
  2. 不会占用大量的资源;
  3. 只需要有一个线程去推进时间轮就可以工作了

举例说明

比如说当前的时间是 2018/10/24 19:43:45,那么就将任务存入 Task[1540381425000],value 则是定时任务的内容。

  1. private Task[很长] tasks;
  2. public List<Task> getTaskList(long timestamp) {
  3. return task.get(timestamp)
  4. }
  5. // 假装这里真的能一毫秒一个循环
  6. public void run(){
  7. while (true){
  8. getTaskList(System.currentTimeMillis()).后台执行()
  9. Thread.sleep(1);
  10. }
  11. }

假如这个数组长度达到了亿亿级,我们确实可以这么干。 那如果将精度缩减到秒级呢?我们也需要一个百亿级长度的数组。

先不说内存够不够,显然你的定时器要这么大的内存显然很浪费。


基于个人的理解对其进行改造和实现

对于以上的描述,我自己还是很不认同的,我为啥要声明这么大的数组,难道不能有一个任务,我就放一个任务么,实际数组的长度应该是你任务数的长度吧。

要不然,为啥要这么浪费。想不通,可能还有别的解释,谁有答案可以告诉我。

在实际的使用中,一般都为秒级,毫秒级都很少,因为毫秒级的不准。

所以,我可以根据这些通过 hash 字典构建一个 这样的时间轮算法,也作为我自己想实现定时任务框架的一部分。

逻辑:

核心为一个字典,key 为时间戳,值为任务列表。

整体就是每个添加的任务都有一个触发的时间(秒),到了这个秒,时间就会触发,自然会去执行相关的任务。

有了任务才会添加,不会任何任务都添加的。

任务触发的时候,会获取任务下一次执行的时间,并插入到任务列表里。

  1. static void Main(string[] args)
  2. {
  3. ScheduledTask scheduledTask = new ScheduledTask(() => { Console.WriteLine($"{DateTime.Now}"); }, new TimeSpan(0, 0, 5));
  4. TimeWheel timeWheel = new TimeWheel();
  5. timeWheel.AddTask(scheduledTask);
  6. timeWheel.Run();
  7. Console.WriteLine("开始运行时间轮!");
  8. Console.ReadLine();
  9. }
  1. /// <summary>
  2. /// 时间轮算法(终极)实现
  3. /// 大部分都是支持秒级,所以,按照秒级进行实现
  4. /// </summary>
  5. public class TimeWheel
  6. {
  7. /// <summary>
  8. /// 任务列表
  9. /// </summary>
  10. public ConcurrentDictionary<long, List<IScheduledTask>> Tasks = new();
  11. public void Run()
  12. {
  13. while (true)
  14. {
  15. var now = DateTime.Now;
  16. Task.Run(() => { Trigger(now); });
  17. var offset = 500 - now.Millisecond;
  18. SpinWait.SpinUntil(() => false, 1000 + offset);
  19. }
  20. }
  21. public void Trigger(DateTime dateTime)
  22. {
  23. var timeStamp = GenerateTimestamp(dateTime);
  24. var oldTimeStamp = timeStamp - 1;
  25. Tasks.TryRemove(oldTimeStamp, out var _);
  26. Tasks.TryGetValue(timeStamp, out var result);
  27. if (result?.Any() == true)
  28. {
  29. foreach (var item in result)
  30. {
  31. Task.Run(item.GetAction());
  32. var NewTime = item.GetNextTime();
  33. if (NewTime.HasValue)
  34. {
  35. AddTask(NewTime.Value, item);
  36. }
  37. }
  38. }
  39. }
  40. public void AddTask(IScheduledTask scheduledTask)
  41. {
  42. var timeStamp = GenerateTimestamp(scheduledTask.GetNextTime().Value);
  43. Tasks.AddOrUpdate(timeStamp, new List<IScheduledTask>() { scheduledTask }, (k, v) =>
  44. {
  45. v.Add(scheduledTask);
  46. return v;
  47. });
  48. }
  49. public void AddTask(DateTime dateTime, IScheduledTask scheduledTask)
  50. {
  51. var timeStamp = GenerateTimestamp(dateTime);
  52. Tasks.AddOrUpdate(timeStamp, new List<IScheduledTask>() { scheduledTask }, (k, v) =>
  53. {
  54. v.Add(scheduledTask);
  55. return v;
  56. });
  57. }
  58. private long GenerateTimestamp(DateTime dateTime)
  59. {
  60. return new DateTimeOffset(dateTime.ToUniversalTime()).ToUnixTimeSeconds();
  61. }
  62. private DateTime GetDatetime(long timeStamp)
  63. {
  64. var d = DateTimeOffset.FromUnixTimeSeconds(timeStamp);
  65. return d.LocalDateTime;
  66. }
  67. }
  1. public interface IScheduledTask
  2. {
  3. public Action GetAction();
  4. public DateTime? GetNextTime();
  5. }
  6. /// <summary>
  7. /// 定时器任务,普通任务
  8. /// 间隔指定的时间
  9. /// </summary>
  10. public class ScheduledTask : IScheduledTask
  11. {
  12. private Action _action;
  13. private TimeSpan timeSpan;
  14. public ScheduledTask(Action action, TimeSpan timeSpan)
  15. {
  16. this._action = action;
  17. this.timeSpan = timeSpan;
  18. }
  19. public Action GetAction()
  20. {
  21. return this._action;
  22. }
  23. public DateTime? GetNextTime()
  24. {
  25. return DateTime.Now.AddSeconds(timeSpan.TotalSeconds);
  26. }
  27. }

最后的效果

当然,也可以通过 CRON 表达式来实现更为高级的。

后期再来个更高级一点的。

github 地址

https://github.com/kesshei/TimeWheelDemo

.Net 之时间轮算法(终极版)的更多相关文章

  1. .Net之时间轮算法(终极版)定时任务

    TimeWheelDemo 一个基于时间轮原理的定时器 对时间轮的理解 其实我是有一篇文章(.Net 之时间轮算法(终极版))针对时间轮的理论理解的,但是,我想,为啥我看完时间轮原理后,会采用这样的方 ...

  2. 时间轮算法的定时器(Delphi)

    源码下载 http://files.cnblogs.com/lwm8246/uTimeWheel.rar D7,XE2 编译测试OK //时间轮算法的定时器 //-- : QQ unit uTimeW ...

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

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

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

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

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

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

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

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

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

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

  8. 经典多级时间轮定时器(C语言版)

    经典多级时间轮定时器(C语言版) 文章目录 经典多级时间轮定时器(C语言版) 1. 序言 2. 多级时间轮实现框架 2.1 多级时间轮对象 2.2 时间轮对象 2.3 定时任务对象 2.4 双向链表 ...

  9. 08重编终极版《东邪西毒:终极版》DVD粤语中字

    1.东邪西毒].Ashes.of.Time.1994.384p.DVDRip.x264.ac3-DTMM.mkv 这个版本最清晰 ,可惜删减了,只有87分钟,粤语,1.4G. 2.东邪西毒(初始版). ...

随机推荐

  1. 攻防世界web进阶题—bug

    攻防世界web进阶题-bug 1.打开题目看一下源码,没有问题 2.扫一下目录,没有问题 3.查一下网站的组成:php+Apache+Ubuntu 只有登录界面 这里可以可以想到:爆破.万能密码.进行 ...

  2. Windows UIA自动化测试框架学习--获取qq好友列表

    前段时间应公司要求开发一款针对现有WPF程序的自动化测试工具,在网上查资料找了一段时间,发现用来做自动化测试的框架还是比较多的,比如python的两个模块pywinauto和uiautomation, ...

  3. 客户案例-SES S.A.

    SES专门为世界上最偏远的地方提供高性能的移动网络连接.广播.维和人员的实时情报和媒体内容. SES是一个全球性组织,专注于提供高性能的视频和虚拟数据解决方案.今天,SES拥有最大的覆盖范围,有超过7 ...

  4. iOS全埋点解决方案-时间相关

    前言 ​ 我们使用"事件模型( Event 模型)"来描述用户的各种行为,事件模型包括事件( Event )和用户( User )两个核心实体.我们在描述用户行为时,往往只需要描述 ...

  5. 150_1秒获取Power BI Pro帐号

    博客:www.jiaopengzi.com 请点击[阅读原文]获取帐号 一.背景 当你来到这篇文章的时候,我想你已经在网上搜索了一圈了.网上有一大把教你如何注册Power BI帐号的方法,我们这里就不 ...

  6. 119_Power Pivot 长尾明细显示为【其他】

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 最近比较忙,太久不没有更新文章,确实没有好的素材,就写一个吧. 在关于产品数据分析的时候,我们经常关注的是主要的 ...

  7. 安装Zabbix到Ubuntu(APT)

    运行环境 系统版本:Ubuntu 16.04.2 LTS 软件版本:Zabbix-4.0.2 硬件要求:无 安装过程 1.安装APT-Zabbix存储库 APT-Zabbix存储库由Zabbix官网提 ...

  8. 【SpringCloud原理】万字剖析OpenFeign之FeignClient动态代理生成源码

    年前的时候我发布两篇关于nacos源码的文章,一篇是聊一聊nacos是如何进行服务注册的,另一篇是一文带你看懂nacos是如何整合springcloud -- 注册中心篇.今天就继续接着剖析Sprin ...

  9. thusc2022游记

    DAY -1: 刷往年相关的题 DAY 0: 刷会儿题了,搞电脑,下obs.不过,发现电脑出了很多问题. obs没有录频效果,因为卡,杀毒软件把vc++全都删了.因此无dll文件错误,搞了一晚上都没搞 ...

  10. 两个月吃透阿里P9推荐260页SpringBoot2企业应用实战pdf入职定P6+

    前言 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置 ...