使用 Traefik 提高 WebSocket 应用性能

本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2018年09月04日 统计字数: 4342字 阅读时间: 9分钟阅读 本文链接: https://soulteary.com/2018/09/04/improve-websocket-application-performance-using-traefik.html


使用 Traefik 提高 WebSocket 应用性能

说起 Node.js 的 WebSocket 方案,可选的方案有许多种,其中许多方案都提供将 WS 服务端口和 HTTP 服务复用的方案,然而这种方案真的是最佳选择吗。

不论是专业做实时通信的 socket.io ,还是用户量最大的 Express 的热门中间件 express-ws 都支持端口复用,比如 WS 和 HTTP 复用 80 端口, WSS 和 HTTPS 复用 443 端口。

这里以 express-ws 底层封装的 ws 库为例,来简单剖析, socket.io 实现类似不过分层较多,有兴趣可以围观代码。

不过在聊 Traefik 之前,我们先得聊聊 Node.js 和 Websocket

关于同域名端口复用

先说结论,优点:

  1. 使用简单,尤其是整个项目代码量少的时候。
  2. 服务域名复用,不需要额外进行域名解析。
  3. 能够简单获取 HTTP 请求中的会话信息,进行简单的验证操作,能够代码级复用逻辑。

缺点也很明显:

  1. 因为复用端口,对于每个数据都需要甄别是应该交给 Express 处理还是 WS 处理,存在性能损耗,如果需要进行压缩等操作,会有更多的损耗。
  2. 相同域名不易进行业务水平扩展,比如需要支持更多的实时业务,原本扩容3实例的 WS 服务即可,由于耦合,不得不将整个服务进行扩展,存在更多资源的损耗。
  3. 由于耦合,复杂度相比较“各自独立”的版本高,在维护过程,如果修改底层代码,难免会让两个服务都不够健壮稳定。

从代码实现角度围观端口复用

express-ws 进行端口复用的时候,会进行大量 hacks 操作,包括扩展路由、改写请求地址添加特殊标记、重写默认响应头...

下面这段示例是官方给出的端口复用的例子。

 
  1. var express = require('express');
  2. var app = express();
  3. var expressWs = require('express-ws')(app);
  4.  
  5. app.use(function (req, res, next) {
  6. console.log('middleware');
  7. req.testing = 'testing';
  8. return next();
  9. });
  10.  
  11. app.get('/', function(req, res, next){
  12. console.log('get route', req.testing);
  13. res.end();
  14. });
  15.  
  16. app.ws('/', function(ws, req) {
  17. ws.on('message', function(msg) {
  18. console.log(msg);
  19. });
  20. console.log('socket', req.testing);
  21. });
  22.  
  23. app.listen(3000);

实际使用的时候,访问 WS 的 /,会访问 Express 的 /.websocket?{QUERY},并使用中间件注入处理过程的方式,抢在默认处理前使用 ws 替换处理过程,修改响应头,输出处理后的内容,并调用 res.end 结束流程。

在路由越来越多、请求量越来越多的情况下,会存在很多不必要的损耗。

如何进行服务拆分

如果不需要端口复用,其实直接使用 ws 来监听独立的新端口即可,参考官方示例,可以很轻松的写出这样一个例子:

 
  1. const WebSocket = require('ws');
  2.  
  3. const wss = new WebSocket.Server({
  4. port: 8080,
  5. perMessageDeflate: {
  6. zlibDeflateOptions: { // See zlib defaults.
  7. chunkSize: 1024,
  8. memLevel: 7,
  9. level: 3,
  10. },
  11. zlibInflateOptions: {
  12. chunkSize: 10 * 1024
  13. },
  14. // Other options settable:
  15. clientNoContextTakeover: true, // Defaults to negotiated value.
  16. serverNoContextTakeover: true, // Defaults to negotiated value.
  17. clientMaxWindowBits: 10, // Defaults to negotiated value.
  18. serverMaxWindowBits: 10, // Defaults to negotiated value.
  19. // Below options specified as default values.
  20. concurrencyLimit: 10, // Limits zlib concurrency for perf.
  21. threshold: 1024, // Size (in bytes) below which messages
  22. // should not be compressed.
  23. }
  24. });
  25.  
  26. wss.on('connection', function connection(ws) {
  27. ws.on('message', function incoming(message) {
  28. console.log('received: %s', message);
  29. });
  30.  
  31. ws.send('something');
  32. });
  33.  
  34. server.listen(8080);

