博客 / 詳情

返回

百萬 TPS 服務發佈無感知!詳解輕量消息隊列無損發佈實踐

作者:辛八

前言

阿里雲輕量消息隊列(原 MNS)【1】是一款易集成、高併發、彈性可擴展的消息隊列服務,助力開發者在分佈式組件間高效傳遞數據,構建鬆耦合架構。它憑藉輕量化架構、高可靠性及動態彈性優勢,在業務異步處理、AI 場景(如 LLM 推理調度、GPU 資源調度)中實現規模化應用,服務涵蓋零售、金融、汽車、遊戲等領域的數千家企業客户。

本文將從開發者視角出發,深入解析輕量消息隊列中一項關鍵能力——“無損發佈”的核心優勢、技術實現以及實踐經驗,如果您的業務也有類似需求,本文將為您提供一套經過生產環境驗證的實踐參考。

1. “無損發佈”的核心優勢與業務價值

(1)核心優勢

“無損發佈”並非一個新概念,在業內有各種各樣的方案。相比之下,輕量消息隊列的“無損發佈”具備以下幾個關鍵優勢:

  • 百萬 TPS 級無感知、無報錯的服務發佈:大多數“無損”方案依然會造成一部分流量的業務中斷,而本方案經百萬 TPS 生產實踐驗證,在發佈過程中,客户側不會有任何業務中斷。
  • 兼容存量用户:客户側無需任何改造,避免“要求客户端升級”這類難以推行的操作。
  • 高魯棒、低維護:方案簡潔、魯棒性強,在不改動架構的情況下無須進行維護。
  • 通用性強:可適配絕大多數基於 HTTP 協議的無狀態應用。

(2)業務價值

面對發佈期間可能出現的分鐘級概率性報錯,我們不禁會問:是否有必要投入資源去解決?我們的業務是否需要借鑑本文方案進行改造?

通過業務改造前後對比(見下圖)可見,實現“無損發佈”帶來的業務收益遠超多數人的預期,下方清晰地展示了其業務價值,為上述問題提供了明確的答案。

image

接下來,將從開發者視角出發,依次介紹輕量消息隊列“無損發佈”的網絡架構、核心實現以及落地實踐。

2. 阿里雲輕量消息隊列“無損發佈”方案解析

(1)網絡架構

image

輕量消息隊列“無損發佈”的網絡架構簡化模型如上圖所示,其設計有以下幾個核心點:

  1. 聚焦網絡入口層:對於無狀態應用(MNS 實際存在有狀態部分,本文暫不涉及),升級過程中只需考慮如何將待發布的應用進行 TCP 連接優雅摘除即可,故重點聚焦於架構的網絡入口層。
  2. 架構通用性強:該架構與大部分 HTTP 業務架構類似,因此具備良好的通用性,可被廣泛採用。
  3. 方案兼容性強:在方案落地過程中,我們遇到了多種不同的部署形態及組件,如 ACK(阿里雲 Kubernetes 服務)、ECS 的不同部署形態,LB 多種不同組件的不同版本,ACK 的不同網絡架構等。儘管過程中面臨諸多挑戰,但最終實現了全面兼容,驗證了該方案具備組件可替換、通用性好的優點。
  4. 與應用解耦:該實現與應用解耦,不需要對應用進行改造(注:如不存在 nginx proxy,也可將對應能力移植到應用上),可適用於大多數場景下的應用。
  5. 客户端無感知:這是本方案的一大優勢,僅需在服務側進行改造即可,Client 無須任何變動,因此可以很好地兼容存量用户。

(2)核心實現流程

image

輕量消息隊列(原 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

image

  • K8S(阿里雲容器服務 ACK)

在 ACK 架構下,由於多增加一層 K8S 的網絡架構,實現過程經歷了較多曲折。最終方案的實現原理涉及 K8S 的 kube-proxy 網絡組件,下面的每個配置幾乎都是都是經過權衡後的標準化配置。為了簡潔説明,下面將重點闡述實現方式。

image

(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 腳本即可,以下為發佈腳本的偽代碼:

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 定義如下:

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)驗證

以模擬場景的測試數據舉例説明,對比本方案在改造前後的差異:

image

從以上的測試結果可以看到,在經過“無損發佈”改造後,發佈期間的客户側的錯誤率歸零。

4. 總結

本文詳細闡述了阿里雲輕量消息隊列(原 MNS)實現“無損發佈”的核心技術路徑。

其關鍵點在於:

首先,通過改造負載均衡(LB)的健康檢查機制與利用其 Draining 能力,實現了新流量的無感知隔離;

其次,通過對 Nginx 源碼的改造,在 HTTP 響應中注入關閉幀,引導客户端主動、優雅地關閉活躍連接,並結合超時機制處理空閒連接,最終確保在應用更新前所有流量被平滑清空。

最終,給出兼容 ECS 與 Kubernetes 等多種部署環境的落地實踐。

正是基於上述這些關鍵點的實現,MNS 才得以在客户側無改造的前提下,對外提供真正意義上的無損能力。無論是在百萬級 TPS 的高併發場景下,還是在複雜的網絡架構中,MNS 都能確保服務發佈和版本迭代對客户業務的零中斷、零感知,從而持續提升了客户的整體體驗。無損發佈不僅是輕量消息隊列眾多優點之一,更是我們產品踐行“追求卓越”“客户第一”理念的切實體現。

【1】阿里雲輕量消息隊列(原 MNS)

https://www.aliyun.com/product/smq

點擊此處,瞭解輕量消息隊列(原 MNS)更多詳情

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.