Gunicorn: 在 Heroku 上返回 >13k 响应时 POST 失败

创建于 2014-08-04  ·  34评论  ·  资料来源: benoitc/gunicorn

嗨,我们在使用 Flask + Gunicorn + Heroku 的生产中遇到了这个问题,但找不到原因或解决方法。

对于一个带有 POST 参数的特定 POST 请求,该请求将失败,并在 Heroku 的路由器中出现 H18 错误(sock=backend),表明服务器在不应该关闭套接字时关闭了套接字。

我们开始减少该失败端点的响应大小,直到将其缩小到 13k 左右。 如果我们发送的数据少于 13k,响应将始终有效。 如果我们发送超过 13k,响应几乎总是不起作用。

复制代码可在https://github.com/erjiang/gunicorn-issue 获得- 只需按原样将 repo 部署到 Heroku 并按照自述文件中的说明进行操作。

( Feedback Requested unconfirmed help wanted - Bugs -

最有用的评论

我能够使用https://github.com/erjiang/gunicorn-issue 上的测试用例进行重现(使用的是 gunicorn 19.9.0、Python 2.7.14、sync worker、 --workers 4 )。 值得注意的是,gunicorn 的访问日志输出报告它认为它返回了一个 HTTP 200。

更新到 Python 3.7.3 + gunicorn master并减少到--workers 1对重现性没有影响,但是从同步工作者切换到 gevent 使错误发生的频率降低(尽管它仍然发生)。 使用--log-level debug没有显示任何重要信息(请求期间唯一的额外输出是[DEBUG] POST /test1行)。

接下来我尝试了--spew ,但是问题不再重现。 这使我尝试添加一个time.sleep(1)前的resp.close()这里这同样阻止了问题。

因此,似乎原因是在close()时套接字发送缓冲区可能不为空,这可能导致响应丢失:

注意: close()释放与连接关联的资源,但不一定立即关闭连接。 如果您想及时关闭连接,请在close()之前调用shutdown() close()

(见 https://docs.python.org/3/library/socket.html#socket.socket.close)

添加sock.shutdown(socket.SHUT_RDWR)文档的前) sock.close()这里解决了我的问题。 另一种解决方法可能是使用SO_LINGER ,但从我读过的内容来看,它有一些权衡。

关于这个主题的文档很难找到,但我发现:
https://stackoverflow.com/questions/8874021/close-socket-directly-after-send-unsafe
https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable

希望有帮助:-)

所有34条评论

非常有用的报告,谢谢@erjiang。

我没有可以测试的 heroku 帐户。 有这个账号的人可以测试一下吗? 抄送@tilgovi @kennethreitz

很高兴,但我可能不会很快得到它。

作为一个快速的健全性检查,我在本地运行它并用 curl 检查了一些东西来比较女服务员和枪炮:

  • [x] Content-Length 相同
  • [x] 相同的正文内容
  • [x] 相同的传输编码(都没有指定分块,都使用 Content-Length)

接下来我很好奇也许在 TCP 级别上是否存在差异。 我将 tcpdump 它们,看看我是否注意到任何可疑的东西。

我确实注意到,即使使用相同的卷曲线,gunicorn 也会断开连接,但女服务员将其打开。 目前还没有任何线索,但这是我唯一能看到的不同之处。

@tilgovi我猜您在女服务员身上看到的行为可以用线程工作者重现。 无论如何感谢照顾这个:)

大家好,
我遇到了同样的问题。 你们中有人有机会更彻底地研究这个问题吗?
@tilgovi @erjiang @benoitc

干杯
格言

@maximkgn你也在用烧瓶吗? 还有更多细节吗?

我正在使用 Django 1.7。
我们有一个特定的 post 响应,它总是长于 13k,并且有一定的概率 ~0.5 客户端中的响应会被截断到 13k 以上。 在 heroku 日志中,我们看到了相同的 h18 错误,在我们确定 python 代码中没有发生错误后,我们不得不得出结论,它发生在 heroku 和我们的 python 之间的 gunicorn 层中。
当我们切换到女服务员/ uwsgi 时,错误停止发生..

@maximkgn如果您使用--threads设置会发生什么?

任何人都可以测试这个?

我对 Flask 和 gunicorn(测试版本 19.3 和 19.4.5)有同样的问题。 @benoitc我尝试了 1、2 和 4 个线程(使用 --threads)选项,但没有任何区别。

让我知道我是否可以以任何方式帮助测试?

@cbaines请求是什么样的?

Friendpaste 可以接受超过 100 万个帖子......所以在 gunicorn 中肯定没有限制。

