【问题标题】:How to design a daemon with a MySQL DB connection [closed]如何设计一个带有 MySQL 数据库连接的守护进程 [关闭]
【发布时间】:2011-05-09 08:06:49
【问题描述】:

假设您正在编写一个为作业队列提供服务的守护进程。各种其他软件将守护程序的作业写入队列。守护程序每隔几秒轮询一次队列以查找待处理的作业。假设队列被实现为 MySQL 数据库中的一个表,并且守护进程是一个简单的循环:

  1. 从队列中获取所有到期作业
  2. 做好工作
  3. 睡N秒
  4. 转到 1

守护程序必须能够承受来自 MySQL 数据库服务器的服务中断和数据库连接中断。

你会设计守护进程在每个周期连接一次数据库服务器吗?即在 1. 之前连接并在 2 和 3 之间断开连接?

或者你会让守护进程保持连接打开?在这种情况下,它还需要 a) 检测服务器或连接何时不工作,b) 断开连接并重新连接,以及 c) 在不累积 DB 连接、无用连接描述符或其他死资源的情况下这样做。

如果您有偏好,为什么?

优点和缺点?

影响设计的因素?

还有其他方法吗?

这里的答案:mysql connection from daemon written in php 并没有说明为什么保持连接打开会更好。我在别处读到 MySQL 中每个连接的开销非常小。因此,为什么永久使用一个服务器连接比每隔几秒连接/断开连接更好,这一点并不明显。

在我的例子中,守护进程是用 PHP 编写的。

【问题讨论】:

  • 你想做什么?根据具体情况,听起来 FIFO 文件或套接字可能更适合触发守护进程......(我会做的是在每个轮询间隔重新创建连接,但我又不会每隔几秒轮询一次)...
  • @ircmaxell:在我的情况下,FIFO 并不方便,因为将作业放入队列的进程需要能够读取它,并且作业队列状态需要在服务器重新启动后继续存在。队列的 MySQL 复制也很方便。
  • 我并不是要通过 FIFO 传递所有数据。只是为了触发守护进程从基于 mysql 的队列中读取。所以基本上你会INSERT INTO queue VALUES (job),然后是file_put_contents('fifo.file', '1');。守护进程会在试图打开 fifo 时进入休眠状态。所以如果它处理完所有它基本上会阻塞fopen('fifo.file', 'r');。下一次进程添加到队列时,它只是通知守护进程,然后立即开始处理作业。但是,当它处于休眠状态时,它不会消耗任何资源,因为它是一个阻塞的系统调用(即使一次是几个小时)。
  • @ircmaxell:如果您想在将作业放入队列后启动守护程序以服务队列,这是一种很好的方法。如果,otoh,排队的作业可能被指定为仅在未来某个时间之后运行,那么它就没有用了。

标签: php database daemon


【解决方案1】:

我实际上在 something 上工作,与您描述的非常接近,但在我的情况下,守护程序不会轮询事件,而是通过 XMPP 异步获取它们(但这不是重点)。

去掉中间人

我认为与其将事件存储在数据库中并使用 MySQL 轮询它们,不如使用 Gearman 从客户端异步发送它们 (example)。

垃圾收集

PHP 并不是真正设计为作为守护程序运行的,直到 PHP 5.3 获得circular reference garbage collection 时,它才成为一个可行的选择。如果您希望有机会在没有内存泄漏的情况下长期运行,那么使用 PHP 5.3 是非常重要的。

关于 GC 要记住的另一件事是,内存只有在不再引用(任何地方)时才可用。因此,如果您将变量分配给全局范围,它将一直存在,直到守护程序退出。重要的是,您创建或使用的任何代码都不会在某些地方构建变量(即静态日志、不删除旧数据等)。

统计缓存

另一件事是每隔一段时间运行clearstatcache 很重要。由于您的 PHP 进程没有重新启动,因此手动执行此调用以防止获取旧的统计数据(这可能会或可能不会影响您)很重要。根据documentation,这些函数被缓存了。

