swoole4.0之打造自己的web开发框架(8)

2019年1月7日 241点热度 0人点赞 0条评论

上一篇:swoole4.0之打造自己的web开发框架(7), 我们了解php,swoole的生命周期,以及正确的单例使用


不管是FPM还是swoole都是多进程单线程的架构,所以单例实现比较简单,不会存在线程安全的问题,用一个全局的static变量即可


严格意义上来讲,单例是不能有状态的,大部分情况也是如此,所以基本用全局单例就可适用,如你的单例有状态(数据、资源)等,那么解法上一篇也给出了,就是把这些有状态的情况拎出来单独处理


swoole因为常驻内存,所以有了很多的性能提升点,但并不能像FPM一样的无痛热更新,这是一个巨大的劣势, 本篇将通过一些手段来实现热更新


热更的原理?

swoole和nginx/fpm 架构类似,都是多进程架构,所以热更的原理也一样,就是给server发一个信号, server接收到信号后,会停止当前的工作进程接受新的请求,处理完当然请求后自动退出,然后会重新拉起一组工作进程,在这个拉起过程中,会有机会重新加载代码,从而达到热更的效果


哪些可热更?

swoole中并不是任何代码都能热更新,所以我们需要知道哪些情况下可以,哪些情况不可以,这就要回到上一篇有关swoole生命周期里的一些内容以及上面的原理介绍:

onWorkerStart之前的代码是不会被热更的


以Family为例,我们可以看一下,在onWorkerStart之前,会加载多少的文件, 我们一起来看一下:

在onWorkerStart回调第一行,输入如下代码:

print_r(get_included_files());

php application index.php 运行服务

Array
(
[0] => /Users/work/github/family/application/index.php
   [1] => /Users/work/github/family/vendor/autoload.php
   [2] => /Users/work/github/family/vendor/composer/autoload_real.php
   [3] => /Users/work/github/family/vendor/composer/ClassLoader.php
   [4] => /Users/work/github/family/vendor/composer/autoload_static.php
   [5] => /Users/work/github/family/vendor/nikic/fast-route/src/functions.php
   [6] => /Users/work/github/family/vendor/symfony/polyfill-ctype/bootstrap.php
   [7] => /Users/work/github/family/framework/Family/Family.php
   [8] => /Users/work/github/family/framework/Family/Core/Config.php
   [9] => /Users/work/github/family/application/config/default.php
)

可以看到,总共有9个文件被加载了,如要改动这9个文件代码,是不能被热加载的

但我们可以看到,这9个文件都是非常基础的框架性文件,几乎没有改动的必要

opcache的影响

如果我们开启了opcache, 那么我们需要在workerStart最开始加入以下代码:

if (function_exists('opcache_reset')) {
//清除opcache 缓存,swoole模式下其实可以关闭opcache
   \opcache_reset();
}

作用是清空opcache缓存,否则新文件不会被加载,其实在swoole这种常驻内存的模式中,opcache的用途不大, 完全可以关闭opcache


除了这些文件之外,其他的文件改动,都可以做热更新


如何热更新

可以向主进程或manager进程发送 USR1 信号: kill -USR1 进程id, 如果我们每次改动都手动敲这个命令,还需要提前通过ps 看进程id,效率很是低下


服务脚本

所以这的的做法就是把这类雷同的操作,做成一个脚本,我们在bin目录下创建一个family.sh脚本,来管理服务的启停,代码如下:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          family application server
# Required-Start:    $remote_fs $network
# Required-Stop:     $remote_fs $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts family server
# Description:       starts the Family Application daemon
### END INIT INFO
#php路径,如不知道在哪,可以用whereis php尝试
PHP_BIN=`which php`
#bin目录
BIN_PATH=`pwd`
#入口文件
SERVER_PATH=$BIN_PATH/..
#脚本执行地址, 可修改为你的php运行脚本#########
APPLICATION_FILE=$SERVER_PATH/application/index.php

#获取主进程id
getMasterPid()
{
if [ ! -f "$BIN_PATH/master.pid" ];then
       echo ''
   else
       PID=`cat $BIN_PATH/master.pid`
echo $PID
fi
}

#获取管理进程id
getManagerPid()
{
if [ ! -f "$BIN_PATH/manager.pid" ];then
       echo ''
   else
       MID=PID=`cat $BIN_PATH/manager.pid`
echo $MID
fi
}

case "$1" in
       #启动服务
       start)
PID=`getMasterPid`
if [ -n "$PID" ]; then
                   echo "server is running"
                   exit 1
               fi
               echo "Starting server "
               $PHP_BIN $APPLICATION_FILE
