Passenger、Sinatra、Nginx、RabbitMQ 和 SSE(Passenger 如何启动进程以及 Ruby 线程在图片中的位置)
Passenger, Sinatra, Nginx, RabbitMQ and SSE (how Passenger starts processes and where Ruby threads fit in the picture)
我当前的设置有问题,它没有按预期工作,并且阻止我进一步建立启用服务器发送事件 (SSE) 的网站。我的主要问题可以在下面以粗体显示,但归结为 "How can I launch an extra thread from a Sinatra web app in a Passenger setup ?".
我使用 Passenger 5.0.21 和 Sinatra 1.4.6。该应用程序是作为 class 标准 Sinatra 应用程序编写的,不是模块化的,但可以根据需要进行更改。
我已将指令 passenger_min_instances 3
放入 Nginx 配置中,以至少启动 3 个 Web 应用程序实例。我的 Sinatra 应用程序的 config.ru
文件中有两个 puts
,所以当线程启动时,我在 /var/log/nginx/passenger.log
中得到反馈,当线程通过其 RabbitMQ 队列接收消息时:
...
Thread.new {
puts " [* #{Thread.current.inspect}] Waiting for logs. To exit press CTRL+C"
begin
q.subscribe(:block => true) do |delivery_info, properties, body|
puts " [x #{Thread.current.inspect}] #{body}"
end
rescue Interrupt => _
ch.close
conn.close
end
}
run Sinatra::Application
我预计这段代码会 运行 n 次,n 是 Passenger 启动的进程数。好像不是这样。
此外,我的 app.rb
包含很多可以简化为的内容:
puts "(CLASS)... Inside thread #{Thread.current.inspect}"
configure do
puts "(CONFIGURE)... Inside thread #{Thread.current.inspect}"
end
get '/debug' do
puts "(DEBUG)... Inside thread #{Thread.current.inspect}"
end
当我重新启动 Nginx 并对 URL /debug
进行第一次 HTTP GET 访问时,进程被实例化并且其中一个进程为请求提供服务。我在 /var/log/nginx/passenger.log
中得到了什么?
(CLASS)... Inside thread #<Thread:0x007fb29f4ca258 run>
(CONFIGURE)... Inside thread #<Thread:0x007fb29f4ca258 run>
[* #<Thread:0x007fb29f7f8038@config.ru:68 run>] Waiting for logs. To exit press CTRL+C
(DEBUG)... Inside thread #<Thread:0x007fb29f4ca8e8@/usr/lib/ruby/vendor_ruby/phusion_passenger
192.168.0.11 - test [30/Dec/2015:10:09:08 +0100] "GET /debug HTTP/1.1" 200 2184 0.0138
以 CLASS
和 CONFIGURE
开头的消息都打印在同一个线程中。我预计这会像它那样在进程实例化时发生,但它只发生了一次,让我认为 Passenger 只触发了一个进程。但是我可以看到 3 个进程 passenger-status --verbose
。创建了另一个线程(在 config.ru
中)以接收 RabbitMQ 消息。
如您所见,第一个进程已处理 1 个请求(为清楚起见已缩短):
$ passenger-status --verbose
----------- General information -----------
Max pool size : 6
App groups : 1
Processes : 3
Requests in top-level queue : 0
----------- Application groups -----------
/home/hydro/web2/public:
App root: /home/hydro/web2
Requests in queue: 0
* PID: 1116 Sessions: 0 Processed: 1 Uptime: 2m 19s
CPU: 0% Memory : 18M Last used: 2m 19s ago
* PID: 1123 Sessions: 0 Processed: 0 Uptime: 2m 19s
CPU: 0% Memory : 3M Last used: 2m 19s ago
* PID: 1130 Sessions: 0 Processed: 0 Uptime: 2m 19s
CPU: 0% Memory : 2M Last used: 2m 19s ago
发布 RabbitMQ 消息供订阅者接收的 ruby 测试程序有时有效,有时无效。也许 Passenger 关闭了 运行ning 进程,即使它在给定时间内没有看到请求。日志中没有任何内容。没有来自订阅者线程的反馈,也没有来自 Passenger 本身的消息。
如果我刷新页面,我会收到 DEBUG
消息和 GET /debug
跟踪。 passenger-status --verbose
显示第一个进程现在已经处理了两个请求。
我在不同的测试中看到,我必须发出大量请求才能让 Passenger 为其他 2 个进程提供服务请求,甚至启动新进程,最多 6 个。让我们从另一台机器上做与
相同的局域网
root@backup:~# ab -A test:test -kc 1000 -n 10000 https://192.168.0.10:445/debug
。 Passenger 已经启动了最多 6 个进程来处理请求 但是 除了 DEBUG
消息和 GET /debug
我在 passenger.log
文件中看不到任何内容] 跟踪好像没有其他进程已启动。
$ passenger-status --verbose
----------- General information -----------
Max pool size : 6
App groups : 1
Processes : 6
Requests in top-level queue : 0
----------- Application groups -----------
/home/hydro/web2/public:
App root: /home/hydro/web2
Requests in queue: 0
* PID: 1116 Sessions: 0 Processed: 664 Uptime: 16m 29s
CPU: 0% Memory : 28M Last used: 32s ago
* PID: 1123 Sessions: 0 Processed: 625 Uptime: 16m 29s
CPU: 0% Memory : 27M Last used: 32s ago
* PID: 1130 Sessions: 0 Processed: 614 Uptime: 16m 29s
CPU: 0% Memory : 27M Last used: 32s ago
* PID: 2105 Sessions: 0 Processed: 106 Uptime: 33s
CPU: 0% Memory : 23M Last used: 32s ago
* PID: 2112 Sessions: 0 Processed: 103 Uptime: 33s
CPU: 0% Memory : 22M Last used: 32s ago
* PID: 2119 Sessions: 0 Processed: 92 Uptime: 33s
CPU: 0% Memory : 21M Last used: 32s ago
所以主要问题是:每次启动进程时如何从 Sinatra Web 应用程序进程启动(RabbitMQ 订阅者)线程?
我希望能够将数据发送到我的 Web 应用程序进程,以便它们可以使用 SSE 将数据发送回 Web 客户端。我希望每个 Web 应用程序进程有两个线程:Sinatra 使用的主线程和我的额外线程来做一些 RabbitMQ 的事情。还有一个 Oracle 数据库和一个 Erlang 后端,但我认为它们在这里不相关。
我还想知道 Passenger 如何处理 Sinatra 网络应用程序的进程实例化。多个 Ruby 环境?如果启动了多个进程, class 怎么可能只被实例化一次?即使启动多个进程,文件 config.ru
(甚至 app.rb
)是否只处理一次?我在网上看了很多东西,但还是想不通。
更一般地说,使用 Ruby、Nginx、Passenger 和 Sinatra 进行 SSE 的正确方法是什么。
为清楚起见,下面列出了有关 Nginx 的详细信息。
Nginx 被配置为站在 Passenger 前面的反向代理,Web 应用程序在 server
和 location /
下配置为具有 SSL 和 HTTP 基本身份验证以及以下指令:
location / {
proxy_buffering off;
proxy_cache off;
proxy_pass_request_headers on;
passenger_set_header Host $http_host;
passenger_set_header X-Real-IP $remote_addr;
passenger_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
passenger_set_header X-Forwarded-Proto $scheme;
passenger_set_header X-Remote-User $remote_user;
passenger_set_header Host $http_host;
passenger_min_instances 3;
proxy_redirect off;
passenger_enabled on;
passenger_ruby /home/hydro/.rbenv/versions/2.3.0/bin/ruby;
passenger_load_shell_envvars on;
passenger_nodejs /usr/bin/nodejs;
passenger_friendly_error_pages on;
}
我认为您当前的架构是错误的。你的 Sinatra 应用程序不应该与额外的线程混合,或者只是保持活动状态以便它可以向你的客户端发送推送 - 你应该有一个单独的推送服务器专门用于推送消息,并让你的 HTTP API 做它做得最好 - 休眠直到收到请求。
你提到你正在使用 nginx,所以我真的建议在这个模块中编译:
https://github.com/wandenberg/nginx-push-stream-module
现在您可以摆脱您的 RabbitMQ 队列 - 任何需要将消息推送到您的推送订阅者之一的进程只需要向该模块的 RESTful API:
卷曲请求示例:
curl -s -v -X POST 'http://localhost/pub?id=my_channel_1' -d 'Hello World!'
当然,出于安全原因,此模块默认只会侦听来自本地主机的请求。
我当前的设置有问题,它没有按预期工作,并且阻止我进一步建立启用服务器发送事件 (SSE) 的网站。我的主要问题可以在下面以粗体显示,但归结为 "How can I launch an extra thread from a Sinatra web app in a Passenger setup ?".
我使用 Passenger 5.0.21 和 Sinatra 1.4.6。该应用程序是作为 class 标准 Sinatra 应用程序编写的,不是模块化的,但可以根据需要进行更改。
我已将指令 passenger_min_instances 3
放入 Nginx 配置中,以至少启动 3 个 Web 应用程序实例。我的 Sinatra 应用程序的 config.ru
文件中有两个 puts
,所以当线程启动时,我在 /var/log/nginx/passenger.log
中得到反馈,当线程通过其 RabbitMQ 队列接收消息时:
...
Thread.new {
puts " [* #{Thread.current.inspect}] Waiting for logs. To exit press CTRL+C"
begin
q.subscribe(:block => true) do |delivery_info, properties, body|
puts " [x #{Thread.current.inspect}] #{body}"
end
rescue Interrupt => _
ch.close
conn.close
end
}
run Sinatra::Application
我预计这段代码会 运行 n 次,n 是 Passenger 启动的进程数。好像不是这样。
此外,我的 app.rb
包含很多可以简化为的内容:
puts "(CLASS)... Inside thread #{Thread.current.inspect}"
configure do
puts "(CONFIGURE)... Inside thread #{Thread.current.inspect}"
end
get '/debug' do
puts "(DEBUG)... Inside thread #{Thread.current.inspect}"
end
当我重新启动 Nginx 并对 URL /debug
进行第一次 HTTP GET 访问时,进程被实例化并且其中一个进程为请求提供服务。我在 /var/log/nginx/passenger.log
中得到了什么?
(CLASS)... Inside thread #<Thread:0x007fb29f4ca258 run>
(CONFIGURE)... Inside thread #<Thread:0x007fb29f4ca258 run>
[* #<Thread:0x007fb29f7f8038@config.ru:68 run>] Waiting for logs. To exit press CTRL+C
(DEBUG)... Inside thread #<Thread:0x007fb29f4ca8e8@/usr/lib/ruby/vendor_ruby/phusion_passenger
192.168.0.11 - test [30/Dec/2015:10:09:08 +0100] "GET /debug HTTP/1.1" 200 2184 0.0138
以 CLASS
和 CONFIGURE
开头的消息都打印在同一个线程中。我预计这会像它那样在进程实例化时发生,但它只发生了一次,让我认为 Passenger 只触发了一个进程。但是我可以看到 3 个进程 passenger-status --verbose
。创建了另一个线程(在 config.ru
中)以接收 RabbitMQ 消息。
如您所见,第一个进程已处理 1 个请求(为清楚起见已缩短):
$ passenger-status --verbose
----------- General information -----------
Max pool size : 6
App groups : 1
Processes : 3
Requests in top-level queue : 0
----------- Application groups -----------
/home/hydro/web2/public:
App root: /home/hydro/web2
Requests in queue: 0
* PID: 1116 Sessions: 0 Processed: 1 Uptime: 2m 19s
CPU: 0% Memory : 18M Last used: 2m 19s ago
* PID: 1123 Sessions: 0 Processed: 0 Uptime: 2m 19s
CPU: 0% Memory : 3M Last used: 2m 19s ago
* PID: 1130 Sessions: 0 Processed: 0 Uptime: 2m 19s
CPU: 0% Memory : 2M Last used: 2m 19s ago
发布 RabbitMQ 消息供订阅者接收的 ruby 测试程序有时有效,有时无效。也许 Passenger 关闭了 运行ning 进程,即使它在给定时间内没有看到请求。日志中没有任何内容。没有来自订阅者线程的反馈,也没有来自 Passenger 本身的消息。
如果我刷新页面,我会收到 DEBUG
消息和 GET /debug
跟踪。 passenger-status --verbose
显示第一个进程现在已经处理了两个请求。
我在不同的测试中看到,我必须发出大量请求才能让 Passenger 为其他 2 个进程提供服务请求,甚至启动新进程,最多 6 个。让我们从另一台机器上做与
相同的局域网
root@backup:~# ab -A test:test -kc 1000 -n 10000 https://192.168.0.10:445/debug
。 Passenger 已经启动了最多 6 个进程来处理请求 但是 除了 DEBUG
消息和 GET /debug
我在 passenger.log
文件中看不到任何内容] 跟踪好像没有其他进程已启动。
$ passenger-status --verbose
----------- General information -----------
Max pool size : 6
App groups : 1
Processes : 6
Requests in top-level queue : 0
----------- Application groups -----------
/home/hydro/web2/public:
App root: /home/hydro/web2
Requests in queue: 0
* PID: 1116 Sessions: 0 Processed: 664 Uptime: 16m 29s
CPU: 0% Memory : 28M Last used: 32s ago
* PID: 1123 Sessions: 0 Processed: 625 Uptime: 16m 29s
CPU: 0% Memory : 27M Last used: 32s ago
* PID: 1130 Sessions: 0 Processed: 614 Uptime: 16m 29s
CPU: 0% Memory : 27M Last used: 32s ago
* PID: 2105 Sessions: 0 Processed: 106 Uptime: 33s
CPU: 0% Memory : 23M Last used: 32s ago
* PID: 2112 Sessions: 0 Processed: 103 Uptime: 33s
CPU: 0% Memory : 22M Last used: 32s ago
* PID: 2119 Sessions: 0 Processed: 92 Uptime: 33s
CPU: 0% Memory : 21M Last used: 32s ago
所以主要问题是:每次启动进程时如何从 Sinatra Web 应用程序进程启动(RabbitMQ 订阅者)线程?
我希望能够将数据发送到我的 Web 应用程序进程,以便它们可以使用 SSE 将数据发送回 Web 客户端。我希望每个 Web 应用程序进程有两个线程:Sinatra 使用的主线程和我的额外线程来做一些 RabbitMQ 的事情。还有一个 Oracle 数据库和一个 Erlang 后端,但我认为它们在这里不相关。
我还想知道 Passenger 如何处理 Sinatra 网络应用程序的进程实例化。多个 Ruby 环境?如果启动了多个进程, class 怎么可能只被实例化一次?即使启动多个进程,文件 config.ru
(甚至 app.rb
)是否只处理一次?我在网上看了很多东西,但还是想不通。
更一般地说,使用 Ruby、Nginx、Passenger 和 Sinatra 进行 SSE 的正确方法是什么。
为清楚起见,下面列出了有关 Nginx 的详细信息。
Nginx 被配置为站在 Passenger 前面的反向代理,Web 应用程序在 server
和 location /
下配置为具有 SSL 和 HTTP 基本身份验证以及以下指令:
location / {
proxy_buffering off;
proxy_cache off;
proxy_pass_request_headers on;
passenger_set_header Host $http_host;
passenger_set_header X-Real-IP $remote_addr;
passenger_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
passenger_set_header X-Forwarded-Proto $scheme;
passenger_set_header X-Remote-User $remote_user;
passenger_set_header Host $http_host;
passenger_min_instances 3;
proxy_redirect off;
passenger_enabled on;
passenger_ruby /home/hydro/.rbenv/versions/2.3.0/bin/ruby;
passenger_load_shell_envvars on;
passenger_nodejs /usr/bin/nodejs;
passenger_friendly_error_pages on;
}
我认为您当前的架构是错误的。你的 Sinatra 应用程序不应该与额外的线程混合,或者只是保持活动状态以便它可以向你的客户端发送推送 - 你应该有一个单独的推送服务器专门用于推送消息,并让你的 HTTP API 做它做得最好 - 休眠直到收到请求。
你提到你正在使用 nginx,所以我真的建议在这个模块中编译:
https://github.com/wandenberg/nginx-push-stream-module
现在您可以摆脱您的 RabbitMQ 队列 - 任何需要将消息推送到您的推送订阅者之一的进程只需要向该模块的 RESTful API:
卷曲请求示例:
curl -s -v -X POST 'http://localhost/pub?id=my_channel_1' -d 'Hello World!'
当然,出于安全原因,此模块默认只会侦听来自本地主机的请求。