受影响的函数包括 stat()、lstat()、file_exists()、is_writable()、is_readable()、is_executable()、is_file()、is_dir()、is_link()、filectime()、fileatime()、 filemtime()、fileinode()、filegroup()、fileowner()、filesize()、filetype() 和 fileperms()。

资源管理

如果您要在进程的整个生命周期中使用 MySQL 之类的东西,我建议您在启动时建立一个连接并使其保持活动状态。尽管它可能在 MySQL 端使用更多内存,但您不必每 1 秒连接一次,从而减少一些延迟和 CPU 开销。

无网址请求

这似乎很明显,但使用 CLI PHP 时没有 URL 请求信息。有些库没有考虑到这一点,这可能会导致一些问题。

LooPHP

我将在这里为我编写的一个框架插入一个无耻的插件,以帮助管理 PHP 守护程序。 LooPHP 是一个运行循环框架,可让您安排事件发生或创建对抽象源(套接字、流等)的侦听。就我而言,我的守护进程不止做一件事情,因此让系统为我跟踪所有计时器非常有帮助,这样我就可以有效地轮询 stream_select 以获取 XMPP 连接。

【讨论】:

  • 这是一些非常有用的评论,肯德尔。谢谢!我很确定我没有循环引用,但我在 5.3.3 上运行,所以没关系。不使用stat,但这是一个很好的一般观点。仅仅因为你写了 LooPHP 就分享它并没有什么可耻的。这比我现在需要的要多,但我还有另一个项目即将推出,可能会觉得它非常有用。
  • @fsb 您如何将作业放入队列中?我可能值得看看 Gearman(我的第一点)。它为您抽象了很多队列处理。
  • 就像我在 OP 中所说的,队列是一个 MySQL 表。所以工作是通过 INSERT 放入其中的。
  • 我会看看 Gearman。在我的例子中,在 MySQL 中使用队列的方便之处在于创建作业的进程很容易检查队列。另外,有些作业会延迟到将来某个特定时间进入队列。并且在 MySQL 中使用队列,它的状态在包括服务器重启在内的各种情况下仍然存在。
  • @fsb 如果您需要排队等待重启,那么 MySQL 可能是一个不错的选择。如果队列只是 1 个单向管道,来自客户端的作业将被推送到守护进程,那么 Gearman 非常适合,但在您的情况下,您需要更多复杂性。
【解决方案2】:

如果您想创建一个可靠的守护程序,您将需要捕获数据库错误/断开连接并在任何一种情况下重新连接(断开连接或保持连接)。既然你无论如何都需要这样做,你不妨重复使用一个连接。

换句话说,仅仅因为您有一个新打开的连接,并不意味着查询不会失败并且需要重新打开连接并再次尝试。

所以,我相信最干净的方法是保持连接。但只是勉强。

【讨论】:

  • 嗨,肖恩。在我的情况下,守护程序可以使用与重新连接相同的循环进行轮询。因此,如果新打开的连接不起作用,或者与 DB 相关的某些内容在步骤 1 中失败(请参阅 OP),则守护进程只会跳到步骤 3。因此,避免了显式 reconnect-on-db-dailure 代码分支。
【解决方案3】:

我认为您能做的最好的事情是测量连接/断开与数据库的连接/断开连接所需的时间。然后尝试找出数据库服务器变得不可用的某种可能性。确定永久服务器连接的成本。最后尝试确定添加处理数据库连接问题的代码的成本(以小时、烦恼或其他为单位)。如果你能成功地确定这些数字,并比较它们,你就有了答案。我(我假设任何人)都很难对这些值做出好的猜测,但结论可能是它是性能与可行性之间的选择(就更低的成本而言)。

【讨论】:

  • 一项关于服务器资源成本与守护进程设计困难的结论性定量研究将比服务器托管费用花费我更多;-)
  • @fsb,哈哈,我相信你是对的。而且您仍然确信自己没有进行微优化或担心您没有的问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-27
  • 2014-03-06
  • 2010-11-13
  • 2015-01-26
相关资源
最近更新 更多