Workerman是一款纯PHP开发的开源高性能的异步PHP socket框架。支持TCP长连接,支持websocket、MQTT等诸多协议。今天我们来介绍一下Workerman中的reusePort属性,有需要的可以参考参考。
Workerman是一个高性能的PHP Socket服务器框架。可以用 Workerman 直接在 TCP 层编程,基本的编程套路是:
$w = new Workerman\Worker('tcp://0.0.0.0:80'); $w->count = 4; $w->onMessage = function(Workerman\COnnection\TcpConnection $connection, array $data) { $connection->send('Hello World'); }; Worker::runAll();
在使用的过程中,不知道你是否留意过 reusePort 这个参数,他默认被设置为 false。这个参数有什么用?什么情况下我们需要把他设置为 true,从而提高性能呢?
关于 reusePort 参数,Workerman官方的文档是这么解释的:
开启监听端口复用后允许多个无亲缘关系的进程监听相同的端口,并且由系统内核做负载均衡,决定将socket连接交给哪个进程处理,避免了惊群效应,可以提升多进程短连接应用的性能。
如果没有深入研究过 Linux 网络编程,很难理解这句话。在此简单解释一下:
服务端程序通常通过监听服务器上的某个端口号,来接收客户端的请求。在Linux中,服务器网卡 + 端口号被抽象成了一个 Socket 。
为了提升性能,一般的服务端程序在运行时都有多个进程(俗称 Worker)监听同一个 Socket,在没有客户端连接到来的时候,这些Worker是处于挂起状态的,不消耗CPU资源。
如果某一刻有一个客户端连接到来,Linux 内核就会同时唤醒这些 Worker,让他们竞争去处理这个连接,
结果只有一个 Worker 可以获得处理这个连接的机会,其他Worker在竞争失败后继续回到挂起状态。唤醒 Worker 的过程是要消耗CPU资源的,Worker 数量越多,消耗的 CPU 资源就越多,造成了资源的浪费。这就是常说的 惊群效应。
你也许会问:为什么不每次只唤醒一个Worker呢?很遗憾,Linux内核并没有这样的功能。
幸好,在 Linux 3.9 及以后的版本,加入 reuseport 特性。这个特性有什么用呢?
在有 reuseport 之前,一个端口号只能被一个 Socket 监听,有了 reuseport 之后,这个限制就被打破了:一个端口号可以被多个 Socket 同时监听。
前面说到,Linux 内核没法做到一次只唤醒一个 Worker,但是,内核可以做到将客户端连接均匀地发送到监听统一端口的一群 Socket 上。
如图所示,每个 Worker 都有自己的 Socket,都监听同一个端口。当有客户端连接到来时,内核转发连接到一个 Socket 上,而这个 Socket 只会唤醒自己隶属的那个 Worker。这样就很巧妙地解决了 惊群效应,提高了整体的性能。
由此,我们可以得出结论:如果你的 Linux 内核版本是 3.9 及以上的话,那么在使用 Workerman 时,可以将 reusePort 设置为 true 提升程序运行效率。
虽然你只要在 Workerman 中把 reusePort 设置为 true,就能享受到 Linux 的这个高级特性。但 Workerman 的源码中,并不只是开启一个内核参数那么简单。Workerman 为你隐藏了许多的设计细节,我们来研究下。
Worker
类是 Workerman 里最主要的类,其中有个 listen()
函数:
protected function listen() { ... if (!$this->_mainSocket) { ... $this->_mainSocket = stream_socket_server(...); ... } ... }
listen()
函数的作用就是在当前进程创建一个 Socket 并开始监听请求。
当 reusePort 为 false 时,主进程在创建 Worker 之前就调用了 listen()
函数:
protected function initWorkers() { .... if (!$worker->reusePort) { $worker->listen(); } .... }
随后主进程通过 pcntl_fork() 创建 Worker。pcntl_fork() 有个特性:创建出来的子进程(Worker)中的变量都是父进程复制而来的,包括父进程创建的 mainSocket。所以,当reusePort为∗∗false∗∗时,所有的Worker都复制父进程的mainSocket。所以,当reusePort为∗∗false∗∗时,所有的Worker都复制父进程的_mainSocket,也即共用一个 Socket。
而当 reusePort 为 true 时,情况就不同了。主进程在创建 Worker 前不会调用 listen()
,而是在创建完 Worker 后由每个 Worker 自行发起 listen()
调用:
protected static function forkOneWorkerForLinux($worker) { ... $pid = pcntl_fork(); if ($pid === 0) { if ($worker->reusePort) { $worker->listen(); } ... } ... }
这样的结果就是,每个子进程(Worker)都创建了自己的 Socket。
最后还有一点,如果想要内核开启 reuseport 功能,需要手动设置 Socket 的 context:
if ($this->reusePort) { $context = stream_context_create(); stream_context_set_option($context, 'socket', 'so_reuseport', 1); }
推荐学习:php视频教程
以上是Workerman中你不得不知道的属性reusePort的详细内容。更多信息请关注PHP中文网其他相关文章!