字節跳動自研線上引流回放系統的架構演進

本文選自“字節跳動基礎架構實踐”系列文章。


“字節跳動基礎架構實踐”系列文章是由字節跳動基礎架構部門各技術團隊及專家傾力打造的技術乾貨內容,和大家分享團隊在基礎架構發展和演進過程中的實踐經驗與教訓,與各位技術同學一起交流成長。


線上流量引流線下環境是一個通用需求,廣泛應用於功能測試、壓測等場景。本文介紹了引流系統在字節跳動的發展過程和系統設計,希望能給大家帶來一點新的思考和收穫。

1. 背景

AB Test (diff 測試) 是在互聯網行業中比較常用的驗證方法,例如 Google 通過 AB 實驗針對廣告和推薦的效果做驗證,Twitter 研發了 Diffy ,把 Diff 驗證能力應用到了 API 接口的質量保障上。通常 AB Test 有兩種形式,一種是線上多個服務版本,通過接入側分流 AB 來做實驗,但對於廣告這類場景,一旦某個模型有問題,就會造成資損。另外一個模式是通過線上的流量複製回放到內部環境,這種方式對於生產是絕對安全的,例如 Twitter 的 AB 驗證服務 diffy 就是走的這個模式。今天字節內部推薦,廣告,等很多業務線都是通過線上流量實時回放的模式做 AB 實驗。為了滿足業務的需求,我們自研了一套線上流量錄製回放系統 ByteCopy 來支撐業務的海量流量吞吐和不斷產生的對於流量錄製回放的新需求。

這篇文章會從業務場景、系統架構、問題分析等幾個方面來介紹 ByteCopy 這套系統的演進過程。

2. 基於 TCPCopy 構建第一代引流系統

2.1 業務需求

剛開始業務的需求還是比較簡單的,就是希望在業務方部署好了目標服務 (HTTP 和 RPC) 後,引流系統可以把對應線上生產的流量複製一份並轉發過去,並且整個引流過程可以靈活管控,只在需要流量的時候開啟。

2.2 系統選型

從引流自身來看,主要分為 2 種類型,主路複製和旁路複製,我們分別來分析一下這兩種模式的優劣。

2.2.1 主路複製

主路複製是指在調用鏈中進行流量複製:一種是在業務邏輯中進行流量複製,如在調用 API/RPC 過程中,由業務方編寫代碼邏輯,記錄 request / response 信息內容;另外一種是在框架(如使用 Dubbo、Service Mesh 等網絡框架)處理邏輯中進行復制。

優點

  • 可以高度結合業務邏輯,實現細粒度定製化流量複製,比如可以只針對某個用戶的流量進行復制,可以最大程度上提升引流源上的有效流量採集比。

缺點

  • 業務邏輯與引流邏輯耦合度較高,功能上相互影響。
  • 每個請求都需要進行額外引流處理,對業務流程存在性能影響。

適用場景

  • 對於流量有細粒度篩選要求的,或與業務邏輯有關,可以選擇主路複製;如 Service Mesh 中根據染色標記,進行流量複製。

2.2.2 旁路複製

對比主路複製,旁路複製突出了業務無感知的特點,一般是由第三方服務在網絡協議棧中,監聽複製流量

優點

  • 與業務解耦,可以獨立部署升級引流模塊,業務方無需關注引流功能實現;通過在協議棧底層進行流量複製,性能較好。

缺點

  • 4 層網卡層面的網絡包抓取後,仍需要進行數據包的重組和解析,需要額外的消耗計算資源。
  • 往往需要全量抓包解析再進行篩選,無法結合業務邏輯進行定製化的採樣。

開源方案 TCPCopy

雖然 Linux 提供了 libpcap 這樣的底層 packet capture 庫,不過本著快速交付業務需求的目標,我們選擇了開源的 TCPCopy 來作為整個引流系統的核心基礎。TCPCopy 在這裡就不多介紹,只在下面附上一張簡單的架構圖,其中 TCPCopy 和 Intercept 是 TCPCopy 的兩個組件,相關細節感興趣的同學可以自行查找資料。

字節跳動自研線上引流回放系統的架構演進

TCPCopy 的主要優勢:

  • 協議無感知,可以透明轉發,能夠支持基於 TCP 的任意應用層協議,如 MySQL,Kafka,Redis 等
  • 實時轉發,延時較低
  • 可以保留原始請求 IP 端口信息,測試服務器可用於統計

同時,也具有以下不足:

  • 無法動態添加多個下游服務器
  • 由於透明轉發,不做協議解析,無法發現數據異常,如部分 TCP 包丟失,測試服務器將收到不完整的數據;此外,也無法對應用層數據進行篩選和修改進行修改
  • 核心組件設計時未進行多線程設計,處理能力存在瓶頸
  • 需要修改 iptables 來丟棄下游服務的回包,用在生產或公共的測試環境存在較大風險

