【问题标题】:Why are my server sent events arriving as a batch?为什么我的服务器发送的事件是成批到达的?
【发布时间】:2018-11-25 00:17:39
【问题描述】:

我有一个基于 Java 8 / Spring4 的 Web 应用程序,它使用服务器发送事件 (SSE) 向运行一些 Javascript 并更新进度条的基于浏览器的客户端报告长期运行进程的进度。在我的开发环境和我们的开发服务器上,SSE 近乎实时地到达客户端。我可以看到他们使用 Chrome 开发工具到达(连同他们的时间戳)并且进度条更新顺利。

但是,当我部署到我们的生产环境时,我观察到不同的行为。在长时间运行的过程完成之前,事件不会到达浏览器。然后它们都突然到达(根据开发工具,这些事件的时间戳都在几百毫秒内)。进度条在持续时间内停留在 0%,然后很快跳到 100%。同时,我的服务器日志告诉我事件是定期生成和发送的。

这是相关的服务器端代码:

public class LongRunningProcess extends Thread {
    private SseEmitter emitter;
    public LongRunningProcess(SseEmitter emitter) {
        this.emitter = emitter;
    }
    public void run() {
        ...
        // Sample event, representing 10% progress
        SseEventBuilder event = SseEmitter.event();
        event.name("progress");
        event.data("{ \"progress\": 10 }"); // Hand-coded JSON
        emitter.send(event);
        ...
    }
}

@RestController
public class UploadController {
    @GetMapping("/start")
    public SseEmitter start() {
        SseEmitter emitter = new SseEmitter();
        LongRunningProcess process = new LongRunningProcess(emitter);
        process.start();
        return emitter;
    }
}

这是相关的客户端 Javascript:

EventSource src = new EventSource("https://www.example.com/app/start");
src.addEventListener('progress', function(event) {
    // Process event.data and update progress bar accordingly
});

我相信我的代码相当典型,并且在 DEV 中运行良好。但是,如果有人能看到问题,请告诉我。

问题可能与我们的生产服务器的配置有关。 DEV 和 PROD 都运行相同版本的 Tomcat。但是,其中一些是通过负载平衡器访问的(F5 在这种情况下)。几乎所有这些都支持 CDN(在我们的例子中是 Akamai)。此设置的某些部分是否会导致 SSE 被缓冲(或排队或缓存),这可能会产生我所看到的内容?

根据基础架构配置理念,我在响应标头中观察到以下内容。在开发环境中,我的浏览器接收到:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: Keep-Alive
Content-Type: text/event-stream;charset=UTF-8
Keep-Alive: timeout=15, max=99
Pragma: no-cache
Server: Apache
Transfer-Encoding: chunked
Via: 1.1 example.com

这是我对事件流的期望。内容长度未知的分块响应。在生产环境中,我的浏览器收到不同的东西:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: keep-alive
Content-Type: text/event-stream;charset=UTF-8
Content-Encoding: gzip
Content-Length: 318
Pragma: no-cache
Vary: Accept-Encoding

这里返回的内容具有已知长度并被压缩。我认为这不应该发生在事件流中。似乎有些东西正在将我的事件流转换为单个文件。关于如何弄清楚这是怎么回事的任何想法?

【问题讨论】:

  • 你找到解决这个问题的方法了吗?我面临同样的问题。我的调查使我得出了同样的结论。禁用默认启用的 CompressingFilter 后,sse 按预期工作。我尝试绕过“文本/事件流”的过滤器,但没有帮助。
  • 你在使用CompressingFilter吗?
  • @Mustafa 不,我没有使用CompressingFilter。我还没有找到解决方案,但是我目前正在跟进可能的线索。如果这会导致解决方案,我会添加更多信息。
  • 我切换到 tomcat 压缩,不再看到 SSE 的这个问题。
  • @Mustafa 你应该记录你所做的作为一个答案,以防其他人发现它有用。

标签: javascript java spring server-sent-events


【解决方案1】:

当使用代理设置时,webpack 开发服务器也会缓冲服务器发送的事件。

【讨论】:

    【解决方案2】:

    我们进行了大量调查,以确定问题的原因是我们网络路径中的元素。所以上面的代码是正确且安全的。如果您发现 SSE 缓冲,您很可能需要检查关键网络元素的配置。

    在我的例子中,Akamai 作为我们的 CDN,使用 F5 设备作为负载平衡器。事实上,两者都可以引入缓冲,这使得诊断问题变得相当困难。

    Akamai 边缘服务器默认缓冲事件流。这可以通过使用 Akamai 的advanced metadata 禁用并通过custom behaviours 进行控制。目前,这无法通过 Amakai 的门户直接控制,因此您需要让他们的工程师为您做一些工作。

    F5 设备似乎也默认缓冲响应数据。幸运的是,这很容易更改,可以通过设备的配置门户自行完成。对于有问题的虚拟设备,转到 Profile : Services : HTTP 并将 Response Chunking 的配置更改为 Preserve(在我们的例子中,它默认为 Selective)。

    完成这些更改后,我开始从我们的 PROD 服务器(而不仅仅是我们的 DEV 服务器)近乎实时地接收 SSE。

    【讨论】:

      【解决方案3】:

      这并不是这个问题的完全解决方案,而是与 SSE、Spring 和压缩的使用有关。

      在我的情况下,我在 Spring 应用程序中配置了 ziplet CompressionFilter,它正在关闭 Http 响应并导致 SSE 失败。这似乎与an open issue in the ziplet project 有关。我在 application.properties (server.compression.enabled=true) 中禁用了过滤器并启用了 Tomcat 压缩,它解决了 SSE 问题。

      请注意,我没有更改默认的 compressionMinSize 设置,这可能与 SSE 流量没有被压缩和通过有关。

      【讨论】:

        【解决方案4】:

        您是否尝试过其他浏览器?我正在尝试调试 SSE 在 iPhone 客户端上工作但在 MacOS/Safari 或 Firefox 上不工作的类似问题。

        您的问题可能有解决方法 - 如果服务器发送“连接:关闭”而不是保持活动状态,或者甚至关闭连接本身,客户端应在几秒钟内重新连接,服务器将发送当前进度条事件。

        我猜关闭连接会刷新导致问题的任何缓冲区。

        【讨论】:

          猜你喜欢
          • 2013-01-11
          • 1970-01-01
          • 2012-12-22
          • 2016-07-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-14
          • 1970-01-01
          相关资源
          最近更新 更多