摘要:在软件项目中,定时器也被应用到了各方各面,本文将从项目入手,讲述定时器,本文的例子都以为例。定时器总类定时器有两种对应重复任务和一次性任务。
在大规模分布式系统中,每个业务都可能是集群,每个业务机都会产生定时任务,不同的业务会有不同的任务管理需求,统一的任务调度和管理变得非常有必要。
定时如何准确,大量的定时被同时触发怎么办?
定时结束的时候,怎么通知业务机去处理呢?
某台业务机下线了怎么办?
如何提供任务更新、删除功能?
基本模型如下图:
定时器在社会中有着广泛的应用,比如每天叫你起床的闹钟。在软件项目中,定时器也被应用到了各方各面,本文将从 web 项目入手,讲述定时器,本文的例子都以 node 为例。
为什么要用定时器?没有什么比机器更加准时!在我接触单片机的时候,已经开始感叹,为什么机器时间可以做到这么准!
比如文章的定时发布、商品的准点开始抢购、活动定时上下架,肯定不会是一个又一个管理员在后台帮你点击按钮,完成操作!系统的准时可以定位到毫秒级,虽然每个用户可能和服务器的时间不一致,秒级的差别还是在可接受范围的,但是在某些领域也会有很多精细到毫秒级的定时任务需求,比如航空航天、定时炸弹等等。
定时器总类定时器有两种 interval、timeout, 对应重复任务和一次性任务。在我的理解里,interval 任务只是在 timeout 的时候再次注册了本任务。
// 重复性任务 var timer = setInterval(function(){ // do something }, milliseconds)
// 一次性任务 var timer = setTimeout(function(){ // do something }, milliseconds)unix crontab 能解决问题吗?
crontab 并不能精确到秒,crontab 的最小粒度是分,即当第一位是「*/1」时,即最小单位是每分钟执行,(不排除你们有奇淫技巧可以做到秒级控制的)。unix 本身支持强大的定时任务管理 crontab,定时的格式也是强大得令人惊叹。
* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun) │ │ │ │ └───── month (1 - 12) │ │ │ └────────── day of month (1 - 31) │ │ └─────────────── hour (0 - 23) │ └──────────────────── minute (0 - 59) └───────────────────────── second (0 - 59, optional)
1)Cron 表达式的格式:秒 分 时 日 月 周 年 (可选)。
字段名 允许的值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日 1-31 , - * ? / L W C
月 1-12 or JAN-DEC , - * /
周几 1-7 or SUN-SAT , - * ? / L C #
年 (可选字段) empty, 1970-2099 , - * /
「?」字符:表示不确定的值
「,」字符:指定数个值
「-」字符:指定一个值的范围
「/」字符:指定一个值的增加幅度。n/m 表示从 n 开始,每次增加 m
「L」字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期 X
「W」字符:指定离给定日期最近的工作日 (周一到周五)
「#」字符:表示该月第几个周 X。6#3 表示该月第 3 个周五
Cron 表达式范例:每隔 5 秒执行一次:/5 * ?
每隔 1 分钟执行一次:0 /1 ?
每天 23 点执行一次:0 0 23 ?
每天凌晨 1 点执行一次:0 0 1 ?
每月 1 号凌晨 1 点执行一次:0 0 1 1 * ?
每月最后一天 23 点执行一次:0 0 23 L * ?
每周星期天凌晨 1 点实行一次:0 0 1 ? * L
在 26 分、29 分、33 分执行一次:0 26,29,33 * ?
每天的 0 点、13 点、18 点、21 点都执行一次:0 0 0,13,18,21 ?
每种开发语言都提供了 crontab 的相关封装,让开发者调用起来得心应手。以 node 为例:
require("crontab").load(function(err, crontab) { // create with string expression var job = crontab.create("ls -la", "0 7 * * 1,2,3,4,5"); });
你在 github 搜索 crontab 能搜到主流语言的实现。
有个问题,定时器不准时!
setInterval 的回调函数并不是到时后立即执行,而是等系统计算资源空闲下来后才会执行。而下一次触发时间则是在 setInterval 回调函数执行完毕之后才开始计时,所以如果 setInterval 内执行的计算过于耗时,或者有其他耗时任务在执行,setInterval 的计时会越来越不准, 延迟很厉害。crontab 也是同样的原理。
var startTime = new Date().getTime(); var count = 0; //耗时任务 setInterval(function(){ var i = 0; while(i++ < 100000000); }, 0); setInterval(function(){ count++; console.log(new Date().getTime() - (startTime + count * 1000)); }, 1000);
结果
126 176 163 112 109 107 203 189 170
当然,不排除你们有奇淫技巧可以做到秒级控制的。
成千上万定时任务时怎么管理?Crontab 存在任务上限(其实我也不知道上限是多少,知道的麻烦告诉我),任务的同步、备份管理都比较麻烦,也会有比较多的并发问题需要处理。在分布式系统中,多带带去部署一个定时任务机器也是可行的。不过任务调度、定时结束通知客户端也需要蛮多工作量的。
unix 的 crontab 不再是我们的第一选择,每种编程可能都有定时任务管理的相关框架。比如 java 的 Quartz,Python 的 APScheduler。nodejs 的 node-schedule。但是这些东西是否能真的满足你的需求呢?
思路和实现So,我们需要一个定时任务管理平台。
目标
业务方可以定义定时时间、时间结束的触发任务
业务方可以更新或者删除已经发布的定时任务
定时任务管理平台统一接收和调度任务
主要解决两个问题:
设置准确的定时时间
时间结束触发客户端,不能重复消费
redis 在 2.8.X 版本可以开启了键空间通知,更多相关请移步 Redis Keyspace Notifications。(默认不开启,3.x 版本好像就失效了。),redis 支持的很多键空间事件,比如:DEL,RENAME,EXPIRE等等,redis 本身可以定义某个键的过期时间,ttl key。
这个值正好用来设置为定时任务的时间。更多相关请移步 Redis Keyspace Notifications。如果客户端订阅了某种规则的键通知,比如过期,那么在某个键过期的时候就会收到一个通知,这个事件就是定时结束,可以告诉业务机可以开启任务了。
可如果有多个 redis 客户端订阅了某个键的过期时间,那么任务还是会被触发很多次。 因为每个客户端
都是平等的,你能订阅,我同样可以订阅。解决办法就是 生产者和消费者模式。同一个过期消息只能被消费一次。
把所有的定时任务按照定时开启的时间倒序排列,存入 sorted Sets , 把时间设置为 score。这样就会形成一个按照时间排好序的集合,可以按照时间先后依次取出所有的任务,需要新增和修改任务,也是可以通过 redis 的命令实现的。
定时管理服务器每 1000ms 去取 sorted sets 顶部的数据,如果获取到的 task 离触发小于 1s,那么就可以执行 pop() 操作,表示这个任务开始被调度执行,因为 redis 的 pop() 是原子性的,同一个 task 永远只会被消费一次。这样就解决了 redis 键空间通知会被重复消费的问题。
伪代码如下:
var taskSorts = new Sets(task1, task2, task3); // 在 redis 中建立按时间排序的集合 // 每隔一秒执行一下操作, var newOne = taskSorts.zrank(-1); // 获取到最快发生的任务 if(newOne.time < 1000){ // 如果满足消费条件 newOne = taskSorts.pop(); // 消费该任务,重复此循环,继续消费下一个任务 setTimeout(function(){ // dosomething }, newOne.time) }任务触发
任务的提交和触发都应该在业务方完成。定时任务管理平台只是帮助管理和调度任务。在定义的任务里面定义好任务执行的回调参数和接口。
客户端定义任务的时候,同时注册好定时结束的回调接口,或者应该在项目启动的时候,就注册好所有回调的接口。因为同一个业务的 A 机器提交了任务,触发的时候可能 A 机器下线了,只能定时任务平台只能去触发业务 A 的 B 机器了。
引入跨服务远程调用。业务和定时任务管理平台可能不在同一个机器,可能分布在不同的 ip。听起来很复杂,实际上跨语言的调用调用方式有很多,比如 REST API、消息队列、RPC。我的团队选择了 Thrift(Facebook 开源的,跨语言的,现在共享给了 Apache 基金)。以上的方式都可以实现任务只被触发了一次,远程通知给客户端(任务注册方)。
成品 -- nodejs 的实现 cron-redishttps://github.com/MZMonster/cron-redis
主要依赖 bull 实现了任务队列的管理功能实现的定时任务管理工具。
demo:
// 就这样定义,3 秒钟之后,hello 函数将被执行。 function hello (x, y){ console.log(new Date()); console.log(x + " + "+ y +" = %s", x+y); } // 我是一个任务 var task1 = { method: hello.name, // 任务回调的函数 params: [2, 3], // 任务执行的参数 rule: moment().add(3, "s").toDate() // 任务执行间隔,支持 crontab 格式 } queue.register(hello) queue.publish(task1);
如果你要求不高,unix 自带的 crontab 也足够你折腾了。使用 redis 来实现定时也是一种极好的思路,cron-redis 值得你去试一试。
该库只是一个定时任务的库,实际上可以通过以上的思路实现微服务————定时任务管理平台。通过 cron-redis 组合远程服务调用 thrift、服务的注册发现工具 zookeeper,定时任务管理平台分分钟就被搭建了(等我下一篇文章吧,分分钟搭建微服务)。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/79079.html
摘要:在软件项目中,定时器也被应用到了各方各面,本文将从项目入手,讲述定时器,本文的例子都以为例。定时器总类定时器有两种对应重复任务和一次性任务。 在大规模分布式系统中,每个业务都可能是集群,每个业务机都会产生定时任务,不同的业务会有不同的任务管理需求,统一的任务调度和管理变得非常有必要。 定时如何准确,大量的定时被同时触发怎么办? 定时结束的时候,怎么通知业务机去处理呢? 某台业务机下线...
摘要:微服务架构概述应用架构的发展应用是可独立运行的程序代码,提供相对完善的业务功能。阿里开源的是的典型实现。它目前由官方开发维护,基于开发,提供一套完整的微服务解决方案。 微服务与Spring Cloud 随着互联网的快速发展, 云计算近十年也得到蓬勃发展, 企业的IT环境和IT架构也逐渐在发生变革,从过去的单体应用架构发展为至今广泛流行的微服务架构。 微服务是一种架构风格, 能给软件应用...
摘要:是由淘宝网发起的服务器项目。回源监控是内容分发网络的简称,其分发的内容来自用户源站,负责回源的模块是最重要组成部分之一,使跨越单机的限制,完成网络数据的接收处理和转发。这部分主要介绍的一些调试技巧和回源资源监控的内容,以及相应的实例分享。 摘要: Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,提供更强大的流量负载均衡能力、全站HTTPS...
摘要:网站都是从小网站一步一步发展为大型网站的,而这之中的挑战主要来自于庞大的用户安全环境恶劣高并发的访问和海量的数据,任何简单的业务处理,一旦需要处理数以计的数据和面对数以亿计的用户时,问题就会变的很棘手下面我们就来说说这个演变过程初始阶段大型 网站都是从小网站一步一步发展为大型网站的,而这之中的挑战主要来自于庞大的用户、安全环境恶劣、高并发的访问和海量的数据,任何简单的业务处理,一旦需要...
阅读 1573·2021-09-23 11:21
阅读 2344·2021-09-07 10:13
阅读 834·2021-09-02 10:19
阅读 1124·2019-08-30 15:44
阅读 1719·2019-08-30 13:18
阅读 1912·2019-08-30 11:15
阅读 1104·2019-08-29 17:17
阅读 2016·2019-08-29 15:31