歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

日均請求量百億級數據處理平台的容器雲實踐

編者按: 七牛雲研發架構師袁曉沛,7 月 15 日在 ArchSummit 全球架構師峰會七牛雲專場上,帶來了題為《自定義數據處理平台的容器雲實踐》的分享,結合七牛雲自定義數據處理平台業務的容器化實踐,從平台的業務特點、為什麼容器化、如何實現容器化以及容器實踐的具體效果等角度進行了分享。以下是對他演講內容的整理。

袁曉沛:曾參與盛大網盤 EverBox、EMC 備份服務 Mozy 後端存儲的設計、開發工作,主要方向在分布式系統的架構設計、開發、性能調優以及後期運維優化。目前在七牛雲的主要工作是基於容器平台構建分布式應用,借助容器的優勢,實現大規模分布式應用的自動化運維以及高可用,以 PaaS 服務的形式提供服務器後端應用,同時致力於讓開發者從繁瑣的後端運維工作中解放出來。

數據處理業務簡介

首先是業務定義:七牛雲數據處理依托於七牛雲存儲中的海量數據,提供零運維、高可用、高性能的數據處理服務,日處理數近百億次,讓用戶輕松應對圖片、音視頻以及其他各類數據的實時、異步處理場景。
數據主要有三種處理方式:
官方數據處理:提供基礎的數據處理服務,包括但不限於圖片的轉碼、水印、原圖保護、防盜鏈等,以及音視頻的轉碼、切片和拼接等。
自定義數據處理:允許用戶構建、上傳自定義的私有數據處理服務,並無縫對接七牛雲存儲上的數據以及其他數據處理服務。
第三方數據處理:一個開放的應用平台,提供大量功能豐富的第三方數據處理服務,比如圖片鑒黃、人臉識別、廣告過濾、語言翻譯、TTS 等。

圖 1
圖 1 是數據處理的一個調用示例。無論是七牛雲的官方數據處理,還是自定義數據處理,或者第三方數據處理,都是以這種形式調用的。最左邊的是一個 URL,代表一個文件,中間綠色的 Facecrop 是數據處理命令,右邊的 200 × 200 是請求參數。

圖 2
如圖 2 所示,從左邊的人物肖像圖原圖到右邊 200 × 200 的小圖片,這個服務可以把人臉從原圖中剪裁出來。通過管道操作,還可以把圖片再存到存儲中,以後直接使用這個小圖,不需要再走數據處理計算。這是一個典型的數據處理調用。

官方數據處理的挑戰

第一,請求量非常大。目前,系統每天有近百億的數據處理請求量。年底,可能會在目前近千台的計算集群基礎上翻好幾倍,整個存量、增量都非常大。
第二,突發流量非常頻繁。很多客戶是從其它雲遷到七牛雲,首次把大量文件遷移到七牛存儲後,往往會發起大量的數據處理請求,這會帶來大量突發流量。
第三,CPU 密集型計算。目前後端集群中,絕大部分的機器都是用來跑圖片、音視頻轉碼的,這些都是 CPU 密集型的計算,這意味著後台需要很多台機器,而且 CPU 的核數越多越好。
第四,IO 操作頻繁。IO 分為磁盤 IO 和網絡 IO,數據處理前,往往需要先把原始文件從七牛存儲中下載到本地磁盤,所以磁盤 IO 和網絡 IO 都很頻繁。

官方數據處理的架構演化


圖 3
圖 3 是數據處理早期的一個架構圖。當時比較簡單,所有請求都經過 FopGate,每個節點上都部署了很多圖片、音視頻或者文檔處理的 Worker,是具體做轉碼的計算服務。整個架構,Worker 在網關上是靜態配置,增加新的 Worker 時需要改網關配置重新加載,做起來會很麻煩。另外,請求進來時,對應要處理的數據也是通過網關服務進入,控制流和數據流放在一起,網關整體壓力非常大,當時出了很多問題。

