双向流模拟同步HTTP连接
AJAX毫无疑问,增强了单个用户的体验。然而,对于如何能够增强“参与感”的技术,能够见到的论著寥寥无几。例如,聊天室,如何能有效、低延迟、高伸缩性的进行多人聊天?我们能够想到的方式有如下几种:
- HTTP pull. 通常的实现方式是隐藏帧刷新,或者XMLHTTP访问。每隔一段时间(2秒或者更多)对服务器进行访问从而获得更新信息。
- HTTP Push. 现在有了一个更流行的名词:Comet。基本实现原理是通过服务端向客户端回复一个永远不会断开的连接。通过不断输出可以直接执行的javascript代码,或者一块可以被应用程序解析的字符串,达到相互通信的目的。
第一种方法的缺陷一目了然:效率低,延迟高。由于并非每一次请求都会有返回的数据,当次请求的返回值则毫无意义。而这其中建立HTTP 连接的时间,服务器处理的时间,客户端消耗时间等,让这些请求毫无价值。没有数据的请求让服务器疲于应付无效请求,浪费了计算资源。另外,由于没有一个合理的请求频率策略,导致应当及时返回的消息在最坏的情况下于延迟时间返回。例如,用户在00:01发送了一条消息,系统每隔2秒扫描一次,在00:03秒,另外一个用户才得到这个消息。当然在聊天应用没有关系,但是实时性要求更高的场景下这些往往不一定可以接受。
第二种方法解决了实时连接的问题,然而这要求更高超的服务器端编程技巧。现在看到的WEB服务器都并非为长连接而设计,你可以看到将这种方案部署到常见的Web应用服务器上的时候性能下降得多么厉害。更为现代的Web服务器,如twisted, 基于MINA的asyncWEB采用了event driven IO以及线程重用等技术,在这方面表现很好。然而,这种长连接最大的问题是,无状态架构不能很好的被应用,从而影响了系统的伸缩性。
目前更好的一种方案是:BOSH。这种原本为了jabber设计的规范,在应付这种需求的时候显得格外合适。它的基本原理不是常见的采用1条HTTP通道,而是不多不少的两条。策略如下:
- 系统启动后,客户端向服务器端发送请求(通道1)
- 如果有消息,服务器直接向客户端返回消息。客户端收到消息后,除了完成业务处理外,还立即向服务器发送一条新的请求。
- 如果没有消息,服务器保持通道1的请求。如果这个时候客户发来另外一个请求(通道2),服务器立即向通道1返回空响应,并保持通道2的请求。
- 如果很长一段时间没有任何消息(例如,30秒),服务端返回空给持有的通道。这个消息立即会触发客户端的一个新的请求。
- 如果很长时间没有接到用户的请求(30秒,例如),服务器断开客户端相关的session信息并清理相关资源。
我们看看有没有解决上述的问题:
- 效率:可以看到,只有当有数据的时候才会真正的完成请求(完成传输),无效的传输大大减少。通过规划合理的心跳检查时间,可以最大化传输的有效性。
- 延迟:由于在任意时候都有一条HTTP通道保持连接,因此可以想象延迟降到了最低。
- 伸缩性:由于采用两条通道轮换处理,常见的无状态架构可以被应用,不会影响伸缩性。
可以看到,通过增加一条HTTP通道进行轮换处理,提升了传输效率,降低了延迟同时不牺牲系统的可伸缩性,值得尝试。