从来没有答案。 关闭问题,因为它不可重现。 如果需要,请随时重新打开一个。

更新依赖项以包含 Flask 1.0.2 和 gunicorn 19.9.0 后仍然重现。 不过,引起 Heroku 某人的注意可能会很好 - 我听说他们有一些专门的 Python 人员。

在此处查看最新提交: https :

我还定期收到大型 GET 请求的 H18 错误。

切换到女服务员确实解决了这个问题。 不知道为什么 gunicorn 会产生它,但正在执行相同的代码。

响应正文为 21.54 KB

更新依赖项以包含 Flask 1.0.2 和 gunicorn 19.9.0 后仍然重现。 不过,引起 Heroku 某人的注意可能会很好 - 我听说他们有一些专门的 Python 人员。

在此处查看最新提交: https :

我在 Heroku 上创建了一个支持票。 如果有任何有用的东西来自它,将在这里更新。

@benoitc看起来像@erjiang提供了一个可重现的例子。 我们可以打开这个备份吗?

重新开张。 我会自行分配并尽可能看一下。

更新依赖项以包含 Flask 1.0.2 和 gunicorn 19.9.0 后仍然重现。 不过,引起 Heroku 某人的注意可能会很好 - 我听说他们有一些专门的 Python 人员。
在此处查看最新提交: https :

我在 Heroku 上创建了一个支持票。 如果有任何有用的东西来自它,将在这里更新。

你收到heroku的回复了吗?

我能够使用https://github.com/erjiang/gunicorn-issue 上的测试用例进行重现(使用的是 gunicorn 19.9.0、Python 2.7.14、sync worker、 --workers 4 )。 值得注意的是,gunicorn 的访问日志输出报告它认为它返回了一个 HTTP 200。

更新到 Python 3.7.3 + gunicorn master并减少到--workers 1对重现性没有影响,但是从同步工作者切换到 gevent 使错误发生的频率降低(尽管它仍然发生)。 使用--log-level debug没有显示任何重要信息(请求期间唯一的额外输出是[DEBUG] POST /test1行)。

接下来我尝试了--spew ,但是问题不再重现。 这使我尝试添加一个time.sleep(1)前的resp.close()这里这同样阻止了问题。

因此,似乎原因是在close()时套接字发送缓冲区可能不为空,这可能导致响应丢失:

注意: close()释放与连接关联的资源,但不一定立即关闭连接。 如果您想及时关闭连接,请在close()之前调用shutdown() close()

(见 https://docs.python.org/3/library/socket.html#socket.socket.close)

添加sock.shutdown(socket.SHUT_RDWR)文档的前) sock.close()这里解决了我的问题。 另一种解决方法可能是使用SO_LINGER ,但从我读过的内容来看,它有一些权衡。

关于这个主题的文档很难找到,但我发现:
https://stackoverflow.com/questions/8874021/close-socket-directly-after-send-unsafe
https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable

希望有帮助:-)

完整的STR:

  1. https://signup.heroku.com创建一个免费的 Heroku 帐户
  2. 安装 Heroku CLI(参见 https://devcenter.heroku.com/articles/heroku-cli)
  3. 使用heroku login登录 CLI
  4. git clone https://github.com/erjiang/gunicorn-issue && cd gunicorn-issue
  5. heroku create (这会创建一个随机生成的免费 Heroku 应用程序,并配置一个名为heroku的 git 远程)
  6. git push heroku master
  7. curl --data "foo=bar" https://YOUR_GENERATED_APP_NAME.herokuapp.com/test1 (失败率超过 75%)
  8. 完成后,运行heroku destroy删除应用程序。

@tilgovi听起来像@edmorley 对出了什么问题sock.shutdown()但我不知道这是否是正确的修复或者它是否会对其他情况产生负面影响。

您好,我遇到了同样的问题,响应大小为 503 KB。 响应数据是 JSON 数组。
观察到的行为是:

  1. 我看到截断的响应正文,并且 http 客户端(Chrome、curl)仍在等待响应。
  2. 大约 75% 的请求的响应时间在 120-130 秒之间。 其余请求在 400 毫秒内解决。
  3. 响应大小小的请求很快。

在两者上都可以

  1. Windows 10 上的本地 Docker 安装
  2. 在 AWS ECS 上运行 docker 容器

运行时环境设置
meinheld-gunicorn-docker图像标记为 _python3.6_,使用 Python 3.6.7、 Flask 1.0.2、 flask-restplus 0.12.1、simpe Flask-caching

Docker 配置:3 个 CPU,RAM 1024 MB

独角兽配置

https://github.com/benoitc/gunicorn/issues/2015 中,其他人遇到了 meinheld 工人挂起的问题,使用不同的工人类型解决了这个问题。 我想知道它是否存在普遍问题。 @stapetro你能尝试不同的工人吗?

你好@jamadden
你的建议解决了这个问题。 _gevent_ 和 _gthread_ 工作类都没有问题。 我搬离了meinheld。 感谢您的快速回复和帮助! :)

