为什么 rails 中的 SSE 连接挂起?

Why SSE in rails hang on the connection?

我正在阅读 this post,其中讨论了 不阻塞服务器线程的 SSE 连接作者正在描述如何解决阻塞问题。

我的困惑是,如果我在服务器端(sse.close)和客户端(source.close())关闭流,为什么首先会出现问题?为什么服务器挂起连接?

问题不在于关闭连接,而是关于何时你正在这样做 - 这种情况发生的时间比常规的要晚(几十秒,甚至几小时)请求,即使是最重的请求,通常也能在几秒钟内处理。

例如,假设(非常简化和近似):

  1. 你有 10 个 threads/workers,没有 SSE
  2. 您在 0.1 秒内为每个 html 页面提供服务
  3. 用户最多会等待 1 秒来加载页面,然后才会泪流满面并离开您的网站
  4. 用户将在请求下一个页面之前阅读该页面 9 秒

这样每个线程每秒可以处理 10 个页面,所有线程 - 每秒 100 个页面,因为每个用户每 10 秒最多请求一个 - 你可以处理 大约 1000 个用户 同时使用您的应用程序。

现在逐步向这些页面添加 SSE 更新:

  1. 第一个用户连接,在 0.1 秒内获得 html,然后一个线程在页面重新加载之前被 SSE 请求占用 9 秒,重新加载后它将再次被同一用户的请求锁定
  2. 您只有 9 个空闲线程
  3. 第二个用户连接,同样重复

这样,同一个系统最多只能处理 10 个用户,少了 100 倍。而且您不能只将线程增加到 1000,因为它们不是免费的(内存、调度程序开销等)。

要注意的是,大部分时间大多数此类连接什么都不做,只是在等待事件,因此它们实际上不需要为它们保留线程。所以在不关闭连接的情况下为其他请求释放线程是合乎逻辑的,这就是劫持所做的。

PS。这种方法可以更进一步 - 客户端实时更新连接可以由 rails 服务器以外的进程保持打开状态(非 ruby 并且效率更高),同时仍在 [=52] 中执行所有事件逻辑=].例如,使用 ActionCable 的 anycable 后端,您可以轻松保持数千个并发连接

在通过 SSE 与客户端的所有通信完成后执行 sse.close(在完成所有繁重的工作并将所有结果发送到客户端之后)。 但在那之前 Rails (Puma) 线程被占用。

post 中描述的方法展示了如何立即释放 Rails (Puma) 线程并在与客户端保持连接的同时继续完成工作。

使用 Rack Hijacking API 你可以立即释放 Rails (Puma) 线程,就在服务器收到请求之后和你实际关闭执行 sse.close 的 SSE 连接之前。