使用PHP和Phalcon作daemon程式
某些情況下,我們除了提供web介面給使用者,還需要執行一些後臺任務。這些任務可能是由使用者觸發的(比如使用者提交了一個請求,而這種請求很特殊,例如從github克隆一個專案並執行構建,至少需要幾分鐘才能執行完成,這種情況不適合阻塞的方式讓瀏覽器等待結果返回);也可能是一些常規性的系統任務(比如將日誌進行歸檔,轉移到統一的地方進行備份)。前者一般是引入訊息佇列,使用者的請求只是增加了一條待構建的訊息到訊息佇列,然後有一個專門的訂閱者讀取訊息,排程分發執行這個任務。後者最簡單的方式便是crontab,但缺點是每個機器需要單獨進行設定,不易維護;當然也可以通過一個統一的排程器,分發任務到多個任務節點的方式來執行。
這裡只說第一種情況,業務背景是許可範圍內按照使用者的配置生成移動端APP。對於Android,專案構建可以使用Ant或者Gradle,這樣可以通過命令列呼叫,在程式中就可以fork一個程式,設定相應的引數來執行了。
對於不同的Android APP,package name需要是不同的;除此之外,對於使用者的一些配置,也會體現到最終待構建的專案檔案中。因此,只能生成一套APP的模板程式碼,按照使用者的輸入,修改java程式碼和資原始檔,最後再執行構建產生apk。這個構建過程可能需要幾分鐘。
這裡我們採用的方案是有一個單獨的daemon程式,讀取訊息佇列,得到前端寫入的待構建的訊息,fork程式執行Ant,完成構建後(成功或失敗)將狀態寫入資料庫,前端採用定期查詢的方式以便獲知任務是否結束。這樣daemon程式的邏輯相對簡單,構建的過程都是發生在其他程式空間的,不會對daemon程式產生影響。
程式碼採用了類似 Phalcon 示例 中Multiple
的形式。
啟動程式
命令列的入口檔案,參考 @guweigang 的falcon,通過一些引數指定執行哪個模組,哪個action,以及傳遞給action的引數。這裡通過getopt
解析命令列引數,-d
決定了是否變成daemon程式。
<?php
/**
* Usage:
* php console.php -d -m module-name -t task-name -a action-name -p parameters
*
* If you has multiple parameters to pass to an action, use it like this:
* -p first -p second
*
*/
error_reporting(E_ALL);
ini_set("memory_limit", "4G");
$HELP_TEXT = <<<EOT
appcreator console
usage: php console.php [OPTION]... [PARAMS]...
-h, --help show help message
-d, --daemon running as daemon
-m, --module specify module
-t, --task specify task
-a, --action specify action
-p, --param parameter passed to action
:)
EOT;
try {
$opts = "dm:t:a:p:h";
$longopts = array(
"module:",
"task:",
"action:",
"param:",
"help",
"daemon",
);
$options = getopt($opts, $longopts);
if (isset($options[`h`]) || isset($options[`help`])) {
print($HELP_TEXT);
exit(0);
}
if (isset($options[`d`]) || isset($options[`daemon`])) {
$pid = pcntl_fork();
if ($pid == -1) {
error_log(`fork process failed`);
exit(1);
} elseif ($pid) {
// if in parent process, exit
exit(0);
}
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
// TODO
$STDIN = fopen(`/dev/null`, `r`);
$STDOUT = fopen(`/dev/null`, `w`);
$STDERR = fopen(`/dev/null`, `w`);
posix_setsid();
}
$args = array();
// 處理傳遞給action的引數
foreach(array(`p`, `param`) as $p) {
if (isset($options[$p])) {
if (is_array($options[$p])) {
$args = array_merge($options[$p]);
} else {
$args[] = $options[$p];
}
}
}
if (!isset($options[`a`]) && !isset($options[`action`])) {
error_log(`Please specify the action you want to run with -a or --action`);
exit(1);
}
/*
* set cli parameters
*/
$args[`module`] = (isset($options[`m`]) || isset($options[`module`])) ? (
isset($options[`m`]) ? $options[`m`] : $options[`module`]) : `appcreator`;
$args[`task`] = (isset($options[`t`]) || isset($options[`task`])) ? (
isset($options[`t`]) ? $options[`t`] : $options[`task`]) : `package`;
$args[`action`] = isset($options[`a`]) ? $options[`a`] : $options[`action`];
/*
* 這裡例項化PhalconCLIConsole並載入服務和模組
*/
$application = new PhalconCLIConsole();
/* load services and modules */
...
/* Finally handle args */
$application->handle($args);
} catch (Exception $e) {
error_log(`[AppCreator]` . $e->getMessage() . ` @ ` . $e->getFile() . `:` . $e->getLine());
echo $e->getMessage();
echo "
Backtrace:" . $e->getTraceAsString()."
";
}
迴圈讀取訊息,執行任務
這裡是真正的daemon程式執行的程式碼。目前的邏輯比較簡單,就是定期獲取前端寫入的待構建的訊息,建立程式執行一次構建任務。
<?php
public function daemonAction()
{
// change to working dir
chdir(WORKING_DIR);
// install signal handlers
$this->_setupSignals();
$nextWakeUp = 0;
// 每隔時間檢查一次當前需不需要生成APP
while(1) {
$this->roused = FALSE;
if ($nextWakeUp <= time()) {
$nextWakeUp = time() + self::SLEEP_SECONDS;
}
sleep($nextWakeUp - time());
pcntl_signal_dispatch();
if ($this->roused) {
continue;
}
$limit = self::MAX_PROCESS - count(self::$childs);
// exceeds process limit
if ($limit <= 0) {
continue;
}
// find todo tasks
$tasks = ...;
foreach ($tasks as $task) {
$pid = $this->_runTask($task);
}
}
}
通過子程式執行任務
_runTask()
其實就是呼叫一個新的PHP程式,執行特定的action。我們在這個action再去fork程式執行Ant
或者Gradle
。
<?php
private function _runTask($task)
{
$pid = pcntl_fork();
if ($pid == -1) {
return FALSE;
} else if ($pid) {
return $pid;
} else {
pcntl_exec($_SERVER[`_`], $this->_getTaskCommand($task));
// fork succeed but exec failed,
// need to be _exit(). actually exit() will cause problem
exit(1);
}
}
signal處理
這裡daemon程式處理的signal比較簡單,主要就是需要退出和需要wait子程式結束的資訊,以便OS清理其留在程式表中的資訊。但是pcntl_signal_dispatch()
有個問題,有可能兩個接近同時退出的子程式,會導致可能當時只發了一個SIGCHLD的訊號,另一個訊號下一次dispatch才會發出,所以只能通過迴圈來wait。
signal handler需要注意一些問題,這篇文章有很詳盡的介紹,非常值得一讀。也可以看看Nginx中是怎麼做的。signal是古老UNIX的產物,signal handler是非同步觸發的,這導致如果signal handler不是可重入的話,很可能會出現問題,雖然一般情況可能不會發生。更現代的處理方式是signalfd(類似的東西還有eventfd,timerfd),這個fd可以通過select
,epoll
來監聽,有時候和程式的事件機制就結合在一起了。
<?php
private function _setupSignals()
{
pcntl_signal(SIGTERM, array($this, "signalHandler"));
pcntl_signal(SIGINT, array($this, "signalHandler"));
pcntl_signal(SIGCHLD, array($this, "signalHandler"));
}
protected function signalHandler($signo)
{
$this->roused = TRUE;
switch($signo) {
case SIGTERM:
case SIGINT:
exit(1);
case SIGCHLD:
while(1) {
$pid = pcntl_wait($status, WNOHANG);
if ($pid == 0) {
break;
}
if ($pid > 0) {
// logs
} else {
break;
}
if (!pcntl_wifexited($status)) {
// error happened
} else if (($code = pcntl_wexitstatus($status)) != 0) {
// error happened
} else {
// normal exit
}
}
break;
default:
break;
}
}
相關文章
- 使用PHP指令碼來寫Daemon程式PHP指令碼
- php程式daemon化的正確做法PHP
- 將 Java 程式作為 Linux 的 Daemon 程式以及防止程式多次執行 (轉)JavaLinux
- Phalcon71.2.1釋出,C開發的PHP7框架PHP框架
- win10 daemon tools怎麼使用_win10虛擬光碟機daemon tools使用教程Win10
- 打算使用 docker laradock 執行 phalcon 學習Docker
- Linux程式設計:將PHP作為Shell指令碼使用(轉)Linux程式設計PHP指令碼
- opentracker改造為daemon守護程式
- Phalcon學習-model
- php獲取使用者當前所使用瀏覽器和作業系統PHP瀏覽器作業系統
- linux系統程式設計之程式(八):守護程式詳解及建立,daemon()使用Linux程式設計
- 選擇使用c語言編寫的phalcon框架C語言框架
- 建立 SysV 風格的 linux daemon 程式Linux
- Linux下開發-守護程式(daemon)Linux
- Phalcon的MVC框架解析MVC框架
- phalcon遇到的那些坑
- phalcon的CLI應用
- 9.1安裝配置Phalcon
- 【PHP】PHP專案製作PHP
- phalcon:跟蹤sql語句SQL
- 使用 Phalanger 整合 PHP 和 .NetPHP
- 在 PBootCMS 中,使用 {php} 和 {eval} 標籤可以在模板中執行 PHP 程式碼bootPHP
- rsync daemon模式配置模式
- 後臺 daemon 非法竊取使用者 iTunesstore 資訊
- 使用php作linux自動執行指令碼PHPLinux指令碼
- Phalcon入門教程之安裝
- phalcon框架中的軟刪除框架
- 在 Linux 命令列中使用和執行 PHP 程式碼(一)Linux命令列PHP
- 在 Linux 命令列中使用和執行 PHP 程式碼(二)Linux命令列PHP
- php程式碼檢測工具使用PHP
- php中使用 ffmpeg(部分程式碼)PHP
- DAEMON Protect 0.6.7脫殼――protect beta-last.exe主程式AST
- PHP熟手使用Rust作為後端開發語言PHPRust後端
- Run a program as a service (daemon)-GOGo
- Starting HAL daemon:[FAILED]AI
- linux中守護程式啟停工具start-stop-daemonLinux
- 使用ZendEncode編譯PHP程式(轉)編譯PHP
- 使用 Requests 庫和 PHP 的下載PHP