后端与系统架构5481 字

石墨文档百万级长连接技术实践

·全库 544 / 611

《石墨文档百万级长连接技术实践》共 0.5 万字,通篇围绕 Redis、场景、架构、Kafka 展开,归在「后端与系统架构」这一层。

覆盖Redis场景架构Kafka监控缓存

适合给 AI 系统搭底座、要管得住的人

目录骨架(8 节)
  1. 2.1 架构
  2. 2.2 痛点
  3. 3.1 整体架构
  4. 3.2 握手流程
  5. 3.3 TLS 内存消耗优化
  6. 3.4 Socket ID 设计
  7. 3.5 集群会话管理方案:事件广播
  8. 3.6 心跳机制
目录 · 8 节

本次分享内容

  1. 网关1.0架构痛点
  2. 网关2.0架构优化
  3. 性能压测
  4. 总结

1 引言

在石墨文档的部分业务中,例如文档分享、评论、幻灯片演示和文档表格跟随等场景,涉及到多客户端数据同步和服务端批量数据推送的需求,一般的 HTTP 协议无法满足服务端主动 Push 数据的场景,因此选择采用 WebSocket 方案进行业务开发。

随着石墨文档业务发展,目前日连接峰值已达百万量级,日益增长的用户连接数和不符合目前量级的架构设计导致了内存和 CPU 使用量急剧增长,因此我们考虑对网关进行重构。

2 网关 1.0

网关 1.0 是使用 Node.js 基于 Socket.IO 进行修改开发的版本,很好的满足了当时用户量级下的业务场景需求。

2.1 架构

网关 1.0 版本架构设计图

网关 1.0 客户端连接流程:

  1. 用户通过 NGINX 连接网关,该操作被业务服务感知;
  2. 业务服务感知到用户连接后,会进行相关用户数据查询,再将消息 Pub 到 Redis;
  3. 网关服务通过 Redis Sub 收到消息;
  4. 查询网关集群中的用户会话数据,向客户端进行消息推送。

2.2 痛点

虽然 1.0 版本的网关在线上运行良好,但是不能很好的支持后续业务的扩展,并且有以下几个问题需要解决:

  • 资源消耗:Nginx 仅使用 TLS 解密,请求透传,产生了大量的资源浪费,同时之前的 Node 网关性能不好,消耗大量的 CPU、内存。
  • 维护与观测:未接入石墨的监控体系,无法和现有监控告警联通,维护上存在一定的困难;
  • 业务耦合问题:业务服务与网关功能被集成到了同一个服务中,无法针对业务部分性能损耗进行针对性水平扩容,为了解决性能问题,以及后续的模块扩展能力,都需要进行服务解耦。

3 网关 2.0

网关 2.0 需要解决很多问题:石墨文档内部有很多组件:文档、表格、幻灯片和表单等等。在 1.0 版本中组件对网关的业务调用可以通过:Redis、Kafka 和 HTTP 接口,来源不可查,管控困难。

此外,从性能优化的角度考虑也需要对原有服务进行解耦合,将 1.0 版本网关拆分为网关功能部分和业务处理部分,网关功能部分为 WS-Gateway:集成用户鉴权、TLS 证书验证和 WebSocket 连接管理等;业务处理部分为 WS-API:组件服务直接与该服务进行 gRPC 通信。可针对具体的模块进行针对性扩容;服务重构加上 Nginx 移除,整体硬件消耗显著降低;服务整合到石墨监控体系。

3.1 整体架构

网关 2.0 版本架构设计图

网关 2.0 客户端连接流程:

  1. 客户端与 WS-Gateway 服务通过握手流程建立 WebSocket 连接;
  2. 连接建立成功后,WS-Gateway 服务将会话进行节点存储,将连接信息映射关系缓存到 Redis 中,并通过 Kafka 向 WS-API 推送客户端上线消息;
  3. WS-API 通过 Kafka 接收客户端上线消息及客户端上行消息;
  4. WS-API 服务预处理及组装消息,包括从 Redis 获取消息推送的必要数据,并进行完成消息推送的过滤逻辑,然后 Pub 消息到 Kafka;
  5. WS-Gateway 通过 Sub Kafka 来获取服务端需要返回的消息,逐个推送消息至客户端。

3.2 握手流程

网络状态良好的情况下,完成如下图所示步骤 1 到步骤 6 之后,直接进入 WebSocket 流程;网络环境较差的情况下,WebSocket 的通信模式会退化成 HTTP 方式,客户端通过 POST 方式推送消息到服务端,再通过 GET 长轮询的方式从读取服务端返回数据。客户端初次请求服务端连接建立的握手流程:

  1. Client 发送 GET 请求尝试建立连接;
  2. Server 返回相关连接数据,sid 为本次连接产生的唯一 Socket ID,后续交互作为凭证;

{"sid":"xxx","upgrades":["websocket"],"pingInterval":xxx,"pingTimeout":xxx}

  1. Client 携带步骤 2 中的 sid 参数再次请求;
  2. Server 返回 40,表示请求接收成功;
  3. Client 发送 POST 请求确认后期降级通路情况;
  4. Server 返回 ok,此时第一阶段握手流程完成;
  5. 尝试发起 WebSocket 连接,首先进行 2probe 和 3probe 的请求响应,确认通信通道畅通后,即可进行正常的 WebSocket 通信。

3.3 TLS 内存消耗优化

客户端与服务端连接建立采用的 wss 协议,在 1.0 版本中 TLS 证书挂载在 Nginx 上,HTTPS 握手过程由 Nginx 完成,为了降低 Nginx 的机器成本,在 2.0 版本中我们将证书挂载到服务上,通过分析服务内存,如下图所示,TLS 握手过程中消耗的内存占了总内存消耗的大概 30% 左右。

资料里没有你的场景?

把你手上的数据和想解决的那个环节发上来,我们按具体情况回,不复述方法论。

发布需求