确保 Rails 和 Sidekiq 不超过最大数据库连接数

Ensure max database connections is not exceeded with Rails and Sidekiq

我在单线程 Rails 应用程序中使用 Nginx 和 Phusion Passenger。这是问题所在。在该应用程序中,我使用多线程 sidekiq 来执行一些后台作业。通常在我的 database.yml 中,我只需要将池值设置为 1。这是一个示例:

default: &default
  adapter: mysql2
  encoding: utf8
  collation: utf8_unicode_ci
  pool: 1
  username: username
  password: password
  host: localhost

原因是因为对于每个打开的tcp套接字连接,当一个http请求通过那个套接字进来时,nginx会接受请求并将信息传递给passenger。 Passenger 检测到它的 Rails 应用程序,它会生成一个 Rails 实例,该实例将响应转换为 html,然后发送回 nginx,然后再传回客户端(浏览器) ) 因此对于每个乘客实例,我只需要一个数据库连接,一个单线程 Rails 应用程序。

但是在我的 sidekiq.yml 中,我将并发设置为 5:

:concurrency: 5 

这意味着对于每个乘客架实例,我将有 5 个由 sidekiq 处理的并发线程加上一个用于主应用程序的连接,即一个乘客实例总共有 6 个数据库连接。

当我查看 passenger-status 时,我注意到 max_pool_size 设置为 6:

----------- General information -----------
Max pool size : 6

那么这是否意味着 passenger 永远不会同时产生超过 6 个 Rails 个实例?如果是这样,这是否意味着我的数学是正确的:6(实例)* 6(数据库连接:sidekiq 5 个,主应用程序 1 个)= 36(我的 [=43 可能的总数据库连接数) =] 应用程序并发处理)。

现在我的 mysql 数据库配置为处理 151 个最大并发连接。

SHOW VARIABLES LIKE "max_connections";
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 151   |
+-----------------+-------+

我只是想确保我对 passenger、rails 和 sidekiq 的计算是正确的。

首先,你的 Sidekiq 进程和你的网络服务器(在你的例子中是 Passenger)是分开的。 Passenger 的线程池大小对你的 Sidekiq 并发没有影响;相反,您的 Sidekiq 配置指定了一个单独的并发。因此,我们将分别考虑两者:

乘客

ActiveRecord 数据库池值是您的网络进程将使用的数据库连接数,所有线程的总数。如果您的 Passenger 服务器设置为多进程模式,那么您的 Web 进程的最大连接数为 db pool size * passenger pool size。另一方面,如果您将其设置为多线程模式(如果可能,我建议您这样做),您的最大连接数仅为 db pool size(乘以 运行ning 中的许多进程;Puma ,例如,运行s 默认情况下两个进程最多有 15 个线程左右,因此在这种情况下最大连接数为 30)。

因此,如果您使用的是多线程模式,1 的池大小绝对不够——您至少需要一个与您期望的线程一样大的池。在多进程模式下,1 可能 工作,但我怀疑它是否真的值得偏离默认值 5,直到你遇到问题。

Sidekiq

Sidekiq 始终 运行 处于多线程模式(从技术上讲,您也可以 运行 多进程,但我假设您不是)。因此,像上面一样,您希望连接池至少与线程数一样大。这可能意味着您在技术上需要两个不同的数据库池值,具体取决于 Rails env 是否正在为 Passenger 或 Sidekiq 旋转 - 有关如何解决该问题的更多信息,请参见 this issue on the Sidekiq repo or this helpful Heroku guide .

总结

不要忘记,除了上述所有内容之外,您可能很容易拥有多台服务器,所有这些服务器都 运行 宁同一个 Rails 应用程序,但只有一个数据库具有一个连接限制。如果您 运行ning Passenger 处于最多 6 个进程的多实例模式,请将数据库池大小设置为 5,然后每个 Web 服务器节点将使用最多 30 个连接。如果它是 运行ning 一个 Sidekiq 服务器,则向其添加 5。您可能不需要超过一个 Sidekiq 服务器,因此 4 个 Web 节点 @ 30 个连接 + 一个 Sidekiq 进程 @ 5 个连接 = 125 个最大连接,完全在您的 MySQL 连接限制内。

我再次查看了 Passenger 文档,虽然上面的答案回答了问题,但我想添加更多细节:

  • HTTP客户端通过TCP向Nginx发送请求

  • 加载到 Nginx 中的 Phusion Passenger 检查请求是否应由 Passenger 处理。如果是,请求将发送到 Passenger Core。

  • Passenger core,使用负载均衡规则,决定一个请求应该转发到哪个进程。

  • Passenger core 还负责应用程序生成:如果它确定拥有更多应用程序进程是必要的或有益的,那么它将在 user-configured 限制下实现这一点:核心将永远不会产生超过 user-configured 最大值的进程。

  • 客芯还有监控统计:passenger-memory-stats和passenger-status

  • 客运核心在崩溃时重启应用进程。

  • 如果您没有将 UstRouter 配置为将数据发送到监控 Web 服务 Union Station,UstRouter 将处于空闲状态并且不会消耗资源

  • Watchdog 监控 Passenger Core 和 UstRouter。如果它们中的任何一个崩溃,它们将由 Watchdog 重新启动。

    passenger-memory-stats 将验证上述三个进程以及生成的机架应用程序:

------ Passenger processes ------

PID    VMSize     Private   Name
---------------------------------
18355  419.1 MB   ?         Passenger watchdog
18358  1096.5 MB  ?         Passenger core
18363  427.2 MB   ?         Passenger ust-router
18700  818.9 MB   256.2 MB  Passenger RubyApp: myapp_rack_rails
24783  686.9 MB   180.2 MB  Passenger RubyApp: myapp_rack_rails

passenger-status表示max_pool_size为6个,即Passenger Core最多产生6个rack app:

----------- General information -----------
Max pool size : 6
App groups    : 2
Processes     : 3

如另一个答案所述,ActiveRecord 数据库池值是您的 Web 进程将使用的数据库连接数,所有线程的总数。

但是由于我使用的是免费的 Passenger 服务器,它是在 multi-process 模式下设置的,所以我的 Web 进程的最大连接数是数据库池大小 * 乘客池大小。因此,由于 Passenger 池大小为 6,如果我的数据库池大小为 1,则为 6 * 1 = 6。这将是 6 个最大数据库连接。

Sidekiq 总是 运行 处于 multi-threaded 模式。

如果有人想使用 sidekiq,他们必须配置他们想要 运行 的线程数或使用默认值 (25)。如果他们正在使用数据库(可能),那么为了不遇到连接超时错误,他们将需要在数据库池中至少拥有与 sidekiq 线程一样多的连接。目前,他们必须在两个不同的地方配置这两个值,ActiveRecord 的 database.yml 中的数据库池,以及通过命令行或 sidekiq yml 文件的 sidekiq 连接。这是一个问题,因为很难记住在修改一个值时需要同时修改两个值。