swoole基础-与传统MVC框架的整合

由于yii2本身就是传统MVC框架的典型代表之一,所以这个问题又可以等同于swoole如何与传统的MVC框架整合,比如laravel\tp等。本文我们就以yii2为例,来简述下在传统MVC框架下是怎么运用swoole的。

我们在去年12月份讲yii2的console知识点的时候,提过swoole。没错,我们可以用yii2自带的console来配和操作swoole。明明很简单,你们却告诉我你不会,我总结一下可能有下面这些原因:

命名空间的问题
日志的使用问题
热重启的问题
如果还有其他我们没有谈到的问题,评论下方留言。

下面我们就这三个问题,来分别谈谈swoole与yii2的那些事。

命名空间的问题

前面我们介绍的swoole,基本上都很贴近原生,当然我们也没有引入过命名空间。

但是在yii2的console中,有命名空间的存在,所以,在实例化swoole_server等相关的对象时,我们就需要在这些扩展对象的前面添加反斜杠”\”,这也是很多基础薄弱的同学不明白为啥在yii2中new swoole_server等对象的时候报undefined 错误了。

比如我们这样写

1
new Swoole\Server('127.0.0.1', 9501);

就会抛出一个Fatal error:Class ‘console\controllers\Swoole\Server’ not found,这便是命名空间所引起的问题,如何避免这个问题呢?像我们上面说的,只需要在swoole对象的前面添加一个反斜杠即可。

比如像下面这样写就没问题

1
new \Swoole\Server('127.0.0.1', 9501);

日志的使用问题

在yii2中,平时我们在业务逻辑中做一些日志追踪,记录等操作,都来源于对yii\log\Logger类的操作。这个类很特殊,怎么个特殊法呢?且听我慢慢道来。

以文件存储为例,在脚本运行时记录的日志信息并没有在脚本运行到记录的那行代码就把日志写到你的文件内,当脚本运行到记录的那行代码时,这些信息只是被暂时性的写入到内存,等脚本完全结束的时候,才最终把这些日志统一一次性输出到文件系统中。

我们再来看一下swoole。swoole是常驻内存型,除非遇到致命错误或者其他非正常中断时,脚本才会执行完毕,所以在swoole程序正常运行时,你所记录的日志并没有正常的记录到文件内。

这就明显突出了常驻内存与yii2的日志管理的矛盾。

有些人可能还不明白,说的啥,听不懂。我们看一个例子

首先我们在console\config\main.php配置文件配置log组件

