百万 TPS 服务发布无感知!详解轻量消息队列无损发布实践

作者:辛八

前言

阿里云轻量消息队列(原 MNS)【1】是一款易集成、高并发、弹性可扩展的消息队列服务,助力开发者在分布式组件间高效传递数据,构建松耦合架构。它凭借轻量化架构、高可靠性及动态弹性优势,在业务异步处理、AI 场景(如 LLM 推理调度、GPU 资源调度)中实现规模化应用,服务涵盖零售、金融、汽车、游戏等领域的数千家企业客户。

本文将从开发者视角出发,深入解析轻量消息队列中一项关键能力------"无损发布"的核心优势、技术实现以及实践经验,如果您的业务也有类似需求,本文将为您提供一套经过生产环境验证的实践参考。

1. "无损发布"的核心优势与业务价值

(1)核心优势

"无损发布"并非一个新概念,在业内有各种各样的方案。相比之下,轻量消息队列的"无损发布"具备以下几个关键优势:

  • 百万 TPS 级无感知、无报错的服务发布:大多数"无损"方案依然会造成一部分流量的业务中断,而本方案经百万 TPS 生产实践验证,在发布过程中,客户侧不会有任何业务中断。
  • 兼容存量用户:客户侧无需任何改造,避免"要求客户端升级"这类难以推行的操作。
  • 高鲁棒、低维护:方案简洁、鲁棒性强,在不改动架构的情况下无须进行维护。
  • 通用性强:可适配绝大多数基于 HTTP 协议的无状态应用。

(2)业务价值

面对发布期间可能出现的分钟级概率性报错,我们不禁会问:是否有必要投入资源去解决?我们的业务是否需要借鉴本文方案进行改造?

通过业务改造前后对比(见下图)可见,实现"无损发布"带来的业务收益远超多数人的预期,下方清晰地展示了其业务价值,为上述问题提供了明确的答案。

接下来,将从开发者视角出发,依次介绍轻量消息队列"无损发布"的网络架构、核心实现以及落地实践。

2. 阿里云轻量消息队列"无损发布"方案解析

(1)网络架构

轻量消息队列"无损发布"的网络架构简化模型如上图所示,其设计有以下几个核心点:

  1. 聚焦网络入口层:对于无状态应用(MNS 实际存在有状态部分,本文暂不涉及),升级过程中只需考虑如何将待发布的应用进行 TCP 连接优雅摘除即可,故重点聚焦于架构的网络入口层。
  2. 架构通用性强:该架构与大部分 HTTP 业务架构类似,因此具备良好的通用性,可被广泛采用。
  3. 方案兼容性强:在方案落地过程中,我们遇到了多种不同的部署形态及组件,如 ACK(阿里云 Kubernetes 服务)、ECS 的不同部署形态,LB 多种不同组件的不同版本,ACK 的不同网络架构等。尽管过程中面临诸多挑战,但最终实现了全面兼容,验证了该方案具备组件可替换、通用性好的优点。
  4. 与应用解耦:该实现与应用解耦,不需要对应用进行改造(注:如不存在 nginx proxy,也可将对应能力移植到应用上),可适用于大多数场景下的应用。
  5. 客户端无感知:这是本方案的一大优势,仅需在服务侧进行改造即可,Client 无须任何变动,因此可以很好地兼容存量用户。

(2)核心实现流程

轻量消息队列(原 MNS)"无损发布"的核心实现流程如上图所示,简化描述如下:

阶段一:摘除待发布应用的连接

  • 步骤一:摘除 TCP 建连请求,且保证残余连接正常转发以及应用正常响应(实现参考下文 4.(1)部分)。
  • 步骤二:优雅关闭残余连接(实现参考下文 4.(2)部分)。

阶段二:发布应用

  • 步骤三:确认应用已无连接以及请求后,进行发布。
  • 步骤四:流量引入发布完成后应用。

在技术方案的设计过程中,我们始终遵循"奥卡姆剃刀法则":极简的往往最鲁棒、通用,而本实现流程正是这一原则的体现。

在技术方案的落地过程中,尽管轻量消息队列(原 MNS)历史较长,架构、组件情况较为复杂,遇到了多种部署架构(如 K8S、ESC),各类组件(如 client、LB、nginx、kube-proxy)的配置与兼容性等问题,该方案在轻量消息队列(原 MNS)的架构中最终得以成功落地,充分验证了其通用性与鲁棒性。

