PHP Swoole学习笔记,持续记录
- Swoole
- 2022-04-13
- 1213热度
- 0评论
PHPStorm Swoole代码提示:https://plugins.jetbrains.com/plugin/13040-swoole-ide-helper/versions
PHP swoole代码提示,类型包:https://github.com/swoole/ide-helper
匿名函数
匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数。最经常用作回调函数 callable参数的值。
匿名函数目前是通过 Closure 类来实现的。
闭包可以从父作用域中继承变量。 任何此类变量都应该用 use
语言结构传递进去。例如:
<?php
$message = 'hello';
/* 继承 $message */
$example = function () use ($message) {
var_dump($message);
};
继承之后的参数,是按值传递的,对它的修改是不影响原变量的,如果需要,可以通过引用传递参数,或者在函数代码块内使用 global声明全局变量进行使用。
在类的方法中使用匿名函数,5.4以上的版本无需使用use引入this , 直接可以在匿名函数中使用this,直接可以在匿名函数中使用this,直接可以在匿名函数中使用this来调用当前对象的方法。在swoole编程中,可以利用此特性减少$serv对象的use引入传递。
如果希望在闭包函数中修改外部变量,可以在use时为变量增加&引用符号即可。注意对象类型不需要加&,因为在PHP中对象默认就是传引用而非传值。
普通函数不能使用use,子函数获取父函数的变量,只能通过匿名函数实现,use只能传递所在作用域的变量;
$sortFun = function ($a, $b) use ($key) {}
PHP对象可以直接通过指定一个属性进行赋值来给对象创建一个新属性。
相关文章:https://nicen.cn/thread.html
Swoole基础
必须每个进程单独创建 Redis、MySQL、PDO 连接,其他的存储客户端同样也是如此。原因是如果共用 1 个连接,那么返回的结果无法保证被哪个进程处理,持有连接的进程理论上都可以对这个连接进行读写,这样数据就发生错乱了。
在 Swoole 内,无法 通过 $_GET/$_POST/$_REQUEST/$_SESSION/$_COOKIE/$_SERVER 等 $_开头的变量获取到任何属性参数。
1.swoole
Swoole的进程不同于平常的PHP脚本,它是常驻内存的。这意味着程序是一直运行,变量也可以一直存在。例如Swoole提供的异步Websocket服务器。
Swoole定时器:https://wiki.swoole.com/#/timer?id=%e5%ae%9a%e6%97%b6%e5%99%a8-timer
Swoole内存管理:https://wiki.swoole.com/#/getting_started/notice?id=%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86
2.swoole_server中对象的4层生命周期
- 程序全局期
- 进程全局期
- 会话期
- 请求期
2.1 程序全局期
在swoole_server->start之前就创建好的对象,我们称之为程序全局生命周期。这些变量在程序启动后就会一直存在,直到整个程序结束运行才会销毁。
变量在Worker进程内对这些对象进行写操作时,会自动从共享内存中分离,变为进程全局对象。进程操作的对象是原对象的拷贝,对该对象的操作不影响原对象;
2.2 进程全局期
swoole拥有进程生命周期控制的机制,一个Worker子进程处理的请求数超过max_request配置后,就会自动销毁。Worker进程启动后创建的对象(onWorkerStart中创建的对象),在这个子进程存活周期之内,是常驻内存的。onConnect/onReceive/onClose 中都可以去访问它。
进程期include/require的文件,在reload后就会重新加载
相关文档:https://www.easyswoole.com/NoobCourse/Swoole/lifecycle.html
2.3 会话期
onConnect到onClose是一次TCP的会话周期,http keep-alive时,一个连接可能会有多个request。 http是无状态的,一个用户可能也不止一个连接,可以通过创建一个session来关联同一个用户的不同请求。
2.4 请求期
请求期就是指一个完整的请求发来,也就是onReceive收到请求开始处理,直到返回结果发送response。这个周期所创建的对象,会在请求完成后销毁。
swoole中请求期对象与普通PHP程序中的对象就是一样的。请求到来时创建,请求结束后销毁。
3.进程隔离
原因就是全局变量在不同的进程,内存空间是隔离的,所以修改全局变量的值是无效的。
所以使用 Swoole 开发 Server 程序需要了解进程隔离问题,Swoole/Server 程序的不同 Worker 进程之间是隔离的,在编程时操作全局变量、定时器、事件监听,仅在当前进程内有效。
不同的进程中 PHP 变量不是共享,即使是全局变量,在 A 进程内修改了它的值,在 B 进程内是无效的
如果需要在不同的 Worker 进程内共享数据,可以用 Redis、MySQL、文件、Swoole/Table、APCu、shmget 等工具实现
不同进程的文件句柄是隔离的,所以在 A 进程创建的 Socket 连接或打开的文件,在 B 进程内是无效,即使是将它的 fd 发送到 B 进程也是不可用的
4.start()干了些什么
- start()运行之后会创建Master 进程 +Manager 进程 +serv->worker_num 个 Worker 进程。
- 启动失败会立即返回 false,启动成功后将进入事件循环,等待客户端连接请求。start 方法之后的代码不会执行。
- 服务器关闭后,start 函数返回 true,并继续向下执行
- 设置了 task_worker_num 会增加相应数量的 Task 进程
- 方法列表中 start 之前的方法仅可在 start 调用前使用,在 start 之后的方法仅可在 onWorkerStart、onReceive 等事件回调函数中使用
5.运行时进程
- Master 主进程,主进程内有多个 Reactor 线程,基于 epoll/kqueue 进行网络事件轮询。收到数据后转发到 Worker 进程去处理;
- Manager 进程,对所有 Worker 进程进行管理,Worker 进程生命周期结束或者发生异常时自动回收,并创建新的 Worker 进程;
- Worker 进程,对收到的数据进行处理,包括协议解析和响应请求;
- Reactor 线程,是在 Master 进程中创建的线程,负责维护客户端 TCP 连接、处理网络 IO、处理协议、收发数据,不执行任何 PHP 代码,将 TCP 客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包;
- Task 进程以及Task Worker进程,是独立于worker进程当中的一个工作进程,用于处理一些耗时较长的逻辑,这些逻辑如果在task 进程当中处理时并不会影响worker 进程处理来自客户端的请求,由此大大提高了swoole处理并发的能力
假设 Server 就是一个工厂,那 Reactor 就是销售,接受客户订单。而 Worker 就是工人,当销售接到订单后,Worker 去工作生产出客户要的东西。而 TaskWorker 可以理解为行政人员,可以帮助 Worker 干些杂事,让 Worker 专心工作。
6.Server 的两种运行模式
SWOOLE_PROCESS 模式的 Server 所有客户端的 TCP 连接都是和主进程建立的,内部实现比较复杂,用了大量的进程间通信、进程管理机制。适合业务逻辑非常复杂的场景。Swoole 提供了完善的进程管理、内存保护机制。 在业务逻辑非常复杂的情况下,也可以长期稳定运行。
SWOOLE_BASE 这种模式就是传统的异步非阻塞 Server。与 Nginx 和 Node.js 等程序是完全一致的。worker_num 参数对于 BASE 模式仍然有效,会启动多个 Worker 进程。当有 TCP 连接请求进来的时候,所有的 Worker 进程去争抢这一个连接,并最终会有一个 worker 进程成功直接和客户端建立 TCP 连接,之后这个连接的所有数据收发直接和这个 worker 通讯,不经过主进程的 Reactor 线程转发。
webscoket
wss启用,https://wiki.swoole.com/#/server/methods?id=__construct
参考:https://my.oschina.net/u/125977/blog/1816423
1.创建websocket服务器(异步)
SwooleWebsocketServer::__construct(string $host = '0.0.0.0', int $port = 0, int $mode = SWOOLE_PROCESS, int $sockType = SWOOLE_SOCK_TCP): SwooleServer
<?php
/*创建websocket服务器对象,监听0.0.0.0:9501端口,开启SSL隧道*/
$ws = new swoole_websocket_server("0.0.0.0", 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
/*配置参数*/
$ws ->set([
'max_conn'=>1000, /*最多连接数量。*/
'task_worker_num' => 2,/*TaskWorker 进程数量。*/
'daemonize' => false, /*守护进程化。*/
/*配置SSL证书和密钥路径*/
'ssl_cert_file' => "/etc/nginx/cert/socket.yuhal.com.pem",
'ssl_key_file' => "/etc/nginx/cert/socket.yuhal.com.key"
]);
/*监听WebSocket连接打开事件*/
$ws->on('open', function ($ws, $request) {
echo "client-{$request->fd} is openn";
});
/*监听WebSocket消息事件*/
$ws->on('message', function ($ws, $frame) {
echo "Message: {$frame->data}n";
$ws->push($frame->fd, "server: {$frame->data}");
});
/*监听WebSocket连接关闭事件*/
$ws->on('close', function ($ws, $fd) {
echo "client-{$fd} is closedn";
});
$ws->start();
2.swoole类短名介绍
例如swoole_websocket_server
等同于SwooleWebsocketServer:https://wiki.swoole.com/#/other/alias?id=%e7%b1%bb%e7%9f%ad%e5%88%ab%e5%90%8d%e6%98%a0%e5%b0%84%e5%85%b3%e7%b3%bb
3.task_worker_num
配置此参数后将会启用 task 功能。所以 Server 务必要注册 onTask、onFinish 2 个事件回调函数。如果没有注册,服务器程序将无法启动。
4.swoole的wss配置了一晚上,怎么都不行,还是Nginx好
location /websocket {
proxy_pass http://s.nicen.cn:5703;
proxy_http_version 1.1;
proxy_read_timeout 3600s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
使用腾讯云CDN时,进行websokect反向代理时,由于cdn链接最多保持10s,将会导致websokect中断。
5.事件执行顺序
- 所有事件回调均在 $server->start 后发生
- 服务器关闭程序终止时最后一次事件是 onShutdown
- 服务器启动成功后,onStart/onManagerStart/onWorkerStart 会在不同的进程内并发执行
- onReceive/onConnect/onClose 在 Worker 进程中触发
- Worker/Task 进程启动 / 结束时会分别调用一次 onWorkerStart/onWorkerStop
- onTask 事件仅在 task 进程中发生
- onFinish 事件仅在 worker 进程中发生
- onStart/onManagerStart/onWorkerStart 3 个事件的执行顺序是不确定的
5.其他
错误码大全:https://wiki.swoole.com/#/other/errno
函数别名汇总:https://wiki.swoole.com/#/other/alias
协程入门
一键协程化:
// 协程生效的范围
Co::set(['hook_flags'=> SWOOLE_HOOK_ALL]); // v4.4+版本使用此方法。
// 或
SwooleRuntime::enableCoroutine($flags = SWOOLE_HOOK_ALL);
1.什么是协程
协程就是《操作系统原理》所说的用户态线程,协程内的代码被阻塞时会自动切换运行其他协程。
与常说的线程相比,协程在用户态,调度由程序自身完成,线程在系统态,调度由操作系统完成。
2.使用须知
- Swoole4 或更高版本拥有高可用性的内置协程,可以使用完全同步的代码来实现异步 IO,PHP 代码没有任何额外的关键字,底层会自动进行协程调度。
- 所有的协程必须在协程容器里面创建,Swoole 程序启动的时候大部分情况会自动创建协程容器,Server的
enable_coroutine
控制事件回调是否自动创建协程。 - 防止多协程同时操作数据,导致运行混乱,协程内部禁止使用全局变量,协程使用 use 关键字引入外部变量到当前作用域禁止使用引用,协程之间通讯必须使用 Channel。
- 在协程编程中可直接使用 try/catch 处理异常。但必须在协程内捕获,不得跨协程捕获异常。当协程退出时,发现有未捕获的异常,将引起致命错误。
- 在一键协程化里面使用原生 PHP 提供的方法,它们的超时时间受 default_socket_timeout 配置影响,开发者可以通过 ini_set('default_socket_timeout', 60) 这样来单独设置它,它的默认值是 60。
- 使用 Coroutine::create 或 go 方法创建协程 ,在创建的协程中才能使用协程 API,而协程必须创建在协程容器里面。
- 在一个协程中可以使用 go 嵌套创建新的协程。因为 Swoole 的协程是单进程单线程模型,使用 go 创建的子协程会优先执行,子协程执行完毕或挂起时,将重新回到父协程向下执行代码,如果子协程挂起后,父协程退出,不影响子协程的执行,
- Swoole 的协程是单进程单线程模型,使用 go 创建的子协程会优先执行,子协程执行完毕或挂起时,将重新回到父协程向下执行代码
如果子协程挂起后,父协程退出,不影响子协程的执行
3.协程HTTP服务端
- 对连接的处理是在单独的子协程中完成,客户端连接的 Connect、Request、Response、Close 是完全串行的。
- 监听的地址若是本地 UNIXSocket 则应以形如 unix://tmp/your_file.sock 的格式填写 。
3.1 websocket处理流程
- $ws->upgrade():向客户端发送 WebSocket 握手消息
- while(true) 循环处理消息的接收和发送
- $ws->recv() 接收 WebSocket 消息帧
- $ws->push() 向对端发送数据帧
- $ws->close() 关闭连接
4.协程设置
协程设置,设置协程相关选项。参考:https://wiki.swoole.com/#/coroutine/coroutine?id=set
<?php
SwooleCoroutine::set(array $options);
5.退出协程
5.1 defer
defer 用于资源的释放,会在协程关闭之前 (即协程函数执行完毕时) 进行调用,就算抛出了异常,已注册的 defer 也会被执行。
需要注意的是,它的调用顺序是逆序的(先进后出), 也就是先注册 defer 的后执行,先进后出。逆序符合资源释放的正确逻辑,后申请的资源可能是基于先申请的资源的,如先释放先申请的资源,后申请的资源可能就难以释放。
5.2 主动退出
在 Swoole 低版本中,协程中使用 exit 强行退出脚本会导致内存错误导致不可预期的结果或 coredump,在 Swoole 服务中使用 exit 会使整个服务进程退出且内部的协程全部异常终止导致严重问题,Swoole 长期以来一直禁止开发者使用 exit,但开发者可以使用抛出异常这种非常规的方式,在顶层 catch 来实现和 exit 相同的退出逻辑。
Swoole v4.1.0 版本及以上直接支持了在协程、服务事件循环中使用 PHP 的 exit,此时底层会自动抛出一个可捕获的 SwooleExitException,开发者可以在需要的位置捕获并实现与原生 PHP 一样的退出逻辑。
5.3 cancel()
可以用于取消某个协程,但不能对当前协程发起取消操作。协程被取消后触发defer回调,然后运行结束。
目前基本支持了绝大部分的协程 API 的取消,包括:
- socket
- AsyncIO (fread, gethostbyname ...)
- sleep
- waitSignal
- wait/waitpid
- waitEvent
- Co::suspend/Co::yield
- channel
- native curl (SWOOLE_HOOK_NATIVE_CURL)
有两个不可中断的场景
- 被 CPU 中断调度器强制切换的协程
- 文件锁操作期间
相关说明:https://zhuanlan.zhihu.com/p/378795262
6.协程化API
系统API:https://wiki.swoole.com/#/coroutine/system
协程通信:https://wiki.swoole.com/#/coroutine/channel
7.补充知识
- 协程没有IO等待 正常执行PHP代码,不会产生执行流程切换
- 协程遇到IO等待 立即将控制权切,待IO完成后,重新将执行流切回原来协程切出的点
- 协程并行协程依次执行,同上一个逻辑
- 协程嵌套执行流程由外向内逐层进入,直到发生IO,然后切到外层协程,父协程不会等待子协程结束
- Swoole的协程在底层实现上是单线程的,因此同一时间只有一个协程在工作,协程的执行是串行的。这与线程不同,多个线程会被操作系统调度到多个CPU并行执行。
- 一个协程正在运行时,其他协程会停止工作。当前协程执行阻塞IO操作时会挂起,底层调度器会进入事件循环。当有IO完成事件时,底层调度器恢复事件对应的协程的执行。
- 对CPU多核的利用,仍然依赖于Swoole引擎的多进程机制。
- Swoole遇到需要等待的IO才会切换,比如SQL查询、比如网络请求等待响应、比如读取文件等找到那个文件。
- 从Mysql获取返回的结果集,这个需要CPU来计算,需要写内存,所以Swoole协程的单线程模型是串行来的,所以对于结果集数据量级过大的查询,效果不明显。
- 搞明白什么时候会发生协程切换,业务无关的任务可以异步处理
- Mysql协程客户端,指定是查询需要等待的时候,会进行协程调度,会运行其它没有阻塞的协程,而没有协程调度的客户端,就会一直等着,不会让出cpu。(所以单个worker进程内,同时处理许多个请求时,不会因为上一个请求卡住下一个),代码上是同步的,底层IO是一直在协程化,异步的。
- php use &引用,不存在的变量,会创建一个变量,禁止使用引入是避免争强,防止造成数据不一致性
8.协程 CPU 密集场景调度实现
如果服务场景是IO密集型,非抢占式可以表现的非常完美,但是如果服务中加入了CPU密集型操作,就不得不考虑重新协程的调度模式。
试想有以下场景,程序中有A,B两个协程,协程A一直在执行CPU密集运算,非抢占式的调度模型中,A不会主动让出控制权,从而导致B得不到时间片,协程得不到均衡调度。导致的问题是假如当前服务A,B同时对外提供服务,B协程处理的请求就可能因为得不到时间片导致请求超时
相关文档:https://wiki.swoole.com/wiki/page/p-tick_scheduler.html
9.协程内存开销
新版本4.0使用了C栈+PHP栈的协程实现方案。Server程序每次请求的事件回调函数中会创建一个新协程,处理完成后协程退出。
在协程创建时需要创建一个全新的内存段作为C和PHP的栈,底层默认分配2M(C)虚拟内存+8K(PHP)内存(PHP-7.2或更高版本)。这里的虚拟内存是指操作系统并不会立即分配2M物理内存,系统会根据在内存实际读写时发生缺页中断,再分配实际内存。
协程与线程不同,在一个进程内创建的多个协程,实际上是串行的。同一CPU时间,只有一个协程在执行,因此协程不存在数据同步问题。
协程内可以安全的修改全局变量的值,而不考虑锁的问题。
在协程的执行过程中,调用IO操作时,会自动让出控制权。这时其他IO操作完成的协程才会执行。当IO操作完成后,底层会重新切换会当前的协程。
10.多进程数据共享
Swoole4由于是单线程多进程的,底层没有使用任何Mutex锁,不存在锁的争抢。 同样带来的问题是,没有超全局变量。只有进程级全局变量,读写PHP全局变量只在当前进程内有效。如果希望多进程共享数据,有3种解决方案:
- 使用Table和Atomic对象,或者其他共享内存数据结构
- 使用IPC进程间通信
- 借助存储实现数据的共享和中转,如Redis、MySQL或文件操作
11.和Go协程的区别
Go的协程是多线程模型,所以按照调度的规则,分配到的CPU时间片比Swoole的单线程要多。
相关文档:https://wiki.swoole.com/wiki/page/p-differences_with_go.html
12.协程客户端
概览:https://wiki.swoole.com/#/coroutine_client/client
Swoole-cli
Swoole-Cli 是一个 PHP 的二进制发行版,集成了 swoole、php 内核、php-cli、php-fpm以及多个常用扩展。
# 查看拓展列表
swoole-cli -m
1.Cygwin和WSL
- Windows Subsystem for Linux(简称WSL)是一个在Windows 1011上能够运行原生Linux二进制可执行文件(ELF格式)的兼容层。
- Cygwin是一个在windows平台上运行的类UNIX模拟环境,在Windows中增加了一个中间层——兼容POSIX的模拟层,并在此基础上构建了大量Linux-like的软件工具。
2.配置文件
swoole-cli 默认不加载任何 php.ini 配置文件。可通过 -d 参数来设置 PHP 选项或使用 -c 参数指定加载的php.ini配置文件。
swoole-cli -d swoole.use_shortname=off bin/hyperf.php start
swoole-cli -c /tmp/php.ini -v
3.启动 PHP-FPM
# 查看帮助文件
swoole-cli -P -h
# 运行 FPM
swoole-cli -P --fpm-config /opt/php-8.1/etc/php-fpm.conf -p /opt/php-8.1/var
# 关闭守护进程
swoole-cli -P --fpm-config /opt/php-8.1/etc/php-fpm.conf -p /opt/php-8.1/var -F
# 使用 root 账户启动
swoole-cli -P --fpm-config /opt/php-8.1/etc/php-fpm.conf -p /opt/php-8.1/var -F -R
4.启动 CLI Server
# 使用 Laravel Artisan 工具
swoole-cli artisan serve
# 启动 CLI Server
swoole-cli -S 127.0.0.1:9001
Coroutine/Scheduler
相关文档:https://wiki.swoole.com/#/coroutine/scheduler?id=coroutinescheduler
所有的协程必须在协程容器里面创建,Swoole 程序启动的时候大部分情况会自动创建协程容器,用 Swoole 启动程序的方式一共有三种:
- 调用异步风格服务端程序的 start 方法,此种启动方式会在事件回调中创建协程容器,参考 enable_coroutine。
- 调用 Swoole 提供的 2 个进程管理模块 Process 和 ProcessPool 的 start 方法,此种启动方式会在进程启动的时候创建协程容器,参考这两个模块构造函数的 enable_coroutine 参数。
- 其他直接裸写协程的方式启动程序,需要先创建一个协程容器 (Coroutinerun() 函数,可以理解为 java、c 的 main 函数)
Swoole连接池
Swoole 从 v4.4.13 版本开始提供了内置协程连接池
相关文档:https://wiki.swoole.com/#/coroutine/conn_pool
Swoole Event
1.EventLoop
EventLoop,即事件循环,可以简单的理解为 epoll_wait,会把所有要发生事件的句柄(fd)加入到 epoll_wait 中,这些事件包括可读,可写,出错等。
对应的进程就阻塞在 epoll_wait 这个内核函数上,当发生了事件 (或超时) 后 epoll_wait 这个函数就会结束阻塞返回结果,就可以回调相应的 PHP 函数,例如,收到客户端发来的数据,回调 onReceive 回调函数。
当有大量的 fd 放入到了 epoll_wait 中,并且同时产生了大量的事件,epoll_wait 函数返回的时候就会挨个调用相应的回调函数,叫做一轮事件循环,即 IO 多路复用,然后再次阻塞调用 epoll_wait 进行下一轮事件循环。
2.IPC进程间通信
Unix Socket,全名 UNIX Domain Socket, 简称 UDS, 使用套接字的 API (socket,bind,listen,connect,read,write,close 等),和 TCP/IP 不同的是不需要指定 ip 和 port,而是通过一个文件名来表示 (例如 FPM 和 Nginx 之间的 /tmp/php-fcgi.sock),UDS 是 Linux 内核实现的全内存通信,无任何 IO 消耗。在 1 进程 write,1 进程 read,每次读写 1024 字节数据的测试中,100 万次通信仅需 1.02 秒,而且功能非常的强大,Swoole 下默认用的就是这种 IPC 方式。
3.Swoole异步事件
相关文档:https://wiki.swoole.com/#/event
Swoole多进程
1.Swoole/Process
创建一个子进程,运行指定的回调函数。
相关文档:https://wiki.swoole.com/#/process/process
$process = new Process(function () use ($n) {
echo 'Child #' . getmypid() . " start and sleep {$n}s" . PHP_EOL;
sleep($n);
echo 'Child #' . getmypid() . ' exit' . PHP_EOL;
});
$process->start();
- 父进程会等待所有子进程运行完毕后才会退出。
- 可以通过event异步监听进程的pipe描述符,触发异步事件。
- SwooleProcess->__construct(callable $function, bool $redirect_stdin_stdout = false, int $pipe_type = SOCK_DGRAM, bool $enable_coroutine = false)
子进程管道通信:
<?php
/**
* @date 2023/6/28
* @author 爱心发电丶
*/
use SwooleProcess;
class Test
{
public Process $process;
function __construct()
{
$this->process = new Process([$this, "__start"], false, 2, true);
$this->process->start(); //启动进程
$this->process->name("SwooleProcess");
}
public function __start()
{
register_shutdown_function(function () {
echo "进程退出n";
});
swoole_event_add($this->process->pipe, function () {
echo $this->process->read() . "n";
});
}
}
$start = new Test();
SwooleCoroutinerun(function () use ($start) {
while (true) {
SwooleCoroutine::sleep(1);
$start->process->write("66666666");
}
});
2. Process支持重定向标准输入和输出,在子进程内 echo 不会打印屏幕,而是写入管道,读键盘输入可以重定向为管道读取数据
3. Process提供了 exec 接口,创建的进程可以执行其他程序,与原 PHP 父进程之间可以方便的通信
4. 在协程环境中无法使用 Process 模块,可以使用 runtime hook+proc_open 实现,参考协程进程管理
2.Swoole/Process/Pool
用来创建进程池,会永远保持指定数量的子进程。
相关文档:https://wiki.swoole.com/#/process/process_pool
$pool->on('WorkerStart', function (ProcessPool $pool, $workerId) {
// 子进程程序代码
});
3.多进程内存共享
相关文档:https://wiki.swoole.com/#/memory/table
4.定时器
- SwooleTimer::tick(),设置一个间隔时钟定时器。
- SwooleTimer::after(),在指定的时间后执行函数。
- SwooleTimer::clear(),使用定时器 ID 来删除定时器。
- Co::sleep(),协程休眠指定时间
- SwooleEvent::wait(),启用事件监听
协程系统级API:https://wiki.swoole.com/#/coroutine/system?id=coroutinesystem
5.addProcess
Server->addProcessaddProcess(),用于添加一个用户自定义的工作进程。不需要执行 start。在 Server 启动时会自动创建进程,并执行指定的子进程函数。
- 创建的子进程可以调用 $server 对象提供的各个方法,如 getClientList/getClientInfo/stats。
- 在 Worker/Task 进程中可以调用 $process 提供的方法与子进程进行通信。
- 在用户自定义进程中可以调用 $server->sendMessage 与 Worker/Task 进程通信。
- 用户进程内不能使用 Server->task/taskwait 接口。
- 用户进程内可以使用 Server->send/close 等接口。
- 用户进程内应当进行 while(true)(如下边的示例) 或 EventLoop 循环 (例如创建个定时器),否则用户进程会不停地退出重启。
生命周期
- 用户进程的生存周期与 Master 和 Manager 是相同的,不会受到 reload 影响。
- 用户进程不受 reload 指令控制,reload 时不会向用户进程发送任何信息。
- 在 shutdown 关闭服务器时,会向用户进程发送 SIGTERM 信号,关闭用户进程。
- 自定义进程会托管到 Manager 进程,如果发生致命错误,Manager 进程会重新创建一个。
- 自定义进程也不会触发 onWorkerStop 等事件。
不兼容更新记录
1.v4.6.3
- 新增 SwooleCoroutinego 函数 (swoole/library@82f63be) (@matyhtf)
- 新增 SwooleCoroutinedefer 函数 (swoole/library@92fd0de) (@matyhtf)
2.v4.6.0
- 将 Event::rshutdown() 标记为已弃用,请改用 Coroutinerun (#3881) (@matyhtf)
问题记录
Redis订阅发布:https://www.runoob.com/redis/redis-pub-sub.html
1.redis订阅
socket has already been bound to another coroutine
redis订阅的消息处理是同步的,处理下一条必须等上一条运行结束。
进程全局期调用redis是通过static进行了缓存,导致之后的进程全部是调用的一个redis连接,导致报错。
2.读写分离
对同一个socket链接的操作要进行完全的读写分离,避免协程对同一个socket同时写发生冲突。
// 并发过高时,push可能不会立即进行。
$ws->push("连接成功");
$subscribe(); //开始订阅
3.内存增长
swoole脚本在一定的内存范围内会一直增长,然后保持在某个大小才会稳定
4.window后台运行php脚本
新建一个vbs脚本和bat批处理
set ws = wscript.createobject("wscript.shell")
ws.run "app.bat /start",0
msgbox "运行中..."
5.内存泄露排查
- pmap,查看指定进程的内存申请状态
- smem,内存使用情况报告工具
- top,查看进程的资源占用情况
PHP的内存分配算法:小于 3072 字节的内存申请 PHP 会认为是小内存,PHP 会把所有申请的小内存块缓存起来,即使释放了也不归还给操作系统,以保证内存管理的效率
PHP 的 gc_mem_caches() 函数是一个垃圾回收函数,用于释放 PHP 内部缓存所占用的内存。这些缓存包括 opcode 缓存、符号表缓存、类缓存等,它们会在 PHP 运行时占用一定的内存空间。
6.websocket报错
使用 "/" 作为websocket路径时,会出现 fd[8] is not a websocket conncetion 报错,是因为浏览器打开页面时默认会触发一次根目录请求。
7. 宝塔swoole使用腾讯oss sdk报错
使用原生 curl hook 的前提是在编译 Swoole 扩展时开启--enable-swoole-curl选项 ,比如支持原生 curl multi
没有启用的话使用腾讯云OSS SDK时会报错,安装pecl:
wget http://pear.php.net/go-pear.phar -O go-pear.php
php go-pear.php
然后使用pecl自定义安装swoole