Ref: https://segmentfault.com/a/1190000024430529

长轮询

概念

就是请求不立马断开,而是保活一段时间。超时之后,断开,然后客户端再次和服务器建立连接。
http 请求是请求 / 响应,完成之后,立马断开。
长轮询:

  • 长:保活长连接,即一般是很短的一段时间内 (比如 1m) 不断开,在这 1m 时间内,服务器如果有新数据就主动写到客户端。
  • 轮询:1m 之后,连接断开,客户端再次和服务器建立连接,也就是多次不停地建立 1m 的连接。

    使用场景

    Apollo
    image.png

    有了 1m 的长连接,为什么还要 5m 一次的短连接,防止 1m 长连接的期间,服务器 push 功能失效,相当于是一个备用机制。

    RocketMQ

    也是一样,客户端和服务器建立长连接 (也是短时间内的保活长连接),在每次的长连接期间,服务器如果有数据就主动写数据到客户端。

    总结

    除了长轮询机制一样,还有注册中心也是一样,apollo 和 rocketmq 都使用了注册中心,其实所有的中间件基本上都是这样,如果要集群 (一般多主多从),就要搞注册中心。

    好处

  • push 模型:可实时写新的数据到客户端。

  • pull 模型:是请求 / 响应模式,完成之后就断开,而不是像 push 模型一样,一直长连接不断开,如果每个连接都不断开,那么服务器连接数量很快会被耗尽。
  • 长轮询的好处是,既有 push 模型的服务器实时写数据到客户端,又有 pull 模型的避免一直长连接。

长轮询是与服务器保持持久连接的最简单的方式,它不使用任何特定的协议,例如 WebSocket 或者 Server Sent Event。容易实现,在很多场景下也很好用。

Ref: https://zh.javascript.info/long-polling

常规轮询

从服务器获取新信息的最简单的方式是定期轮询。也就是说,定期向服务器发出请求:“你好,我在这儿,你有关于我的任何信息吗?” 例如,每 10 秒一次。
作为响应,服务器首先通知自己,客户端处于在线状态,然后 —— 发送目前为止的消息包。
这可行,但是也有些缺点:

  1. 消息传递的延迟最多为 10 秒(两个请求之间)。
  2. 即使没有消息,服务器也会每隔 10 秒被请求轰炸一次,即使用户切换到其他地方或者处于休眠状态,也是如此。就性能而言,这是一个很大的负担。

因此,如果我们讨论的是一个非常小的服务,那么这种方式可能可行,但总的来说,它需要改进。

长轮询

所谓 “长轮询” 是轮询服务器的一种更好的方式。
它也很容易实现,并且可以无延迟地传递消息。
其流程为:

  1. 请求发送到服务器。
  2. 服务器在有消息之前不会关闭连接。
  3. 当消息出现时 —— 服务器将对其请求作出响应。
  4. 浏览器立即发出一个新的请求。

对于此方法,浏览器发出一个请求并与服务器之间建立起一个挂起的(pending)连接的情况是标准的。仅在有消息被传递时,才会重新建立连接。
image.png
如果连接丢失,可能是因为网络错误,浏览器会立即发送一个新请求。
实现长轮询的客户端 subscribe 函数的示例代码:

  1. async function subscribe() {
  2. let response = await fetch("/subscribe");
  3. if (response.status == 502) {
  4. // 状态 502 是连接超时错误,
  5. // 连接挂起时间过长时可能会发生,
  6. // 远程服务器或代理会关闭它
  7. // 让我们重新连接
  8. await subscribe();
  9. } else if (response.status != 200) {
  10. // 一个 error —— 让我们显示它
  11. showMessage(response.statusText);
  12. // 一秒后重新连接
  13. await new Promise(resolve => setTimeout(resolve, 1000));
  14. await subscribe();
  15. } else {
  16. // 获取并显示消息
  17. let message = await response.text();
  18. showMessage(message);
  19. // 再次调用 subscribe() 以获取下一条消息
  20. await subscribe();
  21. }
  22. }
  23. subscribe();

正如你所看到的,subscribe 函数发起了一个 fetch,然后等待响应,处理它,并再次调用自身。

服务器应该可以处理许多挂起的连接
服务器架构必须能够处理许多挂起的连接。
某些服务器架构是每个连接对应一个进程,导致进程数和连接数一样多,而每个进程都会消耗相当多的内存。因此,过多的连接会消耗掉全部内存。
使用像 PHP 和 Ruby 语言编写的后端程序会经常遇到这个问题。
使用 Node.js 编写的服务端程序通常不会出现此类问题。
也就是说,这不是编程语言的问题。大多数现代编程语言,包括 PHP 和 Ruby,都允许实现更适当的后端程序。只是请确保你的服务器架构在同时有很多连接的情况下能够正常工作。

使用场景

在消息很少的情况下,长轮询很有效。
如果消息比较频繁,那么上面描绘的请求 - 接收(requesting-receiving)消息的图表就会变成锯状状(saw-like)。
每个消息都是一个单独的请求,并带有 header,身份验证开销(authentication overhead)等。
因此,在这种情况下,首选另一种方法,例如:WebsocketServer Sent Events