3. 百万 TPS 轻量消息队列"无损发布"实践

(1)摘除 TCP 建连请求

概述

摘除 TCP 建连请求的方式,简单理解就是使用 LB(负载均衡)将对应 RS(后端服务器)摘掉,但要实现真正"优雅"的无损发布(即客户侧无任何报错与感知),需要解决以下几个关键技术问题:

  • 如何摘除新建连请求? -- 需要明确摘除 RS 的管控方式,如果基于 API 可能会因为组件依赖导致可迁移性差。

  • 如何保证优雅? -- 摘除 RS 后,新建连接请求会被拒绝,但存量 TCP 连接也会因路由规则被移除而中断,从而导致流量受损。

  • 如何兼容 K8S 架构? -- 在 K8S 架构下,LB 与 POD 之间多了 kube-proxy 这一网络组件,且这个组件在 ACK 不同网络架构下又有不同表现,该如何兼容?

为了解决以上问题,我们针对 K8S、ECS 架构及不同版本组件,都提出了相应的解决方案。

实现方案

  • ECS
  • K8S(阿里云容器服务 ACK)

在 ACK 架构下,由于多增加一层 K8S 的网络架构,实现过程经历了较多曲折。最终方案的实现原理涉及 K8S 的 kube-proxy 网络组件,下面的每个配置几乎都是都是经过权衡后的标准化配置。为了简洁说明,下面将重点阐述实现方式。

(2)优雅断连

"优雅断连"指的是在业务无中断前提下关闭 TCP 连接,这一实现是本方案中的核心技术难点,也是最重要的部分。

概述

实现"优雅断连",我们需要重点关注两个核心点:

  • TCP 连接只能由客户端关闭

    • 原因:TCP 网络链路为 client -> LB (-> kube-proxy) -> tengine -> 应用,若断连 LB 下游任一连接,都会导致 client 侧依然认为与 LB 的连接存在,会继续往对应连接发送 http 请求,由于下游连接已中断,从而导致业务中断。
  • 不能要求客户升级客户端

    • 原因:无法要求所有客户升级,无损改动无意义。

基于以上两点,我们通过两个方式保证优雅断连:

  • 对于有请求连接

    • 方式:返回 Response 带上 HTTP 关闭帧。
    • 原因:由于 client 对 http 协议的原生兼容,接收到 Response 后会完成本次请求 + 自行关闭连接。
  • 对于无请求连接

    • 方式:在摘除 LB 后等待 socketTimeOut 时间。
    • 原因:等待 socketTimeOut 时间内,对于有请求的连接,已经通过第一种方式关闭掉,对于没请求的连接,client 会自行对该连接标志为废弃连接。

该方案之所以具备通用性,是因为其利用了所有标准 HTTP 客户端均支持的协议特性。

实现方案

前置改造:

  • 改造 nginx 源码 ,提供一个主动向 client 端发 HTTP 协议的关闭帧信息的开关。

原因:nginx(or tengine)不支持用户控制的向 client 端发送关闭帧信息的能力(源码中写死) ,只能通过自行改造源码进行能力支持。(相关讨论参考相关 github issue)

发布步骤:

  • 步骤一:打开 nginx 回复关闭帧的开关 -- MNS 侧是通过 status 标志文件控制,删除 status 标志文件后会摘除 LB 的 RS 以及 nginx 回复关闭帧。
  • 步骤二:等待 socketTimeOut 时间 -- 关闭(keep-alive-timeout 内)无请求的连接。
  • 步骤三:确认已无连接 -- 通过请求量、连接数等可观测手段,确认已无残余连接,此时应用已经摘除所有流量,可以进行升级操作。

(3)CI/CD 接入

前文(1)、(2)聚焦在架构侧的改造,由于其能力实现较为细节和深入,对于平时较少接触相关业务内容的同学来说,可能会较为抽象,甚至产生"接入自动化复杂、门槛高"的误解。实际上,该方案设计简洁,能够轻松融入现有的大部分 CI/CD 和 K8s 体系,下面将介绍如何接入。

ECS

