使用 Traefik 提升 WebSocket 應用性能

本站使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或從新修改使用,但須要註明來源。 署名 4.0 國際 (CC BY 4.0)html

本文做者: 蘇洋node

建立時間: 2018年09月04日 統計字數: 4350字 閱讀時間: 9分鐘閱讀 本文連接: soulteary.com/2018/09/04/…git


使用 Traefik 提升 WebSocket 應用性能

提及 Node.jsWebSocket 方案,可選的方案有許多種,其中許多方案都提供將 WS 服務端口和 HTTP 服務複用的方案,然而這種方案真的是最佳選擇嗎。github

不管是專業作實時通訊的 socket.io ,仍是用戶量最大的 Express 的熱門中間件 express-ws 都支持端口複用,好比 WSHTTP 複用 80 端口, WSSHTTPS 複用 443 端口。web

這裏以 express-ws 底層封裝的 ws 庫爲例,來簡單剖析,socket.io 實現相似不過度層較多,有興趣能夠圍觀代碼。ajax

不過在聊 Traefik 以前,咱們先得聊聊 Node.jsWebsocketdocker

關於同域名端口複用

先說結論,優勢:express

  1. 使用簡單,尤爲是整個項目代碼量少的時候。
  2. 服務域名複用,不須要額外進行域名解析。
  3. 可以簡單獲取 HTTP 請求中的會話信息,進行簡單的驗證操做,可以代碼級複用邏輯。

缺點也很明顯:編程

  1. 由於複用端口,對於每一個數據都須要甄別是應該交給 Express 處理仍是 WS 處理,存在性能損耗,若是須要進行壓縮等操做,會有更多的損耗。
  2. 相同域名不易進行業務水平擴展,好比須要支持更多的實時業務,本來擴容3實例的 WS 服務便可,因爲耦合,不得不將整個服務進行擴展,存在更多資源的損耗。
  3. 因爲耦合,複雜度相比較「各自獨立」的版本高,在維護過程,若是修改底層代碼,不免會讓兩個服務都不夠健壯穩定。

從代碼實現角度圍觀端口複用

express-ws 進行端口複用的時候,會進行大量 hacks 操做,包括擴展路由、改寫請求地址添加特殊標記、重寫默認響應頭...安全

下面這段示例是官方給出的端口複用的例子。

var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);

app.use(function (req, res, next) {
  console.log('middleware');
  req.testing = 'testing';
  return next();
});

app.get('/', function(req, res, next){
  console.log('get route', req.testing);
  res.end();
});

app.ws('/', function(ws, req) {
  ws.on('message', function(msg) {
    console.log(msg);
  });
  console.log('socket', req.testing);
});

app.listen(3000);
複製代碼

實際使用的時候,訪問 WS/,會訪問 Express/.websocket?{QUERY},並使用中間件注入處理過程的方式,搶在默認處理前使用 ws 替換處理過程,修改響應頭,輸出處理後的內容,並調用 res.end 結束流程。

在路由愈來愈多、請求量愈來愈多的狀況下,會存在不少沒必要要的損耗。

如何進行服務拆分

若是不須要端口複用,其實直接使用 ws 來監聽獨立的新端口便可,參考官方示例,能夠很輕鬆的寫出這樣一個例子:

const WebSocket = require('ws');

const wss = new WebSocket.Server({
  port: 8080,
  perMessageDeflate: {
    zlibDeflateOptions: { // See zlib defaults.
      chunkSize: 1024,
      memLevel: 7,
      level: 3,
    },
    zlibInflateOptions: {
      chunkSize: 10 * 1024
    },
    // Other options settable:
    clientNoContextTakeover: true, // Defaults to negotiated value.
    serverNoContextTakeover: true, // Defaults to negotiated value.
    clientMaxWindowBits: 10,       // Defaults to negotiated value.
    serverMaxWindowBits: 10,       // Defaults to negotiated value.
    // Below options specified as default values.
    concurrencyLimit: 10,          // Limits zlib concurrency for perf.
    threshold: 1024,               // Size (in bytes) below which messages
                                   // should not be compressed.
  }
});

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
  });

  ws.send('something');
});

