CloseNowException:该流不可写

use*_*589 8 tomcat http2

我真的很困惑。在我的服务器日志中我看到:

org.apache.coyote.CloseNowException: Connection [215], Stream [95], This stream is not writable
        at org.apache.coyote.http2.Http2UpgradeHandler.reserveWindowSize(Http2UpgradeHandler.java:843) ~[tomcat-coyote.jar:9.0.30]
        at org.apache.coyote.http2.Stream$StreamOutputBuffer.flush(Stream.java:940) ~[tomcat-coyote.jar:9.0.30]
        at org.apache.coyote.http2.Stream$StreamOutputBuffer.doWrite(Stream.java:859) ~[tomcat-coyote.jar:9.0.30]
        at org.apache.coyote.http2.Http2OutputBuffer.doWrite(Http2OutputBuffer.java:59) ~[tomcat-coyote.jar:9.0.30]
        at org.apache.coyote.Response.doWrite(Response.java:601) ~[tomcat-coyote.jar:9.0.30]
Run Code Online (Sandbox Code Playgroud)

当用户单击太快时似乎会发生这种情况,但这没有意义,因为 Tomcat 应该能够满足大量请求。这是在负载非常轻的服务器上,在一台非常快的机器上,每秒可能有 2 或 3 个 HTTP 请求。

这是 Spring Boot 2 和 Tomcat 9.0.30 的情况。实在是令人费解。

我确实在 SO 上看到了类似的问题,有人使用网络推送得到了这个,但我们没有。

以下是我们的 HTTP/2 连接器的配置方式:

<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true"
           keystoreFile="/etc/ssl/keys.p12"
           keystorePass="changeit"
           keyAlias="tomcat"
           sslProtocol="TLS"
           sslEnabledProtocols="TLSv1.3,TLSv1.2"
           connectionTimeout="20000"
           >
  <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"
                   keepAliveTimeout="20000"
                   />
</Connector>
Run Code Online (Sandbox Code Playgroud)

它在 Ubuntu 服务器 18.04 上的 JDK 13.0.2 上运行。

对此有什么想法吗?这绝对是用户注意到的事情,但我不知道如何解决这个问题。

use*_*589 7

这个问题实际上并没有消失。我尝试了很多很多事情。我在 9.0 系列的几个较新版本的 Tomcat 上尝试过这个。我升级到了 JDK 14、14.0.1 和 14.0.2,全部来自 Oracle(我从未尝试过 OpenJDK 版本,无法想象它会有什么不同)。我尝试了各种 TLS 设置,包括将其限制为仅 TLS1.3。

问题仍然存在。

我尝试运行一个完全不同的 Spring Boot 应用程序,该应用程序必须向服务器发出大量小型(500 字节)JSON POST。这些 POST 消息常常会失败。这种情况在某些浏览器上经常发生,但在其他浏览器上则不会。这没有任何明确的模式。这些浏览器可以在 Windows、Linux 和 Safari 以及移动设备上运行。有时有效有时无效。

通常,最有可能失败的 GET 是获取小型静态对象,例如小型 CSS 文件,事实上,它应该是最容易加载且最快的东西。作为一个副作用,无法加载 CSS 会完全破坏网站,所以这真的很糟糕。

因此,我们有许多不同版本的 Tomcat、许多版本的 JDK、许多不同的 http2 和 TLS 配置参数变体,以及两个完全不同的 Spring Boot 应用程序,我们遇到了相同的问题:http2 会话偶尔失败。这里唯一的共同点是,当我切换回 http1.1 时,问题就全部消失了。

Http1.1 尽管可靠,但性能却比 http2 差很多,我希望它能正常工作。

出于彻底的绝望,我想,好吧,我唯一没有改变的是服务器软件,而且我知道 Jetty 的 http2 实现与 Tomcat 完全不同。

我切换到基本的 Jetty 配置,启用了 http2,部署了我的应用程序并对其进行了测试。所有 http2 问题都消失了。它开始按预期运行,性能良好,并且具有 http2 的性能优势。此开关修复了之前出现问题的两个 Spring Boot 应用程序上的问题。顺便说一句,这两个 Spring Boot 应用程序非常不同,一个涉及数据库和 Spring Integration 以及大量功能,另一个则简单得多,涉及读取和写入一些小文件。

在这里我只能得出一个结论:Tomcat 的 http2 实现有问题,它没有经过足够的测试或实际使用,不应该在生产中使用。这是一件痛苦的事情,因为我多年来一直使用 Tomcat 作为我的首选服务器,并且我认为 Tomcat 版本会很稳定并且可以投入生产,但事实并非如此。如果您遇到这些“关闭流”错误,其中客户端根本无法加载,并且是立即发生的,而不是在任何超时之后,并且发生在 http2 而不是 http1 上,那么尝试切换到 Jetty 并查看是否会产生影响。如果任何 Tomcat 开发人员想进一步了解这一点,请回复此内容,我可以将我们的系统设置为演示。

考虑到 Tomcat 是最古老且使用最广泛的 Servlet 容器,我确实对 Tomcat 抱有强烈的偏见,但到目前为止,我得出的结论是 Jetty 将成为我的首选。

我希望这篇文章可以帮助其他可能遇到这个问题的人,因为这确实不容易弄清楚,最后我也没弄清楚发生了什么,除了 Tomcat 的 http2.x 中有问题之外。


小智 6

解决方案非常简单。我曾经也有过一样的问题。

还有其他参数需要配置,以下链接中有详细列表 https://tomcat.apache.org/tomcat-8.5-doc/config/http2.html

对于您的情况,一个简单的解决方案是:

<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"                     
                 overheadCountFactor="-1"
                 overheadDataThreshold="0"
                 overheadWindowUpdateThreshold="0"/>
Run Code Online (Sandbox Code Playgroud)

OverheadCountFactor:计算开销帧以确定连接是否开销过高并应关闭时应用的因子。开销计数从 -10 开始。对于发送或接收的每个数据帧以及接收到的每个标头帧,计数都会减少。 ecc ecc ……如果开销计数超过零,则关闭连接。小于 1 的值将禁用此保护。在正常使用中,3 或更大的值将在任何流完成之前关闭连接。如果未指定,将使用默认值 1。


小智 2

我也遇到过同样的问题。对我来说,解决方案是为升级协议设置 readTimeout="20000":

<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"
        readTimeout="20000" />
Run Code Online (Sandbox Code Playgroud)

但不能保证它对你有用;-)