导读:Server->set()函数用于设置Server运行时的各项参数。本节所有的子页面均为配置数组的元素。从v4.5.5版本起,底层会检测设置的配置项是否正确,如果设...
Server->set() 函数用于设置 Server 运行时的各项参数。本节所有的子页面均为配置数组的元素。
从 v4.5.5 版本起,底层会检测设置的配置项是否正确,如果设置了不是 Swoole 提供的配置项,则会产生一个 Warning。
PHP Warning: unsupported option [foo] in @swoole-src/library/core/Server/Helper.php
设置启动的 Reactor 线程数。【默认值:CPU 核数】
通过此参数来调节主进程内事件处理线程的数量,以充分利用多核。默认会启用 CPU 核数相同的数量。
Reactor 线程是可以利用多核,如:机器有 128 核,那么底层会启动 128 线程。
每个线程能都会维持一个 EventLoop。线程之间是无锁的,指令可以被 128 核 CPU 并行执行。
考虑到操作系统调度存在一定程度的性能损失,可以设置为 CPU 核数 * 2,以便最大化利用 CPU 的每一个核。
- 如果设置的 reactor_num 大于 worker_num,会自动调整使 reactor_num 等于 worker_num ;
- 在超过 8 核的机器上 reactor_num 默认设置为 8。
设置启动的 Worker 进程数。【默认值:CPU 核数】
如 1 个请求耗时 100ms,要提供 1000QPS 的处理能力,那必须配置 100 个进程或更多。
但开的进程越多,占用的内存就会大大增加,而且进程间切换的开销就会越来越大。所以这里适当即可。不要配置过大。
设置 worker 进程的最大任务数。【默认值:0 即不会退出进程】
一个 worker 进程在处理完超过此数值的任务后将自动退出,进程退出后会释放所有内存和资源
这个参数的主要作用是解决由于程序编码不规范导致的 PHP 进程内存泄露问题。PHP 应用程序有缓慢的内存泄漏,但无法定位到具体原因、无法解决,可以通过设置 max_request 临时解决,需要找到内存泄漏的代码并修复,而不是通过此方案,可以使用 Swoole Tracker 发现泄漏的代码。
服务器程序,最大允许的连接数。【默认值:ulimit -n】
如 max_connection => 10000, 此参数用来设置 Server 最大允许维持多少个 TCP 连接。超过此数量后,新进入的连接将被拒绝。
配置 Task 进程的数量。
配置此参数后将会启用 task 功能。所以 Server 务必要注册 onTask、onFinish 2 个事件回调函数。如果没有注册,服务器程序将无法启动。
设置 Task 进程与 Worker 进程之间通信的方式。【默认值:1】
请先阅读 Swoole 下的 IPC 通讯。
| 模式 | 作用 |
|---|
| 1 | 使用 Unix Socket 通信【默认模式】 |
| 2 | 使用 sysvmsg 消息队列通信 |
| 3 | 使用 sysvmsg 消息队列通信,并设置为争抢模式 |
设置 task 进程的最大任务数。【默认值:0】
设置 task 进程的最大任务数。一个 task 进程在处理完超过此数值的任务后将自动退出。这个参数是为了防止 PHP 进程内存溢出。如果不希望进程自动退出可以设置为 0。
设置 task 的数据临时目录。【默认值:Linux /tmp 目录】
在 Server 中,如果投递的数据超过 8180 字节,将启用临时文件来保存数据。这里的 task_tmpdir 就是用来设置临时文件保存的位置。
开启 Task 协程支持。【默认值:false】,v4.2.12 起支持
开启后自动在 onTask 回调中创建协程和协程容器,PHP 代码可以直接使用协程 API。
$server->on('Task', function ($serv, Swoole\Server\Task $task) {
//来自哪个 Worker 进程
$task->worker_id;
//任务的编号
$task->id;
//任务的类型,taskwait, task, taskCo, taskWaitMulti 可能使用不同的 flags
$task->flags;
//任务的数据
$task->data;
//投递时间,v4.6.0版本增加
$task->dispatch_time;
//协程 API
co::sleep(0.2);
//完成任务,结束并返回数据
$task->finish([123, 'hello']);
});
使用面向对象风格的 Task 回调格式。【默认值:false】
设置为 true 时,onTask 回调将变成对象模式。
<?php
$server = new Swoole\Server('127.0.0.1', 9501);
$server->set([
'worker_num' => 1,
'task_worker_num' => 3,
'task_use_object' => true,
// 'task_object' => true, // v4.6.0版本增加的别名
]);
$server->on('receive', function (Swoole\Server $server, $fd, $tid, $data) {
$server->task(['fd' => $fd,]);
});
$server->on('Task', function (Swoole\Server $server, Swoole\Server\Task $task) {
//此处$task是Swoole\Server\Task对象
$server->send($task->data['fd'], json_encode($server->stats()));
});
$server->start();
数据包分发策略。【默认值:2】
| 模式值 | 模式 | 作用 |
|---|
| 1 | 轮循模式 | 收到会轮循分配给每一个 Worker 进程 |
| 2 | 固定模式 | 根据连接的文件描述符分配 Worker。这样可以保证同一个连接发来的数据只会被同一个 Worker 处理 |
| 3 | 抢占模式 | 主进程会根据 Worker 的忙闲状态选择投递,只会投递给处于闲置状态的 Worker |
| 4 | IP 分配 | 根据客户端 IP 进行取模 hash,分配给一个固定的 Worker 进程。 可以保证同一个来源 IP 的连接数据总会被分配到同一个 Worker 进程。算法为 ip2long(ClientIP) % worker_num |
| 5 | UID 分配 | 需要用户代码中调用 Server->bind() 将一个连接绑定 1 个 uid。然后底层根据 UID 的值分配到不同的 Worker 进程。 算法为 UID % worker_num,如果需要使用字符串作为 UID,可以使用 crc32(UID_STRING) |
| 7 | stream 模式 | 空闲的 Worker 会 accept 连接,并接受 Reactor 的新请求 |
$server->set(array( 'dispatch_func' => 'my_dispatch_function', ));
提示
设置 dispatch_func 后底层会自动忽略 dispatch_mode 配置
dispatch_func 对应的函数不存在,底层将抛出致命错误
如果需要 dispatch 一个超过 8K 的包,dispatch_func 只能获取到 0-8180 字节的内容
编写 PHP 函数
由于 ZendVM 无法支持多线程环境,即使设置了多个 Reactor 线程,同一时间只能执行一个 dispatch_func。因此底层在执行此 PHP 函数时会进行加锁操作,可能会存在锁的争抢问题。请勿在 dispatch_func 中执行任何阻塞操作,否则会导致 Reactor 线程组停止工作。
$server->set(array(
'dispatch_func' => function ($server, $fd, $type, $data) {
var_dump($fd, $type, $data);
return intval($data[0]);
},
));
$fd 为客户端连接的唯一标识符,可使用 Server::getClientInfo 获取连接信息
$type 数据的类型,0 表示来自客户端的数据发送,4 表示客户端连接建立,3 表示客户端连接关闭
$data 数据内容,需要注意:如果启用了 HTTP、EOF、Length 等协议处理参数后,底层会进行包的拼接。但在 dispatch_func 函数中只能传入数据包的前 8K 内容,不能得到完整的包内容。
必须返回一个 0 - (server->worker_num - 1) 的数字,表示数据包投递的目标工作进程 ID
小于 0 或大于等于 server->worker_num 为异常目标 ID,dispatch 的数据将会被丢弃
编写 C++ 函数
在其他 PHP 扩展中,使用 swoole_add_function 注册长度函数到 Swoole 引擎中。
C++ 函数调用时底层不会加锁,需要调用方自行保证线程安全性
int dispatch_function(swServer *serv, swConnection *conn, swEventData *data);
int dispatch_function(swServer *serv, swConnection *conn, swEventData *data)
{
printf("cpp, type=%d, size=%d\n", data->info.type, data->info.len);
return data->info.len % serv->worker_num;
}
int register_dispatch_function(swModule *module)
{
swoole_add_function("my_dispatch_function", (void *) dispatch_function);
}
设置消息队列的 KEY。【默认值:ftok($php_script_file, 1)】
仅在 task_ipc_mode = 2/3 时使用。设置的 Key 仅作为 Task 任务队列的 KEY,参考 Swoole 下的 IPC 通讯。
task 队列在 server 结束后不会销毁,重新启动程序后, task 进程仍然会接着处理队列中的任务。如果不希望程序重新启动后执行旧的 Task 任务。可以手动删除此消息队列。
ipcs -q
ipcrm -Q [msgkey]
守护进程化【默认值:false】
设置 daemonize => true 时,程序将转入后台作为守护进程运行。长时间运行的服务器端程序必须启用此项。
如果不启用守护进程,当 ssh 终端退出后,程序将被终止运行。
提示
使用 systemd 或者 supervisord 管理 Swoole 服务时,请勿设置 daemonize => true。主要原因是 systemd 的机制与 init 不同。init 进程的 PID 为 1,程序使用 daemonize 后,会脱离终端,最终被 init 进程托管,与 init 关系变为父子进程关系。
但 systemd 是启动了一个单独的后台进程,自行 fork 管理其他服务进程,因此不需要 daemonize,反而使用了 daemonize => true 会使得 Swoole 程序与该管理进程失去父子进程关系。
启用守护进程后,标准输入和输出会被重定向到 log_file
如果未设置 log_file,将重定向到 /dev/null,所有打印屏幕的信息都会被丢弃
启用守护进程后,CWD(当前目录)环境变量的值会发生变更,相对路径的文件读写会出错。PHP 程序中必须使用绝对路径
systemd
设置 Listen 队列长度
如 backlog => 128,此参数将决定最多同时有多少个等待 accept 的连接。
指定 Swoole 错误日志文件
在 Swoole 运行期发生的异常信息会记录到这个文件中,默认会打印到屏幕。
开启守护进程模式后 (daemonize => true),标准输出将会被重定向到 log_file。在 PHP 代码中 echo/var_dump/print 等打印到屏幕的内容会写入到 log_file 文件。
设置 Server 错误日志打印的等级,范围是 0-6。低于 log_level 设置的日志信息不会抛出。【默认值:SWOOLE_LOG_INFO】
对应级别常量参考日志等级
设置 Server 日志精度,是否带微秒【默认值:false】
设置 Server 日志分割【默认值:SWOOLE_LOG_ROTATION_SINGLE】
| 常量 | 说明 | 版本信息 |
|---|
| SWOOLE_LOG_ROTATION_SINGLE | 不启用 | - |
| SWOOLE_LOG_ROTATION_MONTHLY | 每月 | v4.5.8 |
| SWOOLE_LOG_ROTATION_DAILY | 每日 | v4.5.2 |
| SWOOLE_LOG_ROTATION_HOURLY | 每小时 | v4.5.8 |
| SWOOLE_LOG_ROTATION_EVERY_MINUTE | 每分钟 | v4.5.8 |
设置 Server 日志时间格式,格式参考 strftime 的 format
$server->set([
'log_date_format' => '%Y-%m-%d %H:%M:%S',
]);
在 TCP 中有一个 Keep-Alive 的机制可以检测死连接,应用层如果对于死链接周期不敏感或者没有实现心跳机制,可以使用操作系统提供的 keepalive 机制来踢掉死链接。 在 Server->set() 配置中增加 open_tcp_keepalive => true 表示启用 TCP keepalive。 另外,有 3 个选项可以对 keepalive 的细节进行调整,参考 Swoole 官方视频教程。
$serv = new Swoole\Server("192.168.2.194", 6666, SWOOLE_PROCESS);
$serv->set(array(
'worker_num' => 1,
'open_tcp_keepalive' => true,
'tcp_keepidle' => 4, //4s没有数据传输就进行检测
'tcp_keepinterval' => 1, //1s探测一次
'tcp_keepcount' => 5, //探测的次数,超过5次后还没回包close此连接
));
$serv->on('connect', function ($serv, $fd) {
var_dump("Client:Connect $fd");
});
$serv->on('receive', function ($serv, $fd, $reactor_id, $data) {
var_dump($data);
});
$serv->on('close', function ($serv, $fd) {
var_dump("close fd $fd");
});
$serv->start();
启用心跳检测【默认值:false】
此选项表示每隔多久轮循一次,单位为秒。如 heartbeat_check_interval => 60,表示每 60 秒,遍历所有连接,如果该连接在 120 秒内(heartbeat_idle_time 未设置时默认为 interval 的两倍),没有向服务器发送任何数据,此连接将被强制关闭。若未配置,则不会启用心跳,该配置默认关闭,参考 Swoole 官方视频教程。
连接最大允许空闲的时间
需要与 heartbeat_check_interval 配合使用
array(
'heartbeat_idle_time' => 600, // 表示一个连接如果600秒内未向服务器发送任何数据,此连接将被强制关闭
'heartbeat_check_interval' => 60, // 表示每60秒遍历一次
);
打开 EOF 检测【默认值:false】,参考 TCP 数据包边界问题
此选项将检测客户端连接发来的数据,当数据包结尾是指定的字符串时才会投递给 Worker 进程。否则会一直拼接数据包,直到超过缓存区或者超时才会中止。当出错时底层会认为是恶意连接,丢弃数据并强制关闭连接。
常见的 Memcache/SMTP/POP 等协议都是以 \r\n 结束的,就可以使用此配置。开启后可以保证 Worker 进程一次性总是收到一个或者多个完整的数据包。
array(
'open_eof_check' => true, //打开EOF检测
'package_eof' => "\r\n", //设置EOF
)
启用 EOF 自动分包
当设置 open_eof_check 后,可能会产生多条数据合并在一个包内,open_eof_split 参数可以解决这个问题,参考 TCP 数据包边界问题。
设置此参数需要遍历整个数据包的内容,查找 EOF,因此会消耗大量 CPU 资源。假设每个数据包为 2M,每秒 10000 个请求,这可能会产生 20G 条 CPU 字符匹配指令。
array(
'open_eof_split' => true, //打开EOF_SPLIT检测
'package_eof' => "\r\n", //设置EOF
)
提示
open_eof_check 只检查接收数据的末尾是否为 EOF,因此它的性能最好,几乎没有消耗
open_eof_check 无法解决多个数据包合并的问题,比如同时发送两条带有 EOF 的数据,底层可能会一次全部返回
open_eof_split 会从左到右对数据进行逐字节对比,查找数据中的 EOF 进行分包,性能较差。但是每次只会返回一个数据包
启用 open_eof_split 参数后,底层会从数据包中间查找 EOF,并拆分数据包。onReceive 每次仅收到一个以 EOF 字串结尾的数据包。
启用 open_eof_split 参数后,无论参数 open_eof_check 是否设置,open_eof_split 都将生效。
与 open_eof_check 的差异
设置 EOF 字符串。 参考 TCP 数据包边界问题
需要与 open_eof_check 或者 open_eof_split 配合使用。
打开包长检测特性【默认值:false】,参考 TCP 数据包边界问题
包长检测提供了固定包头 + 包体这种格式协议的解析。启用后,可以保证 Worker 进程 onReceive 每次都会收到一个完整的数据包。
长度检测协议,只需要计算一次长度,数据处理仅进行指针偏移,性能非常高,推荐使用。
提示
package_length_type
包头中某个字段作为包长度的值,底层支持了 10 种长度类型。请参考 package_length_type
package_body_offset
从第几个字节开始计算长度,一般有 2 种情况:
package_length_offset
length 长度值在包头的第几个字节。
struct {
uint32_t type;
uint32_t uid;
uint32_t length;
uint32_t serid;
char body[0];
}length 的值包含了整个包(包头 + 包体),package_body_offset 为 0
包头长度为 N 字节,length 的值不包含包头,仅包含包体,package_body_offset 设置为 N
示例:
长度协议提供了 3 个选项来控制协议细节。
此配置仅对 STREAM 类型的 Socket 有效,如 TCP、Unix Socket Stream
$server->set(array(
'open_length_check' => true,
'package_max_length' => 81920,
'package_length_type' => 'N',
'package_length_offset' => 8,
'package_body_offset' => 16, )
);
长度值的类型,接受一个字符参数,与 PHP 的 pack 函数一致。
目前 Swoole 支持 10 种类型:
| 字符参数 | 作用 |
|---|
| c | 有符号、1 字节 |
| C | 无符号、1 字节 |
| s | 有符号、主机字节序、2 字节 |
| S | 无符号、主机字节序、2 字节 |
| n | 无符号、网络字节序、2 字节 |
| N | 无符号、网络字节序、4 字节 |
| l | 有符号、主机字节序、4 字节(小写 L) |
| L | 无符号、主机字节序、4 字节(大写 L) |
| v | 无符号、小端字节序、2 字节 |
| V | 无符号、小端字节序、4 字节 |
设置长度解析函数
支持 C++ 或 PHP 的 2 种类型的函数。长度函数必须返回一个整数。
| 返回数 | 作用 |
|---|
| 返回 0 | 长度数据不足,需要接收更多数据 |
| 返回 - 1 | 数据错误,底层会自动关闭连接 |
| 返回包长度值(包括包头和包体的总长度) | 底层会自动将包拼好后返回给回调函数 |
提示
实现原理是先读取一小部分数据,在这段数据内包含了一个长度值。然后将这个长度返回给底层。然后由底层完成剩余数据的接收并组合成一个包进行 dispatch。
由于 ZendVM 不支持运行在多线程环境,因此底层会自动使用 Mutex 互斥锁对 PHP 长度函数进行加锁,避免并发执行 PHP 函数。在 1.9.3 或更高版本可用。
请勿在长度解析函数中执行阻塞 IO 操作,可能导致所有 Reactor 线程发生阻塞
$server = new Swoole\Server("127.0.0.1", 9501);
$server->set(array(
'open_length_check' => true,
'dispatch_mode' => 1,
'package_length_func' => function ($data) {
if (strlen($data) < 8) {
return 0;
}
$length = intval(trim(substr($data, 0, 8)));
if ($length <= 0) {
return -1;
}
return $length + 8;
},
'package_max_length' => 2000000, //协议最大长度
));
$server->on('receive', function (Swoole\Server $server, $fd, $reactor_id, $data) {
var_dump($data);
echo "#{$server->worker_id}>> received length=" . strlen($data) . "\n";
});
$server->start();在其他 PHP 扩展中,使用 swoole_add_function 注册长度函数到 Swoole 引擎中。
C++ 长度函数调用时底层不会加锁,需要调用方自行保证线程安全性
#include <string>
#include <iostream>
#include "swoole.h"
using namespace std;
int test_get_length(swProtocol *protocol, swConnection *conn, char *data, uint32_t length);
void register_length_function(void)
{
swoole_add_function((char *) "test_get_length", (void *) test_get_length);
return SW_OK;
}
int test_get_length(swProtocol *protocol, swConnection *conn, char *data, uint32_t length)
{
printf("cpp, size=%d\n", length);
return 100;
}
设置最大数据包尺寸,单位为字节。【默认值:2M 即 2 * 1024 * 1024,最小值为 64K】
开启 open_length_check/open_eof_check/open_eof_split/open_http_protocol/open_http2_protocol/open_websocket_protocol/open_mqtt_protocol 等协议解析后,Swoole 底层会进行数据包拼接,这时在数据包未收取完整时,所有数据都是保存在内存中的。
所以需要设定 package_max_length,一个数据包最大允许占用的内存尺寸。如果同时有 1 万个 TCP 连接在发送数据,每个数据包 2M,那么最极限的情况下,就会占用 20G 的内存空间。
提示
open_length_check:当发现包长度超过 package_max_length,将直接丢弃此数据,并关闭连接,不会占用任何内存;
open_eof_check:因为无法事先得知数据包长度,所以收到的数据还是会保存到内存中,持续增长。当发现内存占用已超过 package_max_length 时,将直接丢弃此数据,并关闭连接;
open_http_protocol:GET 请求最大允许 8K,而且无法修改配置。POST 请求会检测 Content-Length,如果 Content-Length 超过 package_max_length,将直接丢弃此数据,发送 http 400 错误,并关闭连接;
注意
此参数不宜设置过大,否则会占用很大的内存
启用 HTTP 协议处理。【默认值:false】
启用 HTTP 协议处理,Swoole\Http\Server 会自动启用此选项。设置为 false 表示关闭 HTTP 协议处理。
启用 MQTT 协议处理。【默认值:false】
启用后会解析 MQTT 包头,worker 进程 onReceive 每次会返回一个完整的 MQTT 数据包。
$server->set(array(
'open_mqtt_protocol' => true
));
启用 Redis 协议处理。【默认值:false】
启用后会解析 Redis 协议,worker 进程 onReceive 每次会返回一个完整的 Redis 数据包。建议直接使用 Redis\Server
$server->set(array(
'open_redis_protocol' => true
));
启用 WebSocket 协议处理。【默认值:false】
启用 WebSocket 协议处理,Swoole\WebSocket\Server 会自动启用此选项。设置为 false 表示关闭 websocket 协议处理。
设置 open_websocket_protocol 选项为 true 后,会自动设置 open_http_protocol 协议也为 true。
启用 websocket 协议中关闭帧。【默认值:false】
(opcode 为 0x08 的帧)在 onMessage 回调中接收
开启后,可在 WebSocketServer 中的 onMessage 回调中接收到客户端或服务端发送的关闭帧,开发者可自行对其进行处理。
$server = new Swoole\WebSocket\Server("0.0.0.0", 9501);
$server->set(array("open_websocket_close_frame" => true));
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {});
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
if ($frame->opcode == 0x08) {
echo "Close frame received: Code {$frame->code} Reason {$frame->reason}\n";
} else {
echo "Message received: {$frame->data}\n";
}
});
$server->on('close', function ($server, $fd) {});
$server->start();
启用 open_tcp_nodelay。【默认值:false】
开启后 TCP 连接发送数据时会关闭 Nagle 合并算法,立即发往对端 TCP 连接。在某些场景下,如命令行终端,敲一个命令就需要立马发到服务器,可以提升响应速度,请自行 Google Nagle 算法。
启用 CPU 亲和性设置。 【默认 false】
在多核的硬件平台中,启用此特性会将 Swoole 的 reactor线程 /worker进程绑定到固定的一个核上。可以避免进程 / 线程的运行时在多个核之间互相切换,提高 CPU Cache 的命中率。
mask 是一个掩码数字,按 bit 计算每 bit 对应一个 CPU 核,如果某一位为 0 表示绑定此核,进程会被调度到此 CPU 上,为 0 表示进程不会被调度到此 CPU。示例中 pid 为 24666 的进程 mask = f 表示未绑定到 CPU,操作系统会将此进程调度到任意一个 CPU 核上。 pid 为 24901 的进程 mask = 8,8 转为二进制是 1000,表示此进程绑定在第 4 个 CPU 核上。
IO 密集型程序中,所有网络中断都是用 CPU0 来处理,如果网络 IO 很重,CPU0 负载过高会导致网络中断无法及时处理,那网络收发包的能力就会下降。
如果不设置此选项,swoole 将会使用全部 CPU 核,底层根据 reactor_id 或 worker_id 与 CPU 核数取模来设置 CPU 绑定。
如果内核与网卡有多队列特性,网络中断会分布到多核,可以缓解网络中断的压力
array('cpu_affinity_ignore' => array(0, 1)) // 接受一个数组作为参数,array(0, 1) 表示不使用CPU0,CPU1,专门空出来处理网络中断。[~]$ cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 1383283707 0 0 0 IO-APIC-edge timer
1: 3 0 0 0 IO-APIC-edge i8042
3: 11 0 0 0 IO-APIC-edge serial
8: 1 0 0 0 IO-APIC-edge rtc
9: 0 0 0 0 IO-APIC-level acpi
12: 4 0 0 0 IO-APIC-edge i8042
14: 25 0 0 0 IO-APIC-edge ide0
82: 85 0 0 0 IO-APIC-level uhci_hcd:usb5
90: 96 0 0 0 IO-APIC-level uhci_hcd:usb6
114: 1067499 0 0 0 PCI-MSI-X cciss0
130: 96508322 0 0 0 PCI-MSI eth0
138: 384295 0 0 0 PCI-MSI eth1
169: 0 0 0 0 IO-APIC-level ehci_hcd:usb1, uhci_hcd:usb2
177: 0 0 0 0 IO-APIC-level uhci_hcd:usb3
185: 0 0 0 0 IO-APIC-level uhci_hcd:usb4
NMI: 11370 6399 6845 6300
LOC: 1383174675 1383278112 1383174810 1383277705
ERR: 0
MIS: 0
eth0/eth1 就是网络中断的次数,如果 CPU0 - CPU3 是平均分布的,证明网卡有多队列特性。如果全部集中于某一个核,说明网络中断全部由此 CPU 进行处理,一旦此 CPU 超过 100%,系统将无法处理网络请求。这时就需要使用 cpu_affinity_ignore 设置将此 CPU 空出,专门用于处理网络中断。
如图上的情况,应当设置 cpu_affinity_ignore => array(0)
可以使用 top 指令 -> 输入 1,查看到每个核的使用率
启用 tcp_defer_accept 特性【默认值:false】
可以设置为一个数值,表示当一个 TCP 连接有数据发送时才触发 accept。
$server->set(array(
'tcp_defer_accept' => 5
));
提示
客户端连接到服务器后不会立即触发 accept
在 5 秒内客户端发送数据,此时会同时顺序触发 accept/onConnect/onReceive
在 5 秒内客户端没有发送任何数据,此时会触发 accept/onConnect
启用 tcp_defer_accept 特性后,accept 和 onConnect 对应的时间会发生变化。如果设置为 5 秒:
设置 SSL 隧道加密。
设置值为一个文件名字符串,指定 cert 证书和 key 私钥的路径。
openssl x509 -in cert.crt -inform der -outform pem -out cert.pem
注意
-HTTPS 应用浏览器必须信任证书才能浏览网页;
-wss 应用中,发起 WebSocket 连接的页面必须使用 HTTPS ;
- 浏览器不信任 SSL 证书将无法使用 wss ;
- 文件必须为 PEM 格式,不支持 DER 格式,可使用 openssl 工具进行转换。
使用 SSL 必须在编译 Swoole 时加入 --enable-openssl 选项
$server = new Swoole\Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$server->set(array(
'ssl_cert_file' => __DIR__.'/config/ssl.crt',
'ssl_key_file' => __DIR__.'/config/ssl.key',
));
此参数已在 v4.5.4 版本移除,请使用 ssl_protocols
设置 OpenSSL 隧道加密的算法。【默认值:SWOOLE_SSLv23_METHOD】,支持的类型请参考 SSL 加密方法
Server 与 Client 使用的算法必须一致,否则 SSL/TLS 握手会失败,连接会被切断
$server->set(array(
'ssl_method' => SWOOLE_SSLv3_CLIENT_METHOD,
));
设置 OpenSSL 隧道加密的协议。【默认值:0,支持全部协议】,支持的类型请参考 SSL 协议
Swoole 版本 >= v4.5.4 可用
$server->set(array(
'ssl_protocols' => 0,
));
设置 SNI (Server Name Identification) 证书
Swoole 版本 >= v4.6.0 可用
$server->set([
'ssl_cert_file' => __DIR__ . '/server.crt',
'ssl_key_file' => __DIR__ . '/server.key',
'ssl_protocols' => SWOOLE_SSL_TLSv1_2 | SWOOLE_SSL_TLSv1_3 | SWOOLE_SSL_TLSv1_1 | SWOOLE_SSL_SSLv2,
'ssl_sni_certs' => [
'cs.php.net' => [
'ssl_cert_file' => __DIR__ . '/sni_server_cs_cert.pem',
'ssl_key_file' => __DIR__ . '/sni_server_cs_key.pem',
],
'uk.php.net' => [
'ssl_cert_file' => __DIR__ . '/sni_server_uk_cert.pem',
'ssl_key_file' => __DIR__ . '/sni_server_uk_key.pem',
],
'us.php.net' => [
'ssl_cert_file' => __DIR__ . '/sni_server_us_cert.pem',
'ssl_key_file' => __DIR__ . '/sni_server_us_key.pem',
],
]
]);
设置 openssl 加密算法。【默认值:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH】
$server->set(array(
'ssl_ciphers' => 'ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP',
));
服务 SSL 设置验证对端证书。【默认值:false】
默认关闭,即不验证客户端证书。若开启,必须同时设置 ssl_client_cert_file 选项
允许自签名证书。【默认值:false】
根证书,用于验证客户端证书。
$server = new Swoole\Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$server->set(array(
'ssl_cert_file' => __DIR__ . '/config/ssl.crt',
'ssl_key_file' => __DIR__ . '/config/ssl.key',
'ssl_verify_peer' => true,
'ssl_allow_self_signed' => true,
'ssl_client_cert_file' => __DIR__ . '/config/ca.crt',
));TCP 服务若验证失败,会底层会主动关闭连接。
设置是否启用 SSL/TLS 压缩。 在 Co\Client 使用时,它有一个别名 ssl_disable_compression
如果证书链条层次太深,超过了本选项的设定值,则终止验证。
启用服务器端保护,防止 BEAST 攻击。
指定 DHE 密码器的 Diffie-Hellman 参数。
指定用在 ECDH 密钥交换中的 curve。
$server = new Swoole\Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$server->set([
'ssl_compress' => true,
'ssl_verify_depth' => 10,
'ssl_prefer_server_ciphers' => true,
'ssl_dhparam' => '',
'ssl_ecdh_curve' => '',
]);
设置 Worker/TaskWorker 子进程的所属用户。【默认值:执行脚本用户】
服务器如果需要监听 1024 以下的端口,必须有 root 权限。但程序运行在 root 用户下,代码中一旦有漏洞,攻击者就可以以 root 的方式执行远程指令,风险很大。配置了 user 项之后,可以让主进程运行在 root 权限下,子进程运行在普通用户权限下。
$server->set(array(
'user' => 'Apache'
));
设置 Worker/TaskWorker 子进程的进程用户组。【默认值:执行脚本用户组】
与 user 配置相同,此配置是修改进程所属用户组,提升服务器程序的安全性。
$server->set(array(
'group' => 'www-data'
));
重定向 Worker 进程的文件系统根目录。
此设置可以使进程对文件系统的读写与实际的操作系统文件系统隔离。提升安全性。
$server->set(array(
'chroot' => '/data/server/'
));
设置 pid 文件地址。
在 Server 启动时自动将 master 进程的 PID 写入到文件,在 Server 关闭时自动删除 PID 文件。
$server->set(array(
'pid_file' => __DIR__.'/server.pid',
));
配置接收输入缓存区内存尺寸。【默认值:2M】
$server->set([
'buffer_input_size' => 2 * 1024 * 1024,
]);
配置发送输出缓存区内存尺寸。【默认值:2M】
$server->set([
'buffer_output_size' => 32 * 1024 * 1024, //必须为数字
]);
配置客户端连接的缓存区长度。【默认值:2M】
不同于 buffer_output_size,buffer_output_size 是 worker 进程单次 send 的大小限制,socket_buffer_size 是用于设置 Worker 和 Master 进程间通讯 buffer 总的大小,参考 SWOOLE_PROCESS 模式。
$server->set([
'socket_buffer_size' => 128 * 1024 *1024, //必须为数字,单位为字节,如128 * 1024 *1024表示每个TCP客户端连接最大允许有128M待发送的数据
]);
发送缓冲区塞满导致 send 失败,只会影响当前的客户端,其他客户端不受影响 服务器有大量 TCP 连接时,最差的情况下将会占用 serv->max_connection * socket_buffer_size 字节的内存
尤其是外往通信的服务器程序,网络通信较慢,如果持续连续发送数据,缓冲区很快就会塞满。发送的数据会全部堆积在 Server 的内存里。因此此类应用应当从设计上考虑到网络的传输能力,先将消息存入磁盘,等客户端通知服务器已接受完毕后,再发送新的数据。
如视频直播服务,A 用户带宽是 100M,1 秒内发送 10M 的数据是完全可以的。B 用户带宽只有 1M,如果 1 秒内发送 10M 的数据,B 用户可能需要 100 秒才能接收完毕。这时数据会全部堆积在服务器内存中。
可以根据数据内容的类型,进行不同的处理。如果是可丢弃的内容,如视频直播等业务,网络差的情况下丢弃一些数据帧完全可以接受。如果内容是不可丢失的,如微信消息,可以先存储到服务器的磁盘中,按照 100 条消息为一组。当用户接受完这一组消息后,再从磁盘中取出下一组消息发送到客户端。
数据发送缓存区
Master 进程向客户端发送大量数据时,并不能立即发出。这时发送的数据会存放在服务器端的内存缓存区内。此参数可以调整内存缓存区的大小。
如果发送数据过多,数据占满缓存区后 Server 会报如下错误信息:
swFactoryProcess_finish: send failed, session#1 output buffer has been overflowed.
启用 onConnect/onClose 事件。【默认值:false】
Swoole 在配置 dispatch_mode=1 或 3 后,因为系统无法保证 onConnect/onReceive/onClose 的顺序,默认关闭了 onConnect/onClose 事件;
如果应用程序需要 onConnect/onClose 事件,并且能接受顺序问题可能带来的安全风险,可以通过设置 enable_unsafe_event 为 true,启用 onConnect/onClose 事件。
丢弃已关闭链接的数据请求。【默认值:true】
Swoole 在配置 dispatch_mode=1 或 3 后,系统无法保证 onConnect/onReceive/onClose 的顺序,因此可能会有一些请求数据在连接关闭后,才能到达 Worker 进程。
设置端口重用。【默认值:false】
启用端口重用后,可以重复启动监听同一个端口的 Server 程序
仅在 Linux-3.9.0 以上版本的内核可用 Swoole4.5 以上版本可用
设置 accept 客户端连接后将不会自动加入 EventLoop。【默认值:false】
设置此选项为 true 后,accept 客户端连接后将不会自动加入 EventLoop,仅触发 onConnect 回调。worker 进程可以调用 $server->confirm($fd) 对连接进行确认,此时才会将 fd 加入 EventLoop 开始进行数据收发,也可以调用 $server->close($fd) 关闭此连接。
//开启enable_delay_receive选项
$server->set(array(
'enable_delay_receive' => true,
));
$server->on("Connect", function ($server, $fd, $reactorId) {
$server->after(2000, function() use ($server, $fd) {
//确认连接,开始接收数据
$server->confirm($fd);
});
});
设置异步重启开关。【默认值:true】
设置异步重启开关。设置为 true 时,将启用异步安全重启特性,Worker 进程会等待异步事件完成后再退出。详细信息请参见 如何正确的重启服务
reload_async 开启的主要目的是为了保证服务重载时,协程或异步任务能正常结束。
$server->set([
'reload_async' => true
]);
设置 Worker 进程收到停止服务通知后最大等待时间【默认值:3】
经常会碰到由于 worker 阻塞卡顿导致 worker 无法正常 reload, 无法满足一些生产场景,例如发布代码热更新需要 reload 进程。所以,Swoole 加入了进程重启超时时间的选项。详细信息请参见 如何正确的重启服务
提示
底层会增加一个 (max_wait_time) 秒的定时器,触发定时器后,检查进程是否依然存在,如果是,会强制杀掉,重新拉一个进程。
需要在 onWorkerStop 回调里面做收尾工作,需要在 max_wait_time 秒内做完收尾。
依次向目标进程发送 SIGTERM 信号,杀掉进程。
管理进程收到重启、关闭信号后或者达到 max_request 时,管理进程会重起该 worker 进程。分以下几个步骤:
注意
v4.4.x 以前默认为 30 秒
开启 TCP 快速握手特性。【默认值:false】
此项特性,可以提升 TCP 短连接的响应速度,在客户端完成握手的第三步,发送 SYN 包时携带数据。
$server->set([
'tcp_fastopen' => true
]);
开启请求慢日志。 从 v4.4.8 版本开始已移除
由于这个慢日志的方案只能在同步阻塞的进程里面生效,不能在协程环境用,而 Swoole4 默认就是开启协程的,除非关闭 enable_coroutine,所以不要使用了,使用 Swoole Tracker 的阻塞检测工具。
启用后 Manager 进程会设置一个时钟信号,定时侦测所有 Task 和 Worker 进程,一旦进程阻塞导致请求超过规定的时间,将自动打印进程的 PHP 函数调用栈。
底层基于 ptrace 系统调用实现,某些系统可能关闭了 ptrace,无法跟踪慢请求。请确认 kernel.yama.ptrace_scope 内核参数是否 0。
$server->set([
'request_slowlog_file' => '/tmp/trace.log',
]);
$server->set([
'request_slowlog_timeout' => 2, // 设置请求超时时间为2秒
'request_slowlog_file' => '/tmp/trace.log',
]);
必须是具有可写权限的文件,否则创建文件失败底层会抛出致命错误
是否启用异步风格服务器的协程支持
enable_coroutine 关闭时在事件回调函数中不再自动创建协程,如果不需要用协程关闭这个会提高一些性能。参考什么是 Swoole 协程。
配置方法
enable_coroutine 选项影响范围
onWorkerStart
onConnect
onOpen
onReceive
setHandler
onPacket
onRequest
onMessage
onPipeMessage
onFinish
onClose
tick/after 定时器
开启 enable_coroutine 后在上述回调函数会自动创建协程
$server = new Swoole\Http\Server("127.0.0.1", 9501);
$server->set([
//关闭内置协程
'enable_coroutine' => false,
]);
$server->on("request", function ($request, $response) {
if ($request->server['request_uri'] == '/coro') {
go(function () use ($response) {
co::sleep(0.2);
$response->header("Content-Type", "text/plain");
$response->end("Hello World\n");
});
} else {
$response->header("Content-Type", "text/plain");
$response->end("Hello World\n");
}
});
$server->start();
设置当前工作进程最大协程数量。【默认值:100000,Swoole 版本小于 v4.4.0-beta 时默认值为 3000】
超过 max_coroutine 底层将无法创建新的协程,服务端的 Swoole 会抛出 exceed max number of coroutine 错误,TCP Server 会直接关闭连接,Http Server 会返回 Http 的 503 状态码。
在 Server 程序中实际最大可创建协程数量等于 worker_num * max_coroutine,task 进程和 UserProcess 进程的协程数量单独计算。
$server->set(array(
'max_coroutine' => 3000,
));
当发送数据时缓冲区内存不足时,直接在当前协程内 yield,等待数据发送完成,缓存区清空时,自动 resume 当前协程,继续 send 数据。【默认值:在 dispatch_mod 2/4 时候可用,并默认开启】
Server/Client->send 返回 false 并且错误码为 SW_ERROR_OUTPUT_BUFFER_OVERFLOW 时,不返回 false 到 PHP 层,而是 yield 挂起当前协程
Server/Client 监听缓冲区是否清空的事件,在该事件触发后,缓存区内的数据已被发送完毕,这时 resume 对应的协程
协程恢复后,继续调用 Server/Client->send 向缓存区内写入数据,这时因为缓存区已空,发送必然是成功的
改进前
for ($i = 0; $i < 100; $i++) {
//在缓存区塞满时会直接返回 false,并报错 output buffer overflow
$server->send($fd, $data_2m);
}改进后
for ($i = 0; $i < 100; $i++) {
//在缓存区塞满时会 yield 当前协程,发送完成后 resume 继续向下执行
$server->send($fd, $data_2m);
}此项特性会改变底层的默认行为,可以手动关闭
$server->set([ 'send_yield' => false, ]);
设置发送超时,与 send_yield 配合使用,当在规定的时间内,数据未能发送到缓存区,底层返回 false,并设置错误码为 ETIMEDOUT,可以使用 getLastError() 方法获取错误码。类型为浮点型,单位为秒,最小粒度为毫秒
$server->set([
'send_yield' => true,
'send_timeout' => 1.5, // 1.5秒
]);
for ($i = 0; $i < 100; $i++) {
if ($server->send($fd, $data_2m) === false and $server->getLastError() == SOCKET_ETIMEDOUT) {
echo "发送超时\n";
}
}
设置一键协程化 Hook 的函数范围。【默认值:不 hook】
Swoole 版本为 v4.5+ 或 4.4LTS 可用,详情参考一键协程化
$server->set([
'hook_flags' => SWOOLE_HOOK_SLEEP,
]);
设置缓存区高水位线,单位为字节。
$server->set([
'buffer_high_watermark' => 8 * 1024 * 1024,
]);
设置缓存区低水位线,单位为字节。
$server->set([
'buffer_low_watermark' => 1 * 1024 * 1024,
]);
TCP_USER_TIMEOUT 选项是 TCP 层的 socket 选项,值为数据包被发送后未接收到 ACK 确认的最大时长,以毫秒为单位。具体请查看 man 文档
$server->set([
'tcp_user_timeout' => 10 * 1000, // 10秒
]);
Swoole 版本 >= v4.5.3-alpha 可用
指定 stats() 内容写入的文件路径。设置后会自动在 onWorkerStart 时设置一个定时器,定时将 stats() 的内容写入指定文件中
$server->set([
'stats_file' => __DIR__ . '/stats.log',
]);
Swoole 版本 >= v4.5.5 可用
设置此选项后,事件回调将使用对象风格。【默认值:false】
$server->set([
'event_object' => true,
]);
Swoole 版本 >= v4.6.0 可用
设置起始 session ID
$server->set([
'start_session_id' => 10,
]);
Swoole 版本 >= v4.6.0 可用
设置为单一线程。 启用后 Reactor 线程将会和 Master 进程中的 Master 线程合并,由 Master 线程处理逻辑。
$server->set([
'single_thread' => true,
]);
Swoole 版本 >= v4.2.13 可用
设置接收缓冲区的最大队列长度。 如果超出,则停止接收。
$server->set([
'max_queued_bytes' => 1024 * 1024,
]);
Swoole 版本 >= v4.5.0 可用
设置 admin_server 服务,用于在 Swoole Dashboard 中查看服务信息等。
$server->set([
'admin_server' => '0.0.0.0:9502',
]);
Swoole 版本 >= v4.8.0 可用
本文地址:https://www.jinpeng.work/?id=69
若非特殊说明,文章均属本站原创,转载请注明原链接。