完整的STR:

  1. https://signup.heroku.com创建一个免费的 Heroku 帐户
  2. 安装 Heroku CLI(参见 https://devcenter.heroku.com/articles/heroku-cli)
  3. 使用heroku login登录 CLI
  4. git clone https://github.com/erjiang/gunicorn-issue && cd gunicorn-issue
  5. heroku create (这会创建一个随机生成的免费 Heroku 应用程序,并配置一个名为heroku的 git 远程)
  6. git push heroku master
  7. curl --data "foo=bar" https://YOUR_GENERATED_APP_NAME.herokuapp.com/test1 (失败率超过 75%)
  8. 完成后,运行heroku destroy删除应用程序。

我在我的应用程序上有非常相似的行为,并发现当使用 curl -H 而不是 curl --data (因为它是一个 GET 请求)时,它适用于我的应用程序(Django、Gunicorn、Heruko)。 我还没有在 gunicorn 问题应用程序上进行测试。 认为这可能对某人有用。

@mikkelhn是的。 带有 Flask/Flask RestPlus 和 Gunicorn 的应用程序的行为方式如下:回复 POST 请求会给出 503 错误 [if payload > 13k],而如果应用程序回复 GET则不会发生错误。 完全一样的代码!
任何人都可以解释这种非常烦人的行为吗? 改用女服务员是解决此问题的唯一解决方法吗? 我觉得“手动”修改 Gunicorn 不是一个可行的解决方案......

我继续打开一个 PR 在 close() 之前调用 shutdown()。 坦率地说,Heroku 在默认情况下在 Heroku 上被破坏时继续推荐 Gunicorn 有点疯狂。

如果,正如@erijang正确指出的那样,当Gunicorn :哪些是
AFAIK,许多客户选择 Heroku 只是因为它不需要对服务器架构和配置细节有深入的了解......:|

@RinaldoNani你是什​​么意思? 还有我们在谈论哪个工人? .

@benoitc此问题影响多种工作人员类型,如中所述:
https://github.com/benoitc/gunicorn/issues/840#issuecomment -482491267

嗨@benoitc。 正如我在以前的文章中提到,我们已经部署在Heroku上一个非常简单的瓶/ FlaskRestPlus应用,小心Heroku的为Python /瓶服务器端应用程序部署指南(其中,因为我明白,建议使用Gunicorn同步“网络”以下

我们的应用程序的行为反映了该线程的标题。

在本地测试,一切正常,应用程序提供 20k+ JSON 没有问题; 但是当应用程序部署在 Heroku 上时,503 错误问题变得系统化:即使实际上没有流量,也不会交付输出。
正如其他人指出的那样,日志显示在 HTTP 级别一切正常(记录了 200 响应代码)。
如果有效负载小于 13k,则 Heroku/Gunicorn 会按预期响应 POST。
我们遵循@mikkelhn避免使用 POST (?!?) 端点并使用 GET 的想法,这似乎是一种(不是很好)解决问题的方法。

我们不是 Gunicorn 专家,坦率地说,我们希望我们的简单用例可以“开箱即用”。
如果您有任何建议可以帮助我们,我们将永远感激不尽 :)

@RinaldoNani在黑暗中拍摄......在您的请求处理程序中的某个地方,尝试阅读所有request.data 。 例如:

@route('/whatever', methods=['POST'])
def whatever_handler():
    str(request.data)
    return flask.jsonify(...)

这对你的错误有什么影响吗?

在忙着处理 H18 问题超过 2 周后,我在凌晨 1:00 写这篇文章(迫不及待想要分享)。

我正在处理庞大的数据集并响应 18K 到 20K 的记录进行绘图。 H18 是一个非常随机的错误。 有时它会正常工作,但会在所有浏览器上抛出“内容标题长度不匹配”。 我尝试了几乎所有讨论过的关于这个问题的解决方案,但没有任何运气。 我尝试了两件事,最终奏效了:

  1. 将 POST 请求更改为 GET。
  2. 我的数据有 NaN/Null 值,所以我改变了我的模型并提供了一个默认值。 (我认为这解决了问题)
    在此之后,我不再收到此错误。
    希望这可以帮助某人!
此页面是否有帮助?
0 / 5 - 0 等级