协程
- 协程可以简单理解为线程,只不过这个线程是用户态的,不需要操作系统参与,创建销毁和切换的成本非常低,和线程不同的是协程没法利用多核 cpu 的,想利用多核 cpu 需要依赖 Swoole 的多进程模型。
进程模型
四种回调方式:
-
匿名函数
$server->on('Request', function ($req,$resp) use ($a,$b, $c) { echo "hello world"; }); -
静态方法
$server->on('Request', 'A::Test');
-
对象方法
$object = new A();
$server->on('Request', array($object, 'test'));
- 函数
function my_onRequest($req, $resp)
{
echo "hello world";
}
$server->on('Request', 'my_onRequest');
同步 IO / 异步 IO
-
同步 : 这个进程什么事情都不做,等待 MySQL 返回结果,返回结果后再向下执行代码
-
异步 :不需要等待上文执行结果的
协程调度
- 概念:决定到底让 CPU 执行哪个协程的代码的决断过程就是协程调度
- 调度策略:
- 首先,在执行某个协程代码的过程中发现这行代码遇到了 Co::sleep() 或者产生了网络 IO(如mysql查询),那么这个Fd 放到 EventLoop 中。 然后让出这个协程的 CPU 给其他协程使用:即 **yield(挂起) ** 等待 MySQL 数据返回后再继续执行这个协程:即 resume(恢复)
- 其次,如果协程的代码有 CPU 密集型代码,可以开启 enable_preemptive_scheduler,Swoole 会强行让这个协程让出 CPU。
- 优先执行子协程 (即 go() 里面的逻辑),直到发生协程 yield(Co::sleep () 处),然后协程调度到外层协程。
EventLoop
- 所谓 EventLoop,即事件循环,可以简单的理解为 epoll_wait,会把所有要发生事件的句柄(fd)加入到 epoll_wait 中,这些事件包括可读,可写,出错等。
- 当有大量的 fd 放入到了 epoll_wait 中,并且同时产生了大量的事件, epoll_wait 函数返回的时候就会挨个调用相应的回调函数,叫做一轮事件循环,即 IO 多路复用,然后再次阻塞调用 epoll_wait 进行下一轮事件循环。
IPC
-
同一台主机上两个进程间通信 (简称 IPC) 的方式有很多种,在 Swoole 下使用了 2 种方式 Unix Socket 和 sysvmsg。
-
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 消耗。Swoole 下默认用的就是这种 IPC 方式。
-
Swoole 下面使用 UDS 通讯有两种类型:
- SOCK_STREAM 和 SOCK_DGRAM,可以简单的理解为 TCP 和 UDP 的区别,当使用 SOCK_STREAM 类型的时候同样需要考虑 TCP 数据包边界问题。
- 当使用 SOCK_DGRAM 类型的时候不需要考虑 TCP 数据包边界问题,每个 send() 的数据都是有边界的,发送多大的数据接收的时候就收到多大的数据,没有传输过程中的丢包、乱序问题,send 写入和 recv 读取的顺序是完全一致的。send 返回成功后一定是可以 recv 到。
- 在 IPC 传输的数据比较小时非常适合用 SOCK_DGRAM 这种方式,由于 IP 包每个最大有 64k 的限制,所以用 SOCK_DGRAM 进行 IPC 时候单次发送数据不能大于 64k,同时要注意收包速度太慢操作系统缓冲区满了会丢弃包,因为 UDP 是允许丢包的,可以适当调大缓冲区。
-
sysvmsg : 即 Linux 提供的消息队列,这种 IPC 方式通过一个文件名来作为 key 进行通讯,这种方式非常的不灵活,实际项目使用的并不多。
- 此种 IPC 方式只有两个场景下有用:
- 防止丢数据,如果整个服务都挂掉,再次启动队列中的消息也在,可以继续消费,但同样有脏数据的问题
- 可以外部投递数据,比如 Swoole 下的 Worker进程通过消息队列给 Task进程投递任务,第三方的进程也可以投递任务到队列里面让 Task 消费,甚至可以在命令行手动添加消息到队列。
- 此种 IPC 方式只有两个场景下有用:
进程:
- Master :多线程进程
- Reactor :是在 Master 进程中创建的线程,负责维护客户端 TCP 连接、处理网络 IO、处理协议、收发数据,不执行php代码,将 TCP 客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包。
- Worker :接受由 Reactor 线程投递的请求数据包,并执行 PHP 回调函数处理数据, 生成响应数据并发给 Reactor 线程,由 Reactor 线程发送给 TCP 客户端,可以是异步非阻塞模式,也可以是同步阻塞模式,Worker 以多进程的方式运行。
- TaskWorker :接受由 Worker 进程通过 Swoole\Server->task/taskwait/taskCo/taskWaitMulti 方法投递的任务,处理任务,并将结果数据返回(使用 Swoole\Server->finish)给 Worker 进程。完全是同步阻塞模式 TaskWorker 以多进程的方式运行。
- Manager :负责创建 / 回收 worker/task 进程。
- 关系梳理: 可以理解为 Reactor 就是 nginx,Worker 就是 PHP-FPM。Reactor 线程异步并行地处理网络请求,然后再转发给 Worker 进程中去处理。Reactor 和 Worker 间通过 unixSocket 进行通信。
Swoole server
-
在 Swoole\Server 构造函数的第三个参数,可以填 2 个常量值 -- SWOOLE_BASE 或 SWOOLE_PROCESS。
-
process模式
-
process模式下的Server 所有客户端的 TCP 连接都是和主进程建立的,内部实现比较复杂,用了大量的进程间通信、进程管理机制。(适合业务逻辑非常复杂的场景)
-
在 Reactor 线程中提供了 Buffer 的功能,可以应对大量慢速连接和逐字节的恶意客户端。
-
优点 :
- 连接与数据请求发送是分离的,不会因为某些连接数据量大某些连接数据量小导致 Worker 进程不均衡
- Worker 进程发生致命错误时,连接并不会被切断
- 可实现单连接并发,仅保持少量 TCP 连接,请求可以并发地在多个 Worker 进程中处理
-
缺点 :
- 存在 2 次 IPC 的开销,master 进程与 worker 进程需要使用 unixSocket 进行通信
- SWOOLE_PROCESS 不支持 PHP ZTS(线程安全),在这种情况下只能使用 SWOOLE_BASE 或者设置 single_thread 为 true
-
以下非官方总结 :
-
多进程多线程, 有主进程, manager进程, worker进程, task_worker进程
-
主进程下有多个线程, 主线程负责接受连接, 之后交给react线程处理请求。 react线程负责接收数据包, 并将数据转发给worker进程进行处理, 之后处理worker进程返回的数据
-
manager进程, 该进程为单线程, 主要负责管理worker进程, 类似于nginx中的主进程, 当worker进程异常退出时, manager进程负责重新fork出一个worker进程
-
worker进程, 该进程为单线程, 负责具体处理请求
-
task_worker进程, 用于处理比较耗时的任务, 默认不开启
-
worker进程与主进程中的react线程使用域套接字进行通信, worker进程之间不进行通信
-
-
-
base工作模式
-
SWOOLE_BASE 这种模式就是传统的异步非阻塞 Server。与 Nginx 和 Node.js 等程序是完全一致的。
-
worker_num 参数对于 BASE 模式仍然有效,会启动多个 Worker 进程。
-
当有 TCP 连接请求进来的时候,所有的 Worker 进程去争抢这一个连接,并最终会有一个 worker 进程成功直接和客户端建立 TCP 连接,之后这个连接的所有数据收发直接和这个 worker 通讯,不经过主进程的 Reactor 线程转发。
-
BASE 模式下没有 Master 进程的角色,只有 Manager 进程的角色。
-
每个 Worker 进程同时承担了 SWOOLE_PROCESS 模式下 Reactor 线程和 Worker 进程两部分职责。
-
BASE 模式下 Manager 进程是可选的,当设置了 worker_num=1,并且没有使用 Task 和 MaxRequest 特性时,底层将直接创建一个单独的 Worker 进程,不创建 Manager 进程
-
优点: BASE 模式没有 IPC 开销,性能更好。BASE 模式代码更简单,不容易出错
-
缺点:
- TCP 连接是在 Worker 进程中维持的,所以当某个 Worker 进程挂掉时,此 Worker 内的所有连接都将被关闭
- 少量 TCP 长连接无法利用到所有 Worker 进程
- CP 连接与 Worker 是绑定的,长连接应用中某些连接的数据量大,这些连接所在的 Worker 进程负载会非常高。但某些连接数据量小,所以在 Worker 进程的负载会非常低,不同的 Worker 进程无法实现均衡。
- 如果回调函数中有阻塞操作会导致 Server 退化为同步模式,此时容易导致 TCP 的 backlog 队列塞满问题。
-
BASE 模式的适用客户端连接之间不需要交互,可以使用 BASE 模式。如 Memcache、HTTP 服务器等。
-
在 BASE 模式下,Server 方法除了 send 和 close 以外,其他的方法都不支持跨进程执行。
-
以下非官方总结 :
-
采用多进程模型, 这种模型与nginx一致, 每个进程只有一个线程, 主进程负责管理工作进程, 工作进程负责监听端口, 接受连接, 处理请求以及关闭连接
-
多个进程同时监听端口, 会有惊群问题, linux 3.9之前版本的内核, Swoole没有解决惊群问题
-
base模式下, reactor_number参数并没有实际作用
-
如果worker进程数设置为1, 则不会fork出worker进程, 主进程直接处理请求, 这种模式适合调试
-
php代码执行到$serv->start()时,主进程进入int swServer_start(swServer *serv)函数, 该函数负责启动server
-
在函数swServer_start中会调用swReactorProcess_start, 这个函数会fork出多个worker进程
-
主进程和worker进程各自进入自己的事件循环, 处理各类事件
-
-
方法归纳
- process 进程操作
- server 服务端开启
- client 客户端开启
- lock 锁操作 (atomic 原子锁)
- go 携程方法
- channel 通道实例 协程间的通讯
安装命令
sudo yum install -y yum-utils vim
sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum-config-manager --disable 'remi-php*'
sudo yum-config-manager --enable remi-php81
sudo yum install -y php php-fpm php-bcmath php-devel php-pear php-mysqlnd php-redis php-gd php-mbstring php-zip
PHP的几种运行模式cli、fpm、apache、zts
- PHP CLI (Command Line Interface) 是 PHP 的命令行接口,允许用户在命令行中运行 PHP 脚本。
- PHP-FPM (FastCGI Process Manager) 是一个 PHP FastCGI 进程管理器,用于处理来自 Web 服务器的 PHP 请求。
- Apache 是一个流行的开源 Web 服务器软件,可以与 PHP 一起使用来运行 Web 应用程序。
- ZTS (Zend Thread Safety) 是 PHP 的线程安全性模式,允许多线程同时运行 PHP 脚本。