HTTP 服务监听在另外一个端口,可以参考 Express 最简单的示例:

 
  1. const express = require('express')
  2. const app = express()
  3.  
  4. app.get('/', (req, res) => res.send('Hello World!'))
  5.  
  6. app.listen(3000, () => console.log('Example app listening on port 3000!'))

这里分别将代码片段进行保存,当你分别使用 Node.js 执行它的时候,你将会得到监听 3000 端口和 8080 端口的简单服务,支持使用 WS 和 HTTP 进行数据交互。

这样的服务的优势和不足

优势:

  1. 可以轻松针对不同协议的服务进行扩容操作。
  2. 彼此运行时资源隔离,安全性和稳定性更好。
  3. 可以使用相同域名、不同端口部署,也可以使用不同域名,默认端口进行部署,部署选择也更多。

劣势:

  1. 在不依赖 RDS 、 RedisCache 等方案的前提下,请求之间的数据难以共享。
  2. 如果需要都使用默认端口进行部署,那么需要额外进行一个域名的解析。

搭配 Traefik 使用

我将 SSL 证书挂载和 HTTP 压缩放在 Traefik 端处理,相比较 Node.js 来做,一来可以保障业务代码功能独立纯粹,二来性能确实不如它,而且维护起来也比较麻烦(证书管理)。

对于接入网关的服务,只要声明提供 HTTP 和 WS 的端口和对应的域名即可,程序启动之后, Traefik 会自动将应用挂载到对应域名上,并支持 HTTP(S) 和 WS(S) 的服务。

为图简便,我将上面的代码片段保存为一个基础镜像,交付给编排工具使用。

如果你将上面的代码片段保存为一个文件,可以试试下面的配置:

 
  1. version: '3'
  2.  
  3. services:
  4.  
  5. node:
  6. image: docker.lab.com/example.lab.com:0.0.1
  7. restart: always
  8. labels:
  9. - "traefik.enable=true"
  10. - "traefik.web.port=3000"
  11. - "traefik.web.frontend.rule=Host:web.soulteary.com"
  12. - "traefik.ws.port=8080"
  13. - "traefik.ws.frontend.rule=Host:ws.soulteary.com"
  14. networks:
  15. - traefik
  16. expose:
  17. - 3000
  18. - 8080
  19. extra_hosts:
  20. - "web.soulteary.com:127.0.0.1"
  21. - "ws.soulteary.com:127.0.0.1"
  22.  
  23. networks:
  24. traefik:
  25. external: true

使用上面的配置运行之后,你会发现原本的 3000 端口和 8080 端口,都被“改写”成为了 80 和 443 端口上了,Web 应用使用的时候,便不用额外写入“丑陋”的端口号了,但是这样的配置不利于服务扩展,在端口复用优劣小节中我提到过。

那么,如果你有意将代码进行拆分,那么可以试试下面的配置:

 
  1. version: '3'
  2.  
  3. services:
  4.  
  5. web:
  6. image: docker.lab.com/example.lab.com:0.0.1
  7. restart: always
  8. labels:
  9. - "traefik.enable=true"
  10. - "traefik.web.port=3000"
  11. - "traefik.web.frontend.rule=Host:web.soulteary.com"
  12. networks:
  13. - traefik
  14. expose:
  15. - 3000
  16. extra_hosts:
  17. - "web.soulteary.com:127.0.0.1"
  18.  
  19. ws:
  20. image: docker.lab.com/example.lab.com:0.0.1
  21. restart: always
  22. labels:
  23. - "traefik.enable=true"
  24. - "traefik.ws.port=8080"
  25. - "traefik.ws.frontend.rule=Host:ws.soulteary.com"
  26. networks:
  27. - traefik
  28. expose:
  29. - 8080
  30. extra_hosts:
  31. - "ws.soulteary.com:127.0.0.1"
  32.  
  33. networks:
  34. traefik:
  35. external: true

扩容也很简单,如果你要以 2:3 的比例运行不同协议的话,只需要:

 
  1. docker-compose scale web=2 ws=3

其他

如果你还在使用 ajax polling 或许这个方案可以给你更好的体验。

如果你对 Traefik 期望有更多的了解,也欢迎和我沟通讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值