swoole概念梳理

231 阅读1分钟

协程

  • 协程可以简单理解为线程,只不过这个线程是用户态的,不需要操作系统参与,创建销毁和切换的成本非常低,和线程不同的是协程没法利用多核 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 消费,甚至可以在命令行手动添加消息到队列。

进程:

  • 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 脚本。