1
2
3
4
5
6
7
8
9
10
'components' => [
'log' => [
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
],

在console下,没有swoole_server的情况下,我们记录下面这样一行代码,看看日志中有没有记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace console\controllers;
use Yii;
use yii\console\Controller;
/**
* Test Console Application
*/
class TestController extends Controller
{
public function actionIndex ()
{
yii::warning("This is a warning message.");
}
}

控制台下输入如下命令执行

1
./yii test/index

执行完之后,我们打开console/runtime/logs/app.log,你会发现该文件内找到有类似下面这样的记录信息

1
2017-05-12 22:04:31 [-][-][-][warning][application] This is a warning message.

当然也有记录$_SERVER等其他信息。

现在我们在这段代码中增加swoole的代码,看看结果如何

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
namespace console\controllers;
use Yii;
use yii\console\Controller;
/**
* Test Console Application
*/
class TestController extends Controller
{
private $_serv;
private function _prepare()
{
$this->_serv = new \Swoole\Server('127.0.0.1', 9501);
$this->_serv->set([
'worker_num' => 1,
]);
$this->_serv->on('Start', [$this, 'onStart']);
$this->_serv->on('Receive', [$this, 'onReceive']);
$this->_serv->on('Close', [$this, 'onClose']);
}
public function actionStart ()
{
$this->_prepare();
$this->_serv->start();
}
public function onStart($serv)
{
yii::warning("This is a warning message.");
}
public function onReceive($serv, $fd, $fromId, $data)
{
}
public function onClose($serv)
{
}
}

命令行同样以./yii命令运行该脚本,即表示本地9501端口的swoole_server启动了,注意哦,我们在onStart回调内调起了yii::warning,打开日志文件你会发现,没有这条日志,真的什么都没有!其实这条信息有没有呢?有,只是没有到它该刷新出来的时候,很明显我们并不想要这样的结果,怎么办呢?我们可以手动直接把这条信息刷到文件中去。

1
2
3
$logObject = Yii::getLogger();
$logObject->log("This is a warning message.", \yii\log\Logger::LEVEL_WARNING);
$logObject->flush(true);

把上面这三行代码,替换掉我们在onStart回调内写的代码试试?

看最后一个问题,估计也是你们最关心的问题。

热重启的问题

在yii2的console下,不能有效的解决热重启问题,注意我们说的是在console下。

什么意思呢?

我们知道,热重启的实质是重启Worker进程,在Worker进程启动之前加载的文件都不能实现热更新,为了实现热更新,我们需要把被热更新的程序文件,在onWorkerStart回调内重新加载,更新内存中已经被加载过的程序。

在console中我们仍然可以这样做。

我们把主要的业务逻辑与控制器类剥离开来,在onWorkerStart中,实例化主要的业务逻辑类,跟我们邮件发送的案例中一致,只不过需要注意命名空间的问题,我们就不再举例了,这样肯定没问题。

但是为什么我要说yii2的console不能有效的解决热更新呢?

首先我们看看在Worker进程之前,都加载了哪些文件,当然这些已经加载过的文件,是不能被重新加载的。

1
2
3
4
public function onWorkerStart($serv, $workerId)
{
print_r(get_included_files());
}

运行server之后,我们会在终端界面上看到大概有54个文件被加载了,其中就包括了common和console配置文件以及param和param-local文件,也就是说这些文件,一旦我们修改了,是没有办法实现热重启的,只能重启server。

这也就是我上面说的,在console下不能完全有效的实现热重启,如果这些你能接受,你觉得没问题,可以,就这么干没任何问题。

有些人就觉得有瑕疵的代码不好,接受不了,怎么办?

我们回顾下为什么会有这个瑕疵,这些提前加载的文件是怎么被加载的呢?如果我们把这些提前加载的文件放到onWorkerStart回调内再加载是不是就可以了避免了呢?

事实上可以。

我们注意到这个瑕疵是因为命令 ./yii 引起的,当我们运行这个命令的时候,实际上也就创建了一个console应用。(可以打开根目录下的yii文件一探究竟)

为了避免这个问题,我们转换思路,摒弃console,重新拾起 php xxx.php的执行方式,把需要自动加载的类文件以及components的初始化工作放在onWorkerStart回调内去处理即可。

关于swoole的基础,我们就先介绍这么多,有任何问题,尽量文章下留言哦,有好的意见也欢迎大家的反馈,谢谢。哦对了,关于swoole的学习,你可不能偷懒哦,正所谓师傅领进门,修行在个人,后面还需要多看文档,多做总结,多写实例。

补充:如何解决yii2日志按日期分割的问题?

有人说这很简单呀,设置logFile就可以了,代码如下

1
2
3
4
5
6
7
8
9
'log' => [
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
'logFile' => '@runtime/logs/app.log.'.date('Ymd'),
],
],
],

这是正常思维的惯性,我不怪你。

我们刚刚也说了,对yii2来说,配置文件是在onWorkerStart之前加载的,而且只在第一次才被加载到内存中,也就是说这里设置的logFile对应的日期,仅仅是你启动server当天的日期,过了N天之后这些日志还是只会保存在这个文件内。

这如何是好?我们给出一个解决方案,不过最好的方案还是放弃console,在onWorkerStart之后初始化应用。

增加一个全局的类方法,在需要记录日志的时候调用该方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use yii\log\Logger;
public static function flushLog($message)
{
$dispatcher = new yii\log\Dispatcher();
$dispatcher->targets = [
Yii::createObject([
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
'logFile' => '@runtime/logs/app.log.'.date('Ymd'),
])
];

$logger = $dispatcher->getLogger();
$logger->log($message, Logger::LEVEL_ERROR);
$logger->flush(true);
}