Last active
March 18, 2017 16:24
-
-
Save smallslime/c98c0d502edb8c9d8ee165c1af32a980 to your computer and use it in GitHub Desktop.
服务+ZK模式, 支持热更新, 平滑重启
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
final class stdtpl_main | |
{ | |
const P_NAME_MAIN = 'main'; | |
const P_NAME_ZK = 'zk'; | |
const P_NAME_SERVER = 'server'; | |
private static $iTimeWaitForServerPreHeat; | |
private static $iTimeWaitForServerCoolDown; | |
private static $bDebugMode; | |
private static $iPID_Server = 0; | |
private static $iPID_ZK = 0; | |
private static $iProcessID = 0; | |
private static $sProcessName = ''; | |
/** | |
* @param int $iTimeWaitForServerPreHeat 服务预热时间 | |
* @param int $iTimeWaitForServerCoolDown 服务冷却时间 | |
* @param bool $iDebugMode 是否 Debug 模式 | |
*/ | |
public static function main( | |
int $iTimeWaitForServerPreHeat = 10, | |
int $iTimeWaitForServerCoolDown = 5, | |
bool $iDebugMode = false | |
) { | |
# init | |
self::$sProcessName = self::P_NAME_MAIN; | |
self::$iProcessID = posix_getpid(); | |
self::$iTimeWaitForServerPreHeat = $iTimeWaitForServerPreHeat; | |
self::$iTimeWaitForServerCoolDown = $iTimeWaitForServerCoolDown; | |
self::$bDebugMode = $iDebugMode; | |
self::_write_pid(); | |
self::_write_pid(self::P_NAME_ZK, 0); | |
self::_write_pid(self::P_NAME_SERVER, 0); | |
self::stdOut(['msg' => "process started"]); | |
# sig register | |
self::_sig_handler(); | |
# create server && zk process | |
self::$iPID_Server = self::_create_process(self::P_NAME_SERVER); | |
sleep(self::$iTimeWaitForServerPreHeat); | |
self::$iPID_ZK = self::_create_process(self::P_NAME_ZK); | |
# 主循环 | |
while (true) { | |
$iStatus = 0; | |
$iPID = pcntl_wait($iStatus, WNOHANG | WUNTRACED); | |
if ($iPID <= 0) { | |
self::stdOut(['msg' => 'wait none'], true); | |
goto NEXT; | |
} | |
self::stdOut(['msg' => "wait pid $iPID", 'status' => $iStatus]); | |
switch ($iPID) { | |
case self::$iPID_Server: | |
self::$iPID_Server = self::_create_process(self::P_NAME_SERVER); | |
break; | |
case self::$iPID_ZK: | |
self::$iPID_ZK = self::_create_process(self::P_NAME_ZK); | |
break; | |
} | |
NEXT: | |
pcntl_signal_dispatch(); | |
sleep(1); | |
} | |
} | |
/** | |
* @return array [0:<int> 进程号, 1:<string> 进程名, 2:<string> 描述] | |
*/ | |
public static function getProcessDesc() : array | |
{ | |
return [self::$iProcessID, self::$sProcessName, '[' . self::$sProcessName . '-' . self::$iProcessID . ']']; | |
} | |
public static function stdOut(array $aData, bool $bAsDebug = false) | |
{ | |
if ($bAsDebug && !self::$bDebugMode) { | |
return; | |
} | |
list(, , $sProcessDesc) = self::getProcessDesc(); | |
$aTidyData = [$sProcessDesc]; | |
foreach ($aData as $sK => $sV) { | |
$aTidyData[] = "$sK : $sV"; | |
} | |
fprintf(STDOUT, implode(' ; ', $aTidyData) . "\n"); | |
} | |
public static function stdErr(array $aData) | |
{ | |
list(, , $sProcessDesc) = self::getProcessDesc(); | |
$aTidyData = [$sProcessDesc]; | |
foreach ($aData as $sK => $sV) { | |
$aTidyData[] = "$sK : $sV"; | |
} | |
fprintf(STDERR, implode(' ; ', $aTidyData) . "\n"); | |
} | |
/** | |
* @param bool $bReset 传 true 直接重设信号 | |
*/ | |
private static function _sig_handler(bool $bReset = false) | |
{ | |
if ($bReset) { | |
pcntl_signal(SIGQUIT, SIG_DFL); | |
pcntl_signal(SIGTERM, SIG_DFL); | |
pcntl_signal(SIGUSR1, SIG_DFL); | |
pcntl_signal(SIGUSR2, SIG_DFL); | |
goto END; | |
} | |
$_funcKillProcess = function () { | |
$bResult = false; | |
# zk kill | |
$bRS = posix_kill(self::$iPID_ZK, SIGQUIT); | |
if (!$bRS) { | |
//@todo 报警! ZK 无法退出 | |
self::stdErr(['msg' => 'process ' . self::P_NAME_ZK . ' quit failed', 'pid' => self::$iPID_ZK]); | |
goto FUNC_KILL_FIN; | |
} | |
self::stdOut(['msg' => 'process ' . self::P_NAME_ZK . ' quit succ', 'pid' => self::$iPID_ZK]); | |
self::_write_pid(self::P_NAME_ZK, 0); | |
# wait cooldown | |
self::stdOut(['msg' => 'wait for a while', 'second' => self::$iTimeWaitForServerCoolDown]); | |
sleep(self::$iTimeWaitForServerPreHeat); | |
# server term | |
$bRS = posix_kill(self::$iPID_Server, SIGTERM); | |
if (!$bRS) { | |
//@todo 报警! 服务无法退出 | |
self::stdErr( | |
['msg' => 'process ' . self::P_NAME_SERVER . ' quit failed', 'pid' => self::$iPID_Server] | |
); | |
goto FUNC_KILL_FIN; | |
} | |
self::stdOut(['msg' => 'process ' . self::P_NAME_SERVER . ' quit succ', 'pid' => self::$iPID_Server]); | |
self::_write_pid(self::P_NAME_SERVER, 0); | |
$bResult = true; | |
FUNC_KILL_FIN: | |
return $bResult; | |
}; | |
$funcQuit = function () use ($_funcKillProcess) { | |
self::stdOut(['msg' => 'start quit']); | |
$bRS = $_funcKillProcess(); | |
self::_write_pid(self::P_NAME_MAIN, 0); | |
if ($bRS) { | |
self::stdOut(['msg' => 'finish with quit']); | |
exit(); | |
} else { | |
self::stdErr(['msg' => 'sub process quit failed']); | |
//@todo 异常报警, 退出所有进程 | |
exit(255); | |
} | |
}; | |
$funcReload = function () use ($_funcKillProcess) { | |
self::stdOut(['msg' => 'start reload']); | |
$bRS = $_funcKillProcess(); | |
# kill 成功, 这里信号一定能 wait 到 | |
if ($bRS) { | |
self::stdOut(['msg' => 'clean old process finished']); | |
# restart server | |
while (true) { | |
$iStatus = 0; | |
self::stdOut(['msg' => 'wait for ' . self::P_NAME_SERVER, 'pid' => self::$iPID_Server]); | |
if (pcntl_waitpid(self::$iPID_Server, $iStatus) > 0) { | |
self::stdOut(['msg' => 'restarted process ' . self::P_NAME_SERVER]); | |
self::$iPID_Server = self::_create_process(self::P_NAME_SERVER); | |
break; | |
} | |
sleep(1); | |
} | |
# wait | |
self::stdOut(['msg' => 'wait for a while', 'second' => self::$iTimeWaitForServerPreHeat]); | |
sleep(self::$iTimeWaitForServerPreHeat); | |
# restart zk | |
while (true) { | |
$iStatus = 0; | |
self::stdOut(['msg' => 'wait for ' . self::P_NAME_ZK, 'pid' => self::$iPID_ZK]); | |
if (pcntl_waitpid(self::$iPID_ZK, $iStatus) > 0) { | |
self::stdOut(['msg' => 'restarted process ' . self::P_NAME_ZK]); | |
self::$iPID_ZK = self::_create_process(self::P_NAME_ZK); | |
break; | |
} | |
sleep(1); | |
} | |
self::stdOut(['msg' => 'finish reload']); | |
} else { | |
self::stdErr(['msg' => 'sub process quit failed']); | |
// @todo 直接杀掉所有相关进程 ? | |
exit(255); | |
} | |
}; | |
# 注册信号 | |
pcntl_signal(SIGTERM, $funcQuit); | |
pcntl_signal(SIGUSR1, $funcReload); | |
pcntl_signal(SIGUSR2, $funcReload); | |
END: | |
} | |
private static function _create_process(string $sName) : int | |
{ | |
$iPID = pcntl_fork(); | |
if ($iPID < 0) { | |
exit; | |
} | |
if ($iPID == 0) { | |
# set name | |
self::$sProcessName = $sName; | |
self::$iProcessID = posix_getpid(); | |
# write pid file | |
self::_write_pid(); | |
self::stdOut(['msg' => "process started"]); | |
# reset sig_handler | |
self::_sig_handler(true); | |
# run | |
require __DIR__ . "/process/$sName.php"; | |
exit; | |
} | |
return $iPID; | |
} | |
private static function _write_pid(string $nsProcessName = null, int $niPID = null) | |
{ | |
$sPIDFile = __DIR__ . "/pid/" . ($nsProcessName === null ? self::$sProcessName : (string)$nsProcessName); | |
$mRS = file_put_contents($sPIDFile, $niPID === null ? posix_getpid() : (int)$niPID); | |
if ($mRS === false) { | |
self::stdErr(['msg' => 'write pid file failed', 'pid_file' => $sPIDFile]); | |
//@todo 异常报警, 退出所有进程 | |
} else { | |
self::stdOut(['msg' => 'write pid file succ', 'pid_file' => $sPIDFile]); | |
} | |
} | |
} | |
stdtpl_main::main( | |
isset($argv[1]) ? (int)($argv[1]) : 5, | |
isset($argv[2]) ? (int)$argv[2] : 10, | |
!empty($argv[3]) | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment