【问题标题】:Stop processing request when request times out请求超时时停止处理请求
【发布时间】:2013-10-29 18:40:05
【问题描述】:

据我所知,由于 Node.js 中事件循环的性质,在请求已被移交给回调函数后,无法轻松阻止请求的处理。

由于每个事件回调不绑定到任何其他特定的流程或事件,一旦回调被添加到事件循环的队列或当前正在处理,即使 request.on('timeout ') 处理程序被触发。

如果请求超时,有什么方法可以停止处理请求?我能想到的部分解决这个问题的唯一方法是在请求期间执行的每个异步回调的顶部添加一个检查,以查看请求是否超时,如果超时,则立即返回并停止连续的回调。

但是,如果已经有一个从超时请求发出的未完成的异步调用正在处理中,这并不能解决问题。在这种情况下,除非您破坏与正在执行异步处理的任何操作(例如执行 MySQL 查询)的连接,否则它将继续并最终调用与其关联的异步回调。

这在 PHP + Apache 等其他编程范例中不是问题,因为 apache 会为每个传入的请求创建一个新的 PHP 进程,因此如果请求超时,该进程最终会被终止并停止整个执行流程。

有没有人遇到过这个问题并找到了现有的库或自定义方法来轻松解决这个问题?

编辑:添加特定示例:

现在我们有一个内置的 API 节点处理传入请求以跟踪分析,将请求数据发布到兔子队列,然后返回响应。如果请求进来并且由于某种原因请求需要超过 30 秒的时间来处理,它将超时并且将向客户端发送错误响应。此时,客户端将尝试再次拨打相同的电话(这样我们就不会丢失分析数据)。

但是,由于请求本身与发布到 rabbit 的调用之间没有任何联系(除了启动发布的请求是该请求),因此无法停止发布该数据。因此,如果 rabbit 服务器最终完成了对发布请求的处理,现在将会出现重复数据,因为客户端现在已经发出了 2 个(或更多)请求。

阻止初始请求完成发布的唯一方法是在请求超时时销毁rabbit连接。这样,初始发布就会停止,并且来自客户端的重试请求不会重复它。但是,对于在整个请求中进行的任何异步调用,都需要对连接进行相同类型的手动销毁。如果在超时发生时我有 5 个未完成的异步调用,我将需要销毁所有 5 个异步连接(无论它们是与 rabbit、mysql、mongo 等的连接)。

但是,问题不仅仅在于请求超时时已经建立的连接。即使请求在发布到rabbit之前超时,被调用来处理该请求的函数已经执行并且不会停止,除非在各个位置进行一些检查以检查请求是否超时并返回马上。

【问题讨论】:

  • 您可以将rabbit连接存储在一个带有id(可以是客户端ip或cookie值或其他)的全局对象上,您可以在下一个请求时检索此连接。您还可以将兔子连接存储在套接字本身并监听“关闭”,并在发生这种情况时终止兔子连接。杀死意味着调用“关闭”或设置一个标志,以便在您自己的代码中保释。选择第一个选项,您可以通过某个 id 将多个 socket/http 连接汇聚到单个服务器“连接”。
  • @DDS 现在我将兔子连接附加到请求中,然后在超时时关闭连接。但是,我希望有一个更通用的解决方案,包括停止处理整个请求流的能力。现在我想不出一个好方法,除非我将完全与节点相反的东西引入节点(为每个传入请求创建一个新进程)或冗余/冗长(在每个异步调用之前添加一个检查以查看是否请求已超时)。这两种方法似乎都不是很直观。
  • 如果你使用node 0.11.x,你可以使用基于生成器的函数(gen-runsuspend),你可以修改它以在你想中止时将异常扔到生成器函数中该函数,结合忽略原始的下一个回调。然后,您可以对“正在运行的函数”(当然实际上是暂停的)进行真正的“中止”调用。这实质上会在每次回调调用后进行检查,并将“纾困”一直推送到函数出口(除非您捕获并忽略错误)。
  • 如果你使用node 0.11.x,你仍然可以使用生成器(ES6),但你必须将它编译成vanilla JS(ES5),你可以使用traceur

标签: node.js timeout request


【解决方案1】:

对于更具体的问题,您可能会得到更好的答案。而且像“一个 mysql 查询”这样的技术细节比像“一个请求”这样模糊的非技术术语更容易讨论。

我能想到的部分解决这个问题的唯一方法是在请求期间执行的每个异步回调的顶部添加一个检查,以查看请求是否超时,如果超时,则返回立即停止后续的回调

您现在不需要,也永远不需要 node.js 中的代码。没有人会这样做,因为这不是问题。即使是这样,在堆栈中解决比在应用程序中更低的问题也是有意义的。

这在 PHP + Apache 等其他编程范例中不是问题,因为 apache 会为每个传入的请求创建一个新的 PHP 进程,因此如果请求超时,该进程最终会被终止并停止整个执行流程。

这在 node.js 中几乎没有问题,因为对于传入的 HTTP 请求,甚至没有新进程甚至新线程。发生的结局本质上是:

  • 客户端连接并发送 GET 请求
  • 操作系统收到连接
  • 节点收到GET请求
  • 假设此时客户端进程(浏览器)在客户端计算机上突然被杀死
  • 具体的结果有几种情况。如果在浏览器所在的房子里断电,网络上没有任何东西,在其他情况下,客户端我会发送数据包以提前关闭 TCP 连接。
  • 但是在节点端这并不重要。你处理请求,进行数据库查询,渲染 HTML,最坏的情况是一旦工作完成,没人关心,但这只是一个 GET 请求。您的应用程序应该每秒执行许多此类操作。将完成该请求的处理视为“问题”是错误的。这不是问题,也没有我听说过可以用任何编程语言实际完成的事情。服务器实际上无法区分一个只是正确等待响应的客户端与 200 毫秒前断电的客户端。 HTTP 没有“取消”方法。事实上,完成整个 HTTP 请求可能在语义上更正确。例如,如果 Web 浏览器向您发送信用卡购买的完整 HTTP POST,但在您响应之前断开连接,则该交易应该完成。在启动后中止它是不正确的。

【讨论】:

  • 我不同意。并非所有 http 请求都在亚秒级时间内提供服务。有些可能需要数小时(下载)并占用大量资源(读取大文件)。此外,这些很可能会被客户端丢弃(厌倦了等待)。考虑到这一点,提前退出可能是有利的。 AFAICT,当莎拉说您需要手动执行此操作时,她是对的。也就是说,没有必要在每次回调时都这样做。如果有一个循环就足够了。如果没有循环或管道,那么可能不值得提供提前退出机制(如您的回答所述)。
  • 啊,是的,很清楚,具体的例子给你明确的答案。对于未指定的节点/php 应用程序,下载的百分比很低。基于 FUD 的一般问题得到广泛的答案。
  • 我正在寻找一个广泛的答案,因为这是一个广泛而深远的问题。每当您有显式或隐式超时或丢弃请求时,就会出现问题。如果请求因任何原因被关闭(由于超时或请求被客户端或某处关闭),则无法轻松停止该请求的处理。请参阅我的编辑以获取更多说明。
  • @Sarah 你有没有想过这个问题?我有类似的问题。
猜你喜欢
  • 2022-12-14
  • 2016-04-01
  • 2011-02-26
  • 2016-02-06
  • 2017-09-03
  • 2011-07-11
  • 2012-09-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多