从 HAProxy 到 Envoy:数百万并发 WebSocket 连接迁移实践

某海外大型协作平台拥有覆盖全球的用户群。在业务高峰期,同时在线用户数量可达数百万级。平台内的大部分沟通都依赖大量短消息的实时收发,而这些实时通信能力背后,离不开稳定、高效的 WebSocket 连接和入口负载均衡系统。

自早期架构演进以来,该平台长期使用 HAProxy 作为所有入站流量的负载均衡器。随着业务规模持续增长,HAProxy 在配置更新、热重启、长连接管理和负载均衡能力方面逐渐暴露出一些运维挑战。为了解决这些问题,团队最终选择将 WebSocket 协议栈从 HAProxy 迁移至 Envoy Proxy。

从 HAProxy 到 Envoy:数百万并发 WebSocket 连接迁移实践

本文将围绕 WebSocket 连接、HAProxy 负载均衡、Envoy 动态配置、热重启机制和生产环境迁移实践,介绍这次大规模迁移的背景、过程、挑战与结果。

WebSocket 如何支撑实时通信

为了实现消息的即时传递,平台使用 WebSocket 连接。WebSocket 是一种双向通信链路,它支撑了诸如“某某正在输入……”这样的实时提示,也让用户几乎可以即时看到对方发送的内容。

从 HAProxy 到 Envoy:数百万并发 WebSocket 连接迁移实践

WebSocket 连接会接入一个名为 wss(WebSocket Service)的系统,并通过面向公网的 WebSocket 域名对外提供访问。需要说明的是,这些域名并不是普通网站地址;如果直接在浏览器中访问,通常只会收到 HTTP 404 错误。

从 HAProxy 到 Envoy:数百万并发 WebSocket 连接迁移实践

WebSocket 连接最初是普通的 HTTPS 连接,随后客户端会发起协议切换请求,将连接升级为 WebSocket。在该平台内部,不同用途对应不同类型的 WebSocket 服务,例如消息服务、在线状态服务,以及其他实时通信服务。其中还有一个 WebSocket 端点专门面向应用程序,因为应用程序同样需要与平台进行实时交互。

过去,团队在某云服务商的多个区域中部署了一组专门处理 WebSocket 流量的 HAProxy 实例。这些实例负责在尽可能靠近用户的位置终止 WebSocket 连接,并将请求转发至相应的后端服务。

为什么从 HAProxy 迁移到 Envoy Proxy

虽然团队长期使用 HAProxy,并且已经积累了较成熟的大规模运维经验,但随着业务规模持续增长,一些运维层面的挑战逐渐显现。这些问题促使团队开始评估其他替代方案,其中最重要的候选之一就是 Envoy Proxy。

热重启与动态配置:Envoy 相比 HAProxy 的优势

在该平台内部,后端服务端点列表会频繁变化,例如新增实例、下线实例或调整服务部署。为了让负载均衡器感知这些变化,HAProxy 通常有两种配置更新方式。

第一种方式是使用 HAProxy Runtime API。团队曾在部分 HAProxy 实例上采用过这种方式,并在相关复盘文章中总结过经验。

第二种方式是将后端配置渲染到 HAProxy 配置文件中,然后重新加载 HAProxy。此前的 WebSocket 负载均衡层采用的就是这种方式。

每次 HAProxy 重启时,系统都会创建一组新进程来处理新的入站连接。为了避免长连接用户频繁断线,旧进程会继续运行数小时,等待已有 WebSocket 连接自然断开。然而,团队也不能让过多旧进程长期共存,因为每个旧进程都持有一份当时的配置副本。如果旧进程过多,实例就无法尽快收敛到新的配置版本。

因此,团队必须定期回收旧的 HAProxy 进程,并限制 HAProxy 的重启频率,以避免后端端点频繁变化时触发过多重启。无论采用哪种方式,都需要额外的基础设施来管理 HAProxy 的重新加载流程。