圖 4
圖 4 是數據處理目前的主體架構。右圖左上角增加了一個 Discovery 組件,收集 Agent 上報的信息,Agent 和 Worker 把自己能做什麼事都上報到 Discovery。每增加新的主機 Agent 和 Worker 時,不再需要靜態配置,只需要指向的這個 Discovery 服務。網關可以通過 Discovery 獲取信息,根據進入的具體請求,將這個請求路由到不同的 Agent ,Agent 再把請求轉到 Worker 處理。Agent 組件還有負載均衡的作用,請求會選擇後端相應的節點執行,另外也起到下載文件的作用。下載文件的數據流不經過網關服務,Worker 向 Agent 發起一個下載請求,然後這個 Agent 到存儲上下載數據,放到自己的緩存中。
這個架構解決了一些問題,但是之前提及的挑戰依然存在。主要的問題是 FopGate 在過載時依然會崩潰,每個主機會過載出問題,造成請求變慢或者宕機。接下來討論一下如何解決這些問題。

如何應對官方數據處理的挑戰

系統測量

第一,測量 FopGate 的服務能力。按照線上的配置,針對同比例縮小的集群做性能測試,測試出 FopGate 單機最大的請求數和句柄數,根據實際的業務量,確定機器配置和數量。
第二,測量某種數據處理的資源使用范式。大多數據處理是 CPU 密集型計算,我們需要找到單個處理的資源使用規律。測試方式如下:取線上約 10 萬或 100 萬個請求,針對一台測試機器壓力測試。比如測 Image,測試這台機器的變量有以下三個:Image 實例數,並發處理數和隊列長度。最終拿到的一個結果是,做圖片剪裁、圖片處理、圖片轉碼等服務,一個進程一個 CPU 的邏輯核,同時處理一個任務,對它來說是最快的,多個任務同時處理反倒會讓CPU 的處理變慢。
第三,測量單實例的服務能力。一個程序實現得很好,可以把多核利用起來。針對這樣的實例,分配多個 CPU 線程、調大並發、並壓測這個的實例,試圖用 10 萬個請求看整體處理速度,發現整體處理速度並沒有起多個實例,但每個實例限制 CPU 線程數、限制並發好。所以最終的結論是操作系統對於 CPU 的調度,比進程要好;相比起一個大實例、接受高並發請求,我們更傾向於運行多個實例、但每個實例限制並發。

增加隊列

這個主要的考慮是提高服務質量,避免單實例過載。前面得到結論,一個圖片處理的實例,一個核,並發量為 1 時最快。我們為每台節點機器加了一個排隊機制,排隊之後不爭搶資源,整體處理速度反而變快。
另外,從運營角度講,可以用隊列長度來區分免費用戶和付費用戶。比如,可以將免費用戶的排隊長度設置得長一點,比如設成 10,意味著最後一個請求要等前面 9 個請求處理完才能處理。而對於付費用戶,可以將隊列長度調短一點,只有 3 或者 5,這樣平均等待時間就變短。總原則是區別付費用戶和免費用戶,免費用戶保證高可用,但是不能保證高質量和低延遲,因為資源有限;對於付費用戶可以保證高質量,因為可以通過排隊長度控制這個事情。

限流

為什麼有了隊列之後還要限流?兩個原因:
第一,大量長鏈接影響 FopGate 性能。因為七牛雲處理最多的是圖片、音頻和視頻三種請求。圖片請求往往比較快,高峰期時可能幾秒、幾十毫秒完成,但是視頻和音頻轉碼往往比較慢,可能需要好幾分鐘或者好幾十分鐘,它取決於一個具體的轉碼參數和轉碼時長。如果不限流,網關會維持大量的長鏈接,累積大量句柄,輕則影響的性能,嚴重的情況下會造成宕機。
第二,突發流量,導致隊列過長。隊列太長,有大量任務積壓的情況下,會有任務在處理之前就超時。與其超時,還不如直接限流,拒絕處理不掉的請求。
限流手段有三種:
第一,並發 HTTP 請求限制。超過這個限制,直接拒絕請求並返回。但這並不是最好的方式,因為超時的請求已經解析處理過,這對於性能是有一定損耗的。最好的做法是用一個信號量控制 TCP accept,比如控制信號量個數是 1 萬個,1 萬個請求正在並發處理時就不 accept 。這是最好的方式,但實現略復雜。
第二,單用戶請求數限制。有些用戶可能會在特殊情況下發起大量的請求,為了不讓他影響別的用戶,系統會限制單個用戶的請求量。
第三,Cmd 數限制。主要指具體某個操作,比如圖片查看,要對 Cmd 數進行限制。因為不能讓大量同樣的 Cmd 把資源消耗殆盡,影響其他 Cmd 的操作。

合理協調 IO 和 CPU

為什麼要合理協調?因為數據處理的流程是:下載、寫盤、處理、寫盤、返回,涉及到網絡 IO 和磁盤 IO。整個優化方式總原則是就近計算,將下載和計算部署在一起。目前,新的架構中是混合部署的。本來可以設計,將下載部署在其他機器上,但這樣會增加一次網絡的路由次數。所以,從一台機器的 Agent 下載後,直接在本機處理,不會再經過一次網絡路由。另外一種方式是掛載 ramfs,直接把下載內容放在內存中。比如內存分 8 G,下載完成後直接放在內存中,要用的時候直接進內存讀,這樣可以節約磁盤 IO 的開銷,也能整體加快單個請求處理速度。
前面這些是官方數據處理的一些挑戰、架構演化以及我們采取的一些對策,後面講一下自定義數據處理所面臨的挑戰。

自定義數據處理的挑戰

第一,處理程序由客戶提供,我們不知道客戶在程序裡做了什麼事情,也不知道客戶的程序會使用多少資源,這意味著我們至少要做兩點:一是安全性,不能訪問到別的資源,二是隔離性,單個程序不能無限制使用資源。
第二,業務規模不確定性,無法估算量有多大。這個帶來的挑戰是業務可伸縮性,需要客戶可控。
基於這三個需求:安全性、隔離性、可伸縮性,Docker 非常適合這個業務場景,整個自定義數據處理在 14 年開始基於 Docker 實現。

圖 5
圖 5 是自定義數據處理的業務流程:
第一,用戶要創建一個自定義數據處理,即注冊一個 UFOP。
第二,用戶在本機上開發自定義數據處理程序。開發這個程序要遵循七牛 UFOP 范式。
第三,把這個程序提交一個構建版本。
第四,切換到這個版本,設置實例數。實例啟動後,該 UFOP 就可以訪問了。
這是迭代的,有一個箭頭直接指回開發自定義數據處理程序,意味著升級程序、構建另一個新版本、切換到新版本實例的一個過程。

業務流程-注冊


圖 6
圖 6 是注冊的過程,第一個 qufopctl 是客戶端工具,第二個是 reg 注冊,第三個 ufop-demo 是名字,最後 -m 2 是模式,支持私有和公有模式。

圖 7
圖 7 是自定義數據處理後端注冊的流程,qufopctl 把前面描述的注冊命令,發到 ufop-controller,它會進一步走鑒權服務,鑒權成功之後通過 Keeper 服務存盤到 DB。注冊成功後,用戶開始在本地實現 UFOP 應用。

圖 8
圖 8 左邊是一個最小的 UFOP 代碼,簡單來說就是在 UFOP 請求路由上加一個 UFOP 請求,拿到待處理文件 URL 後下載對應文件,然後獲取文件的類型和長度,並作為結果返回。右上角是我們定義的一個描述性文件。2014 年,我們做的時候 Docker 還沒有那麼火,所以沒有直接用 Dockerfile,而是自己定義描述模式。第一行是引用源;第二行是構建腳本,把外面的程序改成可寫;第三是執行,怎麼執行這個程序。右下角是要打包成 tar 包的本地目錄。

業務流程-構建


圖 9
如圖 9 所示為構建命令,是把本地的程序上傳到服務端,並構建一個版本。

圖 10
圖 10 是構建的後端。整個後端的過程如下:qufopctl 把程序 tar 包上傳到 Kodo,發起構建請求,再把構建請求轉發到構建機器,接著把 UFOP 描述文件轉化為 Dockerfile,基於 Dockerfile 構建 Docker image,最後推送到 Docker Registry。
下面是構建後端我們踩過的一些小坑。

使用 Debian 鏡像服務

原先使用 AppRox,是 Debian 包下載、緩存服務,但實際上經常下載超時,而且下載出錯後,自己沒辦法辨別本地文件存在問題,需要手動清除,比較糟糕。後來,搭建了一個鏡像源。鏡像源的做法是首次全量下載,全部下載完畢後,設置一個定時增量同步的時間,在凌晨,一天一次,每天更新的量只有幾十兆,幾分鐘便可完成。從此,再也沒有出現過這方面的問題。

避免 Docker 構建緩存


圖 11
Docker 的構建緩存比較容易出錯。圖 11 第一個是錯誤用法:第一步,下載一個 jdk 的壓縮包,第二步,創建一個目錄,將壓縮包解壓到這個目錄,並且把原來的壓縮包刪除。分了兩條命令,但是容器其實會對每一條命令做緩存。
假如我們第一次運行這兩條命令時是沒有問題的。第二次運行時,我們覺得第二條命令中 OPT 目錄不好,想到另外一個目錄,於是對第二條語句做了更改,那麼就會出問題。為什麼?因為第一條命令沒有任何變化,所以容器構建系統認為,沒有任何的變化就不去執行,於是直接跑第二條命令,第二條在前一次執行時,已經將壓縮包刪除,因此會跑失敗。這種錯誤很容易犯,解決方法是把壓縮包的下載、目錄的創建、解壓縮和刪除壓縮包,都放在同一條命令裡。當這條命令被修改後,每次都會重新執行,就不會出現這個問題了。

調整實例數


圖 12

圖 13
接下來是調整實例數,前面已經構建完成,程序已經上傳到服務器端。qufopctl resize ufop-demo -n 3,這條命令會增加 3 個實例。
整個後端的過程如圖 13 所示,qufopclt 發送請求到 ufop-controller,中心調度器 Scheduler 下面有很多個 node,運行著我們實現的 Daemon 服務,所有容器都是由 scheduler 下發給某個 Daemon,這些 Daemon 啟動對應的容器。Daemon 會把所在機器剩余資源的情況,包括CPU、內存和磁盤,實時報給 Scheduler,Scheduler 會根據實例的規格要求、找到相應滿足要求的 node。這些容器實例創建成功後,Daemon 會把具體的端口信息返回到 Scheduler,Scheduler 再通過 Keeper 服務持久化,這樣就結束了。 Docker Registry 是 Docker 的鏡像倉庫。

升級實例


圖 14
到前面這一步,UFOP 已經運行起來並且可以使用,如圖 14 所示為升級實例的過程。我們有一個 Upgrade 的命令,-r 是一個實例,現在要做的是升級前兩個實例,後端會發生什麼事情呢?

圖 15
圖 15 所示為一個灰度升級階段。首先原始階段有三個實例,假設每個版本都是 V1,現在要升級兩個實例,後端的做法是增加兩個實例,實例 4 和實例 5,這兩個對應的版本是 V2,圖 15 中橙色部分。等 V4 和 V5 徹底被啟動後,我們將老的實例刪除,它的結果是原來的一個 V1, 兩個 V2,整體灰度升級過程就是這樣子。
有些細節值得注意:
第一,新實例 WarmUp,往往剛開始很多內部組件沒有初始化,像內存池、線程池、連接池需要初始化,所以初始請求處理得往往比較慢,但是又不能不發請求,因為不發請求永遠熱不起來。做法是設定預熱時間段,定時間段的權重,保證在這個時間段預熱好之後就承擔正常的流量。
第二,老實例 CoolDown。如果直接刪除老實例,對服務的可用性沒有影響,因為老的請求正在被處理。做法是:使用 Docker StopWait,當用 Docker Stop 停止一個容器時,它默認的行為是先發一個 SIGTERM,如果不指定 StopWait 時間,就會馬上發一個 SIGKILL 信號,這樣你的容器將被直接殺死。若一個容器正在處理請求,那麼我們希望能有一段時間讓我們將請求處理完後,優雅的關閉,否則會影響可用性。所以使用這個命令時,可以設置 StopWait 時間,若等待超過設置的時間,卻發現容器沒有優雅關閉,再發 SIGKILL 信號。另外在業務層面,老實例在 CoolDown 時,應該設成一個關閉中的狀態,避免新的請求再打進去。
第三,保留足夠的計算冗余。這個是如何考慮的呢?因為升級需要考慮步長,如果升級的步長大於計算的冗余實例數,意味著剩余的等待實例會承擔更多的請求,多於預期的請求會影響服務的質量,因為請求的時間會變長。所以選用的升級步長應該小於冗余的實例數。