echo " done"
       ;;
       #停止服务
       stop)
PID=`getMasterPid`
if [ -z "$PID" ]; then
                   echo "server is not running"
                   exit 1
               fi
               echo "Gracefully shutting down server "
               kill $PID
sleep 1
               if [ -n "$PID" ]; then
                   unlink $BIN_PATH/master.pid
unlink $BIN_PATH/manager.pid
fi
               echo " done"
       ;;
       #查看状态
       status)
PID=`getMasterPid`
if [ -n "$PID" ]; then
                   echo "server is running"
               else
                   echo "server is not running"
               fi
       ;;
       #退出
       force-quit)
$0 stop
;;
       #重启
       restart)
$0 stop
$0 start

;;
       #reload
       reload)
MID=`getManagerPid`
if [ -z "$MID" ]; then
                   echo "server is not running"
                   exit 1
               fi
               echo "Reload server ing..."
               kill -USR1 $MID
echo " done"
       ;;
       #reload task进程
       reloadtask)

MID=`getManagerPid`

if [ -z "$MID" ]; then
                   echo "server is not running"
                   exit 1
               fi
               echo "Reload task ing..."
               kill -USR2 $MID
echo " done"
       ;;
       #提示
       *)
echo "Usage: $0 {start|stop|force-quit|restart|reload|status}"
               exit 1
       ;;
esac

有了这个脚本,我们只需要如此:

./family.sh  start ,就可以启动服务

./family.sh  stop ,就可以停止服务

./family.sh  reload, 就可以reload

在框架代码里,也有些调整:

$http->on('start', function (\swoole_server $serv) {
//服务启动
   //日志初始化
   Log::init();
file_put_contents(self::$rootPath . DS . 'bin' . DS . 'master.pid', $serv->master_pid);
file_put_contents(self::$rootPath . DS . 'bin' . DS . 'manager.pid', $serv->manager_pid);
Log::info("http server start! {host}: {port}, masterId:{masterId}, managerId: {managerId}", [
'{host}' => Config::get('host'),
'{port}' => Config::get('port'),
'{masterId}' => $serv->master_pid,
'{managerId}' => $serv->manager_pid,
]);
});

$http->on('shutdown', function () {
//服务关闭,删除进程id
   unlink(self::$rootPath . 'DS' . 'bin' . DS . 'master.pid');
unlink(self::$rootPath . 'DS' . 'bin' . DS . 'manager.pid');
Log::info("http server shutdown");
});

我们在服务启动的时候,会把主进程和管理进程id分别写到 bin/master.pid 、bin/manager.pid 里,在服务停止的时候,会删除这两个pid文件

自动热更新

有了脚本,只是可以提高我们手写命令的效率,并不能自动热更新

在开发环境,我们可以通过 fswatch 来自动监控文件变化,然后执行脚本, 代码如下:

#!/bin/bash
DIR=`pwd`
checkExt=php
fswatch $DIR/.. | while read file
do
   filename=$(basename "$file")
extension="${filename##*.}"
   #php文件改动,则reload
   if [ "$extension" == "$checkExt" ];then
       #reload代码
       $DIR/family.sh reload
fi
done

然后开一个窗口 执行 ./fswatch 启动,就可以愉快的热更新了


在生产环境,我们可以在代码部署完成,自动执行shell勾子,来reload代码


奇技淫巧

在开发环境,我们也可以有一些小技巧做自动reload

  1. 设置max_request = 1,这样每处理一个请求之后,就可以reload了

  2. 开放一个接口,如 xxx/reload, 在代码显示调用$serv->reload

  3. 写一个定时器,固定时间自动 reload

本篇的内容基本完成,至此整个系列也差不多告一段落了,大家有兴趣可以后续共同完善这个框架,可以给我提PR,另外,要保障一个项目的质量,测试是避免不了的,有兴趣的同学,可以引入phpunit,对框架加入更多的测试用例


查看原文,了解swoole的各项配置

github地址(最新代码都已上传):
https://github.com/shenzhe/family

 ----------伟大的分割线-----------

PHP饭米粒(phpfamily) 由一群靠谱的人建立,愿为PHPer带来一些值得细细品味的精神食粮!

饭米粒只发原创或授权发表的文章,不转载网上的文章

所发的文章,均可找到原作者进行沟通。

也希望各位多多打赏(算作稿费给文章作者),更希望大家多多投搞。

投稿请联系:

[email protected]

本文由 半桶水 授权 饭米粒 发布,转载请注明本来源信息和以下的二维码(长按可识别二维码关注)

图片



37500swoole4.0之打造自己的web开发框架(8)

这个人很懒,什么都没留下

文章评论