可扩展,延迟的PHP处理

时间:2010-06-25 02:32:58

标签: php events asynchronous daemon

我正在开发一个需要延迟PHP事件的在线PHP应用程序。基本上我需要能够在初始命中URL之后执行任意PHP代码x很多秒(但可能是几天)。我需要相当精确地执行这些PHP事件,我也希望它具有相当的可扩展性。我正在努力避免安排一个cron作业每秒运行一次。我正在调查Gearman,但它似乎没有提供任何计划事件的能力,据我所知,PHP并不真正意味着作为守护进程运行。

如果我能告诉某个外部进程在下一个事件应该运行的确切时间在PHP服务器上轮询“事件检查器”URL,那将是理想的。此轮询时间需要能够随意减少或增加,因为事件可以被删除并添加到队列中。有什么想法以优雅的方式实现这个目标吗?从外部调用PHP(必须解析HTTP请求或通过CLI调用)只需要很多开销就可以满足我的需求。

我当前的计划是编写一个PHP守护程序,该守护程序将使用gearman从PHP服务器运行事件和接口。 PHP守护进程将围绕SplMinHeap构建,因此希望性能不会差。这个想法在我的口中留下了不好的味道,我想知道是否有人有更好的想法?想法略有改变。阅读编辑2。

编辑:

我正在创建一个在线游戏,让玩家轮流使用可变时间限制。我正在使用XMPP和BOSH来允许我向客户推送消息,但是我已经完成了所有这些工作。现在我正在尝试添加一个任意事件,该事件在从客户端播放之后触发,以便让客户端(以及游戏中的其他人员)花费很长时间。我无法在客户端使用定时触发器,因为这样可以被利用(因为客户端可以自己玩)。希望有所帮助。

编辑2:

谢谢大家的反馈。虽然我认为你的大多数想法在小范围内都能很好地发挥作用,但我觉得它们不能很好地扩展(外部事件管理器)或缺乏该项目所需的准确性(CRON)。此外,在这两种情况下,它们都是外部部件,可能会失败并增加已经很复杂的系统的复杂性。

我个人认为唯一符合此项目要求的 clean 解决方案是编写一个处理延迟事件的PHP守护程序。我已经开始编写我认为第一个 PHP runloop的内容。它处理观察套接字和执行延迟的PHP事件。希望当我接近完成这个项目时,我可以发布源代码,如果你们有兴趣的话。到目前为止,在测试中它已经证明是有希望的解决方案(没有内存泄漏或不稳定的问题)。

编辑3: 这是一个名为LooPHP的PHP事件循环库的链接,供感兴趣的人使用。

TL; DR要求

  • 在延迟时间(从几秒到几天)调用(最好是原生地)PHP
  • 任意处理事件的创建/更新/删除(我预计会有大量已取消的电话)。
  • 处理预定的高负荷事件(每台服务器每秒100-1000次)
  • 通话时间应在预定时间的一秒钟内
  • 此时我不愿意将代码库重写为另一种语言(可能有一天我会)

15 个答案:

答案 0 :(得分:10)

让您的php脚本进行exec调用,以便在您需要时使用命令“at”安排PHP脚本运行

exec(“在22:56 / usr / bin / php myscript.php”);

在指定时间执行命令。

来自手册页:

允许相当复杂的时间规范,扩展POSIX.2        标准。它接受HH:MM形式的时间来运行spe的工作        一天中的时间。 (如果那个时间已经过去,第二天就是        假设。)你也可以指定午夜,中午或下午茶时间(下午4点)和        你可以在AM或PM后面加上一个时间来运行        早上或晚上。你还可以说一下工作的哪一天,        通过以可选年份的形式在月份名称日中给出日期,或者        提供MMDDYY或MM / DD / YY或DD.MM.YY表格的日期。具体说明        日期的阳离子必须遵循一天中的时间规范。您        也可以给出像现在这样的时间+计算时间单位,时间单位        可以是几分钟,几小时,几天或几周,你可以告诉你运行        今天的工作是在今天加上时间,明天就要完成工作        把明天的时间加上后缀。

此外,如果您需要一秒钟的分辨率,让您的脚本在分钟开始时运行,那么只需要等待一段时间才能执行。

答案 1 :(得分:7)

我认为只有PHP的解决方案很难实现(几乎不可能)。我提出了两个解决问题的方法。

PHP / Redis解决方案

肯德尔问的问题:

  • redis有多稳定:

Redis非常稳定。开发人员真的写了一些干净的C代码。你应该在github上查看;)。许多大型网站也在使用redis。例如github。他们有一个非常有趣的博客post他们如何快速制作github :)。 superfeedr也使用redis。有很多大公司正在使用redis;)。我会建议你去谷歌;)。

  • 如何对PHP友好:

PHP非常友好。很多用户都在为redis编写PHP库。协议非常简单。您可以使用telnet调试它;)。例如,快速查看predis已实现阻塞弹出。

  • 我将如何删除活动:

