Skip to content

Instantly share code, notes, and snippets.

@smallslime
Last active March 18, 2017 16:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smallslime/c98c0d502edb8c9d8ee165c1af32a980 to your computer and use it in GitHub Desktop.
Save smallslime/c98c0d502edb8c9d8ee165c1af32a980 to your computer and use it in GitHub Desktop.
服务+ZK模式, 支持热更新, 平滑重启
<?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