《日處理20億數據,實時用戶行為服務系統架構實踐》要點:
本文介紹了日處理20億數據,實時用戶行為服務系統架構實踐,希望對您有用。如果有疑問,可以聯系我們。
攜程實時用戶行為服務作為基礎服務,目前普遍應用在多個場景中,比如猜你喜歡(攜程的推薦系統)、動態廣告、用戶畫像、瀏覽歷史等等.
以猜你喜歡為例,猜你喜歡為應用內用戶提供潛在選項,提高成交效率.旅行是一項綜合性的需求,用戶往往需要不止一個產品.作為一站式的旅游服務平臺,跨業務線的推薦,特別是實時推薦,能實際滿足用戶的需求,因此在上游提供打通各業務線之間的用戶行為數據有很大的必要性.
攜程原有的實時用戶行為系統存在一些問題,包括:1)數據覆蓋不全;2)數據輸出沒有統一格式,對眾多使用方提高了接入成本;3)日志處理模塊是web service,比較難支持多種數據處理策略和實現方便擴容應對流量洪峰的需求等.
而近幾年旅游市場高速增長,數據量越來越大,并且會持續快速增長.有越來越多的使用需求,對系統的實時性,穩定性也提出了更高的要求.總的來說,當前需求對系統的實時性/可用性/性能/擴展性方面都有很高的要求.
這樣的背景下,我們按照如下結構重新設計了系統:
圖1:實時用戶行為系統邏輯視圖
新的架構下,數據有兩種流向,分別是處理流和輸出流.
在處理流,行為日志會從客戶端(App/Online/H5)上傳到服務端的Collector Service.Collector Service將消息發送到分布式隊列.數據處理模塊由流計算框架完成,從分布式隊列讀出數據,處理之后把數據寫入數據層,由分布式緩存和數據庫集群組成.
輸出流相對簡單,Web Service的后臺會從數據層拉取數據,并輸出給調用方,有的是內部服務調用,比如推薦系統,也有的是輸出到前臺,比如瀏覽歷史.系統實現采用的是Java+Kafka+Storm+Redis+MySQL+Tomcat+Spring的技術棧.
目前系統每天處理20億左右的數據量,數據從上線到可用的時間在300毫秒左右.查詢服務每天服務8000萬左右的請求,平均延遲在6毫秒左右.下面從實時性/可用性/性能/部署幾個維度來說明系統的設計.
作為一個實時系統,實時性是首要指標.線上系統面對著各種異常情況.例如如下幾種情況:
系統從設計之初就考慮了上述情況.
首先是用storm解決了突發流量洪峰的問題.storm具有如下特性:
圖2:Storm特性
作為一個流計算框架,和早期大數據處理的批處理框架有明顯區別.批處理框架是執行完一次任務就結束運行,而流處理框架則持續運行,理論上永不停止,并且處理粒度是消息級別,因此只要系統的計算能力足夠,就能保證每條消息都能第一時間被發現并處理.
對當前系統來說,通過storm處理框架,消息能在進入kafka之后毫秒級別被處理.此外,storm具有強大的scale out能力.只要通過后臺修改worker數量參數,并重啟topology(storm的任務名稱),可以馬上擴展計算能力,方便應對突發的流量洪峰.
對消息的處理storm支持多種數據保證策略,at least once,at most once,exactly once.對實時用戶行為來說,首先是保證數據盡可能少丟失,另外要支持包括重試和降級的多種數據處理策略,并不能發揮exactly once的優勢,反而會因為事務支持降低性能,所以實時用戶行為系統采用的at least once的策略.這種策略下消息可能會重發,所以程序處理實現了冪等支持.
storm的發布比較簡單,上傳更新程序jar包并重啟任務即可完成一次發布,遺憾的是沒有多版本灰度發布的支持.
圖3:Storm架構
在部分情況下數據處理需要重試,比如數據庫連接超時,或者無法連接.連接超時可能馬上重試就能恢復,但是無法連接一般需要更長時間等待網絡或數據庫的恢復,這種情況下處理程序不能一直等待,否則會造成數據延遲.實時用戶行為系統采用了雙隊列的設計來解決這個問題.
圖4:雙隊列設計
生產者將行為紀錄寫入Queue1(主要保持數據新鮮),Worker從Queue1消費新鮮數據.如果發生上述異常數據,則Worker將異常數據寫入Queue2(主要保持異常數據).
這樣Worker對Queue1的消費進度不會被異常數據影響,可以保持消費新鮮數據.RetryWorker會監聽Queue2,消費異常數據,如果處理還沒有成功,則按照一定的策略(如下圖)等待或者重新將異常數據寫入Queue2.
圖5:補償重試策略
另外,數據發生積壓的情況下,可以調整Worker的消費游標,從最新的數據重新開始消費,保證最新數據得到處理.中間未經處理的一段數據則啟動backupWorker,指定起止游標,在消費完指定區間的數據之后,backupWorker會自動停止.(如下圖)
圖6:積壓數據消解
作為基礎服務,對可用性的要求比一般的服務要高得多,因為下游依賴的服務多,一旦出現故障,有可能會引起級聯反應影響大量業務.項目從設計上對以下問題做了處理,保障系統的可用性:
首先是系統層面上做了全棧集群化.kafka和storm本身比較成熟地支持集群化運維;web服務支持了無狀態處理并且通過負載均衡實現集群化;Redis和DB方面攜程已經支持主備部署,使用過程中如果主機發生故障,備機會自動接管服務;通過全棧集群化保障系統沒有單點.
另外系統在部分模塊不可用時通過降級處理保障整個系統的可用性.先看看正常數據處理流程:(如下圖)
圖7:正常數據流程
在系統正常狀態下,storm會從kafka中讀取數據,分別寫入到redis和mysql中.服務從redis拉取(取不到時從db補償),輸出給客戶端.DB降級的情況下,數據流程也隨之改變(如下圖)
圖8:系統降級-DB
當mysql不可用時,通過打開db降級開關,storm會正常寫入redis,但不再往mysql寫入數據.數據進入reids就可以被查詢服務使用,提供給客戶端.另外storm會把數據寫入一份到kafka的retry隊列,在mysql正常服務之后,通過關閉db降級開關,storm會消費retry隊列中的數據,從而把數據寫入到mysql中.redis和mysql的數據在降級期間會有不一致,但系統恢復正常之后會通過retry保證數據最終的一致性.redis的降級處理也類似(如下圖)
圖9:系統降級-Redis
唯一有點不同的是Redis的服務能力要遠超過MySQL.所以在Redis降級時系統的吞吐能力是下降的.這時我們會監控db壓力,如果發現MySQL壓力較大,會暫時停止數據的寫入,降低MySQL的壓力,從而保證查詢服務的穩定.
為了降低故障情況下對下游的影響,查詢服務通過Netflix的Hystrix組件支持了熔斷模式(如下圖).
圖10:Circuit Breaker Pattern
在該模式下,一旦服務失敗請求在給定時間內超過一個閾值,就會打開熔斷開關.在開關開啟情況下,服務對后續請求直接返回失敗響應,不會再讓請求經過業務模塊處理,從而避免服務器進一步增加壓力引起雪崩,也不會因為響應時間延長拖累調用方.
開關打開之后會開始計時,timeout后會進入Half Open的狀態,在該狀態下會允許一個請求通過,進入業務處理模塊,如果能正常返回則關閉開關,否則繼續保持開關打開直到下次timeout.這樣業務恢復之后就能正常服務請求.
另外,為了防止單個調用方的非法調用對服務的影響,服務也支持了多個維度限流,包括調用方AppId/ip限流和服務限流,接口限流等.
由于 在線旅游 行業近幾年的高速增長,攜程作為行業領頭羊也蓬勃發展,因此訪問量和數據量也大幅提升.公司對業務的要求是可以支撐10倍容量擴展,擴展最難的部分在數據層,因為涉及到存量數據的遷移.
實時用戶行為系統的數據層包括Redis和MySQL,Redis因為實現了一致性哈希,擴容時只要加機器,并對分配到新分區的數據作讀補償就可以.
MySQL方面,我們也做了水平切分作為擴展的準備,分片數量的選擇考慮為2的n次方,這樣做在擴容時有明顯的好處.因為攜程的mysql數據庫現在普遍采用的是一主一備的方式,在擴容時可以直接把備機拉平成第二臺(組)主機.假設原來分了2個庫,d0和d1,都放在服務器s0上,s0同時有備機s1.擴容只需要如下幾步:
遷移過程利用MySQL的復制分發特性,避免了繁瑣易錯的人工同步過程,大大降低了遷移成本和時間.整個操作過程可以在幾分鐘完成,結合DB降級的功能,只有在DNS切換的幾秒鐘時間會產生異常.
整個過程比較簡單方便,降低了運維負擔,一定程度也能降低過多操作造成類似GitLab式悲劇的可能性.
前文提到Storm部署是比較方便的,只要上傳重啟就可以完成部署.部署之后由于程序重新啟動上下文丟失,可以通過Kafka記錄的游標找到之前處理位置,恢復處理.
另外有部分情況下程序可能需要多版本運行,比如行為紀錄暫時有多個版本,這種情況下我們會新增一個backupJob,在backupJob中運行歷史版本.
作者:陳清渠,畢業于武漢大學,多年軟件及互聯網行業開發經驗.14年加入攜程,先后負責了訂單查詢服務重構,實時用戶行為服務搭建等項目的架構和研發工作,目前負責攜程技術中心基礎業務研發部訂單中心團隊.
文章來自微信公眾號:互聯網架構師
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/4109.html