我认为你应该使用像ZRemCommand这样的东西。

  

Redis是一个高级键值存储。   它类似于memcached但是   数据集不是易失性的,也不是值   可以是字符串,完全像   memcached,但也列出,设置和   有序集。所有这些数据类型都可以   用原子操作来操纵   推送/弹出元素,添加/删除   元素,执行服务器端联合,   交集,集之间的差异,   等等。 Redis支持不同的   一种分类能力。

我想出了什么(伪代码......):

processor.php:

<?php
######----processer.php
######You should do something like nohup php processor.php enough times for processors to run event. 
#$key: should be unique, but should also be used by wakeup.php
while(true) {
    $event = blpop($key); #One of the available blocking threads will wakeup and process event
    process($event); #You should write process. This could take some time so this process could not be available
    zrem($key1, $event); #Remove event after processing it. Added this later!!!!!!
}

client.php:

######----client.php
######The user/browser I guess should generate these events.
#$key1: should be unique.
#$millis: when event should run
#$event: just the event to work on.

if ("add event") {
  zadd($key1, $millis, $event);
} else if ("delete event") {
  zremove($key1, $event)
}

#Get event which has to be scheduled first
$first = zrange($key1, 0, 0);

if ($oldfirst <> $first) { #got different first event => notify wakeup.php.
    lpush($key2, $first);
}

$oldfirst = $first;

wakeup.php:

####wakeup.php
#### 1 time do something like nohup php wakeup.php
#http://code.google.com/p/redis/wiki/IntroductionToRedisDataTypes => read sorted set part.
while(true) {
    $first = zrange($key1, 0, 0);
    $event = blpop($key2, $timeoutTillFirstEvent);

    if ($event == nill) {
        #Blockingqueue has timedout which means event should be run by 1 of blocking threads.
        blpop($key2, $first);
    }    
}

根据这一点,您还可以使用PHP编写一个非常高效的调度程序(好的redis是C,所以快速反应:)),它也非常有效:)。我也想编写这个解决方案,所以保持调整;)。我想我可以在一天内写出一个可用的原型....

我的java解决方案

今天早上我想我创建了一个java program你可以用来解决你的问题。

  1. 下载

    访问github's download page下载jar文件(包含所有依赖项)。

  2. 安装

    java -jar schedule-broadcaster-1.0-SNAPSHOT-jar-with-dependencies-1277709762.jar

  3. <强> Run simple PHP snippets

    1. 首先php -f scheduler.php
    2. 下一页php -f receiver.php
  4. <强> Questions

    我创建了这些小片段,希望您能理解如何使用我的程序。 WIKI中还有一些文档。

  5. App Engine的TaskQueue

    快速解决方案是使用具有合理免费配额的Google的应用引擎任务队列。之后你必须为你使用的东西买单。

      

    使用此模型,App Engine的任务   队列API允许您指定任务   作为HTTP请求(两者的内容)   请求作为其数据,以及   请求的目标URL作为其代码   参考)。以编程方式提及   到此处的捆绑HTTP请求   时尚有时被称为“网络   钩“。

         

    重要的是,离线性质   Task Queue API允许您指定   网络挂钩提前,没有   等待他们的实际执行。   因此,应用程序可能会创建许多   网络挂钩,然后交给他们   关闭App Engine;系统会   然后异步处理它们   背景(通过'调用'HTTP   请求)。这个Web钩子模型启用   高效的并行处理 - App   引擎可以调用多个任务,或   网钩,同时。

         

    总结一下,Task Queue API   允许开发人员执行工作   背景,异步,由   在线下网络中工作的分块   挂钩。系统将调用这些   代表应用程序的Web挂钩,   安排最佳性能   可能会执行多个webhook   在平行下。这种粒状模型   工作单元,基于HTTP   标准,允许App Engine   有效地执行背景   以某种方式处理   任何编程语言或网络   应用程序框架。

答案 2 :(得分:4)

这似乎是数据库中事件队列的完美位置。

让用户创建的事件(通过访问网页触发)创建一个进入数据库的条目,其中包含要执行操作的说明以及何时应该发生的时间戳。您守护程序(持久应用程序或由CRON触发)检查数据库是否存在应该发生的事件($TriggerTime <= time())并且尚未标记为“已处理”。如果您发现这些事件中的一个或多个,请执行该指令,最后在DB中将事件标记为“已处理”,或者只是删除该条目。

使用数据库存储事件(而不是驻留在应用程序RAM中的某些内容)的好处是,您可以从崩溃中恢复而不会丢失数据,您可以在一个数据库中读取多个工作程序一次一个事件,您可以简单地修改事件。

