如何确定是什么原因导致我的带有 nginx 的节点服务器在高请求率时崩溃?

如何确定是什么原因导致我的带有 nginx 的节点服务器在高请求率时崩溃?

我们正在开发一款 SaaS 产品,这是一款易于使用的发票软件。我们使用 React 作为前端应用程序,并使用 Node(使用 fastify web 框架和 sequelize 作为 ORM)开发 RESTful API 服务。对于数据库,我们使用 PostgreSQL。我们遵循多租户数据库架构,其中每个租户都有一个单独的数据库。我们希望开发 RESTful API 服务器,以便它每秒可以处理大约 500 个请求。对 REST API 的每个请求都会向 Postgres 发出大约 2.1 个请求。为了确保我们有足够的连接来处理来自多个用户的请求,我们将 PG 中的 max_connections 增加到大约 2000。我们使用 nginx 作为反向代理来处理对我们的 REStful API 的请求。

为了测试服务器是否能够处理我们想要的负载,我开发了一个脚本。此脚本模拟来自多个用户对服务器的请求。我使用以下逻辑(为此我选择了 9 个最常用的端点)

  • 从 375 位唯一用户开始
  • 在 5 秒内向来自每个用户的 9 个端点发送请求(每个端点 5 秒)。因此总共 37545 秒内 9 个请求)- 因此对于 375 个用户来说,将是 3759/45 转/秒 = 75 转/秒
  • 每次增加 125 个用户。因此,45 秒后,它将在 45 秒内发送 500*9 个请求 = 100 RPS
  • 重复此操作直到我们从服务器收到错误。

(注意:我们在单个虚拟机上运行所有内容 [RESTful API、PG、用于发送请求的脚本]。该虚拟机有 7 个核心和 14 GB RAM)

这是脚本的相关部分

      for (
        let numUsers = usersStart;
        numUsers <= usersEnd && failed === false;
        numUsers += usersGap
      ) {
        const totalRequests = userList.length * requestsFromEachUser;
        const waitTime = (userRequestInterval * 1e3) / numUsers;

        console.log(
          `>>> Sending ${totalRequests} requests from ${userList.length} users where each user will send ${requestsFromEachUser} requests`
        );

        const queueStartTime = Date.now();
        let cumulativeWaitTime = waitTime;

        // Each user in the group will send request 4 times in 20 seconds
        for (
          let requestNum = 0;
          requestNum < requestsFromEachUser;
          ++requestNum
        ) {
          for (let i = 0; i < userList.length && failed === false; ++i) {
            requests.push(
              sendRequest(userList[i], urls[requestNum], b, numUsers)
            );

            if (cumulativeWaitTime > MIN_WAIT_TIME) {
              await new Promise((resolve) =>
                setTimeout(resolve, cumulativeWaitTime)
              );
              cumulativeWaitTime = waitTime;
            } else {
              cumulativeWaitTime += waitTime;
            }
          }
        }

        const queueEndTime = Date.now();

        const queueExecutionTime = (queueEndTime - queueStartTime) / 1e3;
        const rps = totalRequests / queueExecutionTime;

        if (failed === false) {
          console.log(
            `>>> Sent ${totalRequests} requests from ${userList.length} users in ${queueExecutionTime} seconds with RPS = ${rps} (expected RPS = ${userList.length / userRequestInterval})`
          );
        }

        selectRandomUsers(userList, DBs, usersGap);
      }

我在将请求直接发送到后端时遇到的错误是 ECONNRESET。我得到的速度大约是 280 个请求/秒。当我使用 nginx 时,这个速度会降低到大约 225 个请求/秒。当使用单个数据库进行测试时(而不是为每个租户使用单独的数据库),速度会有所提高,但只能达到大约 360 个请求/秒

这是我正在使用的 nginx 配置

user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 30000;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;
    # server_tokens off;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

    log_format  main_ext  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      '"$host" sn="$server_name" '
                      'rt=$request_time '
                      'ua="$upstream_addr" us="$upstream_status" '
                      'ut="$upstream_response_time" ul="$upstream_response_length" '
                      'cs=$upstream_cache_status' ;


    access_log /var/log/nginx/access.log main_ext;
    error_log /var/log/nginx/error.log debug;

    ##
    # Gzip Settings
    ##

    gzip on;

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

我有以下问题:

  • 我们遵循的确保能够处理与数据库的多个连接的方法是否正确?(即通过增加 max_connections)。如果是,那么这种方法的局限性是什么?有没有更好的方法?如果没有,可能的解决方案是什么?
  • 为什么我会收到 ECONNRESET 错误?
  • 为什么使用nginx的时候速度会变慢?
  • 如何确定导致服务器故障的原因?

我尝试检查 RESTful API 的日志,但似乎请求根本无法到达。我还尝试查看 nginx 日志,但它没有提供有关错误的任何信息。

答案1

性能调优很有趣,但更重要的是,Nginx、API、数据库和测试脚本的“一切”都来自同一台服务器。至少您的答案中有一些线索 - 您通过测试多租户数据库与单租户数据库提供了一些见解,并看到了显著的改进。

快速检查:worker_connections 30000 太多了。绝对最大值是 worker_rlimit_nofile / worker_processes。理论最大值可能因很多因素而异,但您不需要配置超过4096

自从worker_processes 自动;根据您的核心,最多可产生 7 个,继续进行硬编码工作进程 5;这将产生最多 4096 * 5 个 worker_connections(这个数字仍然是 2000 Postgres 的 10 倍),理论上它将释放 CPU 以用于其他任务。Nginx 非常智能,它会将连接任务集中在更少的核心上,并留下至少 1 个核心用于除连接请求之外的 Nginx 内部任务……如果看起来这个 mod 产生了显著的结果,但结果很小,比如 +15-25%,那么您可以考虑实际调整为仅 4 个 worker_processes,甚至更多,如果可能的话,调整所有软件以使用 CPU Affinity。CPU Affinity 是 Nginx 的一个功能,但除非 PG、Python 等可以很好地运行,否则仅靠它可能还不够。

如果涉及 SSL,您可以通过调用释放内存ssl_缓冲区大小4k;与 Nginx 默认的 16k 相比。而且在我看来,您只需要 TLSv1.2,因为如果客户端可以处理,它将选择 v1.3,而好的客户端不需要坚持使用 v1.1。SSL 的加入很好地解释了为什么 9 个 DB 租户产生 225,而 1 个 DB 产生 360。我不是说要放弃它……我是说要调整它,并提供这部分解释为什么引入 Nginx 会导致开销。这与连接管理有关,而不仅仅是吞吐量,特别是当您在整个测试过程中逐步引入新的“客户端”时,除非重用连接,否则必须进行握手。

我们没有看到您的服务器{...}conf 详细信息或上游 {...}其中包含有关 keepalive 等的详细信息。显然,管理 Keepalive(代理连接的数量)很重要,并且需要在这里进行一些调整。

还有许多其他因素,但请尝试使用当前反馈进行快速测试,并考虑提供一些有关您的配置的更多相关详细信息。

请记住,“每秒请求数”并不总是等于“每秒事务数”。开始对结果进行分类,区分处理的请求数与完成的请求或“事务”数(即成功的用例请求)。我非常肯定,您会明白,理解这一点对于确定时间花在哪里以及需要进一步调整系统的位置至关重要。

相关内容