爲啥寫這篇文章?由於我如今作的這套實時計算系統在公司裏很難玩下去了。去年年初來到ctrip,主要就是作兩個實時應用,一個是實時報警,功能是作出來了,但應用效果很差;一個是XXX(敏感應用,不敢寫出來,以XXX代替),也是實現了功能需求,但想繼續按本身的思路往下走是不可能了,我捉急的表達能力很難讓上頭去理解實時計算和傳統的request-response方式的應用不一樣點在哪裏,爲啥要這麼幹,也很難明白上頭喜歡的系統是什麼樣的,真是撓破頭。個人方式看來是作不下去了,但總以爲仍是有點價值,因此寫下來,看看有沒有也想山寨的同窗,能夠做爲參考,下面一段是扯淡,想看實際內容的請跳到系統結構。
至於爲何起這個標題:html
後續系統設計都是以這些爲出發點的前端
storm 是目前比較火熱的實時處理系統,雖然不能和H系的比,但資料也仍是很多,我這就默認你們已經知道storm的概況了,具體的資料就不舉了!java
國內而言,阿里系對storm的應用比較多,網上有不少的文章;在ctrip,也有另一個team在用storm作前端的用戶行爲分析,感受是蠻成功的,應該算公司裏拿得出手的項目之一了。還有不少公司也是用storm在作一些實時的業務開發。mysql
storm自己只是提供了一個實時處理的框架,幫咱們解決了大部分的可靠性,數據流向組織,併發性等基本問題,可是實際的數據處理仍是要根據業務需求去開發代碼適配。所以它只是解決了實時計算的組織和管理,而對計算自己是沒有支持的,直接用是達不到我想要的「不寫代碼只配置」的效果,因此我把重心放在esper上,storm只做爲外部的容器,幫我作數據的簡單處理和sharding,節點的自動分配和重啓,數據源的組織和數據結果的分配等等外圍的功能,計算就交給esper了。git
esper 絕壁是個好東西,功能強大,但門檻過高。資料的話官網上看下例子,也有一篇很詳盡的文檔,這是html版,有興趣的同窗能夠上官網下pdf版。
簡單的介紹的話,esper是一個用於針對數據流進行流式處理的lib庫(java和.net都有),他跟傳統的數據庫不一樣之處在於:數據庫是數據先寫進來,再定義一個sql,而後去拉取數據並計算一次獲得結果;esper是定義一個計算規則,並做爲一個計算節點,而後再把數據不停的推給他,由計算節點不停的作增量計算,並出來一系列的結果。
上圖是傳統的數據庫(也有用nosql的)的方式,目前不少項目應該都是採起這種設計,好比我要作一個5分鐘的數據統計,就須要不停的跑sql去拉數據作統計和計算(固然能夠提早作一些預聚合,數據庫也應該有觸發器之類的功能,不過只能做爲優化,也很難去掌控)。這種pull的方式容易理解,也有已經成熟到爛街的數據庫技術支持,pull的時候靠分庫分表來sharding,只是要本身寫點聚合的代碼,對單個查詢來講很快,由於只要一次數據庫查詢,一跳就完了,數據的查詢和存儲都由數據庫來保證高效。但對於數據量大,而後又要實時更新的場景來講,這種低效的方式是走不通的,圖上就能夠看到由於數據和計算節點的分離,勢必形成冗餘的數據被屢次拉取(或者要寫複雜的代碼去優化),並且實時度靠輪詢來達到。這種設計要麼只能適合於數據量和計算量比較小的場景,要麼只能適合於人傻錢多的場景。但它好理解。程序員
也有說拿redis作counter的,但這種方式仍是解決不了數據和計算分離的問題;在者,對於功能稍複雜的計算就力不從心了,並且redis的邏輯抽象程度遠不如sql,開發工做比sql都要大不少。具體的不展開了
此圖中下方每一個方框表明一個用esper實現的實時計算引擎,配置好運算規則後,咱們主動去把數據餵過去,引擎不停的作增量計算,每次有新結果就經過回調通知咱們。這個圖是公司內部寫ppt時臨時畫的,不大恰當。由於esper是個lib,單實例的可靠性和性能無法scale,因此咱們是架在storm上,由storm去自動部署和分配多個esper進程,並在前端作sharding來達到高擴展性。github
select avg(price) from StockTickEvent.win:time(30 sec) select a.id, count(*) from pattern [ every a=Status -> (timer:interval(10 sec) and not Status(id=a.id) ] group by id
esper聲稱本身是SEP(stream event processing)和cep(complex event processing)。上面從官網抄了兩個比較有表明性的例子來分別說明。redis
這兩個例子還只是揭露了esper能力和複雜度冰山一角。對於流式的數據處理和事件間pattern的描述,它提供了不少的底層支持和選項。基本咱們的需求都能獲得知足,一種功能還能用好多種寫法來實現,越用就越是以爲它強。
然而,強大的東西不容易掌控。我是在前公司paypal時據說這個開源軟件的,當時老闆的另一個team花了很大的力氣和資源在上面,但願作實時大數據,我離開不久據說這個team就散了,大概由於沒出好的成果,員工圖譜上,我和cto之間那條線上的老闆們都陸續黯然離開了;而我本身,是在來到ctrip才接觸,由於大概知道前同事的不順利,因此是帶着敬畏心在作,當心翼翼,儘可能讓它更易用,結果雖然功能是出來了,獎也拿了,仍是被罵,結局也離屎差很少了。因此想用它的要小心啊,不祥之物啊:)
不過失敗不能白失敗,經驗教訓要總結下的,但願能成爲它人的成功之母:算法
我同事在opentsdb(後端是hbase)的思路上開發了一個叫dashboard的系統,能夠對海量metrics進行實時的存儲和查詢。,同事對整個先後端都進行了重寫和改進,細節上有蠻多獨到之處,我全部程序的監控,包括storm的監控和想要作的計算平臺的監控,都是基於此。這應該是ctrip裏很是好的一個系統了。sql
下面分別從邏輯和物理上描述整個系統的結構。
目前的系統設計,對一個應用,包含了四大部分
因此整套系統是一個典型的輸入(part 1) --> 處理(part 2&3) --> 輸出(part 4)的結構,每一個應用只需給出四大部分的配置,就能夠獲得一個實時事件處理應用。
這裏須要補充說明的是:
OPERATION(input1, input2)--> output1
的最簡單的範式,input和output都是系統裏的數據流,用variable的叫法更貼切。對於data source來講,框架部分開發出不一樣類型數據源的數據抽取驅動,能夠對如下數據類型數據源進行數據拉取:
對於每一種數據源,大體只須要定義元數據信息,就能夠完成外部的數據拉取併到系統內部數據(通常稱爲event,包括name、key、timestamp、value四大固有屬性和其餘屬性)格式的轉換:
目前大部分數據源都是根據應用寫死,但長期但願抽象出特徵來,能夠經過配置自動完成,只剩下少數特別的經過開發完成。
這一份的結果是咱們能夠從每一個datasource源源不斷的獲得數據,所以就是一條條數據流,通過alignment後(時間對齊),要求全部事件都以相同的時鐘下匯聚成一個邏輯上的總的數據流,這是系統最大的limitation)
對於進入系統的數據流(stream),咱們能夠對此進行一些操做(包括但不限於split/join/aggreation/filtering),實時造成新的數據流,獲得一系列variable(能夠對應爲BI中的維度,風控模型中的variable),以下圖所示
進入這部分系統時有兩個數據流DS1, DS2;通過處理後,獲得6個Variable(每一個Variable主要包括name,key,timestamp,value幾個固有屬性和其餘用戶定義的屬性),每一個variable其實也是隨時間變化的數據流,經過加工後的數據會更有利於做進一步的決策;最後在某一個時間點,對全部variable進行切片,能夠獲得一系列的latest值,這樣就能夠作爲決策規則(模型)的輸入依據,這一部分由rule management部分完成。
實際上,從數學的角度看,這部分工做但願以variable(數據流/維度,counter只是其中一種)這種數據流做爲基本單元,完成一個帶少許操做符的代數系統,從而整個計算過程能夠由這樣一些基本的操做符去搭建一個DAG,而不是從頭至尾所有由程序員編碼實現功能。
操做部分基本由esper完成,經過封裝,將esper實現相關的部分封裝掉,只提供邏輯上的運算符給用戶/admin,減輕使用負擔。目前只提供少許基本的操做類型:
a. (已實現)單線聚合/過濾。提供基於單個數據流(variable或data source)的按時間聚合、按條件過濾。經過此操做能夠實現split/aggregate/filter等邏輯操做
b. (已實現)雙線merge。對兩條數據流(variable)進行合併操做,實現簡單的join(根據key和timestamp嚴格匹配)。
c. (未實現)多線merge。將多條數據流(variable)根據key去全部的latest值,進行合併計算。
以上是系統中如今和將要實現的操做符,目前看效果不是很好,好比操做符a帶的功能太多,但願一個操做符就能解決多個問題,對用戶並不貼切,應該拆分爲aggregate,filter(split用多個filter來實現)。
對於每一個應用來講,直接拿底層框架的操做符進行配置能夠基本知足需求。在資源充足的狀況下,每一個應用能夠在框架的variable體系開發一套更高一級抽象層的操做符,來方便應用的使用。參見實時報警的實現
同時,variable部分會提供dump操做,按期的將variable值dump到hbase中,供後續查詢。這個功能主要是爲了過後分析和離線查詢,目前尚未在生產啓用。最近打算先簡單寫一路進dashboard,以利用其實時查詢和展示的能力。至因而否之後要擴展,要看最後項目的發展了
Rule這塊會直接對上一部的variable進行操做,經過用戶提供的閾值來得出有價值的信息(暫且稱爲alert),而且根據後續用戶配置的action操做分發到外部處理的地方。
目前,規則管理準備實現如下三種:
這裏須要註明的事,rule和variable有部分重合,rule的一些功能後續也能擴展到variable裏實現,二者的區別在於:
Dispatch引擎會將rule engine產生的alerts進行分發,供用戶進行進一步訪問。這一快將與data source一塊造成相對應的關係,目標是經過配置將alerts推送到制定的地方:
App 管理做爲後續計劃(nice to have),應該是沒可能實現了,本打算實現如下功能。
這一部分主要設計了邏輯上數據流的定義,以及其整個生命週期,下一段講一下咱們簡單的物理結構是如何去分別實現各個功能的。
以前已經提到過,咱們是storm+esper的形式,esper負責內部絕大部分的計算,storm負責外圍的組織。整個系統實現起來很是簡單,如圖
外部數據過來有兩種方式:
storm的多個spout/bolt節點獲取外部數據後,成爲統一的格式event(包含name,key,timestamp和其它特定屬性),並sharding(須要key)到不一樣esper 節點,做進一步計算。
每個esper節點,都已是運行在單個機器的單個進程上了,因此在這裏將不一樣來源的數據流對齊(須要timestamp),並做一些優化處理(好比去除冗餘的對象)。最後這些event就分配到esper引擎了。
variable計算引擎一方面將這些外部數據轉化爲簡單變量(一種我本身約定格式的esper數據流),這些簡單變量在一些操做符下能夠生成其它變量,有最簡單的操做符
insert into outputVar select * from inputVar where $condition
insert into outputVar select count/sum/avg(*) as value,... from inputVar:time:win(5 min)
insert into outputVar1 select * from inputVar where $condition;insert into outputVar2 select * from inputVar where not $condition
insert into outputVar select inputVar1.*, inputVar2.* from inputVar1.latest(key), inputVar2.latest(key) where inputVar1.key = inputVar2.key
,這個僞epl表示將兩個原始數據流,以其key做爲 單位,將最新值拼接起來,這裏還可做一些運算。舉個例子,有一個變量源數據來源於pc訪問,另外一個變量數據來源於移動app(可能用戶再家裏同時用手機和pc訪問),按ip/uid去作join,就能夠獲得這個ip的完整視圖這些基本的操做符都很是簡單,不會太多,開發起來也比較容易。只是要定義一下配置項,以及運算到底層esper語句的映射。咱們目前新加一個基本variable操做的話,後端只用新增一個文件便可,但前端比較難作,尚未想到很好的解決方法。
固然,對於一些特殊的應用,簡單的操做符可能抽象度較低,難以直接使用,能夠在簡單操做之上進一步封裝,詳細的參考case study裏面實時報警的就能夠了
變量出來以後,就能夠經過rule來過濾出咱們感興趣的內容了:
insert into rule1 select * from inputVar where value > 5
insert into rule1 select ... from inputVar1.latest(key), inputVar2.latest(key) where inputVar1.key = inputVar2.key and (inputVar1.value 5 or inputVar2.value <10)
最後產生的結果,經過各類方式送到系統下游的各個handler。因爲storm這種靈活的代碼運行框架,這一點很容易作到,不詳細敘述了。
ctrip內部用dashboard系統對各個應用的內部狀態進行監控,包括物理的/邏輯的,利用hbase的特性,得到了實時聚合和展現的能力。咱們的實時報警項目,就但願能自動化的去拉取數據,找出異常點。
公司內部也有其餘報警系統,會對訂單這些做一些同環比,閾值報警,系統跑的很好頗有效。咱們這邊的報警系統更多的在於系統監控方面,會有一些更大的挑戰,一是數量更大,我可能要對每一個hostip,每一個url,甚至是他們的組合做爲單位去檢查並報警;二是簡單的閾值規則對上層業務來講有價值,對底層來講沒太大指導做用,超出定義的閾值是比較常見的狀況,結局就是大量的報警郵件發出來了,但沒人關心,本身反倒成了公司內部最大的垃圾郵件製造商,因此須要各類更加複雜類型的報警:
這裏能夠看出,儘管有各類複雜的規則類型,但基本的操做是相同的,因此只要在基本操做上封裝一層便可。下面是個人一條規則配置,因爲沒有太多資源專門管理這些五花八門的規則,我將全部配置項揉到了一塊兒,看着複雜些,但管理成本稍微低些(由於各個部分的配置相互間能夠排列組合,分開來成本過高)。
{ "namespace": "ns", "group": "group, "name": "rulename", // 這三項只是名字標識,方便rule管理 "config": { "dashboardUrl": "http://xxxxx", // dashboard url,返回json數據 "timeAdjustment": 300, // 表示取多久以前的數據,同環比就在這個配置項有不一樣 "dataType": "Single", // 是否拉取到的值直接報警,仍是要作同環比,或者是多值計算 "period": 0, // 如下四項配置同環比的參數 "ops": "-", "oldCondition": "", "newCondition": "", "secondUrl": "", // 如下四項配置配置雙metrics計算 "secondTimeAdjustment": 0, "dualOps": "-", "firstCondition": "", "secondCondition": "", "valueType": "", // 如下三項配置是對以前的原始值或計算值直接檢測,仍是要看變化率(差或比值) "formerPointCondition": "", "latterPointCondition": "", "triggerType": "fixed", // 如下四項配置閾值類型,要麼直接給閾值,要麼給個標識符,用戶本身去寫複雜的閾值 "lower": 0, "upper": 30000, "thresholdName": "", "conditionWindow": 0, // 如下兩項控制規則屢次命中才報警,減小誤報 "conditionCount": 0, "cooldown": 600, // 報警cooldown,防止短時間內重複報警 "to": "wenlu@ctrip.com", // 報警郵件配置 "cc": "", "bcc": "", "mailInterval": 60, "cats": "", "catsEnv": "", "catsLevel": "info", "catsMessage": "", "catsDevid": "", "catsName": "", "catsPriority": "info", "type": "MetricsAlertingRule", // 表示規則類型,不一樣值會觸發不一樣操做 "desc": "" }, "type": "MetricsAlertingRule", "desc": "", "status": "on", "app_subid": "auto@hotel_product_common_utility_logging_responsetime_30s" // 自動生成底層數據時用給的標識符 }
這算是一條頂層規則,系統會自動生成一系列底層的data source/variable/rule/action,對頂層的CRUD操做也會自動映射爲底層的操做。
這樣開發一條新規則只用考慮如何去運用底層的計算平臺,能夠在更高的抽象層次上去開發,而不是從頭至尾從新開發一遍。
整體而言,實時報警的功能是實現了,但運用的很差,並且是給底層人民用的,可視度不高。
XXX應用是敏感項目,會講的含糊些。
這個項目數據量比較大,基本上對ctrip的每次訪問都要促發一系列的運算和規則檢驗。過濾後,每秒k級數據,目前有幾十個變量和規則,意味着每秒上w次的聚合操做和檢測。生產上用了三臺機器跑storm集羣,實時上藉助於storm+esper的高效,單臺測試機(16g內存+6核cpu),已經能基本扛得住如此量的運行(不過最近隨着變量的增長和流量的上升已經比較勉強,須要考慮可能的優化了)。
公司另一個項目,用的是我以前提的數據庫的方式。他們的數據跨度比較大,須要很長時間段的數據,而不侷限於當前,總數據量是xxx項目當前時間段數據的幾倍,但計算觸發頻次較低,每秒幾十次,若是不算db的話,用了10臺服務器,聽說cpu使用率20%不到。雖然我很不喜歡拿這兩個系統去比較,由於解決的問題和適用的場景不同,就好像拿喬丹和貝利去比較,但由此得出XXX項目效率比較低這種結論是很難讓人心服口服的。esper這種基於本地內存的效率遠不是基於db或者是redis這種異地內存的系統能夠比擬的,他的滑動窗口的計算效率也不是簡單算法就能超過的。若是說可靠性和成熟度卻是可讓人服帖的。
這個項目一方面展現了當前架構能扛得住中等流量的衝擊,另外一方面本意是嘗試讓用戶能自主的動態的建立變量:
上圖的拓撲是根據用戶的需求演化而來,其實具備必定的普適性,經過必定的過濾找到具體的感興趣的數據,進行聚合獲得統計特徵,在此之上才能建出靈活的規則來。
圖中最下方有個join變量,目前還沒用到,是爲了多數據源(多設備或者多數據中心的數據)的數據整合。有種說法是直接從數據源上進行合併,但這樣會增長數據源的複雜度,破壞總體的結構,還不能徹底覆蓋;另外一方面,join過濾後的數據遠比join原始數據高效的多得多
這一套以前一半用代碼,一半靠實時計算系統的規則,最近才抽象出來,原本打算徹底遷移到實時計算系統中,把整個圖的掌控交給用戶,由用戶去控制整個DAG的結構構造和節點配置,這樣靈活性比較高,經過統一的監控功能可讓用戶掌控每一環節的具體信息。這讓我想起去年有家國內公司來推銷大數據的,他們學術背景是可視計算。如今想來若是這套能實現的話,是否是也有點可視計算的味道了。
不過現實是這條路已經斷掉了,一是咱們team,尤爲是個人前端能力還比較差,另外一方面老闆以爲這個太複雜,易用性比較差,不能讓人家去管理樹(內部介紹還停留在樹一層),因而改爲了與業務適配,拓撲定死的結構,只讓用戶配底層的聚合變量,內部稱爲counter。如今只但願能知足用戶需求,能多多的建出變量來,這樣纔有往下一步走的可能。
如下基本是未實現,只是思考過的部分
insert into varInfo select count(*),.... from inputVar.win:time_length(1 min)
就能夠得到每一個變量的運行時統計,直接導入ctrip的dashboard系統,就能夠得到監控和展現能力,這樣還能導入到實時報警應用裏面去,得到實時報警能力(用戶提的需求,因此說多讓用戶參與討論是頗有用的,不要把用戶當傻瓜)。對XXX項目而言,這些統計信息甚至能做爲業務指標使用,例如若是我須要公司某一業務線的訪問量,只要配個變量+監控就ok了。沒什麼難度,但應該頗有用其實爲啥要這麼折騰,除了人懶想寫點一本萬利的代碼外,主要是從前公司那裏產生了完整的生態系統纔是最重要的這一個想法。因此力圖往這個方向靠。
此圖是我在paypal待了一年後最大的感覺:別看技術爛,只要生態系統建成了,整個系統能有機的跑起來,就能源源不斷的帶來收益。
整個系統是一個循環往復的過程:
這這個一套系統纔是完整的有機體,本文所述的內容都只是描述了其中最不重要的在線那一塊,而創建整個體系和完善離線系統纔是更重要的,這個是真正作一個完善有用的實時系統須要解決的。我本身還沒作到,就很少說了
想說的都說了,沒想到這麼長。最後只想說句作實時計算真不容易,越作越以爲能力和經驗上的不足益發明顯了,已經不是簡單搭個開源軟件就能搞定的。但願後面能看到別人的作法,有機會能跟風。
什麼,你竟然看到結束了,請你喝酸梅湯。