此外,有许多人使用PHP作为服务器等的通用守护程序脚本语言.Cron可以执行PHP脚本(并确认该“app”的实例已在运行)来检查事件队列每隔一段时间。您可以让一个小应用程序在一分钟不活动后死亡,然后由CRON重新启动。该应用程序可以以您选择的快速频率(例如1s)检查数据库中的条目。通常,Cron不能比每分钟一次更快地完成计时事件。

答案 3 :(得分:2)

我还建议使用队列策略,但您似乎不喜欢将数据库用作队列。您有一个XMPP基础架构,因此利用它:使用pubsub节点并将您的事件发布到此节点。可以选择将Pubsub配置为以持久方式存储未获取的项目。

您的守护程序进程(无论使用何种语言)可以在启动时获取所有存储的项目并订阅更改以获得有关传入操作的通知。这样您就可以以优雅的异步方式解决问题。

答案 4 :(得分:1)

您可以使用Node.JS,它是一个基于事件的,基于JavaScript的Web服务器。在一个秘密的内部端口上运行它,该脚本接收来自PHP脚本的通知,然后安排在xx秒后运行的操作。 Node.JS中的操作可以像在主Web服务器上运行PHP脚本一样简单。

答案 5 :(得分:0)

答案 6 :(得分:0)

我不确定你为什么要避免使用cron。您可以在表中创建请求队列,并让cron启动进程以检查当前作业。

根据您的具体要求,有一些问题。例如:

  • 电话会有多精确?
  • 每次通话需要多长时间?
  • 正常和峰值负荷是多少? 任何给定的时期?

因此,如果您想要精确执行,或执行时间超过一秒,或者存在重负载的可能性,那么cron方法可能会遇到问题。

我有许多运行PHP的守护进程(使用daemontools)。使用这种方法,您可以将请求保存在核心中,并在内部执行您想要的任何时间。

但是,如果你想要的确切和可靠的时间,你应该完全放弃PHP。

答案 7 :(得分:0)

A无法想到任何能满足你要求的事情:

  • 必须非常精确
  • 延迟很长一段时间
  • 删除/更改活动时间的能力

琐碎的方法是使用以下功能的组合:

set_time_limit(0);
ignore_user_abort(true);
time_sleep_until(strtotime('next Friday'));
// execute code

然而,就像@deceze所说的那样,这可能不是一个好主意,因为如果你设置了一个高延迟,Apache最终可能会杀死子进程(除非你使用PHP CLI,这将使它更容易)。除非您设置更复杂的逻辑和数据库来保存事件,否则它也不允许您更改/删除事件。另外,如果你想走这条路,register_shutdown_function()可能会有用。

更好的方法是在我看来建立一个CRON工作。

答案 8 :(得分:0)

我会使用cron每隔一段时间运行一个PHP文件(即5分钟)。 PHP文件将检查是否有任何事件需要在下一个间隔内触发,获取间隔事件列表,并睡眠直到下一个事件。醒来,点击列表中的下一个事件,一直睡到下一个事件,重复直到完成。

你甚至可以通过分叉或启动另一个php文件来实际触发事件来扩展它。然后你可以同时发射多个事件。

答案 9 :(得分:0)

如何使用cron运行检查程序,例如可以从DB执行内容。

或使用“at”linux命令安排执行某些命令?

答案 10 :(得分:0)

这是正确的答案,但你可能不喜欢它。

PHP完全被设计为用作请求 - 响应(http)语言,因此不支持您正在寻找的东西 - 它很好地破解并找到方法,但它只是那个,一个黑客,无论你最终得到什么“解决方案”。

你真正需要的是支持xmpp的事件驱动语言,为此你需要看看node.js / v8和支持的XMPP库 - 这本身就支持并且专为你需要的而设计。您也可以沿着Java路线前进,但如果您想快速移植并获得大量新功能并支持您正在做的事情,那么节点就是其中之一。

如果你坚持使用PHP(因为我已多次使用多次),那么“最轻”且最有效的方法就是在数据库中使用事件队列的持久PHP守护程序 - 遗憾的是!

答案 11 :(得分:0)

存在纯PHP解决方案。几乎是埃文在他的回答中所说的。只需为事件引入“处理”状态,就可以减少DB上的负载(并锁定问题)。当处理脚本从队列(DB)中获取事件时,它们被标记为“正在处理”并已提交。脚本完成后,它们被标记为“已处理”。如果出现错误或脚本失败,则“处理”事件必须更新回原始状态。 (这是对Evan的答案的评论,但我还没有足够的声誉)

答案 12 :(得分:0)

  • 使用点火时间将所有任务存储在数据库中
  • Cron工作每小时运行一次
    • 阅读下一个60分钟的工作
    • 主循环
      • 如果无事可退出
      • 微睡到下一份工作
      • 在开火时间&lt; = now + 0.5秒
      • 发送所有工作

答案 13 :(得分:0)

用redis结账。可能对您的问题有用

https://github.com/chrisboulton/php-resque-scheduler

答案 14 :(得分:-9)

使用睡眠功能: http://php.net/sleep