Nginx 绝大多数情况下都是以 master-worker 方式运行的,向 Nginx 发送一个请求,它会被随机分配给其中一个 worker 进程进行处理。如果我们想查看指定的 worker 进程上的数据,或者说向指定的 worker 进程发送请求,由于上述的运行机制,可能需要反复执行很多次才能刚好碰到请求被分配给我们需要查看的 worker 进程上。
在某些场景下,上述的问题可能导致不符合预期的结果,例如:在运行带有 nginx-http-flv-module 的 Nginx 时,如果录制功能被配置为手动:
record all manual;
那么向 Nginx 发送开始录制的请求,然后过段时间之后,再发送停止录制的请求,由于上述的随机分配请求的机制,接受开始录制的请求和停止录制的请求的 worker 进程很可能不是同一个 worker 进程,这会导致无法停止录制的问题。
针对这个问题,nginx-rtmp-module 的作者曾提供了一个补丁集合,需要修改 Nginx 本身的源代码:nginx-patches,其中的 per-worker-listener 就是用来解决这个问题的,其配置为:
listen port per_worker;
其思路是在创建 worker 进程之后,worker 进程创建 socket,然后按照 worker 进程的编号监听对应的端口,例如:假设第一个 worker 进程监听的是 80 端口,那么第二个 worker 进程监听的就是 81 端口,以此类推。
但是因为 Nginx 本身在持续迭代,这个补丁已经无法在比较新的 Nginx 上使用,而且这个补丁还存在一个问题,当热更新 Nginx 时,由于旧的 worker 进程可能还没有关闭监听的端口,新的 worker 进程试图在该端口上监听时,会产生与下面类似的错误:
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
其实 nginx-rtmp-module 在支持多进程时,使用的 unix domain socket 使用了与上述类似的方法,所以存在的问题也类似。而在前年,有网友(vacing)给 nginx-http-flv-module 提交了一个补丁,解决了 unix domain socket 的 Address already in use 问题。解决的思路很简单,将 worker 进程创建的 unix domain socket 后缀修改为 worker 进程的 pid,而不是 worker 进程的编号。
但是,这种解决方法不适用于 AF_INET/AF_INET6 地址家族,因为端口号的范围是 [0, 65536],而进程的 pid 不受这个范围约束。
前段时间在实现完 Enhanced-RTMP(ERTMP)功能之后(见:Enhanced-RTMP 技术实现 和 Enhanced-RTMP v2 技术实现,由于在 Windows 上更方便录屏,所以在 Windows 上演示),有时需要验证在多 worker 进程模式下是否能正确运行,也需要查看指定的 worker 进程的一些数据是否正确,但是又懒得改 Nginx 的源代码,就只开了两个 worker 进程,然后反复发送请求,试的次数多了请求总会发送到想查看数据的 worker 进程上。
但是遇到正如本文开始描述的场景时,有时需要在指定时刻停止录制,那么再反复发送请求就不行了。这几天看了下 nginx-1.28.0 的源代码,按照自己的思路添加了向指定的 worker 进程发送请求的功能,解决了 Address already in use 的问题,还顺带发现和修复了 nginx-http-flv-module 录制功能的一个 bug,见 [fix] fixed bad status of stop request for record.
配置关键字没有使用 nginx-rtmp-module 作者给的 per_worker,而是使用了新的关键字:
listen 80 mutexport;
即 mutual exclusive port 的缩写,不过原理基本相同。
由于要求每个 worker 进程都监听独立的端口,所以如果系统支持 SO_REUSEPORT,那么 listen 配置项的 mutexport 不能与 reuseport 共存(红色的错误打印是因为编译时添加了 ASAN):

另外,由于 unix domain socket 没有端口的概念,所以如果 listen 配置了监听 unix domain socket,mutexport 也不支持(红色的错误打印是因为编译时添加了 ASAN):

下面是演示截图,其中涉及到的 Nginx 配置为:
# ...
worker_processes 4;
# ...
http {
# ...
server {
listen 80 mutexport;
# ...
}
# ...
}
rtmp {
# ...
}
下面是查看 Nginx 的状态以及监听的端口信息:

从上面的截图中可以看出,master 进程打开了 80~83 端口。由于 netstat 不显示子进程打开的端口信息,用 lsof 查看打开指定端口的 Nginx 进程:

从上面的截图中可以看出,master 打开了 80~83 端口,每个 worker 进程按照自己的编号分别打开了 80,81,82,83 端口。
然后是以不同的端口访问不同的 worker 进程:




从上面的几个截图中可以看出,以不同的端口访问 Nginx 时,返回的是对应的 worker 进程的数据。注意地址栏里的端口与脚标里的进程 pid 和本地的 unix domain socket 后缀的关系。
最后是热更新,验证会不会出现 Address already in use 问题,首先是让旧的 master 进程拉起新的 master 进程和新的 worker 进程:

从上面的截图中可以看出,新的 master 的父进程是旧的 master,由于 netstat 不显示子进程打开的端口信息,使用 lsof 查看:

从上面的截图中可以看出,旧的 master 和新的 master 都打开了 80~83 端口,而它们的子进程按编号分别打开了 80,81,82,83 端口。
最后,向旧的 master 进程发送退出信号,其 worker 进程也退出,netstat 查看 Nginx 的端口信息,显示的是新的 master 进程打开的端口信息:

由于 netstat 不显示子进程打开的端口信息,使用 lsof:

656

被折叠的 条评论
为什么被折叠?



