以类似KSplice的方式修补PHP脚本

时间:2011-12-04 19:37:15

标签: php runtime dynamic patch

这主要是实验,但我认为它确实有一些实际用途。

我们的想法是能够在运行时修补PHP脚本,而无需重新加载请求。

例如,假设我在PHP中编写了一个WebSocket服务器,并且该服务器正在运行以下类...

class MyServerApp extends WebSocketServerApp {
    protected $clients = array();

    public function onConnect($client){
        $this->clients[$client->getId()] = $client;
    }

    public function onDisconnect($client){
        unset($this->clients[$client->getId()]);
    }

    public function onData($client, $data){
        $client->send($data); // perform echo functionality
    }

}

基本上,服务器创建一个MyServerApp类的实例并适当地调用它。上面的应用程序是一个echo服务器;它完全响应所有客户请求。

现在假设我修改了服务器源代码并希望保持现有服务器运行,但是改变行为(以免松散现有客户端)。应用服务器方便地有一个onTick()事件,我们可以用它来检查源代码的更改:

class MyServerApp extends WebSocketServerApp {
    // the existing code from above goes here

    /**
     * @var integer Timestamp of when the server was last patched.
     */
    public $last_patch = 0;

    public function __construct(){
        $this->last_patch = time();
    }

    public function onTick(){
        if($this->last_patch < filemtime(__FILE__)){
            // include __FILE__;
        }
    }
}

补丁检查可能会很好地工作,但实际的补丁方法不会(顺便说一句,它目前已经过评论)。 主要原因是服务器类已经定义。

那么你将如何进行实际修补?以某种方式覆盖函数或类?

可能的解决方案

  • 使用更适合的语言(例如js / node.js)允许覆盖(@teresko)
  • 使用classkit(和/或替代方法)覆盖PHP函数/类(@MatějZábský)
  • 将服务器逻辑卸载到匿名函数并简单地覆盖此类函数*
  • 重命名被欺骗的类并正常加载它们(肯定会泄漏内存)(@ chris)

[*] server.php看起来像是:

$server['onData'] = function(){ /* new function body */ };

备注

另一方面,这种架构提出了一些真正需要解决的问题:

  • 对正在运行的服务器造成永久性损坏
  • 内存泄漏(某些资源泄露,可能包括函数)
    • (PHP有功能的GC吗?)

4 个答案:

答案 0 :(得分:1)

你可以在没有任何扩展的情况下做到这一点......但是溶剂不仅仅是脏的......它是代码的敌基督!

如果在方法中使用eval,则评估的代码可以使用$ this来访问私有和受保护的属性和方法!

您可以将源代码存储在任何类型的文件或数据库中,每当更新源代码时,您都可以将更新的源代码加载到字符串中并在方法中对其进行评估。

如果您希望能够将类作为普通类或动态源类使用,那么事情会变得有点复杂:

  1. 创建服务器类
  2. 创建一个覆盖所有方法的子类(使用eval)
  3. 检查更新。
  4. 如果文件已更新,请使用tokenizer函数扫描源函数定义(函数标记[ - &gt; whitespace | comments] - &gt;具有正确函数名称的字符串标记 - &gt; not'{' - &gt;'{')
  5. 将所有代币联合起来,直到你打开卷曲的支架为止。
  6. 在您的评估方法中使用生成的字符串标记。
  7. 这不是斯巴达......这是疯狂的。

    如果您想了解有关PHP文件结构的更多信息,请继续。

    但是从来没有在高效的环境中使用它!!!!!!!

答案 1 :(得分:0)

您可能需要查看classkit(特别是其功能classkit_method_redefineclasskit_import)。

但那真是太可怕了。你确定在重启脚本时你不能承受毫秒的停机时间(就像每个人一样)吗?

这也可能无法长期持续(如果被覆盖的方法被泄露,我不会感到惊讶)。此外,我不确定tick回调是否真的是为了在生产服务器上使用而构建的(它可能会有一些性能开销)。

编辑:

可悲的是,有关PHP垃圾收集器的文章数量相当有限。 Here是一个(它是PHP中性能优化系列的一部分),但它没有详细说明如何处理函数或类型。

根据我的经验,连续运行数天的PHP脚本需要每隔几天重新启动一次(我使用Cron-job尝试每隔几分钟启动一次脚本以及一个锁定文件),因为之后,脚本变得更少了而且不太稳定。

答案 2 :(得分:0)

如果你想重新定义一个类,我认为你需要使用像runkit这样的扩展。但是,您也可以为新类提供某种形式的序列名称,如MyClassVersion56。使用工厂来快速启动类,并且工厂知道最新类的名称,因为您在某处设置了变量。您甚至可以str_replace源代码来自动进行串行命名。

如果长时间运行php脚本,请密切关注内存使用情况......

有动态定义程序行为的方法,但是在某些时候你开始重新编写一种编程语言,当你已经使用像php这样的动态语言时。但是,如果行为的变化范围有限,请考虑动态代码执行,就像您尝试的那样。

答案 3 :(得分:0)

我能想到的唯一好方法是将工作分成两个脚本: 一个脚本保存连接,一个脚本完成实际工作。

connction scrpts将请求数据写入数据库或memcache驱动的“作业池”,并查找可以在“结果池”中返回的结果。

工作脚本从池中获取作业,处理它们,并将结果放在“结果池”中。

通过这种方式,您可以在工作脚本中编辑逻辑并重新启动它而不会丢失连接。

我不认为这是实现服务器的好方法,但选择取决于你。