最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
浅谈 Websocket 和反向代理实践
时间:2022-06-29 02:54:55 编辑:袖梨 来源:一聚教程网
目录
什么是 Websocket
为什么使用 Websocket
性能理论分析
Websocket 服务端和反代实践
对反代性能进行测试
Websocket 和 HTTP
什么是 Websocket
Websocket 是起初由 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:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
服务端返回响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
为什么是 Websocket
由于基本的 HTTP 是半双工的请求-响应的模式,客户端发起一个请求后服务端才能返回一个响应,在设计之初是没有考虑服务端主动推送数据、没有考虑数据实时更新的情况的;在 Websocket 出现之前,为了保持数据的实时更新和服务端主动推送数据给客户端,最早也是最简单粗暴的实现是让客户端不断发送请求(即轮询),后来出现了 Comet 模型和 SSE(Server Sent Event),Comet 是让服务器在没有接到浏览器显式请求的情况下,实现推送数据的一类技术的总称,是一类模式而不是一个标准,其原理是大多都是服务端延迟完成 HTTP 的响应,又可以分为长轮询和流两种实现方式。
Polling(轮询):客户端定期发起请求,无论请求的内容是否存在,服务器马上返回响应。对于出现时间不可预测的内容,轮询效率很低。
Long-Polling(长轮询):包括插入
Streaming(流):包括隐藏 iframe 和 XHR,客户端发送请求,服务器保持连接,并持续发回响应,如 Google talk 使用了嵌入隐藏 iframe,而 Gmail 使用的是 XHR。
但是这些技术都是基于 HTTP 的,而 HTTP/1.1 的标准 RFC 7230 并不建议客户端打开过多的连接,基于 HTTP 的传输技术不仅实现繁杂、开销较大(比如频繁的 TCP 握手和 HTTP header 传递),而且总是会受到 HTTP 单向传输和连接数的限制。
一言蔽之,HTTP 不是为实时全双工的目的而设计的协议,在 HTTP 的基础上模拟全双工制约太大,因此要提高性能一个独立于 HTTP 的协议是必要的,使用独立的协议可以完全摆脱这些限制。
性能分析
先前提到,在 HTTP 下实现双向持续通信一大问题就是 HTTP 有 header,有时 header 比需要传输的消息本身还大,Websocket 刚被提出时,号称只有2字节的额外开销,能将 HTTP 的延迟降低到三分之一,一个很大的原因就是 Websocket 有很小的 overhead,payload 大小不同的时候的开销如下表:
PAYLOAD CLIENT-TO-SERVER SERVER-TO-CLIENT
< 126 6 2
< 64k 8 4
< 2**63 12 8
此外,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
Forward Proxy: 前向代理模式下,客户端将主动识别代理并使用 CONNECT 方法让服务器 向源站打开隧道避免此问题;
Shell
CONNECT example.com:80 HTTP/1.1
Host: example.com
Reverse Proxy: 反向代理模式下,由于客户端不能察觉代理的存在,nginx 从1.3.13版本之后使用了一个特殊的模式,当从源站接到 HTTP 101 时,nginx 将维持客户端和服务端之间的连接,因为 Upgrade 头不能传递到后端,需要在手动添加 header:
Shell
location /chat/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
一个更加优雅的解决:使用 map 指令,map 是ngx_http_map_module中的指令,可以将变量组合成为新的变量,下面的配置根据客户端传来的连接中是否带有 Upgrade 头来决定是否给源站传递 Connection 头:
Shell
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
...
location /socket/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
默认情况下,连接将会在无数据传输60秒后关闭,proxy_read_timeout 参数可以延长这个时间;或者源站通过定期发送 ping 帧以保持连接并确认连接是否还在使用。
使用 nginx 对 Websocket 进行反代
之前提到,Websocket 是工具性协议,而其开源实现大多是基于后端语言如 Python 和 nodejs,关于选用什么样的服务端和客户端,nginx 官网有过示例,在这里我们大体遵从这篇文章的示范进行测试。需要注意,由于 nodejs 在社区发展中遇到过分支间的斗争,版本号存在历史遗留的一个超大跨度问题,加上 nodejs 本身更新迭代很快,Debian8 默认源里的 nodejs 版本号尚在 0.x 不足以支持本次测试的需要,而主流 LTS 支持版本已经在4.x,我们使用 nodejs 官方目前建议的方法进行安装 4.x 版本的 nodejs:
curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
apt-get update && apt-get install -y nodejs
执行npm install -g ws wscat使用 npm 安装ws和wscat,其中,ws是 nodejs 的 Websocket 实现,我们借助他搭建简单的 Websocket echo server;wscat是一个可执行的 Websocket 客户端,我们用来调试 Websocket 服务是否正常。
完成安装后实现一个简单的服务端:
Shell
console.log("Server started");
var Msg = '';
var WebSocketServer = require('ws').Server
, wss = new WebSocketServer({port: 8010});
wss.on('connection', function(ws) {
ws.on('message', function(message) {
console.log('Received from client: %s', message);
ws.send('Server received from client: ' + message);
});
});
这个简单的服务端实现的是向客户端返回客户端发送是消息,执行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:
可以定义并发度
输出结果简洁明了
针对 Websocket,没有 HTTP 功能
Artillery:
可以自定义 payload、多后端带权重轮询
不能定义并发度
也可以测试 HTTP、支持 cookie
使用 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 可以转发到不同的后端。
defaults
mode http
log global
option httplog
option http-server-close
option dontlognull
option redispatch
option contstats
backlog 10000
timeout client 25s
timeout connect 5s
timeout server 25s
timeout tunnel 3600s
timeout tarpit 60s
option forwardfor
frontend ft_web
bind *:8020 name http
maxconn 10000
default_backend bk_web
backend bk_web
balance roundrobin
server websrv1 127.0.0.1:8010 maxconn 10000
同样使用 Thor 和 Artillery 进行测试,nginx、haproxy 和直接连接性能并无明显区别,也可能是服务端写的很简单的缘故。
HTTP & Websocket
Websocket 出现的一个最大的原因就是 HTTP 是半双工的,Websocket 解决了服务端主动向客户推送数据的难题,但是 HTTP/2 出现了,联想到之前 HTTP/2 的 server push 特性,那么有了 HTTP/2 之后是否还需要 Websocket 这样基于 TCP 的独立双工协议,InfoQ 和 Stackoverflow 的结论都是认为 HTTP/2 不能代替 Websocket 这样的推送技术,主要原因是 HTTP/2 的 Server push 实际上是通过服务端在主页响应之前,通过一个 PUSH PROMISE 告诉客户端有哪些比较重要资源需要预先加载,只能被浏览器执行用于加载文件;如果客户端没有请求,服务端并不能主动推流,实际上并没有改变 HTTP 半双工协议的性质。
相关文章
- 《原神》5.2卡池抽取建议 11-14
- 《原神》5.2版本新怪物介绍 11-14
- 《原神》希诺宁增伤触发方法 11-14
- 《原神》循音觅奇活动入口 11-14
- 《原神》循音觅奇兑换码获取方法 11-14
- 《原神》花羽会活动飞行技巧介绍 11-14