前八个问题前面已经讲过了,下面分享一下。没有看过上一篇的可以点这里~9.Swoole如何正确?重启服务我们修改PHP代码后,往往需要重启服务才能使代码生效。繁忙的后端服务器随时都在处理请求。如果管理员通过杀进程的方式终止/重启服务器程序,可能会导致代码被执行。半终止不能保证整个业务逻辑的完整性。Swoole提供了灵活的终止/重启机制。管理员只需要向Server发送一个特定的信号或者调用reload方法,工作进程就可以结束并重启。但是有几点需要注意:首先注意新修改的代码必须在OnWorkerStart事件中重新加载才能生效。比如不允许在OnWorkerStart之前通过composer的autoload加载一个类。其次,reload还需要配合max_wait_time和reload_async这两个参数。设置好这两个参数后,就可以实现异步安全重启。如果没有这个特性,当Worker进程收到重启信号或达到max_request时,会立即停止服务。这时候Worker进程中可能还有事件监听,这些异步任务就会被丢弃。设置好以上参数后,会先创建一个新的Worker,旧的Worker会在完成所有事件后自行退出,即reload_async。如果老Worker还没有退出,则在底层增加一个定时器。如果老Worker在约定时间内(max_wait_time秒)没有退出,底层会被强制终止。示例:set(array('worker_num'=>1,'max_wait_time'=>60,'reload_async'=>true,));$serv->on('receive',function(Swoole\Server$serv,$fd,$reactor_id,$data){echo"[#".$serv->worker_id."]\tClient[$fd]接收数据:$data\n";Swoole\Timer::tick(5000,function(){echo'tick';});});$服务->开始();比如上面的代码如果没有reload_async,那么在onReceive中创建的timer就会丢失,也就没有机会去处理timer中的回调函数了。进程退出事件为了支持异步重启特性,在底层新增了一个onWorkerExit事件。当老Worker即将退出时,会触发onWorkerExit事件。在这个事件回调函数中,应用层可以尝试清理一些长连接的Sockets,直到事件循环中没有fd或者达到max_wait_time,退出进程。$serv->on('WorkerExit',function(Swoole\Server$serv,$worker_id){$redisState=$serv->redis->getState();if($redisState==Swoole\Redis::STATE_READYor$redisState==Swoole\Redis::STATE_SUBSCRIBE){$serv->redis->close();}});同时,我们在SwoolePlus中加入了检测文件变化的功能,使得文件发生变化时无需手动重新加载或发送信号自动重启worker。10.发送后为什么不立即关闭不安全?发送后立即关闭是不安全的,无论是服务端还是客户端。send操作成功只代表数据已经成功写入操作系统的socketbuffer,并不代表对端真的收到了数据。操作系统是否发送成功,对方服务器是否收到,或者服务器端程序是否处理过,都没有办法保证。close后的逻辑可以参考下面的linger设置。这个逻辑和电话通讯是一样的。A跟B说了些什么,A说完就挂了电话。所以B听到没有,A不知道。如果A说完,B说好,B挂断电话,就绝对安全了。当一个socket被linger关闭后,如果buffer中还有数据,操作系统底层会根据linger的设置来决定如何处理。structlinger{intl_onoff;intl_linger;};发送数据发送完毕后释放资源,即优雅退出。l_onoff!=0,l_linger=0,close时立即返回,但不会发送未发送的数据,而是通过一个RST包强制关闭socket描述符,即强制退出。l_onoff!=0,l_linger>0,closes不会立即返回,内核会延迟一段时间,这个时间由l_linger的值决定。如果未发送的数据(包括FIN包)在超时前发送完毕并得到对端确认,close会正确返回,socket描述符会优雅退出。否则close会直接返回错误值,未发送的数据会丢失,socket描述符会被强行退出。如果socket描述符设置为非阻塞,close会直接返回一个值。11.连接已关闭。问题如下:NOTICEswFactoryProcess_finish(ERRNO1004):send165bytefailed,becauseconnection[fd=123]isclosedNOTICEswFactoryProcess_finish(ERROR1005):connection[fd=123]doesnotexists当服务器响应时,客户端已经断开连接,常见于:浏览器疯狂刷新页面(还没加载完就被刷了)ab压测中途取消wrk基于时间的压测(未完成的请求会在时间到时取消)及以上几种情况都是正常的,可以忽略,所以错误级别为NOTICE。如果由于其他情况无故导致大量连接断开,则需要引起注意。12.学习Swoole需要掌握哪些基础知识1.多进程/多线程了解Linux操作系统进程和线程的概念了解Linux进程/线程切换调度的基础知识了解inter-的基础知识进程通信,如管道、UnixSocket、消息队列、共享内存2、SOCKET了解SOCKET的基本操作如accept/connect、send/recv、close、listen、bind了解SOCKET接收缓冲区、发送缓冲区、阻塞/非-阻塞、超时等概念3、IO多路复用理解select/poll/epoll理解基于select/epoll的事件循环,Reactor模型理解可读事件和可写事件4、TCP/IP网络协议理解TCP/IP协议理解TCP、UDP传输协议5、调试工具使用gdb调试Linux程序使用strace跟踪进程系统调用使用tcpdump跟踪网络通信进程其他Linux系统工具,如如ps、lsof、top、vmstat、netstat、sar、ss等。13.我可以共享Redis或MySQL连接吗?绝对不。Redis、MySQL、PDO连接必须为每个进程单独创建,其他存储客户端也是如此。原因是如果共享一个连接,则不能保证返回的结果是由哪个进程处理的。持有连接的进程理论上可以读写连接,所以数据会乱序。因此,多个进程之间,一定不能共享连接。在Swoole\Server中,connection对象应该在onWorkerStart中创建。在Swoole\Process中,connection对象应该在Swoole\Process->start之后的子进程的回调函数中创建这个问题描述的信息对于使用pcntl_fork的程序同样有效例子:$server=newSwoole\Server("0.0.0.0",9502);//必须在onWorkerStart回调中创建redis/mysql连接$server->on('workerstart',function($server,$id){$redis=newRedis();$redis->connect('127.0.0.1',6379);$server->redis=$redis;});$server->on('receive',function(Swoole\Server$server,$fd,$from_id,$data){$value=$server->redis->get("key");$server->send($fd,"Swoole:".$value);});$server->start();十四、CalltoundefinedfunctionCo\Run()本文档中的大部分例子都是使用Co\run()来创建一个协程容器,了解什么是协程容器如果遇到如下错误:PHPFatalerror:UncaughtError:CalltoundefinedfunctionCo\Run()PHPFatalerror:UncaughtError:Calltoundefinedfunctiongo()说明你的Swoole扩展版本低于v4.4.0或者协程的简称被手动关闭。提供了三种解决方案。如果版本过低,请将扩展版本升级到>=v4.4.0或使用go关键字替换Co\Run创建协程如果关闭了协程的短名称,请打开协程的短名称协程;使用协程::creat方法代替Co\Run或go创建协程;十五、资源暂时不可用[11]客户端swoole_client报swoole_client::recv():recv()failed。Error:Resourcetemporaryunavailable[11]inrecv表示服务器在指定时间内没有返回数据,如果出现接收超时,可以通过tcpdump查看网络通信过程,查看服务器是否发送数据。服务器的$serv->send函数需要检测是否返回true。更多的时间,需要增加swoole_client的超时时间。从码农到架构师,可以访问更多的学习内容。
