浅谈 Websocket 和反向代理实践

目录

什么是 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(长轮询):包括插入 <scrpit> 标签实现长轮询和 XMLHttpRequest(XHR)客户端发送请求,服务器保留连接和请求持续一段时间,如果需要的内容在预定时间内出现则返回,如需要的内容没有出现,服务器将关闭请求。但是当消息量较大时,长轮询相比轮询没有性能提升,反而可能因为需要维护长连接造成性能比轮询更差。
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 半双工协议的性质。

时间: 2024-09-20 01:01:47

浅谈 Websocket 和反向代理实践的相关文章

浅谈Java反射与代理_java

Java反射机制与动态代理,使得Java更加强大,Spring核心概念IoC.AOP就是通过反射机制与动态代理实现的. 1 Java反射 示例: User user = new User(); user.setTime5Flag("test"); Class<?> cls = Class.forName("com.test.User"); //接口必须public,无论是否在本类内部使用!或者使用cls.getDeclaredMethod(),或者遍历修

浅谈Flux架构及Redux实践

Flux概述 Flux是Facebook用来构建用户端的Web应用程序的体系架构,与其它形式化的框架相比,它更像是一个架构思想,用于管理和控制应用中数据的流向.这里应用中的数据指包括但不限于来自服务端的数据页面中view的一些状态(如一个面板是展开还是关闭),临时存储在本地需要持久化到服务端的数据等. 好了,说了这么多好像还是一脸懵逼,不慌,接下来看看展开式. MVC 在讲述Flux之前,我们看看之前传统的MVC架构以及在前端中的一些问题继而思考Flux带来的改变.MVC(Model-View-

我的网站架构经验之反向代理篇

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 对于站长来说,网站的正常运行是最基本的,如果网站运行不稳定,什么百度权重,友情链接,用户体验都TMD的扯淡去吧.今天天气不错,特记录一下平常在使用过程中及帮朋友架构的一些技巧.我在这里主要谈的是反向代理的方法,在此以lnmp环境为例,windows平台也类似,此架构适用于10-50万独立IP的网站(身边的朋友主要是视频类的). linux系统

Nginx的反向代理与负载均衡

1.1 集群是什么 简单地说,集群就是指一组(若干个)相互独立的计算机,利用高速通信网络组成的一个较大的计算机服务系统,每个集群节点(即集群中的每台计算机)都是运行各自服务的独立服器.这些服务器之间可以彼此通信,协同向用户提供应用程序.系统资源和数据,并以单一系统的模式加以管理.当用户客户机请求集群系统时,集群给用户的感觉就是一个单一独立的服务器,而实际上用户请求的是一组集群服务器. 打开谷歌.百度的页面,看起来好简单,也许你觉得用几分钟就可以制作出相似的网页,而实际上,这个页面的背后是由成千上

Nginx学习之反向代理WebSocket配置实例

写在开始 去年,做过一款竞赛打分的APP.具体需求,同组教师之间可以相互通信,及时通知同组人员,其他组员做了那些操作(当然,这只是针对特定操作). 实现方案 采用目前比较成熟的WebSocket技术,WebSocket协议为创建客户端和服务器端需要实时双向通讯的webapp提供了一个选择.其为HTML5的一部分,WebSocket相较于原来开发这类app的方法来说,其能使开发更加地简单.大部分现在的浏览器都支持WebSocket,比如Firefox,IE,Chrome,Safari,Opera,

Nginx反向代理websocket配置实例_nginx

最近有一个需求,就是需要使用 nginx 反向代理 websocket,经过查找一番资料,目前已经测试通过,本文只做一个记录 复制代码 代码如下: 注: 看官方文档说 Nginx 在 1.3 以后的版本才支持 websocket 反向代理,所以要想使用支持 websocket 的功能,必须升级到 1.3 以后的版本,因此我这边是下载的 Tengine 的最新版本测试的 1.下载 tengine 最近的源码 复制代码 代码如下: wget http://tengine.taobao.org/dow

浅谈百度外链对网站关键词排名的影响

浅谈百度外链对网站关键词排名的影响 建站的朋友都知道,网站优化无外乎内容,外部链接,关键词.而今天在这里要跟大家谈的是,外部链接跟关键词排名的关系.首先,我们需要知道,什么叫外部链接?外部链接,也叫反向链接,就是从外部网站指向自己网站的链接;查询外部链接数量可以用工具查询,也可以使用查询指令在搜索引擎查询.我们查询各大搜索引擎的反链,其目的也就是要对查询的结果进行分析,根据查询的结果分析出反链的存在形式: 1.超链接形式 这种反链可以是关键词链接,比如:广州尔码,也可以是网址链接;而关键词链接又

浅谈iOS Crash(二)

浅谈iOS Crash(一) 一.僵尸对象(Zombie Objects) 1.概述 僵尸对象:已经被释放掉的对象.一般来说,访问已经释放的对象或向它发消息会引起错误.因为指针指向的内存块认为你无权访问或它无法执行该消息,这时候内核会抛出一个异常( EXC ),表明你不能访问该存储区域(BAD ACCESS).(EXC_BAD_ACCESS类型错误) 调试解决该类问题一般采用NSZombieEnabled(开启僵尸模式). 2.使用NSZombieEnabled Xcode提供的NSZombie

浅谈web网站架构演变过程

原文:浅谈web网站架构演变过程 前言 我们以javaweb为例,来搭建一个简单的电商系统,看看这个系统可以如何一步步演变.   该系统具备的功能:   用户模块:用户注册和管理 商品模块:商品展示和管理 交易模块:创建交易和管理    阶段一.单机构建网站 网站的初期,我们经常会在单机上跑我们所有的程序和软件.此时我们使用一个容器,如tomcat.jetty.jboos,然后直接使用JSP/servlet技术,或者使用一些开源的框架如maven+spring+struct+hibernate.