相比之下,Envoy 支持动态配置的集群和端点。也就是说,即使端点列表发生变化,Envoy 也不需要重新加载。如果代码或配置发生变化,Envoy 还可以在不中断现有连接的情况下完成热重启。Envoy 会使用 inotify 监控文件系统中的配置更新;在热重启过程中,它还会将统计信息从父进程复制到子进程,因此仪表和计数器不会被重置。

这些能力显著降低了运维成本,也使团队不再需要额外维护一套专门用于配置变更和重启管理的服务。

Envoy 的高级负载均衡能力

Envoy 提供了多种高级负载均衡能力,包括:

  • 内置的区域感知路由;
  • 通过异常值检测实现的被动健康检查;
  • 紧急路由能力:正常情况下,Envoy 只会将流量路由到健康的后端服务器;但也可以配置为当健康服务器数量低于某个阈值时,将流量发送到所有后端服务器,而不再严格依据健康状态筛选。这一能力曾在一次由基础设施大范围网络问题引发的故障中发挥重要作用。

基于这些原因,团队在 2019 年决定将入口负载均衡层从 HAProxy 迁移到 Envoy Proxy,并率先从 WebSocket 协议栈开始。

此次迁移的主要目标包括:提升可运维性,利用 Envoy 的新能力,并推动底层基础设施进一步标准化。通过在各个组件中逐步从 HAProxy 迁移到 Envoy,团队无需再同时理解两套软件的特性、维护两种配置体系、管理两套构建与发布流程。

在迁移启动时,该平台的服务网格已经使用 Envoy Proxy 作为数据平面。同时,内部也有熟悉 Envoy 的工程师,因此团队能够较快获得相关技术支持。

如何生成 Envoy 配置

迁移的第一步,是审查现有 WebSocket 层配置,并生成与之等价的 Envoy 配置。对团队来说,管理 Envoy 配置是整个迁移过程中最大的挑战之一。

Envoy 功能丰富,配置模型也与 HAProxy 明显不同。Envoy 配置主要围绕四个核心概念展开:

  • 监听器(Listener):接收请求的入口,可以是 TCP 套接字、SSL 套接字或 Unix 域套接字;
  • 集群(Cluster):表示请求将被发送到的内部服务,例如消息服务器、在线状态服务器等;
  • 路由(Route):用于连接监听器与集群,决定请求应被转发至哪个后端;
  • 过滤器(Filter):用于对请求进行处理、转换或扩展。

该平台的配置管理主要通过内部配置管理系统完成。最初引入 Envoy 时,团队将 Envoy 配置部署为模板文件,但这种方式管理起来繁琐且容易出错。为了解决这个问题,团队构建了一套配置管理库和自定义资源,用于生成 Envoy 配置。

从 HAProxy 到 Envoy:数百万并发 WebSocket 连接迁移实践

在配置管理系统内部,Envoy 配置采用单例模式,这与“每台主机只有一份 Envoy 配置”的事实相匹配。所有资源都会基于这个单例进行操作,例如添加监听器、路由或集群。配置管理流程运行结束后,系统会生成、验证并安装 envoy.yaml 文件。团队不会写入中间配置,因为这些中间配置可能处于无效状态。

一个典型示例是:创建一个包含两条路由的 HTTP 监听器,并将流量分别路由到两个动态集群。

从 HAProxy 到 Envoy:数百万并发 WebSocket 连接迁移实践

在 Envoy 中复现原有复杂的 HAProxy 配置并不轻松。好在 Envoy 已经具备大部分所需能力,因此多数情况下,团队只需要在内部配置管理库中补充相应支持即可。对于少数 Envoy 当时尚不具备的能力,团队也进行了扩展,其中一部分贡献给了上游,另一部分则作为内部扩展继续维护。

如何测试和验证 Envoy WebSocket 配置

测试新的 Envoy WebSocket 层是一个持续迭代的过程。团队通常会先手写 Envoy 配置进行原型验证,并在开发机器上完成本地测试。每份测试配置通常只包含一个监听器、一条路由和一个集群。等手写配置验证通过后,再将相应变更合入内部配置管理库。

