坑系列之阿里SLB上使用Webscoket

Websocket是HTML5以後的一個新事物,能夠方便的實現客戶端到服務端的長會話,特別適合用於客戶端須要接收服務端推送的場景。例如在線客服聊天,提醒推送等等。改變了以往客戶端只能經過輪詢或者long poll來獲取服務端狀態的限制。html

和HTTP協議有什麼關係

首先咱們來看一下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

怎麼經過nginx轉發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版本已是一個至關落後的版本了。

在此記錄一下心路歷程,爲了讓其餘使用阿里雲的同窗不要重蹈覆轍。

相關文章
相關標籤/搜索