• 服务热重启
    • 热重载进程

    服务热重启

    由于 swoole 常驻内存的特性,修改文件后需要重启worker进程才能将被修改的文件重新载入内存中,我们可以自定义Process的方式实现文件变动自动进行服务重载

    热重载进程

    新建文件 App/Process/HotReload.php 并添加如下内容,也可以放在其他位置,请对应命名空间

    1. <?php
    2. /**
    3. * Created by PhpStorm.
    4. * User: evalor
    5. * Date: 2018-11-26
    6. * Time: 23:18
    7. */
    8. namespace App\Process;
    9. use EasySwoole\Component\Process\AbstractProcess;
    10. use EasySwoole\EasySwoole\ServerManager;
    11. use EasySwoole\Utility\File;
    12. use Swoole\Process;
    13. use Swoole\Table;
    14. use Swoole\Timer;
    15. /**
    16. * 暴力热重载
    17. * Class HotReload
    18. * @package App\Process
    19. */
    20. class HotReload extends AbstractProcess
    21. {
    22. /** @var \swoole_table $table */
    23. protected $table;
    24. protected $isReady = false;
    25. protected $monitorDir; // 需要监控的目录
    26. protected $monitorExt; // 需要监控的后缀
    27. /**
    28. * 启动定时器进行循环扫描
    29. */
    30. public function run($arg)
    31. {
    32. // 此处指定需要监视的目录 建议只监视App目录下的文件变更
    33. $this->monitorDir = !empty($arg['monitorDir']) ? $arg['monitorDir'] : EASYSWOOLE_ROOT . '/App';
    34. // 指定需要监控的扩展名 不属于指定类型的的文件 无视变更 不重启
    35. $this->monitorExt = !empty($arg['monitorExt']) && is_array($arg['monitorExt']) ? $arg['monitorExt'] : ['php'];
    36. if (extension_loaded('inotify') && empty($arg['disableInotify'])) {
    37. // 扩展可用 优先使用扩展进行处理
    38. $this->registerInotifyEvent();
    39. echo "server hot reload start : use inotify\n";
    40. } else {
    41. // 扩展不可用时 进行暴力扫描
    42. $this->table = new Table(512);
    43. $this->table->column('mtime', Table::TYPE_INT, 4);
    44. $this->table->create();
    45. $this->runComparison();
    46. Timer::tick(1000, function () {
    47. $this->runComparison();
    48. });
    49. echo "server hot reload start : use timer tick comparison\n";
    50. }
    51. }
    52. /**
    53. * 扫描文件变更
    54. */
    55. private function runComparison()
    56. {
    57. $startTime = microtime(true);
    58. $doReload = false;
    59. $dirIterator = new \RecursiveDirectoryIterator($this->monitorDir);
    60. $iterator = new \RecursiveIteratorIterator($dirIterator);
    61. $inodeList = array();
    62. // 迭代目录全部文件进行检查
    63. foreach ($iterator as $file) {
    64. /** @var \SplFileInfo $file */
    65. $ext = $file->getExtension();
    66. if (!in_array($ext, $this->monitorExt)) {
    67. continue; // 只检查指定类型
    68. } else {
    69. // 由于修改文件名称 并不需要重新载入 可以基于inode进行监控
    70. $inode = $file->getInode();
    71. $mtime = $file->getMTime();
    72. array_push($inodeList, $inode);
    73. if (!$this->table->exist($inode)) {
    74. // 新建文件或修改文件 变更了inode
    75. $this->table->set($inode, ['mtime' => $mtime]);
    76. $doReload = true;
    77. } else {
    78. // 修改文件 但未发生inode变更
    79. $oldTime = $this->table->get($inode)['mtime'];
    80. if ($oldTime != $mtime) {
    81. $this->table->set($inode, ['mtime' => $mtime]);
    82. $doReload = true;
    83. }
    84. }
    85. }
    86. }
    87. foreach ($this->table as $inode => $value) {
    88. // 迭代table寻找需要删除的inode
    89. if (!in_array(intval($inode), $inodeList)) {
    90. $this->table->del($inode);
    91. $doReload = true;
    92. }
    93. }
    94. if ($doReload) {
    95. $count = $this->table->count();
    96. $time = date('Y-m-d H:i:s');
    97. $usage = round(microtime(true) - $startTime, 3);
    98. if (!$this->isReady == false) {
    99. // 监测到需要进行热重启
    100. echo "severReload at {$time} use : {$usage} s total: {$count} files\n";
    101. ServerManager::getInstance()->getSwooleServer()->reload();
    102. } else {
    103. // 首次扫描不需要进行重启操作
    104. echo "hot reload ready at {$time} use : {$usage} s total: {$count} files\n";
    105. $this->isReady = true;
    106. }
    107. }
    108. }
    109. /**
    110. * 注册Inotify监听事件
    111. */
    112. private function registerInotifyEvent()
    113. {
    114. // 因为进程独立 且当前是自定义进程 全局变量只有该进程使用
    115. // 在确定不会造成污染的情况下 也可以合理使用全局变量
    116. global $lastReloadTime;
    117. global $inotifyResource;
    118. $lastReloadTime = 0;
    119. $files = File::scanDirectory(EASYSWOOLE_ROOT . '/App');
    120. $files = array_merge($files['files'], $files['dirs']);
    121. $inotifyResource = inotify_init();
    122. // 为当前所有的目录和文件添加事件监听
    123. foreach ($files as $item) {
    124. inotify_add_watch($inotifyResource, $item, IN_CREATE | IN_DELETE | IN_MODIFY);
    125. }
    126. // 加入事件循环
    127. swoole_event_add($inotifyResource, function () {
    128. global $lastReloadTime;
    129. global $inotifyResource;
    130. $events = inotify_read($inotifyResource);
    131. if ($lastReloadTime < time() && !empty($events)) { // 限制1s内不能进行重复reload
    132. $lastReloadTime = time();
    133. ServerManager::getInstance()->getSwooleServer()->reload();
    134. }
    135. });
    136. }
    137. public function onShutDown()
    138. {
    139. // TODO: Implement onShutDown() method.
    140. }
    141. public function onReceive(string $str)
    142. {
    143. // TODO: Implement onReceive() method.
    144. }
    145. }

    添加好后在全局的 EasySwooleEvent.php 中,注册该自定义进程

    1. public static function mainServerCreate(EventRegister $register)
    2. {
    3. $swooleServer = ServerManager::getInstance()->getSwooleServer();
    4. $swooleServer->addProcess((new HotReload('HotReload', ['disableInotify' => false]))->getProcess());
    5. }

    因为虚拟机中inotify无法监听到FTP/SFTP等文件上传的事件,将 disableInotify 设置为 true ,可以关闭inotify方式的热重启,使得虚拟机环境下,强制使用文件循环扫描来触发重载操作,同理 OSX 开发环境下,没有Inotify扩展,将自动使用扫描式重载