數據流


圖 16
剛才講的是控制流,包括如何注冊、如何創建一個新的實例。而這些數據流的請求過來是什麼樣的?這涉及到一些內部組件,即雲存儲的服務器。服務器後面有數據處理,自定義數據處理會從之前說的狀態服務中獲取某一個後端實例列表,由它做負載均衡傳給後端。在後端數據流的維度上,每台機器還有一個 Fetcher,它接收到請求後會把請求路由到本機,並執行實例。同時,它會做數據下載的工作,我們出於對用戶隱私安全的考慮做了一些混淆,不允許他直接下載,而是轉化成本地的。這是數據流的流程。右邊有一個組件叫 DiskCache,是一個緩存集群。

圖 17
圖 17 是數據鏈路的 V2 版本,是一個思路,還沒有完全實現。唯一的改動是在實例之間加了一個隊列,當然這個隊列是可選的。當前所有請求都放在這個隊列中,由 UFOP 實例獲取請求,處理完成後將結果返回,這是一步處理的過程。這個隊列有什麼作用?前面提到,調整實例可以做手動伸縮,沒有說自動伸縮。而這個隊列可以做自動伸縮。

自動伸縮設置

自動伸縮需要用戶做一些配置,一個是默認的實例數,也就是第一次用戶上傳一個自定義數據處理的版本,上傳完成後調整實例數,調整成 10 個,還要設置一個平均單實例待處理任務數的配置,假如,這個設置是 10,當隊列裡面有小於 100 個待處理任務數時是不需要伸縮的,但是當隊列裡面的任務數大於 100,比如到 110 就要伸縮了,因為單個實例平均待處理的任務數已經超過,這個時候再增加一個實例是最適合的選擇。通過對隊列的監控,可以達到自動伸縮。
自動伸縮的後端

圖 18
圖 18 是自動伸縮的後端。最上面中間有一個 Scaler 組件,會實時監控隊列長度,拿到每個隊列的配置,即平均單任務待處理任務數。它根據這兩個信息,可以實時地發出調整實例的請求,比如平均單任務處理數超過預想時,它可以自動增加一個實例,並告訴調度器,調度器會找相應的節點啟動實例,啟動後告訴 Keeper,Keeper 會把這些信息記錄下來。回到前面的數據流,及時從裡面獲取信息,達到自動伸縮的目的。

如何應對自定義數據處理的挑戰

解決方案:
第一,安全性。這一部分做法比較簡單。自定義數據處理單個物理機上的容器數約幾十個,這與 CPU 的核數有關。可以設置某個范圍的端口不互訪,以達到容器不能互訪的目的,從而獲得安全性。
第二,隔離性。可以限定某一個容器只用指定配額的 CPU 和內存,這取決於用戶的配置。
第三,可伸縮性。首先實現了一個簡單高效的容器調度系統,它是支持秒級伸縮的;其次是暴露伸縮 API ,用戶可以手動伸縮達到伸縮目的;最後是利用隊列長度,達到自動伸縮的目的。
視頻實錄:《自定義數據處理平台的容器化實踐》:http://v.qq.com/x/page/c03150up4cm.html

「七牛架構師實踐日」——這裡只談架構 七牛架構師實踐日是由七牛雲發起的線下技術沙龍活動,聯合業內資深技術大牛以及各大巨頭公司和創業品牌的優秀架構師,致力於為業內開發者、架構師和決策者提供最前沿、最有深度的技術交流平台,幫助大家知悉技術動態,學習經驗成果。
七牛架構師實踐日第十期【泛娛樂+直播 技術最佳實踐】將於 7 月 31 日與大家在 上海 見面,目前活動正在火熱報名中,點擊此處了解更多信息,期待你的參與。
Copyright © Linux教程網 All Rights Reserved