当 运行 多个 cron 作业同时进行时,如何克服服务器负载问题?

How to overcome server load issues when running multiple cron jobs simultaneously?

我有一个网站可以显示来自游戏服务器的数据。游戏有不同的“域”(实际上只是单独的服务器)供用户玩游戏。

现在,我有 14 个 cron 作业 运行ning 每 6 小时在不同的时间间隔。 运行 的所有 14 个文件几乎相同,每个文件大约需要 75 分钟(一小时 15 分钟)才能完成 运行。

我曾想过只使用 cron 中的 1 个文件 运行 并循环遍历每个服务器,但这只会导致一个文件 运行 for 18小时 左右。 我当前的 VPS 设置为仅允许 1 vCPU,因此我正在努力完成任务并保持在分配的服务器负载范围内。

鉴于网站需要每 6 小时更新一次数据,这是不可行的。

我开始研究消息队列并将一些信息传递给将执行相关工作的后台进程。我开始尝试使用 resquephp-resque,但我的后台工作人员一启动就死了。所以,我继续 ZeroMQ,无论如何,这似乎更符合我的需要。

我已经通过 Composer 设置了 ZMQ,安装过程中的一切都很顺利。在我的工作脚本(cron 每 6 小时调用一次)中,我有:

$dataContext = new ZMQContext();
$dataDispatch = new ZMQSocket($dataContext, ZMQ::SOCKET_PUSH);
$dataDispatch->bind("tcp://*:50557");

$dataDispatch->send(0);

foreach($filesToUse as $filePath){
    $dataDispatch->send($filePath);
    sleep(1);
}

$filesToUse = array();
$blockDirs = array_filter(glob('mapBlocks/*'), 'is_dir');
foreach($blockDirs as $k => $blockDir){
    $files = glob($rootPath.$blockDir.'/*.json');
    $key = array_rand($files);
    $filesToUse[] = $files[$key];
}

$mapContext = new ZMQContext();
$mapDispatch = new ZMQSocket($mapContext, ZMQ::SOCKET_PUSH);
$mapDispatch->bind("tcp://*:50558");

$mapDispatch->send(0);

foreach($filesToUse as $blockPath){
    $mapDispatch->send($blockPath);
    sleep(1);
}

$filesToUse 是用户提交的文件数组,其中包含用于查询游戏服务器的信息。如您所见,我正在遍历数组并将每个文件发送到 ZeroMQ listener 文件,其中包含:

$startTime = time();

$context = new ZMQContext();

$receiver = new ZMQSocket($context, ZMQ::SOCKET_PULL);
$receiver->connect("tcp://*:50557");

$sender = new ZMQSocket($context, ZMQ::SOCKET_PUSH);
$sender->connect("tcp://*:50559");

while(true){
    $file = $receiver->recv();

    // -------------------------------------------------- do all work here
    // ... ~ 75:00 [min] DATA PROCESSING SECTION foreach .recv()-ed WORK-UNIT
    // ----------------------------------------------------------------------

    $endTime = time();
    $totalTime = $endTime - $startTime;
    $sender->send('Processing of domain '.listener::$domain.' competed on '.date('M-j-y', $endTime).' in '.$totalTime.' seconds.');
}

然后,在 final listener 文件中:

$context = new ZMQContext();
$receiver = new ZMQSocket($context, ZMQ::SOCKET_PULL);
$receiver->bind("tcp://*:50559");

while(true){
    $log = fopen($rootPath.'logs/sink_'.date('F-jS-Y_h-i-A').'.txt', 'a');
    fwrite($log, $receiver->recv());
    fclose($log);
}

当工作脚本是 运行 来自 cron 时,我的日志中没有收到确认文本。

Q1) 这是最有效的方式来做我想做的事情吗?
Q2) 我在这里尝试使用或实施 ZeroMQ 不正确吗?

而且,看起来,使用 cron 同时调用 14 个文件会导致负载远远超过分配。我知道我可以在一天中的不同时间将作业设置为 运行,但如果可能的话,我希望将所有更新保持在同一时间表上。


更新:

我已经将我的 VPS 升级到 2 CPU 核心,所以问题的负载方面不再那么重要了。

上面的代码也已更改为当前设置。

我在代码更新后收到一封来自 cron 的电子邮件,其中包含错误:

Fatal error: Uncaught exception 'ZMQSocketException' with message 'Failed to bind the ZMQ: Address already in use'

运行 通过 cron 或 ZeroMQ 编写的脚本对 CPU 的需求绝对没有影响。两者之间的唯一区别是 cron 作业每隔一段时间启动您的脚本,而消息队列将根据某些用户操作启动您的脚本。

归根结底,您需要更多可用线程来 运行 您的脚本。但在您走这条路之前,您可能想看看您的脚本。也许有一种更有效的方式来编写它们,这样它们就不会占用太多资源?您是否查看了 CPU 利用率?大多数网络托管服务都有内置指标,您可以通过他们的控制台调出这些指标。您使用的资源可能没有您想象的那么多。

