Websocket是HTML5以后的一个新事物,能够方便的实现客户端到服务端的长会话,特别适合用于客户端须要接收服务端推送的场景。例如在线客服聊天,提醒推送等等。改变了以往客户端只能经过轮询或者long poll来获取服务端状态的限制。html
首先咱们来看一下Websocket协议和HTTP有什么关系呢?
本质上说,Websocket和HTTP就不是一个协议,层级不同。可是为了兼容现有浏览器的握手规范,必须借助HTTP协议创建链接。前端
这是一个Websocket的握手请求nginx
GET wss://server.example.com/ HTTP/1.1 Host: server.example.com Pragma: no-cache Cache-Control: no-cache Connection: Upgrade Upgrade: websocket Origin: https://server.example.com Accept-Encoding: gzip, deflate, br Sec-WebSocket-Version: 13 Sec-WebSocket-Key: fFFIlFcwULSAmQacRAbS2A== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
这里面有几个和通常HTTP Request不同的地方,web
Connection: Upgrade Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: fFFIlFcwULSAmQacRAbS2A== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
这是告诉服务端这不是一个普通的请求,而是Websocket协议。Sec-WebSocket-Key 是一个Base64 encode的值,是浏览器随机生成的,用于让服务端知道这是一个全新的socket客户端。浏览器
服务端若是开启了Socket监听,那么就会返回这样的Response服务器
HTTP/1.1 101 Switching Protocols Date: Fri, 09 Mar 2018 16:24:45 GMT Connection: upgrade upgrade: websocket sec-websocket-accept: i/tCy92JmOXIoZwGi8ROh6CgUwk=
表示接收了请求,而且即将切换到Websocket协议,因此code是101。Sec-WebSocket-Accept 这个则是通过服务器确认,而且加密事后的 Sec-WebSocket-Key。到这里HTTP协议的任务就已经完成,以后的通讯都是基于Websocket协议了。websocket
本质上说握手请求就是一个特殊的HTTP Request,只是须要加一些上文提到的特殊内容,从Nignx官方介绍能够看到app
location /wsapp/ { proxy_pass http://wsbackend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; }
只是在Request header加了两个属性,而且强制升级到HTTP 1.1,缘由是HTTP 1.0不支持keep alive。若是使用HTTP 1.0发握手请求,服务端返回101之后就会直接结束此次HTTP会话了。这一点也为以后的坑埋下了伏笔。负载均衡
自从上线了Websocket服务以后,就会常常发现socket没法创建,得到504的超时响应。socket
HTTP/1.1 504 Gateway Time-out Date: Fri, 09 Mar 2018 03:34:54 GMT Content-Type: text/html Content-Length: 272 Connection: keep-alive
并且这一响应只有在通过SLB(负载均衡)时才有,若是直接请求到咱们本身的nginx是没有问题的。可是基于对阿里的信任,仍是以为问题应该仍是咱们本身这儿。从code review到nginx配置,折腾了五六个小时。
最后只有本身搭建的nginx access log上寻找蛛丝马迹,一开始抓到一些响应都是499的返回,而且request_time时间都在60s上下。
[09/Mar/2018:15:04:51 +0800] 100.97.89.10 - - - 10.0.21.11 to: 10.0.20.11:8011: GET /ws/?id=168451&url= http://server.example.com/ HTTP/1.0 upstream_response_time - msec 1520579091.139 request_time 60.000 status 499 client - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
就考虑是否是socket服务端创建链接后响应不及时,让SLB发现60s没有报文交互直接就切断请求了。
可是由于咱们在前端是作了心跳的,即便服务端不响应,只要socket创建经过心跳确定也会在60s内进行交互。不该该出现上面的场景。
以后咱们把access log中socket创建成功的请求和不成功的请求分开放到一块儿对比,发现不成功的都是HTTP 1.0的协议。
[09/Mar/2018:15:03:51 +0800] 100.97.88.238 - - - 10.0.20.11 to: 127.0.0.1:8011: GET /ws/?id=168451&url= http://server.example.com HTTP/1.1 upstream_response_time 11.069 msec 1520579031.198 request_time 11.|
069 status 101 client - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36 |
[09/Mar/2018:15:04:32 +0800] 100.97.88.254 - - - 10.0.20.11 to: 127.0.0.1:8011: GET /ws/?id=168451&url= http://server.example.com HTTP/1.0 upstream_response_time - msec 1520579072.716 request_time 36.755 s|
tatus 499 client - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
就好像这两个请求,同一个页面发出的,可是一个成功一个失败。失败的正好就是HTTP/1.0,为何会有两个版本的协议呢,
为了证据更加“确凿”,咱们对请求进行了抓包分析,并将Sec-WebSocket-Key打印到Nginx的access log中方便trace同一个请求。
GET http://server.example.com/ws/ HTTP/1.1 Host: app.linkflowtech.com Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: http://server.example.com Sec-WebSocket-Key: 8+qDYeKJGFTWKB2ov4p5TA== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
[09/Mar/2018:17:07:07 +0800] 100.97.88.252 - - - 10.0.21.11 to: 10.0.20.11:8011: GET /ws/ HTTP/1.0 upstream_response_time - msec 1520586427.537 request_time 59.999 status 499 client - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36 8+qDYeKJGFTWKB2ov4p5TA==
2018-03-09 17:12:04
能够看到都是 8+qDYeKJGFTWKB2ov4p5TA== 的请求,可是在通过SLB进入nginx时候协议降级到了1.0.这叫一个酸爽,赶忙给阿里云开了工单,通过大概3~4个小时的交流。最终得到一个连接,里面有这样的描述
如何在阿里云负载均衡上启用WS/WSS支持?
无需配置,当选用HTTP监听时,默认支持无加密版本WebSocket协议(WS协议);当选择HTTPS监听时,默认支持加密版本的WebSocket协议(WSS协议)。
注意:须要将实例升级为 性能保障型实例。详细参见如何使用负载均衡性能保障型实例。
这个大坑就在"注意"那一段,咱们的SLB是性能共享型而不是性能保障型。看来也不是阿里云的问题,是咱们的SLB档次不够高啊。知道缘由后,马上付费升级了保障型。实测一下全部问题都解决了。
虽然问题解决了,可是其实很难理解厂商的逻辑,为何性能共享型中某些SLB节点就会降级HTTP协议版本呢,要知道1.0版本已是一个至关落后的版本了。
在此记录一下心路历程,为了让其余使用阿里云的同窗不要重蹈覆辙。