ECS 接入 CI/CD,只需改造 offline 部分以及 online 部分 CI/CD 脚本即可,以下为发布脚本的伪代码:

bash 复制代码
pubstart)
    offline
    stop_http
    stopjava
    startjava
    start_http
    online
;;	

offline_http() {
    echo "[ 1/10] -- offline http from load balance server"
    # 删除标志文件
    # 效果:
    # 1. (改造一)LB健康检查失败,摘除新建连请求
    # 2. (改造二)nginx对所有response带上HTTP中断帧,client侧受到response后关闭连接
    rm -f $STATUSROOT_HOME/status

    # 效果:应用侧对所有请求快速返回,防止极端情况
    curl localhost:7001/shutDownGracefully

    # 等待socketTimeout + LB健康检查时间
    # 效果:client侧(到发布中应用的)所有连接到达Timeout,发布中应用的所有连接被摘除
    sleep $SOCKET_TIMEOUT + $HEALTH_CHECK

}

stop_http() {
    关闭nginx
}

stopjava() {
    关闭java
}

startjava() {
    启动新java包
}

start_http() {
    启动nginx
}

online() {
    # 回挂标志文件
    touch $STATUSROOT_HOME/status
}

K8S 架构

K8S 架构下,pod 关闭过程中原生预留了优雅关闭的接口,将上述脚本放至 preStop 即可,yaml 定义如下:

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: main-container
        image: my-image:latest
        lifecycle:
          # 将下线脚本定义为preStop即可
          preStop:
            exec:
              command: 
                - sh
                - /home/admin/offline.sh
        ports:
        - containerPort: 8080
      - name: sidecar-container
        image: sidecar-image
        lifecycle:
          # 其他sideCar等待即可,防止影响主容器
          preStop:
            exec:
              command: 
                - sh
                - -c
                - sleep 100

(4)验证

以模拟场景的测试数据举例说明,对比本方案在改造前后的差异:

从以上的测试结果可以看到,在经过"无损发布"改造后,发布期间的客户侧的错误率归零。

4. 总结

本文详细阐述了阿里云轻量消息队列(原 MNS)实现"无损发布"的核心技术路径。

其关键点在于:

首先,通过改造负载均衡(LB)的健康检查机制与利用其 Draining 能力,实现了新流量的无感知隔离;

其次,通过对 Nginx 源码的改造,在 HTTP 响应中注入关闭帧,引导客户端主动、优雅地关闭活跃连接,并结合超时机制处理空闲连接,最终确保在应用更新前所有流量被平滑清空。

最终,给出兼容 ECS 与 Kubernetes 等多种部署环境的落地实践。

正是基于上述这些关键点的实现,MNS 才得以在客户侧无改造的前提下,对外提供真正意义上的无损能力。无论是在百万级 TPS 的高并发场景下,还是在复杂的网络架构中,MNS 都能确保服务发布和版本迭代对客户业务的零中断、零感知,从而持续提升了客户的整体体验。无损发布不仅是轻量消息队列众多优点之一,更是我们产品践行"追求卓越""客户第一"理念的切实体现。

【1】阿里云轻量消息队列(原 MNS)

www.aliyun.com/product/smq

点击此处,了解轻量消息队列(原 MNS)更多详情

相关推荐
cherishSpring7 小时前
Eureka服务端启动
云原生·eureka
慢慢慢时光8 小时前
本地k8s集群的搭建
云原生·容器·kubernetes
橘子编程8 小时前
Kubernetes (K8S)知识详解
云原生·容器·kubernetes
观无11 小时前
基于Eureka和restTemple的负载均衡
云原生·eureka·负载均衡
跑不完的脚本11 小时前
基于K8s ingress灰度发布配置
docker·云原生·容器·kubernetes
SilentCodeY17 小时前
Docker 数据目录迁移完整流程
docker·云原生·linux系统·迁移
来自于狂人19 小时前
基于大模型打造故障预警服务器巡检机器人
运维·服务器·人工智能·云原生·机器人
Cyber4K20 小时前
Haproxy算法精简化理解及企业级高功能实战
云原生·实战·haproxy
大咖分享课1 天前
云原生环境下的安全控制框架设计
云原生·零信任·云原生安全·kubernetes安全·安全框架设计·微服务安全