我在负载均衡器后面有一组 Java Vertx 服务器,用于处理高峰流量。前一分钟,它可能处理 150k r/m,下一分钟,它可能处理 2mm r/m,然后又降回 150k r/m。我发现在这些高峰期间,整个服务器群可能会在几分钟内无响应并断开连接,而任何一个盒子上的 CPU 和内存压力几乎都达不到 50% 的利用率。
为了测试到底是什么原因导致中断,我设置了一台与我的生产服务器群中的一台规格相匹配的测试服务器,以查看在它崩溃之前我可以向它发送多少数据。我的测试涉及使用另外 10 台机器,每台机器打开 500 个与服务器的 https 连接并发送 1mm 请求,每个请求负载约 2kb。总共打开了 5k 个并发连接,总共发送了 10mm 请求,数据传输量约为 20gb。
一旦连接打开,我每分钟可以发出大约 70 万个请求。我只需向健康端点发出请求并记录响应时间即可监控服务器的可用性。响应时间很快,只有几十毫秒。我对这些结果很满意。
但在数据开始大量涌入之前,这 10 台机器必须先建立 5000 个连接。在此期间,服务器没有响应,甚至在我尝试检查健康端点时可能会超时。我相信这就是导致我的生产机群中断的原因——新连接突然增加。一旦建立连接,服务器就可以轻松处理所有传入的数据。
我已经更新了 nofile ulimit、net.core.netdev_max_backlog、net.ipv4.tcp_max_syn_backlog 和 net.core.somaxconn,但是在几秒钟内接收到 5k 个新连接请求时它仍然会挂起。
我可以做些什么来更快地建立新的连接?
编辑:
实际的服务器在 docker 容器中运行。我的网络设置未应用于容器。接下来将尝试这样做,看看是否有区别。
編輯 ...:
一切都在 SSL 中。通过纯 HTTP 建立如此多的连接几乎是即时的。所以我必须弄清楚如何更快地建立 TLS 连接。
編輯 ...:
我发现本机 java 安全 ssl 处理程序是瓶颈。切换到netty-tcnative
(又称本机 OpenSSL)几乎解决了我的 HTTPS 问题。
答案1
感谢@MichaelHampton 的帮助。
我找到了解决我的问题的方法,希望它可以帮助其他人(特别是如果您使用 Java)。
我听到过许多建议,只是增加nofiles
以允许更多的连接,但我想首先重申,问题不在于服务器无法建立更多的连接,而是它无法足够快地建立连接并断开连接。
我第一次尝试解决这个问题是通过增加连接队列net.ipv4.tcp_max_syn_backlog
,net.core.somaxconn
并在适当的应用程序服务器配置中再次增加连接队列。对于 vertx,这是server.setAcceptBacklog(...);
。这导致在队列中接受更多连接,但并没有使建立连接的速度更快。从连接客户端的角度来看,它们不再因溢出而重置连接,建立连接所需的时间更长。因此,增加连接队列并不是真正的解决方案,只是用一个问题换了另一个问题。
为了缩小连接过程中瓶颈的范围,我尝试使用 HTTP 而不是 HTTPS 进行相同的基准测试,发现问题完全消失了。我遇到的具体问题是 TLS 握手本身以及服务器满足它的能力。
通过对我自己的应用程序进行更多的研究,我发现用本机 SSLHandler(OpenSSL)替换 Java 默认的 SSLHandler 大大提高了通过 HTTPS 连接的速度。
以下是我针对特定应用程序所做的更改(使用 Vertx 3.9.1)。
- 添加 netty-tcnative 依赖项
<!-- https://mvnrepository.com/artifact/io.netty/netty-tcnative -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId>
<version>2.0.31.Final</version>
<classifier>osx-x86_64</classifier>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-tcnative -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId>
<version>2.0.31.Final</version>
<classifier>linux-x86_64-fedora</classifier>
<scope>compile</scope>
</dependency>
第一个依赖项用于在运行时测试 osx。第二个用于编译后的 centos linux。linux-x86_64
也可用于其他版本。我尝试使用,boringssl
因为openssl
它不支持ALPN
,但经过几个小时后,我无法让它工作,所以我决定暂时不使用 http2。大多数连接在断开连接之前只发送 1-2 个小请求,这对我来说真的不是问题。如果你可以使用boringssl
,那可能是首选。
- 因为我没有使用依赖项的超级版本。我需要安装 centos 的操作系统依赖项。这已添加到 Dockerfile 中
RUN yum -y install openssl
RUN yum -y install apr
- 要告诉 vertx 服务器使用 OpenSSL 而不是 Java 版本,请在服务器上设置 OpenSSL 选项(即使只是默认对象)
httpServerOptions.setOpenSslEngineOptions(new OpenSSLEngineOptions());
- 最后,在我的运行脚本中,我添加了
io.netty.handler.ssl.openssl.useTasks=true
Java 选项。这告诉 ssl 处理程序在处理请求时使用任务,以便它是非阻塞的。
java -Dio.netty.handler.ssl.openssl.useTasks=true -jar /app/application.jar
经过这些更改后,我能够以更少的开销更快地建立连接。以前需要花费数十秒的时间,并且会导致频繁的连接重置,现在只需 1-2 秒,并且不会重置。可能会更好,但与我之前的情况相比已经有很大进步。
答案2
很好的修复!
因此,这似乎是 SSL 层的问题,它肯定要进行更多处理,比如网络握手和加密转换,这些都需要资源。除非您的 SSL 可以将部分处理卸载到硬件上,否则 SSL 肯定会增加服务器的负载,而且正如您所发现的,并非所有 SSL 库都是平等的!
这些问题非常适合使用前端反向代理。理想情况下,它可以放在应用程序之前,处理与客户端的所有 SSL 连接,然后向后端执行 http 操作。
您的原始应用程序需要做的事情少一些,因为您的前端反向代理可以吸收所有的 SSL 工作和 TCP 连接管理。
Apache 和 NGNIX 可以做到这一点,并且有相当多的选项来将这些连接负载平衡到负载最少的后端服务器。
您会发现 NGNIX 可以比 Java 更快地完成 SSL 终止,即使 Java 可以,您也会将连接管理的处理分散到多台机器上,从而减少后端服务器上的负载(内存/CPU/磁盘 io)。这样做的副作用是使后端配置更简单。
缺点是您在代理和应用程序之间使用 http,这在某些超安全的环境中是不可取的。
祝你好运!