团队使用 curl 测试了多类 HTTP 路由行为,包括:

  • 根据特定请求头和 Cookie 路由到指定后端;
  • 根据路径、前缀和查询参数路由到指定后端;
  • SSL 证书相关行为。

当出现异常情况时,团队会在本地机器上启用 Envoy 调试日志。调试日志能够清楚说明 Envoy 为什么会将某个请求路由到某个特定集群。Envoy 的调试日志非常有用,但也非常冗长,并且会带来较高开销,因此强烈不建议在生产环境中启用。

可以通过以下命令启用调试日志:

curl -X POST http://localhost:<envoy_admin_port>/logging?level=debug

Envoy 管理界面在早期调试中同样非常有用,尤其是以下端点:

  • /clusters:显示所有已配置的集群,包括每个集群中所有上游主机的信息,以及每台主机的统计数据;
  • /certs:以 JSON 格式显示所有已加载的 TLS 证书,包括文件名、序列号、主题备用名称和剩余有效天数;
  • /listeners:显示所有已配置的监听器及其名称和地址。

内部配置管理库会使用 --mode validate 命令行选项运行 Envoy,作为配置验证步骤,以防止无效配置被安装。也可以手动执行这一验证:

sudo /path/to/envoy/binary -c </path/to/envoy.yaml> --mode validate

Envoy 还提供 JSON 格式的监听器日志。团队会将这些日志导入内部日志管道;在导入之前,会先对日志中的个人身份信息进行脱敏处理。这些日志在排查问题时也非常有价值。

在开发环境中确认配置无误后,团队便开始准备在生产环境中进行进一步验证。

如何将 WebSocket 流量迁移到生产环境

为了尽可能降低迁移风险,团队构建了一套新的 Envoy WebSocket 协议栈,其配置与现有 HAProxy 层保持一致。这样一来,团队就可以以逐步、可控的方式将流量迁移到新的 Envoy 协议栈,并在必要时快速切回 HAProxy。

这种做法的缺点是云资源成本上升:迁移期间使用的资源数量几乎翻倍。但为了确保客户能够无感完成这次 WebSocket 迁移,团队认为这项投入是必要且值得的。

团队通过某 DNS 服务商管理 WebSocket 域名记录,并使用加权路由,将流量从 haproxy-wss 逐步转移到 envoy-wss 对应的网络负载均衡器 DNS 名称。

最初几个区域采用分阶段部署,流量比例依次提升到 10%、25%、50%、75% 和 100%。随着团队对新的 Envoy 层和部署流程越来越有信心,后续区域的推进速度也明显加快:此前某个区域的完整迁移耗时一周,而后续区域只用两天就完成了 25%、50%、75% 和 100% 的流量切换。

虽然整个迁移过程总体平稳,没有造成明显中断,但期间仍出现了一些小问题,例如超时配置差异、请求头行为差异等。在迁移过程中,团队多次回滚、修复并重新部署。

从 HAProxy 到 Envoy:数百万并发 WebSocket 连接迁移实践

经过 6 个月的持续推进,迁移最终完成:全球范围内,整个 HAProxy WebSocket 协议栈都被 Envoy Proxy 取代,且客户侧没有感知到明显影响。

迁移复盘:做得好的地方与不足

迁移过程本身相对平静,甚至有些乏味。但对于基础设施迁移来说,乏味往往是一件好事:刺激通常意味着问题不断,而乏味则说明系统运行正常。

团队发现,旧的 HAProxy 配置是在多年演进中自然增长起来的。它很大程度上受 HAProxy 配置模型影响:所有监听器都集中在一份大型配置中。相比之下,Envoy 的配置模型具有更明确的作用域。请求进入某个监听器后,只有该监听器内部的规则会应用于该请求;请求进入某条路由后,也只有该路由上的规则会生效。这让团队更容易将规则与对应请求关联起来。

不过,从旧 HAProxy 配置中提取关键信息,并将其与大量技术债区分开来,耗费了大量时间。团队经常需要判断某条规则存在的原因:它是有意设计,还是意外遗留?哪些行为已经被其他服务依赖?例如,某些服务原本应该只运行在两个虚拟主机中的一个上,但在 HAProxy 中却同时存在于两个虚拟主机中。由于现有代码依赖这种行为,团队不得不在 Envoy 中复现这个错误。

团队也曾忽略 HAProxy 协议栈中的一些细微行为。有时,这些细节非常关键,甚至一度影响到每日活跃用户数等关键业务指标。此外,还有大量小问题需要逐一修复。负载均衡器的行为非常复杂,除了投入时间、持续调试和反复验证之外,并没有真正的捷径。

在迁移初期,团队没有为负载均衡器配置建立完善的测试框架。也就是说,当时并没有自动化测试来验证测试 URL 是否会被路由到正确端点,也没有自动化测试来验证请求头和响应头的相关行为。团队手里只有一份 HAProxy 配置。

事实证明,测试在迁移过程中至关重要。它不仅能验证行为是否正确,还能为“为什么会有这样的预期行为”提供上下文。由于缺少测试,团队经常需要与各服务负责人沟通,才能弄清楚他们依赖了哪些既有行为。

团队构建的配置管理资源有意只支持 Envoy 的部分功能。这样做的好处是库本身更简洁,团队只需要关注实际使用到的能力。缺点是,每当团队希望使用新的 Envoy 功能时,都必须先在配置管理库中添加相应支持。

例如,SNI HTTPS 监听器是在开发过程中单独实现的,因为当时团队认为,单独编写它比在现有监听器中加入支持更简单。而对于虚拟主机支持,团队已经开发并投入使用了大量代码;如果要重构其他团队正在使用的资源,会耗费大量时间。因此,内部配置管理库中的虚拟主机支持一度只是一个权宜方案,后续仍需要进一步完善。

为了更安全地修改 Envoy 配置管理库,团队引入了一套全面的测试,用于生成其他团队的完整配置。这样,在更新 Envoy 相关资源时,团队就可以清楚看到所有生成出来的 Envoy 配置会受到怎样的影响,或者是否完全不受影响。

和其他任何大型迁移一样,这次 WebSocket 迁移的关键在于沟通。团队需要把目标拆解、风险识别、测试验证、发布节奏、回滚预案和复盘经验串联起来,并让相关方持续保持一致。对于类似的大型研发协作场景,PingCode 这类智能化研发管理工具可以帮助团队将目标、需求、项目、测试、发布和知识沉淀统一管理,让迁移过程中的关键信息更容易被追踪、复用和度量。客户体验团队也是重要合作伙伴:他们能够监控收到的工单,并及时发现任何可能表明用户受到迁移影响的信号。

后续规划:入口负载均衡全面标准化到 Envoy

尽管迁移过程中偶尔遇到一些小挫折,但总体来看,Envoy WebSocket 迁移取得了显著成功。

随后,团队又将另一项关键服务——软件客户端指标采集管道——迁移到了 Envoy Proxy。该管道与其他入口负载均衡器相互隔离。与此同时,内部 Web 和 API 流量的负载均衡器也接近完成向 Envoy 的迁移。

这项大规模迁移的最后一步,是将用于在边缘终止入站流量的常规 HTTP 协议栈,也就是非 WebSocket 协议栈,从 HAProxy 迁移到 Envoy;这项工作也在持续推进中。

最终目标是将入口负载均衡器和服务网格数据平面全面标准化为 Envoy Proxy。这将显著降低团队的认知负担和运维复杂性,并让 Envoy 的高级能力应用于整个负载均衡基础设施。

自迁移到 Envoy 以来,平台峰值负载已经大幅超过以往水平,并且运行稳定,没有出现明显问题。

文章包含AI辅助创作,作者:liu,如若转载,请注明出处:https://docs.pingcode.com/baike/5244062

(0)
liuliu
免费注册
电话联系

4008001024

微信咨询
微信咨询
返回顶部