[1.31] 新型计划任务:以接口形式实现的计划任务

1.31.1 这里所说的计划任务

计划任务主要负责处理一些耗时的操作,或者非用户触发的作业。

有些人会称它为后台任务,或者推送作业,又或者定时任务。这时则统称为:计划任务。

例如,当你发布一条微信朋友圈后需要通知上百个好友时;当一条后台的推荐资讯需要推送到每个用户的客户端时;当需要将本地的静态资源如图片同步到CDN时。
显然这些动则需要分钟级别的操作,不应该在客户端调用接口时同步处理(但让我惊讶的是现实真的有人会这么做!),又或者非用户触发而需要后台处理(但更让我惊讶的是竟然也有系统是在用户请求时附带进行处理,而且还是国内某个知名的会员中心!)。

这里不仅仅是提供实现计划任务的约束和机制,更多的是引导大家更好地应对此类问题。

1.31.2 计划任务的关键环节

(1)触发

首先,是何时何地由何用户产生一条待执行的计划任务,我们可以把这个场景点称为一个触发点。
通常的做法,我们会先纪录下此触发点的场景信息,并放入到一个队列里面,以便等待计划任务消费。

(2)调度

其次,是通过何种机制进行计划任务的调度。
这里不仅有技术层面的问题,还有业务的问题,如每次批量处理多少,间隔多少,是否需要失败重试等等?

(3)消费

最后,则是具体的计划任务执行,以完成必要的操作,也称为消费。
很多传统的做法,都是把这些操作和接口混在一起的,而这里,PhalApi则会以一种更为明朗的方式来实现,从而自底而上,支持更多的调度方式和触发机制。

1.31.3 传统的计划任务

如果以一图而鳖之,上图虽然简化,但可以很好地说明传统计划任务的结构体系。
即:很多项目都是使用内嵌的方式来包含计划任务,这样明显会把接口服务系统和后台计划任务混在一起,增加了系统间的耦合性。
虽然小项目可以忍受或者适合这种混合,但是出于长远考虑,进行有意识地分解还是很有好处的。

而且这种混合潜意识下又让开发人员不加判断就进行调用,这会严重增加接口的反应时间。
我曾目睹一个接口耗时了近36秒之久,在对这个旧系统的接口进行一番排查后,原来是这个接口在发布后对上百个好友做了通知推送导致产生了上百条insert语句。

(1)传统的调度方式

我们重点关注一下传统计划任务的调度方式,在过去,我们通常会有两种方式:一种是启动死循环的进程,另一种是启动一个crontab之类的定时任务。
当然,上述的在接口请求时同步进行调度也算一种方式,但不是正规的做法。

如果采用死循环的方式,我们还需要考虑代码更新升级后,对脚本的重启,以便载入新的代码。如果是sh循环调用PHP脚本,则可以忽略。

1.31.4 新型的计划任务

(1)以接口的形式提供计划任务服务

PhalApi中最具特色的做法是,将计划任务的执行消费实现,以接口形式来提供。
这样的好处在于,我们作为接口开发人员,可以以熟悉的方式来进行计划任务的开发。
但更大的得益在于,将计划任务通过接口的形式提供后,我们会看到更为广阔的使用场景:我们可以使用MQ队列消费,可以同步请求也可以异步请求。

(2)系统架构

我们所做的,不仅仅只是把原来混合型的代码作简单分解,如下:

而是以一种更为正统的做法,为此我们添加了一些必要的节点来设计此构架。新的实现方式下的体系结构如下:

节点说明

在上图中,应用节点还是我们的接口系统;MQ队列则是用于存放待消费的场景信息,同其他的MQ一样;计划任务则可以分为两部分,API接口实现和任务调度。
计划任务这两部分,物理部署上可以合在一起,也可以分开,这取决于应用系统是采用分布式的做法,还是单一的服务器。

执行流程

由上图可以看出,一个完整的计划任务流程为:

  • 1. 应用产生一条新的计划任务,并存放于MQ队列
  • 2. 计划任务定时或者不停扫描新的计划任务;若有,则进行调度
  • 3. 计划任务API完成需要的工作,并将结果返回调度器

(3)单个添加,批量处理

(4)MQ共享

无论是分布式还是本地一体化,MQ队列都应该是可以共享访问的,以便为应用节点、计划任务调度节点所访问,如下图所示:

首选redis MQ

因为MQ作为频繁读写的媒介,应该优先使用高效缓存来提高系统的吞吐率以及增加并发的能力。此外,作为临时一次性的数据,使用高效缓存也是大有好处的(但我们也需要考虑到数据丢失的情况)。
而且,为了支持 单个添加,批量处理,第三方缓存应该很好地支持队列的操作。
所以,redis是一个不错的选择。

如下,是redis简单的队列操作:

$redis = new Redis();
$redis->connect(‘127.0.0.1‘, 6300);

$redis->lpush(‘test_key‘, ‘www‘);
$redis->lpush(‘test_key‘, ‘phalapi‘);
$redis->lpush(‘test_key‘, ‘net‘);

echo $redis->lpop(‘test_key‘), "\n";
echo $redis->lpop(‘test_key‘), "\n";
echo $redis->lpop(‘test_key‘), "\n";

数据库MQ

如果考虑到redis扩展不好安装,或者应用喜欢使用数据库来存放MQ,也是可以的。只需要用SQL的一些基本的操作语句便可做到FIFO。

文件MQ

文件MQ也是一种方式,但很少使用。

(5)更丰富的调度方式

接口同步调度

虽然也是同步调度,但是我们将计划任务隔离后,便于日后发现此同步的计划任务影响到接口的响应时间时,可以及时轻松地切换到后台异步处理的方式。

回归传统的调度

我们也可以沿用传统的做法,即使用死循环的脚本调度,或者crontab类的定时任务。

MQ队列消费

既然我们以接口服务的形式提供计划任务的操作,那么可以把同一接口的调度放置到同一队列中进行维护和消费。

接口异步调度

当计划任务以接口服务提供后,我们可以使用另一种免MQ的做法,即使用接口的异步调度。如下:

这样既可以避免死循环带来的性能负载问题,也可以避免定时任务带来的延时问题,可以说异步调度是一种折中完美的做法。
但这也可能是一种不负责任或者不安全的做法,因为我们无法跟进异步计划任务的结果。

本地调度和远程调度

本地调度是指在执行过程中构建模拟接口的调用而无须经过网络请求,远程调度则是通过远程接口请求来实现。
如果把本地调度和远程调度,跟同步/异步组合起来,我们可以得到以下三种有意义的组合:

  • 本地同步调度
  • 远程同步调度
  • 远程异步调度

(6)计划任务的划分

service即类型

明显地,接口服务名称service即可作为计划任务划分的依据。

不同的service作为不同的队列,不同类型的计划任务;而相同的service则作为相同的队列相同的计划任务。

接口参数即参数

接口参数即可计划任务执行时所需要的上下文信息。

1.31.5 PhalApi中计划任务的核心设计解读

(1)桥接模式 - 数据与行为独立变化

为了给计划任务一个执行的环境,我们提供了 计划任务调度器 ,即:Task_Runner。
每个计划任务需要调度的接口是不一样的,即不同的接口服务决定不同的行为;每个行为需要的数据也不一样,即不同的接口参数决定不同的数据。

自然而言的,Task_Runner按照桥接模式,其充当的角色如下:

然后,我们就可以这样各自实现:

(2)适配器模式 - 对象适配器和类适配器

在对MQ进行实现时,我们提供的Redis MQ队列、文件MQ队列和DB MQ队列,都使用了适配器模式,以重用框架已有的功能。
其中,Redis MQ队列和文件MQ队列是属于对象适配器,DB MQ队列是类适配器。 对于对象适配器,我们也提供了外部注入,以便客户端在使用时可以轻松定制扩展,当然也可以使用默认的缓存。

如下:

这样以后,我们可以这样根据创建不同的MQ队列:

//Redis MQ队列
$mq = Task_MQ_Redis();
//或
$mq = Task_MQ_Redis(new PhalApi_Cache_Redis(array(‘host‘ => ‘127.0.0.1‘, ‘port‘ => 6379)));

//文件MQ队列
$mq = new Task_MQ_File();
//或
$mq = new Task_MQ_File(new PhalApi_Cache_File(array(‘path‘ => ‘/tmp/cache‘)));

//DB MQ队列
$mq = new Task_MQ_DB();

//Array MQ队列
$mq = new Task_MQ_DB();

(3)模板方法 - 本地和远程两种调度策略

在完成底层的实现后,我们可以再来关注如何调度的问题,目前可以有本地调度和远程调度两种方式。

  • 本地调度:是指本地模拟接口的请求,以实现接口的调度
  • 远程调度:是指通过计划任务充当接口客户端,通过请求远程服务器的接口以完成接口的调度

为此,我们的设计演进成了这样:

上图多了两个调度器的实现类,并且远程调度器会将远程的接口请求功能委托给连接器来完成。

(4)设计审视

好了!让我们再回头审视这样的设计。

首先,我们在高层,也就是规约层得到了很好的约定。
不必过多地深入理解计划任务内部的实现细节,我们也可以轻松得到这样的概念流程:
** 计划任务调度器(Task_Runner)从MQ队列(Task_MQ)中不断取出计划任务接口服务(PhalApi_Api)进行消费。**

再下一层,则是具体的实现,即我们所说的实现层。
客户可以根据自己的需要进行选取使用,他们也可以扩展他们需要的MQ。重要的是,他们需要自己实现计划任务的接口服务。

(5)没有引入工厂方法的原因

我们在考虑是否需要提供工厂方法来创建计划任务调度器,或者MQ。
但我们发现,设计是如此明了,不必要再引入工厂方法来增加使用的复杂性,因为存在组合的情况。而且,对于后期客户端进行扩展也不利。

当我们需要启动一个计划任务时,可以这样写:

$mq = new Task_MQ_Redis();
$runner = new Task_Runner_Local($mq);

$runner->go(‘MyTask.DoSth‘);

上面简单的组合可以有:4种MQ * 2种调度 = 8种组合。

所以,我们最后决定不使用工厂方法,而是把这种自由组合的权利交给客户端。

(6)失败重试与并发问题

除了对计划任务使用什么模式进行探讨外,我们还需要关注计划任务其他运行时的问题。

一个考虑的是失败重试,这一点会发生在远程调度中,因为接口请求可能会超时。这时我们采用的是失败轮循重试。
即,把失败的任务放到MQ的最后,等待下一批次的尝试。连接器在进行请求时,也会进行一定次数的超时重试。这里主要是为了预防接口服务器崩溃后的计划任务丢失。

另一个则是并发的问题。这里并没有过多地进行加锁策略。
而是把这种需要的实现移交给了客户端。因为加锁会使得计划任务更为复杂,而且有时不一定需要使用,如一个计划任务只有一个进程时,也就是单个死循环的脚本进程的情况。

(7)客户端的使用

最后,客户端的使用就很简单了:

$mq = new Task_MQ_Redis();
$taskLite = new Task_Lite();

$taskLite->add(‘MyTask.DoSth‘, array(‘id‘ => 888));


[1.31] 新型计划任务:以接口形式实现的计划任务

时间: 2024-08-28 21:26:40

[1.31] 新型计划任务:以接口形式实现的计划任务的相关文章

最好的计划是略有闲余的计划,用于缓冲必然出现的错误与突发事件(转)

最近在看一本名为<稀缺>的书,作者从行为经济学的角度解释了穷人为什么会更穷,忙碌的人越来越没有时间,节食的人总是失败.由于缺乏闲余导致的带宽负担会进一步导致稀缺,由于总是优先处理紧急的事情而不去处理从长远来看重要的事情导致我们反复低效处理紧急事务并越陷越深. 从这本书中我们可以体会到在项目管理中我们经常犯的一些常识性错误和可能的纠正策略.本文只能通过我自己的一些体会来说明问题,而<稀缺>一书的作者是通过大量的行为学实验得到的结论,对此有兴趣的童鞋可以自己去看这本书. 关于A经理的故

十九、dbms_resource_manager(用于维护资源计划,资源使用组和资源计划指令)

1.概述 作用:用于维护资源计划,资源使用组和资源计划指令;包dbms_resource_manager_privs用于维护与资源管理相关的权限. 2.包的组成 1).dbms_resource_manager.create_plan作用:建立资源计划语法:dbms_resource_manager.create_plan(plan in varchar2,comment in varchar2,cpu_mth in varchar2 default 'EMPHASIS',active_sess

【计划】2017年5月计划

由于上次计划制定时已经是月中了,弄得有点多,削减了一点. 从清北回来,最后一次借着众dalao意外失利,我接近AK了(有一个数据点有点小问题,拿了290分),小激动,愈发砥砺前行. 1.完成四月计划(5.11日) 2.清北学堂Day1题目(18日) 3.清北学堂Day2题目(25日) 4.清北学堂Day3题目(30日) 嗯..总之先这样 还剩Day4和Day5,争取下次去清北前搞完吧 Day6一天可完,上午数论,下午AK(逃)  DayNOIP原题,打算以后抽个时间集中做一下 就这样.

分享,创造的灵魂——分享广东省”英课“计划青年教师信息化新课堂计划试验课程申报表

记得前天看物理学家费曼的书籍<发现的乐趣>,其中有一句话谈到科学Idea的分享,原话忘记了,大意如下:我很乐意和大家一起分享我的Idea,因为在分享与讨论中可以完善自己的想法,我不怕大家偷走我的Idea,因为当你们去试验我的想法时,我又已经远远的跑在了前面.这就是真正的大家,分享一个Idea,能够让分享的双方都有获益.狭隘的保护自己的Idea,不一定能够做出伟大的成就.在博士研究生期间,和比利时一个科学家小组交流甚多,获得了他们大量的帮助:同时,自己因为比利时的团队做过一些微小的贡献--把原来

25、Linux计划任务详解

Linux任务计划 相信每个人都有使用闹钟的习惯,比如提醒一次,工作日提醒,女朋友生日提醒(首先你要先有个女朋友),在设定闹钟之后,我们可以在设定的时间去提醒你做什么事情 Linux计划任务工具介绍 在各类系统上面都有计划任务功能,在linux上面主要两种工具,at和crontab at命令是专门来执行处理一次性的任务计划的 crontab可以根据定义的时间周期,循坏的去做一些事情 任务计划分类: 一次性的任务计划:只执行一次就结束 周期性的任务计划:每隔一定时间就去执行 at一次性任务 假如我

转:[windows]DOS批处理添加任务计划

自动创建每周运行一次的计划任务 创建计划任务可用at,schtasks命令,schtasks提供了很多参数 命令schtasks SCHTASKS /Create [/S system [/U username [/P [password]]]] [/RU username [/RP password]] /SC schedule [/MO modifier] [/D day] [/M months] [/I idletime] /TN taskname /TR taskrun [/ST sta

Oracle SQL操作计划基线总结(SQL Plan Baseline)

一.基础概念 Oracle 11g開始,提供了一种新的固定运行计划的方法,即SQL plan baseline,中文名SQL运行计划基线(简称基线),能够觉得是OUTLINE(大纲)或者SQL PROFILE的改进版本号.基本上它的主要作用能够归纳为例如以下两个: 1.稳定给定SQL语句的运行计划.防止运行环境或对象统计信息等等因子的改变对SQL语句的运行计划产生影响! 2.降低数据库中出现SQL语句性能退化的概率.理论上不同意一条语句切换到一个比已经运行过的运行计划慢非常多的新的运行计划上!

Python全栈_Day10_nfs和计划任务

1. nfs 1.1 安装 yum install rpcbind nfs-utils -y 1.2 配置 配置文件路径:/etc/exports NFS配置文件格式: <共享目录> <客户端1>(选项) <客户端2>(选项) 共享目录:NFS服务端共享给客户端的目录 客户端:网络中能访问这个共享的主机,多个客户端使用空格分开 选项:设置目录的权限,用户映射等.多个选项使用逗号隔开. 常用选项包含: sync:以同步方式执行文件系统的输入输出动作. async:以非同步

linux 计划任务(十)

[教程主题]: 计划任务 [1]at 在windows系统中,windows提供了计划任务这一功能,在控制面板 -< 性能与维护 -< 任务计划, 它的功能就是安排自动运行的任务. 通过'添加任务计划'的一步步引导,则可建立一个定时执行的任务. 在linux系统中你可能已经发现了为什么系统常常会自动的进行一些任务?这些任务到底是谁在支配他们工作的?在linux系统如果你想要让自己设计的备份程序可以自动在某个时间点开始在系统底下运行,而不需要手动来启动它,又该如何处置呢? 这些例行的工作可能又分