為了滿足字節的需求,我們在整體架構上引入了一些其他組件來彌補 TCPCopy 自身的不足。

2.3 系統架構

為了解決 TCPCopy 存在的不足,我們在通過 TCPCopy 直接進行流量轉發的方案基礎上又進行了一些優化。

首先,在 TCPCopy 和被測服務之間額外引入了七層代理進行數據轉發。七層代理本身可以校驗請求的完整性,避免不完整的請求被轉發到測試服務對干擾測試造成干擾。

此外,我們將七層代理和 TCPCopy 的 intercept 組件部署在一批專用於流量轉發的服務器上,進行轉發任務時只需要修改這批服務器的 iptables ,而被測服務只需在測試機上正常運行,不用進行額外配置,因此可以儘量避免修改 iptables 帶來的風險。

為了能夠更好地融入公司的技術生態系統,同時支持更豐富的測試場景,我們還專門實現了一個用於測試的七層代理。具體加入了以下能力:

  • 接入了公司的服務發現框架,被測實例只需註冊指定的服務名,就可以收到代理發送的流量。因此流量可以被同時轉發到多個被測實例,也可以動態地添加或刪除被測實例
  • 支持流量過濾。從收到的流量中篩選出指定方法的流量進行轉發。比如可以過濾掉轉發流量中包含寫操作的流量,從而避免對存儲造成汙染
  • 引入流控機制。支持對轉發的流量進行限速,以及通過將收到的請求多次重複發送實現加壓,從而支持簡單的壓測場景

最後,為了讓引流功能變得易用,我們把 TCPCopy 的兩個組件,以及我們的七層代理進行了封裝,打包成了一個平臺提供給用戶。用戶只需要指定引流源和被測服務的 IP 和端口,以及引流任務的持續時間,即可進行一次引流測試。

線上的整體架構大概如下圖所示,用戶提交任務後,我們會負責進行各個組件的調度、配置和部署,將流量從線上轉發到用戶的待測實例。

字節跳動自研線上引流回放系統的架構演進

2.5 存在的問題

隨著規模的逐漸發展和更多用戶場景的提出,這套架構也逐漸暴露出了一些問題。

2.5.1 TCPCopy 存在性能問題

TCPCopy 在實現上沒有進行多線程的設計,因此實際的轉發吞吐能力較為有限,對於一些高帶寬的測試場景無法很好地支持。

2.5.2 現有實現無法支持響應錄製等更多場景

TCPCopy 定位只是請求複製轉發工具,只會複製線上流量的請求部分,而不會複製線上流量的響應。我們接到了一些想要對線上流量進行分析的用戶的需求,他們希望能夠同時收集線上流量的請求和響應,TCPCopy 沒法支持這類場景。

3. 自研 ByteCopy,開啟海量流量和複雜業務場景的支持

前面提到了第一代引流系統存在一些性能和靈活性的問題,與此同時業務也提出了一些新的需求,例如支持 MySQL 協議,支持歷史流量的存儲和回放等。考慮到在現有的 TCPCopy 的架構上很難做擴展,所以我們考慮推翻現有架構,重新構建字節新一代的引流系統 - ByteCopy (寓意是複製線上每一個字節)

在以上演進的基礎上,我們可以按職責把七層流量複製大致分解為下面三個模塊

  • 流量採集
  • 流量解析
  • 流量應用

針對 3 個模塊我們分別展開介紹

3.1 流量採集

字節跳動自研線上引流回放系統的架構演進

流量採集模塊會依據服務部署的平臺以不同方式拉起,如在 Kubernetes 會由 Mesh Agent 喚起,使用 libpcap 監聽特定端口流量,在用戶態重組 TCP 層包,batch 發送至 Kafka。

默認場景下,流量採集模塊只會對被採集的服務監聽的 IP 和端口進行抓包。此外,為了提供出口流量採集(即採集某一服務對其下游依賴發的調用)的能力,流量採集模塊還對接了公司的服務發現框架。在需要對出口流量進行採集時,流量採集模塊會查詢下游依賴服務所有實例的 IP 和端口,對這部分流量也進行採集。(這一方案存在一定問題,後續會詳細介紹)

由於流量採集進程和應用進程是部署在同個 Docker 實例或物理機裡,業務會對流量採集模塊的資源佔用比較敏感,我們除了在代碼層優化,還會用 cgroups 對資源使用做硬性限制。

