【问题标题】:Passenger, Sinatra, Nginx, RabbitMQ and SSE (how Passenger starts processes and where Ruby threads fit in the picture)Passenger、Sinatra、Nginx、RabbitMQ 和 SSE(Passenger 如何启动进程以及 Ruby 线程在图中的位置)
【发布时间】:2023-03-10 17:28:01
【问题描述】:

我当前的设置存在问题,它无法按预期工作,并阻止我进一步开发启用服务器发送事件 (SSE) 的网站。我的主要问题可以在下面以粗体显示,但归结为“如何在乘客设置中从 Sinatra 网络应用程序启动额外线程?”。

我使用乘客 5.0.21 和 Sinatra 1.4.6。该应用程序是作为经典的 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

CLASSCONFIGURE 开头的两条消息都打印在同一个线程中。我希望这会像它一样在进程实例化时发生,但它只发生了一次,让我认为Passenger 只触发了一个进程。但是我可以看到passenger-status --verbose 的3 个进程。另一个线程被创建(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 也可能会关闭正在运行的进程。日志中不显示任何内容。订阅者线程没有反馈,乘客本身也没有消息。

如果我刷新页面,我会收到 DEBUG 消息和 GET /debug 跟踪。 passenger-status --verbose 表明第一个进程现在已经处理了两个请求。

我在不同的测试中看到,我必须触发大量请求才能让乘客为其他 2 个进程提供服务请求,甚至启动最多 6 个新进程。让我们从同一 LAN 中的另一台机器执行此操作与
root@backup:~# ab -A test:test -kc 1000 -n 10000 https://192.168.0.10:445/debug。乘客已启动最多 6 个进程来处理请求我在 passenger.log 文件中看不到任何内容,除了 DEBUG 消息和 GET /debug 跟踪,就好像没有其他进程一样开始了。

$ 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 后端,但我认为它们在这里不相关。

我还想知道在 Sinatra Web 应用程序的情况下,Passenger 如何处理进程实例化。多个 Ruby 环境 ?如果启动多个进程,怎么会看起来类只实例化一次?即使启动多个进程,文件config.ru(甚至app.rb)是否只处理一次?我在网上阅读了很多东西,但无法弄清楚。

更一般地说,使用 Ruby、Nginx、Passenger 和 Sinatra 进行 SSE 的正确方法是什么。

为了清楚起见,下面列出了有关 Nginx 的详细信息。

Nginx 被配置为站在Passenger 前面的反向代理,Web 应用程序在serverlocation / 下配置,具有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;
}

【问题讨论】:

    标签: nginx rabbitmq sinatra passenger server-sent-events


    【解决方案1】:

    我认为您当前的架构是错误的。您的 Sinatra 应用程序不应该与额外的线程混合,或者只是保持活动状态以便它可以向您的客户端发送推送 - 您应该有一个单独的推送服务器专门用于推送消息,并让您的 HTTP API 做它最擅长的事情 -休眠直到收到请求。

    你提到你使用的是 nginx,所以我真的建议在这个模块中编译:

    https://github.com/wandenberg/nginx-push-stream-module

    现在您可以摆脱 RabbitMQ 队列 - 任何需要将消息推送到您的推送订阅者之一的进程只需向该模块的 RESTful API 发送 HTTP 请求:

    curl 请求示例:

    curl -s -v -X POST 'http://localhost/pub?id=my_channel_1' -d 'Hello World!'
    

    当然,出于安全原因,这个模块默认只会监听来自本地主机的请求。

    【讨论】:

    • 我认为你是对的。我对架构的选择是错误的。我喜欢一个单独的推送服务器与 HTTP 通信的想法。它很容易与 Erlang 或 Oracle 数据库一起使用。
    • 我还找到了nchan module
    猜你喜欢
    • 2023-03-11
    • 2012-04-14
    • 1970-01-01
    • 2010-10-23
    • 2013-06-13
    • 2015-08-10
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    相关资源
    最近更新 更多