Apache mod_proxy_ajp 模块过早地将流量发送到备用后端服务器

Apache mod_proxy_ajp module prematurely sending traffic to spare backend server

我们有一对 Apache 2.4 网络服务器(web02web03)运行 mod_proxy_ajp 与一对 Tomcat 7.0 通信.59 台服务器(app02app03)。

app03 上的 Tomcat 服务器是备用服务器,除非 app02 完全离线,否则不应获取流量。

web02 和 web03 上的 Apache 配置:

<Proxy balancer://ajp_cluster>
  BalancerMember ajp://app02:8009 route=worker1 ping=3 retry=60
  BalancerMember ajp://app03:8009 status=+R route=worker2 ping=3 retry=60
  ProxySet stickysession=JSESSIONID|jsessionid lbmethod=byrequests
</Proxy>

Tomcat app02 和 app03 上的 AJP 配置:

<Connector protocol="AJP/1.3" URIEncoding="UTF-8" port="8009" />

我们发现 Apache 开始将流量发送到 app03 的问题,即使 app02 仍然可用但可能有点忙,它仍被标记为备用。

Apache SSL 错误日志:

[Thu Sep 12 14:23:28.028162 2019] [proxy_ajp:error] [pid 24234:tid 140543375898368] (70007)The timeout specified has expired: [client 207.xx.xxx.7:1077] AH00897: cping/cpong failed to 10.160.160.47:8009 (app02)
[Thu Sep 12 14:23:28.028196 2019] [proxy_ajp:error] [pid 24234:tid 140543375898368] [client 207.xx.xxx.7:1077] AH00896: failed to make connection to backend: app02
[Thu Sep 12 14:23:28.098869 2019] [proxy_ajp:error] [pid 24135:tid 140543501776640] [client 207.xx.xxx.7:57809] AH01012: ajp_handle_cping_cpong: ajp_ilink_receive failed, referer: https://site.example.com/cart
[Thu Sep 12 14:23:28.098885 2019] [proxy_ajp:error] [pid 24135:tid 140543501776640] (70007)The timeout specified has expired: [client 207.xx.xxx.7:57809] AH00897: cping/cpong failed to 10.160.160.47:8009 (app02), referer: https://site.example.com/cart

我们的 Apache 日志中有数百条这样的消息。

关于使 Apache 坚持 app02 除非它完全离线的设置有什么建议吗?

您在 Tomcat 连接器中遇到线程耗尽导致 httpd 认为 app02 处于错误状态 - 在某种程度上确实如此。

简短的回答是将您的 Tomcat AJP 连接器切换为使用 protocol="org.apache.coyote.ajp.AjpNioProtocol"

长答案,嗯,相当长。

mod_jk 在 httpd 和 Tomcat 之间使用持久连接。对此的历史论据是性能。它节省了为每个请求建立新的 TCP 连接的时间。通常,测试表明此参数不成立,并且建立新的 TCP 连接或执行 CPING/CPONG 以确认连接有效所花费的时间(如果使用持久连接,则需要执行此操作) ) 需要足够接近的时间。无论如何,持久连接是 mod_jk.

的默认设置

使用持久连接时mod_jk为每个 httpd 工作线程创建一个连接并在工作线程中缓存该连接。

Tomcat7.x中的默认 AJP 连接是 BIO 连接器。此连接器使用阻塞 I/O 并且每个连接需要一个线程。

当 httpd 配置的工作线程数多于 Tomcat 的线程数时,就会出现此问题。最初一切正常。当 httpd worker 遇到需要传递给 Tomcat 的第一个请求时,mod_jk 会为该 httpd worker 创建持久连接并为请求提供服务。需要传递给 Tomcat 的由该 httpd worker 处理的后续请求将使用该缓存连接。请求被(有效地)随机分配给 httpd 工作者。随着越来越多的 httpd 工作人员看到他们的第一个请求需要传递给 Tomcat,mod_jk 为每个工作人员创建了必要的持久连接。 Tomcat 的许多连接很可能大多处于空闲状态。闲置程度取决于 httpd 上的负载以及传递给 Tomcat.

的请求的比例

一切都很好,直到更多的 httpd 工作人员需要创建到 Tomcat 的连接,Tomcat 有线程。请记住,Tomcat AJP BIO 连接器需要每个连接一个线程,因此 maxThreads 本质上是 Tomcat 允许的最大 AJP 连接数。此时 mod_jk 无法创建请求,因此启动了故障转移过程。

有两种解决方法。第一个 - 我上面描述的那个 - 是删除每个连接一个线程的限制。通过切换到NIO AJP 连接器,Tomcat 使用一个Poller 线程来维护数千个连接,只将那些有数据要处理的传递给一个线程进行处理。 Tomcat 处理的限制是 maxThreads 是 Tomcat 可以在该连接器上处理的最大并发请求数。

第二种解决方案是禁用持久连接。 mod_jk 创建一个连接,将其用于单个请求,然后关闭它。这减少了 mod_jk 在 httpd 和 Tomcat.

之间的任何一点所需的连接数

抱歉,上面的文字太多了。我还在各种演示文稿中介绍了这一点,包括 this one.