此外流量採集平臺是多租戶設計,對一個服務可能同時存在多個用戶的不同規格的採集需求,如用戶 A 希望採集 env1 環境 5% 實例流量,用戶 B 希望採集 env1 環境 1 個實例的流量及 env2 環境 1 個實例的流量,如果簡單地獨立處理用戶 A 和 B 的請求,會出現 env1 環境部署 5%+1 實例 env2 部署 1 實例這種冗餘部署。我們的做法是把用戶的請求規格和採集模塊的實際部署解耦,用戶提交一個規格請求後,會先和已有的規格合併,得到一個最小部署方案,然後更新部署狀態。

3.2 流量解析

引流源採集上來的原始流量還是第四層協議,為了支持一些更復雜的功能,比如過濾,多路輸出,歷史流量存儲,流量查詢及流量可視化等等,我們需要將四層流量解析到七層。字節跳動內部服務使用得比較多的協議是 Thrift 和 HTTP ,這兩個根據協議規範即可很好地完成解析。

流量解析有一個難點是判斷流量的邊界,區別於 HTTP/2 等的 Pipeline 連接複用傳輸形式,Thrift 和 HTTP/1.X 在單條連接上嚴格按照請求-響應對來進行傳輸,因此可以通過請求和響應的切換分隔出完整的請求或響應流量。

3.3 流量應用

對於線上採集的流量,不同用戶會有不同的業務用途,如壓測平臺可能希望將流量先持久化到 Kafka,然後利用 Kafka 的高吞吐發壓;有些研發同學只是簡單從線上引一份流量轉發到自己的開發環境做新特性測試;有些同學希望轉發 QPS 能達到一定水位以實現壓測的目的;還有的是特定流量會觸發線上 coredump ,他們希望把這段流量錄製下來線下 debug 等等。針對不同的場景,我們實現了若干流量輸出形式。

字節跳動自研線上引流回放系統的架構演進

下面會著重介紹轉發和存儲。

3.3.1 轉發

字節跳動自研線上引流回放系統的架構演進

結構如上圖,emitter 會在 zookeeper 上註冊自身,scheduler 感知到 emitter 節點信息,將任務根據各個 emitter 節點的標籤和統計信息過濾/打分,然後調度到最合適的節點上。這裡有個問題是為什麼不直接使用無狀態服務,由每個 emitter 實例均等地轉發,而採用 sharding 方案,主要是基於下面幾點考慮:

  1. 如果每個任務均攤到所有實例上執行,那每個實例需要和全部下游 endpoint 建立連接,在海量任務下的 goroutine、連接、內存等資源佔用是不可接受的
  2. 通過將單個任務的處理收斂到少數實例上,提高了對單個 endpoint 的請求密度,從而避免因為連接 idle 時間過長被對端 close,複用了連接。

由於 emitter 對性能比較敏感,我們為此也做了很多優化,比如使用了 fasthttp 的 goroutine 池避免頻繁申請 goroutine,對連接的 reader/writer 對象池化,動態調節每個 endpoint 的工作線程數量以自適應用戶指定 QPS,避免 goroutine 浪費及閒置長連接退化成短連接,全程無鎖化,通過 channel+select 做線程同步和數據傳遞等等。

3.3.2 存儲

字節跳動自研線上引流回放系統的架構演進

存儲分為了兩層,數據層和索引層,採用雙寫模型,並有定時任務從數據層糾錯索引層保證兩者的最終一致性。存儲需 要支持回放和查詢兩種語義,Data Layer 抽象成了一個支持 KV 查詢,按 Key 有序,大容量的存儲模型,Index Layer 是 Multi-index->Key 映射模型,通過這兩層即可滿足流量查詢+回放的需求。所以 Data Layer 和 Index Layer 底層實現是模塊化的,只需符合上述模型並實現模型定義 API。

Data Layer 的理想數據結構是 LSM tree,寫入性能出色,對於流量回放場景,需要按 key 有序掃描流量記錄,因為 LSM 滿足按 key 的局部性和有序性,可以充分利用 page cache 和磁盤順序讀達到較高回放性能。分佈式 LSM Tree 業界比較成熟的開源產品是 HBase,字節跳動內部也有對標產品 Bytable,我們同時實現了基於這兩個引擎的 Data Layer,經過一系列性能 benchmark 我們選擇了 Bytable 作為 Data Layer 實現。

Index Layer 使用了 ES 的能力,因而可以支持用戶的複合條件查詢,我們會預先內置一些查詢索引,如源服務,目標服務,方法名,traceid 等等,流量查詢目前的使用場景一個是作為服務 mock 的數據源,可以屏蔽掉功能測試或者 diff 中不必要的外部依賴,還有一個功能是流量可視化,用戶通過請求時間,traceid 等等,查看特定請求的內容。其他場景功能還有待進一步發掘。

3.4 業務場景支持

3.4.1 支持通用的 diff 能力