运行 一个循环遍历所有服务器的文件所花费的时间比 运行 单独加载文件的累积时间要长得多,这一事实表明您的脚本不是正确地多线程。您的脚本的单个实例不会用完所有可用资源,因此您只有在 运行 多个脚本实例时才能看到速度提升。

是的,这种方式似乎不是 ZeroMQ 功能的最新使用方式。

好消息是可以重新设计解决方案以更接近最佳实践。

动机

ZeroMQ 无疑是一个非常强大且非常智能的工具箱,用于可扩展、轻量级的分布式处理系统设计、控制及其性能和事件管理。关于设计 ZeroMQ 系统的最佳工程实践,已发布许多资源。

轻量级根本不意味着金子弹或零开销的永动机。

ZeroMQ 仍然消耗额外的资源,对于目标生态系统,对于那些具有极简资源足迹的生态系统来说更多(某些 VPS 系统 vCPU/vCPU-core 上隐藏的超线程限制仿真,作为这里的一个很好的例子),人们可能会意识到,调整线程并发影响每个 Context() 实例消耗额外 ZeroMQ I/O-threads ( 1+ ) 的成本没有任何好处.

异常处理?
不,异常预防和阻塞避免是生产级不间断的 alpha/omega ,分布式处理系统。你的经历变得越来越痛苦,但你会学到很多关于 ZeroMQ 的软件设计实践。要学习的其中一个教训是资源管理和优雅终止。每个进程负责释放自己分配的所有资源,因此必须系统地释放并以明确的方式释放被各自 .bind()-s 阻塞的端口.

( Plus 很快就会意识到,由于操作系统开销,端口发布 不是 即时的,这超出了一个人的代码控制 - 所以不要依靠这样的端口立即成为 RTO 以供下一次端口重用(人们可能会在这里找到很多关于以这种方式阻止的端口的帖子))。


关于资源利用率信封的事实 [FIRST]:

虽然关于处理性能/资源利用率信封的量化事实目前仍然缺失,但附加的图片可能有助于识别这些知识的关键重要性。

vCPU-workload Envelope 一旦市场在 Sunday 22:00 GMT+0000

下一个 24/5 开始

仍然 +55% CPU-功率利用率 vCPU-工作负载和其他资源-使用包络线


Cron、队列和相对优先级设置 hack [下一个]:

没有详细说明持续 75 分钟的 WORK-UNIT 是否存在 CPU-bound 或 I/O-bound 问题,系统配置可能适度 cron-jobs 的相对优先级,以便您的系统性能在高峰时段“集中”在主要作业上。有机会创建一个具有自适应 nice 优先级的单独队列。 @Pederabo 提出了一个很好的技巧:

cron usually runs with nice 2 but this is controlled by the queuedefs file. Queues a, b, and c are for at, batch, and cron.
- should be able to add a line for queue, say, Z which defines a special queue and set the nice value to zero.
- should be able to run the script from the console with at -q Z ....
- if that works well, put the at command in crontab.
The "at" command itself will run with cron's default priority, but it only takes a few seconds. Then the job it creates will run with whatever you set in the queuedefs file for queue Z.


避免不必要的开销[总是]:

总有理由不浪费 CPU-clks。越是简约的系统设计。在同一个 localhost 上使用 tcp:// transport-class 可能是原型制作阶段的 PoC 实践,但永远不会进入 24/7 生产阶段。尝试避免所有从未使用过的服务 - 为什么在消耗更多操作系统资源的情况下攀登 L3(ZeroMQ 在这个阶段 不是零复制 - 所以双重分配出现在这里)在同一时间交付时 localhostipc://inproc:// transport-classes 对于这种作案手法要好得多(也参考. 下面是真正分布式的 )


主要问题(处理设计,使用ZeroMQ工具)

基于给定的、对意图的高级描述,似乎有一种方法可以完全避免 cron 机制并允许整个处理管道/分发/收集成为一个不间断的 ZeroMQ 分布式处理系统,你可以在其中构建一个自治的 CLI 接口(一个 r/KBD 终端与不间断处理系统进行临时通信)以便:

  • 消除对操作系统功能/限制的依赖
  • 减少与并发系统级流程维护相关的总体开销
  • 共享一个中央 Context()(因此只需支付额外的最低成本 I/O-thread),因为处理似乎不是消息密集型/超低延迟敏感

您的 ZeroMQ 生态系统可以帮助您构建正确扩展甚至自适应扩展功能,因为可扩展的分布式处理不会仅限制您的 VPS localhost 设备(如果您的 VPS 超线程限制不允许这种协同处理来满足您的 WORK-UNIT-s 性能范围的 24/7 流程)。

所有这一切只是通过修改足够的运输-class从ipc://tcp:// 允许一个人将任务( WORK-UNIT-s )从字面上分发到全球任何你可以“插入”以增加你的处理能力的处理节点..所有这些都没有源代码更改的 SLOC。

值得花时间重新决定设计策略,不是吗?