目录
什么是 WebsocketWebsocket 是起初由 HTML5 定义的一个建立在单 TCP 连接上的全双工通信协议,后从 HTML5 规范独立并由 RFC 6445 标准化,但仍被习惯性地称为 HTML5 Websocket。 Websocket 工作在 HTTP 的80和443端口并使用前缀 ws://或者 wss://(with ssl) 进行协议标注,但是实际上这个协议和 HTTP 并没有什么关联性,参见 RFC 6455 Section 1.7 的说明,在建立时,使用 HTTP/1.1 101状态码进行协议切换,当前标准不支持两个客户端之间不借助 HTTP 直接建立 Websocket 连接,参见 StackOverflow:
服务端返回响应:
为什么是 Websocket由于基本的 HTTP 是半双工的请求-响应的模式,客户端发起一个请求后服务端才能返回一个响应,在设计之初是没有考虑服务端主动推送数据、没有考虑数据实时更新的情况的;在 Websocket 出现之前,为了保持数据的实时更新和服务端主动推送数据给客户端,最早也是最简单粗暴的实现是让客户端不断发送请求(即轮询),后来出现了 Comet 模型和 SSE(Server Sent Event),Comet 是让服务器在没有接到浏览器显式请求的情况下,实现推送数据的一类技术的总称,是一类模式而不是一个标准,其原理是大多都是服务端延迟完成 HTTP 的响应,又可以分为长轮询和流两种实现方式。
但是这些技术都是基于 HTTP 的,而 HTTP/1.1 的标准 RFC 7230 并不建议客户端打开过多的连接,基于 HTTP 的传输技术不仅实现繁杂、开销较大(比如频繁的 TCP 握手和 HTTP header 传递),而且总是会受到 HTTP 单向传输和连接数的限制。 一言蔽之,HTTP 不是为实时全双工的目的而设计的协议,在 HTTP 的基础上模拟全双工制约太大,因此要提高性能一个独立于 HTTP 的协议是必要的,使用独立的协议可以完全摆脱这些限制。 性能分析先前提到,在 HTTP 下实现双向持续通信一大问题就是 HTTP 有 header,有时 header 比需要传输的消息本身还大,Websocket 刚被提出时,号称只有2字节的额外开销,能将 HTTP 的延迟降低到三分之一,一个很大的原因就是 Websocket 有很小的 overhead,payload 大小不同的时候的开销如下表:
此外,Websocket 允许传输二进制内容,减少了转码的开销。 关于性能开销的分析参考了 http://tavendo.com/blog/post/dissecting-websocket-overhead/ 可以看到,大体的结论是,TCP 上的开销比 Websocket 本身的开销要大的多。 在 Websocket 和 Comet 的对比测试中,Websocket 针对大量用户请求很小的消息这样一个场景,比轮询极大的减少了非必需的网络传输,提高了带宽利用率:
nginx 官方也做过 nginx 对 Websocket 反代的性能测试:NGINX WebSocket Performance 搭建 Websocket 服务端和代理服务端关于 Websocket 的服务端实现,W3C 给出了 Websocket API,但是到现在实现 Websocket 的通用服务端较少,考虑到 Websocket 这个东西更多的是工具而不是目的,一个纯粹的 Socket Server 实际上并没有什么实际作用,更多的是实现了 Websocket 封装的后端语言的库,JS 有 socket.io、Python 有 tornado 都实现了 Websocket 库。 Websocket 的实现方式是通过发送 Upgrade 头,但是 Upgrade 和 Connection 都是逐跳(hop-by-hop)的 header,即只在一跳内有效而不会被传递到源站,这个 header 在正常情况下经过代理的时候会被去掉,因此需要在反代服务器上做一点设置。 Nginx 在博客上给出了一个实践样例:Using Nginx as a Websocket Proxy
使用 nginx 对 Websocket 进行反代之前提到,Websocket 是工具性协议,而其开源实现大多是基于后端语言如 Python 和 nodejs,关于选用什么样的服务端和客户端,nginx 官网有过示例,在这里我们大体遵从这篇文章的示范进行测试。需要注意,由于 nodejs 在社区发展中遇到过分支间的斗争,版本号存在历史遗留的一个超大跨度问题,加上 nodejs 本身更新迭代很快,Debian8 默认源里的 nodejs 版本号尚在 0.x 不足以支持本次测试的需要,而主流 LTS 支持版本已经在4.x,我们使用 nodejs 官方目前建议的方法进行安装 4.x 版本的 nodejs:
执行 npm install -g ws wscat使用 npm 安装 ws和 wscat,其中, ws是 nodejs 的 Websocket 实现,我们借助他搭建简单的 Websocket echo server; wscat是一个可执行的 Websocket 客户端,我们用来调试 Websocket 服务是否正常。 完成安装后实现一个简单的服务端:
这个简单的服务端实现的是向客户端返回客户端发送是消息,执行 node server.js运行这个简单的 echo 服务。 使用 wscat --connect ws://127.0.0.1:8010,输入任意内容进行测试,得到相同返回则说明运行正常。再使用上面的 nginx 配置对 simple server 进行反代,端口开在8020。 常见的压力测试工具 Apache JMeter 使用插件后可以对 Websocket 进行测试,但是这个插件较老,而且 JMeter 本身使用比较麻烦。而类似 ab 的轻量级测试工具目前发现的有 thor 和 Artillery,这两工具都是 nodejs 写的,nginx.com 在测试中使用的是 thor。 Thor:
Artillery:
使用 Artillery 进行测试: artillery quick --duration 60 --rate [number]ws://127.0.0.1:8020或 thor --amount 10000 --concurrent [number]ws://127.0.0.1:8020,二者 在简单模式下发送的 payload 不相同,结果不能直接进比较。 由于 Websocket 原理,总请求数增加的时候会吃满一个网卡上的所有端口导致测试结束,而且测试中发现,传输速率较大时,前端 nginx 的 CPU 占有率较低,反而是后端 node 把一个 CPU 吃满。 主要原因还是源站过于简单,而由于 Websocket 的工具性质,也很难有代表性的性能评价标准,只能知道 nginx 在作为 Websocket 反代的时候能轻松抗住上万并发。 使用 haproxy 对 Websocket 进行反代除了 nginx 之外,实际上还有 haproxy 也可以进行 Websocket 代理,haproxy 上这个功能出现的比 nginx 还要稍微早一点(haproxy 在2012年就有了 Websocket 反代支持,nginx 是在2013年初),且 haproxy 在两种模式下都支持 Websocket:TCP 模式下转发协议包,HTTP 模式下代替客户端和源站进行 Websocket 握手,本部分参考:Websocket load-balancing with haproxy-Blog.haproxy.com
haproxy 的 TCP mode 基本没有什么讨论的,HTTP mode 则有点像 nginx 的模式,通过 判断Sec-WebSocket-Protocol header,haproxy 可以转发到不同的后端。
同样使用 Thor 和 Artillery 进行测试,nginx、haproxy 和直接连接性能并无明显区别,也可能是服务端写的很简单的缘故。 HTTP & WebsocketWebsocket 出现的一个最大的原因就是 HTTP 是半双工的,Websocket 解决了服务端主动向客户推送数据的难题,但是 HTTP/2 出现了,联想到之前 HTTP/2 的 server push 特性,那么有了 HTTP/2 之后是否还需要 Websocket 这样基于 TCP 的独立双工协议,InfoQ 和 Stackoverflow 的结论都是认为 HTTP/2 不能代替 Websocket 这样的推送技术,主要原因是 HTTP/2 的 Server push 实际上是通过服务端在主页响应之前,通过一个 PUSH PROMISE 告诉客户端有哪些比较重要资源需要预先加载,只能被浏览器执行用于加载文件;如果客户端没有请求,服务端并不能主动推流,实际上并没有改变 HTTP 半双工协议的性质。
详细讨论参见:HTTP/2 中 Server push 的讨论 此外,SSE 和 Websocket 理论存在建立在 HTTP/2 上的可能性,IETF 在这个方向有一个草案:WebSocket over HTTP/2.0,可惜这个草案一直没有进一步具体定义,也没有具体实现,基本处于停滞状态。 关于 HTTP 和 websocket 的选择,可以参考微软的这篇博客:When to use a HTTP call instead of a WebSocket (or HTTP 2.0),总结一下是:
这些情况应该使用 HTTP:
这些情景应该使用 Websocket:
(责任编辑:IT) |