Diff 驗證是互聯網公司在快速迭代下保持產品質量的一個利器,類似 Twtiter 的 Diffy 項目,都是通過線上流量的錄製回放來實現。但是它的適用場景也很有限,因為是直接在生產環境上通過 AB 環境做回放,無法支持寫的流量。雖然阿里巴巴的 doom 平臺可以解決寫場景的回放隔離問題,但是它是在應用程序中通過 AOP 來實現的,強綁定 java 生態。

通過 ByteCopy 的無侵入引流和流量存儲回放能力,結合我們自研的 ByteMock 組件,我們提供了面向業務的無侵入 diff 解決方案,並解決了寫隔離的問題。

字節跳動自研線上引流回放系統的架構演進

在一個生產環境下的 (A,B,C) 鏈路中,通過 ByteCopy 實現了針對每一跳 (request, response) 的採集,在做 A 的 diff 驗證的時候,通過 ByteCopy 實現對於 A 服務請求的回放,同時,基於 ByteMock 來實現對於服務 B 的 mock,並支持針對一個 trace 的 response 回放 (依賴 ByteCopy 中流量存儲,實現精準的回放)。因為 B 是 mock 的,即使是一個寫的請求,也可以做到對於線上沒有任何影響。

4. 未來展望

4.1 更精準的流量採集

前面提到,在進行出口流量的採集時,會對下游依賴服務的所有實例的 IP:端口 進行抓包。而實際的生產環境中,同一臺服務器上,可能會部署具有相同下游依賴的多個服務,只依賴四層數據,無法判斷抓到的數據到底來自哪一個服務,會造成抓包、處理和轉發流程中都會存在資源浪費的問題。目前來看基於網卡抓包的方案應該沒法很好地解決這個問題,我們也在嘗試探索一些其他的流量採集的方案,比如探索用 ebpf 進行進程級別的流量採集。

4.2 引流回放系統的重新定義

現階段我們的引流回放系統只會根據用戶的配置被動進行流量採集,而為了及時拿到流量進行測試,用戶一般都會選擇實時引流進行測試。而實際上並不是所有的場景都一定需要實時的流量進行測試,我們在規劃逐步將引流回放系統從一個按照用戶要求進行流量轉發回放的工具,轉變為一個線上複製流量的取用的平臺。

在流量存儲能力的基礎上,對於有測試需求的服務,平臺主動錯峰、定時發起流量錄製任務,從而維護一個不斷更新的流量池,用戶需要流量時直接從流量池中取用,這樣一來,既可以儘量避免引流操作對和線上業務搶佔計算資源,也可以使得流量的可用性更高。

4.3 特定場景下的流量存儲優化

隨著基於流量錄製回放的上層應用的完善,為了更多的業務方便接入試用,我們正在考慮朝著常態化的引流去演進。這個勢必對我們的流量存儲帶來新的挑戰,無論是的數據規模,存儲形態以及查詢性能。我們希望可以基於現有架構的存儲系統,構建流量存儲的解決方案,支持海量數據吞吐的同時,能夠支持點查(基於 TraceId ),和 time-range scan 等多種複雜的高性能查詢方式。另外我們也在積極和安全團隊合作,確保相關核心流量數據在存儲時候能夠實現脫敏,同時不斷強化對於流量存儲使用的安全審計。

5. 總結

到今天為止,ByteCopy 系統已經支撐了字節絕大部分業務線在不同場景下的各種引流需求, 我們一直在努力豐富 ByteCopy 的功能場景,不斷提升系統穩定性和吞吐容量,此外我們也在積極構建 ByteMock 等自研的研發組件,通過和 ByteCopy 形成組合拳,解鎖生產流量在研發活動中更多的使用場景,幫助業務團隊更好地去構建各種有趣的產品。

更多分享

字節跳動表格存儲中的事務

iOS大解密:玄之又玄的KVO

今日頭條 Android '秒' 級編譯速度優化

字節跳動分佈式表格存儲系統的演進

字節跳動基礎架構團隊

字節跳動基礎架構團隊是支撐字節跳動旗下包括抖音、今日頭條、西瓜視頻、火山小視頻在內的多款億級規模用戶產品平穩運行的重要團隊,為字節跳動及旗下業務的快速穩定發展提供了保證和推動力。

公司內,基礎架構團隊主要負責字節跳動私有云建設,管理數以萬計服務器規模的集群,負責數萬臺計算/存儲混合部署和在線/離線混合部署,支持若干 EB 海量數據的穩定存儲。

文化上,團隊積極擁抱開源和創新的軟硬件架構。我們長期招聘基礎架構方向的同學,具體可參見 job.bytedance.com (文末“閱讀原文”),感興趣可以聯繫郵箱 [email protected]

歡迎關注字節跳動技術團隊


分享到:


相關文章: