高度并发的 Apache Async HTTP 客户端 IOReactor 问题
Highly Concurrent Apache Async HTTP Client IOReactor issues
应用说明:
- 我正在使用由 Comsat 的 Quasar FiberHttpClient(版本 0.7.0)包装的 Apache HTTP 异步客户端(版本 4.1.1),以便 运行 并执行高度并发的 Java 应用程序使用光纤在内部将 http 请求发送到多个 HTTP 端点
- 应用程序 运行在 tomcat 之上(但是,纤程仅用于内部请求调度。tomcat servlet 请求仍以标准阻塞方式处理)
- 每个外部请求在内部打开 15-20 个光纤,每个光纤构建一个 HTTP 请求并使用 FiberHttpClient 发送它
- 我正在使用 c44xlarge 服务器(16 核)来测试我的应用程序
- 我连接到抢占保持活动连接的端点,这意味着如果我尝试通过重新使用套接字来维护,连接将在请求执行尝试期间关闭。因此,我禁用连接回收。
根据以上部分,这是我的光纤 http 客户端(当然我使用的是单个实例)的调整:
PoolingNHttpClientConnectionManager connectionManager =
new PoolingNHttpClientConnectionManager(
new DefaultConnectingIOReactor(
IOReactorConfig.
custom().
setIoThreadCount(16).
setSoKeepAlive(false).
setSoLinger(0).
setSoReuseAddress(false).
setSelectInterval(10).
build()
)
);
connectionManager.setDefaultMaxPerRoute(32768);
connectionManager.setMaxTotal(131072);
FiberHttpClientBuilder fiberClientBuilder = FiberHttpClientBuilder.
create().
setDefaultRequestConfig(
RequestConfig.
custom().
setSocketTimeout(1500).
setConnectTimeout(1000).
build()
).
setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE).
setConnectionManager(connectionManager).
build();
打开文件的 ulimits 设置得非常高(软硬值均为 131072)
- Eden 设置为 18GB,总堆大小为 24GB
- OS Tcp 堆栈也调整得很好:
kernel.printk = 8 4 1 7
kernel.printk_ratelimit_burst = 10
kernel.printk_ratelimit = 5
net.ipv4.ip_local_port_range = 8192 65535
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 16777216
net.core.wmem_default = 16777216
net.core.optmem_max = 40960
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.netdev_max_backlog = 100000
net.ipv4.tcp_max_syn_backlog = 100000
net.ipv4.tcp_max_tw_buckets = 2000000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_sack = 0
net.ipv4.tcp_timestamps = 1
问题描述
- 在中低负载下一切正常,连接被租用,cloesd 和池补充
- 超出某些并发点,IOReactor 线程(其中 16 个)似乎在死亡之前停止正常运行。
- 我写了一个小线程来获取池统计数据并每秒打印一次。在大约 25K 租用连接时,实际数据不再通过套接字连接发送,
Pending
stat clibms 也达到了飙升的 30K 待处理连接请求
- 这种情况持续存在,基本上使应用程序无法使用。在某些时候 I/O Reactor 线程死了,不确定什么时候,到目前为止我还没有能够捕捉到异常
lsof
ing java 进程,我可以看到它有数万个文件描述符,几乎所有的文件描述符都在 CLOSE_WAIT 中(这是有道理的,因为 I/O 反应器线程 die/stop 正在运行,但从未真正关闭它们
- 在应用程序中断期间,服务器overloaded/cpu压力不大
问题
- 我猜我正在某处达到某种边界,尽管我对它可能存在的内容或位置一无所知。除了以下
- 我是否有可能到达 OS 端口(毕竟所有应用请求都来自单个内部 IP)限制并创建一个错误,使 IO Reactor 线程死亡(类似于 open文件限制错误 ) ?
忘了回答这个问题,但我在发布问题后大约一周才知道发生了什么:
某种错误配置导致 io-reactor 仅生成 2 个线程。
即使在提供更多反应器线程后,问题仍然存在。事实证明,我们的传出请求主要是 SSL。 Apache SSL 连接处理将核心处理传播到 JVM 的 SSL 设施,这对于每秒处理数千个 SSL 连接请求来说效率不够高。更具体地说,SSLEngine 中的一些方法(如果我没记错的话)是同步的。在高负载下进行线程转储显示 IORecator 线程在尝试打开 SSL 连接时相互阻塞。
甚至尝试以连接租用超时的形式创建压力释放阀也没有用,因为创建的积压工作量很大,导致应用程序无用。
将 SSL 传出请求处理卸载到 nginx 执行得更糟 - 因为远程端点抢先终止请求,无法使用 SSL 客户端会话缓存(JVM 实现也是如此)。
最后在整个模块前面放了一个信号量,在任何给定时刻将整个模块限制在 ~6000,这解决了问题。
应用说明:
- 我正在使用由 Comsat 的 Quasar FiberHttpClient(版本 0.7.0)包装的 Apache HTTP 异步客户端(版本 4.1.1),以便 运行 并执行高度并发的 Java 应用程序使用光纤在内部将 http 请求发送到多个 HTTP 端点
- 应用程序 运行在 tomcat 之上(但是,纤程仅用于内部请求调度。tomcat servlet 请求仍以标准阻塞方式处理)
- 每个外部请求在内部打开 15-20 个光纤,每个光纤构建一个 HTTP 请求并使用 FiberHttpClient 发送它
- 我正在使用 c44xlarge 服务器(16 核)来测试我的应用程序
- 我连接到抢占保持活动连接的端点,这意味着如果我尝试通过重新使用套接字来维护,连接将在请求执行尝试期间关闭。因此,我禁用连接回收。
根据以上部分,这是我的光纤 http 客户端(当然我使用的是单个实例)的调整:
PoolingNHttpClientConnectionManager connectionManager = new PoolingNHttpClientConnectionManager( new DefaultConnectingIOReactor( IOReactorConfig. custom(). setIoThreadCount(16). setSoKeepAlive(false). setSoLinger(0). setSoReuseAddress(false). setSelectInterval(10). build() ) ); connectionManager.setDefaultMaxPerRoute(32768); connectionManager.setMaxTotal(131072); FiberHttpClientBuilder fiberClientBuilder = FiberHttpClientBuilder. create(). setDefaultRequestConfig( RequestConfig. custom(). setSocketTimeout(1500). setConnectTimeout(1000). build() ). setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE). setConnectionManager(connectionManager). build();
打开文件的 ulimits 设置得非常高(软硬值均为 131072)
- Eden 设置为 18GB,总堆大小为 24GB
- OS Tcp 堆栈也调整得很好:
kernel.printk = 8 4 1 7 kernel.printk_ratelimit_burst = 10 kernel.printk_ratelimit = 5 net.ipv4.ip_local_port_range = 8192 65535 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.core.rmem_default = 16777216 net.core.wmem_default = 16777216 net.core.optmem_max = 40960 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216 net.core.netdev_max_backlog = 100000 net.ipv4.tcp_max_syn_backlog = 100000 net.ipv4.tcp_max_tw_buckets = 2000000 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 10 net.ipv4.tcp_slow_start_after_idle = 0 net.ipv4.tcp_sack = 0 net.ipv4.tcp_timestamps = 1
问题描述
- 在中低负载下一切正常,连接被租用,cloesd 和池补充
- 超出某些并发点,IOReactor 线程(其中 16 个)似乎在死亡之前停止正常运行。
- 我写了一个小线程来获取池统计数据并每秒打印一次。在大约 25K 租用连接时,实际数据不再通过套接字连接发送,
Pending
stat clibms 也达到了飙升的 30K 待处理连接请求 - 这种情况持续存在,基本上使应用程序无法使用。在某些时候 I/O Reactor 线程死了,不确定什么时候,到目前为止我还没有能够捕捉到异常
lsof
ing java 进程,我可以看到它有数万个文件描述符,几乎所有的文件描述符都在 CLOSE_WAIT 中(这是有道理的,因为 I/O 反应器线程 die/stop 正在运行,但从未真正关闭它们- 在应用程序中断期间,服务器overloaded/cpu压力不大
问题
- 我猜我正在某处达到某种边界,尽管我对它可能存在的内容或位置一无所知。除了以下
- 我是否有可能到达 OS 端口(毕竟所有应用请求都来自单个内部 IP)限制并创建一个错误,使 IO Reactor 线程死亡(类似于 open文件限制错误 ) ?
忘了回答这个问题,但我在发布问题后大约一周才知道发生了什么:
某种错误配置导致 io-reactor 仅生成 2 个线程。
即使在提供更多反应器线程后,问题仍然存在。事实证明,我们的传出请求主要是 SSL。 Apache SSL 连接处理将核心处理传播到 JVM 的 SSL 设施,这对于每秒处理数千个 SSL 连接请求来说效率不够高。更具体地说,SSLEngine 中的一些方法(如果我没记错的话)是同步的。在高负载下进行线程转储显示 IORecator 线程在尝试打开 SSL 连接时相互阻塞。
甚至尝试以连接租用超时的形式创建压力释放阀也没有用,因为创建的积压工作量很大,导致应用程序无用。
将 SSL 传出请求处理卸载到 nginx 执行得更糟 - 因为远程端点抢先终止请求,无法使用 SSL 客户端会话缓存(JVM 实现也是如此)。
最后在整个模块前面放了一个信号量,在任何给定时刻将整个模块限制在 ~6000,这解决了问题。