server.listen(8080);
複製代碼

HTTP 服務監聽在另一個端口,能夠參考 Express 最簡單的示例:

const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(3000, () => console.log('Example app listening on port 3000!'))
複製代碼

這裏分別將代碼片斷進行保存,當你分別使用 Node.js 執行它的時候,你將會獲得監聽 3000 端口和 8080 端口的簡單服務,支持使用 WSHTTP 進行數據交互。

這樣的服務的優點和不足

優點:

  1. 能夠輕鬆針對不一樣協議的服務進行擴容操做。
  2. 彼此運行時資源隔離,安全性和穩定性更好。
  3. 可使用相同域名、不一樣端口部署,也可使用不一樣域名,默認端口進行部署,部署選擇也更多。

劣勢:

  1. 在不依賴 RDSRedis Cache 等方案的前提下,請求之間的數據難以共享。
  2. 若是須要都使用默認端口進行部署,那麼須要額外進行一個域名的解析。

搭配 Traefik 使用

我將 SSL 證書掛載和 HTTP 壓縮放在 Traefik 端處理,相比較 Node.js 來作,一來能夠保障業務代碼功能獨立純粹,二來性能確實不如它,並且維護起來也比較麻煩(證書管理)。

對於接入網關的服務,只要聲明提供 HTTPWS 的端口和對應的域名便可,程序啓動以後,Traefik 會自動將應用掛載到對應域名上,並支持 HTTP(S)WS(S) 的服務。

爲圖簡便,我將上面的代碼片斷保存爲一個基礎鏡像,交付給編排工具使用。

若是你將上面的代碼片斷保存爲一個文件,能夠試試下面的配置:

version: '3'

services:

  node:
    image: docker.lab.com/example.lab.com:0.0.1
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.web.port=3000"
      - "traefik.web.frontend.rule=Host:web.soulteary.com"
      - "traefik.ws.port=8080"
      - "traefik.ws.frontend.rule=Host:ws.soulteary.com"
    networks:
      - traefik
    expose:
      - 3000
      - 8080
    extra_hosts:
      - "web.soulteary.com:127.0.0.1"
      - "ws.soulteary.com:127.0.0.1"

networks:
  traefik:
    external: true
複製代碼

使用上面的配置運行以後,你會發現本來的 3000 端口和 8080 端口,都被「改寫」成爲了 80443 端口上了,Web 應用使用的時候,便不用額外寫入「醜陋」的端口號了,可是這樣的配置不利於服務擴展,在端口複用優劣小節中我提到過。

那麼,若是你有意將代碼進行拆分,那麼能夠試試下面的配置:

version: '3'

services:

  web:
    image: docker.lab.com/example.lab.com:0.0.1
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.web.port=3000"
      - "traefik.web.frontend.rule=Host:web.soulteary.com"
    networks:
      - traefik
    expose:
      - 3000
    extra_hosts:
      - "web.soulteary.com:127.0.0.1"

  ws:
    image: docker.lab.com/example.lab.com:0.0.1
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.ws.port=8080"
      - "traefik.ws.frontend.rule=Host:ws.soulteary.com"
    networks:
      - traefik
    expose:
      - 8080
    extra_hosts:
      - "ws.soulteary.com:127.0.0.1"

networks:
  traefik:
    external: true
複製代碼

擴容也很簡單,若是你要以 2:3 的比例運行不一樣協議的話,只須要:

docker-compose scale web=2 ws=3
複製代碼

其餘

若是你還在使用 ajax polling 或許這個方案能夠給你更好的體驗。

若是你對 Traefik 指望有更多的瞭解,也歡迎和我溝通討論。


我如今有一個小小的折騰羣,裏面彙集了一些喜歡折騰的小夥伴。

在不發廣告的狀況下,咱們在裏面會一塊兒聊聊軟件、HomeLab、編程上的一些問題,也會在羣裏不按期的分享一些技術沙龍的資料。

喜歡折騰的小夥伴歡迎掃碼添加好友。(請註明來源和目的,不然不會經過審覈) 關於折騰羣入羣的那些事

關於折騰羣入羣的那些事

相關文章
相關標籤/搜索