導(dǎo)語:系列一搞定異常發(fā)現(xiàn)后,就輪到異常定位了。所謂異常定位,就是找到異常發(fā)生處,比如異常是發(fā)生在哪個(gè)模塊或哪行代碼。目前筆者在FinTech領(lǐng)域供職,身臨BigData場景,本文將介紹BigData下app異常定位的設(shè)計(jì)思路和解決方案,以及業(yè)內(nèi)比較、如何開放、系統(tǒng)能力評估。

1、技術(shù)難點(diǎn):跳入大數(shù)據(jù)的坑
單個(gè)異常的定位并不難,比如通過app端回傳的異常棧一眼就能定位crash在哪。但筆者在FinTech領(lǐng)域供職,所在部門要接入最多上千個(gè)金融類app,和所有BigData場景一樣,本來看似簡單的問題將變得極端復(fù)雜,難點(diǎn)包括:
數(shù)據(jù)維度/靈活性:一開始,我們設(shè)計(jì)以app模塊為服務(wù)治理單元,不久便發(fā)現(xiàn)各個(gè)app對異常定位的要求并不相同,上至整個(gè)app,下至單個(gè)頁面元素,比如A要求模塊級別的定位,B要求頁面級別的定位,這要求對上千家app各種維度的數(shù)據(jù)進(jìn)行分析。
實(shí)效性:金融行業(yè)有很高的止損訴求(畢竟關(guān)系到錢),很多核心服務(wù)的治理是要實(shí)時(shí)的,尤其實(shí)時(shí)發(fā)現(xiàn)和定位異常,而在高并發(fā)大流量場景下做實(shí)時(shí)具有很大挑戰(zhàn)性。
實(shí)時(shí)一般指低延遲,是個(gè)相對的概念。由于異步架構(gòu)和網(wǎng)絡(luò)開銷,本文系統(tǒng)核心的實(shí)時(shí)能力在s級,這在我們業(yè)務(wù)范圍內(nèi)已經(jīng)足夠。當(dāng)然,對于少部分延遲要求極低的服務(wù),可以單獨(dú)做到更低延遲,但過于定制化,不是本文重點(diǎn)
- 跨平臺:為了實(shí)現(xiàn)app服務(wù)治理,要采集分析app端和服務(wù)端的數(shù)據(jù),里面會(huì)包含各種DSL定義的接口數(shù)據(jù)
需要強(qiáng)調(diào)一點(diǎn),我們優(yōu)先解決的是規(guī)模問題,至于BigData領(lǐng)域的彈性計(jì)算,全權(quán)交給了公司的云服務(wù)。
2、設(shè)計(jì)思路:單調(diào)到靈活
近幾個(gè)月,我們經(jīng)歷了3個(gè)階段。
階段1:單維度模式。最初,以app模塊為服務(wù)治理單元,對數(shù)據(jù)源的每條數(shù)據(jù)流格式要求很簡單,只需包含模塊名和該模塊數(shù)據(jù),我們稱之為單維度模式。這種模式簡單,但單調(diào),因?yàn)橹荒芊治鲆阅K為維度的數(shù)據(jù)。
階段2:多維度模式。不久,我們發(fā)現(xiàn)服務(wù)治理單元遠(yuǎn)不止app模塊,理論上所有維度都能充當(dāng)治理單元,這就要求兼容各個(gè)維度甚至是服務(wù)端api維度的數(shù)據(jù)。為此,我們調(diào)整了數(shù)據(jù)流格式,包含4個(gè)必選項(xiàng)appId、os、appVersion、dataType和其他可選項(xiàng),其中data可用來傳遞任意數(shù)據(jù)項(xiàng),實(shí)現(xiàn)數(shù)據(jù)維度的無限擴(kuò)展,我們稱之為多維度模式。該階段最關(guān)鍵是制定了一套通用數(shù)據(jù)源格式標(biāo)準(zhǔn),相比于單維度模式,這套標(biāo)準(zhǔn)能輕松擴(kuò)展到任意類型的異常定位,進(jìn)而擴(kuò)展到任意維度的服務(wù)治理。
* @param appId app唯一標(biāo)示,必選
* @param os 操作系統(tǒng),必選
* @param appVersion app版本號,必選
* @param dataType 數(shù)據(jù)類型,對應(yīng)策略名,用來指定使用哪個(gè)策略,必選
* @param osVersion 操作系統(tǒng)版本號,可選
* @param deviceId 設(shè)備號,可選
* @param deviceName 設(shè)備名,可選
* @param channel 渠道號,可選
* @param location 位置,可選
* @param ip ip號碼,可選
* @param mobilePhone 手機(jī)號,可選
* @param networkEnv 網(wǎng)絡(luò)環(huán)境類型,可選
* @param telecomOperator 基礎(chǔ)運(yùn)營商名稱,可選
* @param time 數(shù)據(jù)發(fā)生時(shí)間,后臺可根據(jù)數(shù)據(jù)類型的不同,選擇性信任此參數(shù),可選
* @param userAgent 用戶代理類型UA,可選
* @param screenSize 屏幕大小,可選
* @param data 數(shù)據(jù)詳情,可選
階段3:DSL。多維度模式只是統(tǒng)一了數(shù)據(jù)源,我們還需要對原始數(shù)據(jù)進(jìn)行分析,轉(zhuǎn)化成知識,這些知識告訴我們什么時(shí)候出了異常(異常發(fā)現(xiàn))、異常在哪里(異常定位)。面對多維度數(shù)據(jù)多場景化,系統(tǒng)的分析能力必須足夠靈活,能覆蓋任意異常的分析,歸根結(jié)底,就是要實(shí)現(xiàn)條件可定義。條件可定義的意思是針對任意類型異常,都能設(shè)置一個(gè)條件,當(dāng)數(shù)據(jù)流滿足該條件時(shí),就意味著此類型異常發(fā)生并能進(jìn)行定位。雖然策略平臺早在單維度模式階段就能通過配置策略來定義條件,但比較粗糙,為了更靈活定義條件,策略平臺引入了DSL對條件進(jìn)行表達(dá)。
為何叫
條件可定義而非異??啥x,是因?yàn)椴呗云脚_不單單用于異常發(fā)現(xiàn)和定位,還能用于數(shù)據(jù)統(tǒng)計(jì),即能夠定義當(dāng)滿足何種條件時(shí)就統(tǒng)計(jì)何種數(shù)據(jù)。而且,不單能統(tǒng)計(jì)移動(dòng)端(類似Google Analytics、TalkingData),還可以統(tǒng)計(jì)服務(wù)端(比如api統(tǒng)計(jì))
3、解決方案:SQL on jstorm
3.1、具體異常定位實(shí)例
假設(shè)有一個(gè)iOS應(yīng)用,應(yīng)用ID為100,要為該app的一個(gè)abc模塊設(shè)置crash監(jiān)控,要求如果一個(gè)小時(shí)內(nèi)crash數(shù)目>100,就向abc維護(hù)者發(fā)送監(jiān)控報(bào)警郵件。
我們的系統(tǒng)是如何定義一條策略來表達(dá)上面的crash監(jiān)控需求?

如上圖所示,我們先配置3個(gè)知識條件,表示當(dāng)數(shù)據(jù)流符合appId=100、os=ios、module=abc時(shí),將產(chǎn)生一個(gè)知識數(shù)據(jù),這個(gè)知識數(shù)據(jù)就是應(yīng)用的abc模塊在最近一小時(shí)內(nèi)累積的crash個(gè)數(shù)。每來一個(gè)符合這3個(gè)知識條件的原始數(shù)據(jù)流,該知識數(shù)據(jù)就變化一次,即crash個(gè)數(shù)就增1。該知識數(shù)據(jù)(crash個(gè)數(shù))將在redis中保留1個(gè)小時(shí)。接著,我們又配置了1個(gè)應(yīng)用條件,表示一旦知識數(shù)據(jù)符合該條件(crash個(gè)數(shù)>100),則觸發(fā)應(yīng)用策略(發(fā)送報(bào)警郵件)。除了可以設(shè)置觸發(fā)何種應(yīng)用策略外,我們還能設(shè)置符合應(yīng)用條件時(shí)是否將知識數(shù)據(jù)進(jìn)行持久化,這可用來做數(shù)據(jù)統(tǒng)計(jì),用在本節(jié)實(shí)例身上,就是可以統(tǒng)計(jì)abc模塊每小時(shí)的crash個(gè)數(shù)。
總結(jié)起來,配置一個(gè)策略其實(shí)主要就是配置一系列條件,這個(gè)過程分為兩部分:1)先配置N個(gè)知識條件,當(dāng)實(shí)時(shí)數(shù)據(jù)流滿足這N個(gè)條件,就產(chǎn)生相應(yīng)的知識數(shù)據(jù);2)再配置1個(gè)應(yīng)用條件,當(dāng)知識數(shù)據(jù)滿足該條件,則啟動(dòng)相應(yīng)的應(yīng)用策略或持久化。
3.2、整體架構(gòu)
本節(jié),我們對策略平臺整體架構(gòu)做一個(gè)講解。

首先,使用者要在策略平臺配置策略,包括配置N個(gè)知識條件和1個(gè)應(yīng)用條件。
接著,策略平臺把知識條件轉(zhuǎn)成一條sql。如上文所述,我們使用DSL來表達(dá)條件,而常見的DSL或結(jié)構(gòu)化格式包括sql、json、xml等,我們最終選擇sql,理由見下文。然后,將策略的所有配置項(xiàng)存入mongoDB,除了知識條件sql和應(yīng)用條件外,其他配置項(xiàng)還包括策略名、描述、知識緩存時(shí)間等基本信息。
一旦有數(shù)據(jù)產(chǎn)生,數(shù)據(jù)源需按照標(biāo)準(zhǔn)格式把數(shù)據(jù)傳給策略平臺,即必須包含appId、os、appVersion、dataType。dataType是策略名,用來指定本次數(shù)據(jù)使用哪個(gè)策略來處理。數(shù)據(jù)流先到消息隊(duì)列排隊(duì),再由jstorm異步拉取。對于那些延遲要求極低的核心服務(wù),數(shù)據(jù)源的數(shù)據(jù)將直達(dá)jstorm,不在消息隊(duì)列排隊(duì)。
jstorm拉取到數(shù)據(jù)流后,SQL-like引擎先根據(jù)dataType取出對應(yīng)的策略,解析策略中的sql,得到程序可識別的結(jié)構(gòu)化知識條件,再把策略和數(shù)據(jù)流傳給第2個(gè)bolt。第2個(gè)bolt中,知識分析模塊分析數(shù)據(jù)流,如果數(shù)據(jù)流符合知識條件,則產(chǎn)生相應(yīng)的知識數(shù)據(jù),然后使用inc或set命令把新的知識數(shù)據(jù)存入redis,并把新知識數(shù)據(jù)傳給第3個(gè)bolt。第3個(gè)bolt中,應(yīng)用分析模塊對新知識數(shù)據(jù)進(jìn)行分析,如果知識數(shù)據(jù)符合策略中的應(yīng)用條件,則觸發(fā)應(yīng)用策略或知識數(shù)據(jù)持久化。
為了區(qū)別于狹義上的數(shù)據(jù)庫sql,這里用了SQL-like
如果知識數(shù)據(jù)設(shè)置為可持久化,則需要定時(shí)將該知識數(shù)據(jù)從redis中不斷同步進(jìn)mongoDB。如果沒有定時(shí)同步,只依靠數(shù)據(jù)流來觸發(fā)持久化,一旦redis重啟或redis過期時(shí)間到來之前沒有數(shù)據(jù)流,知識數(shù)據(jù)將會(huì)丟失
3.3、系統(tǒng)核心
策略平臺的核心支撐技術(shù)是sql on jstorm,本質(zhì)上是一種SQL-like引擎(或QueryEngine)+計(jì)算框架組合,這樣的組合業(yè)內(nèi)并不陌生,比如hive on spark/hadoop、Greenplum等。
在大數(shù)據(jù)領(lǐng)域,計(jì)算框架基本算是標(biāo)配,它們可以充分利用集群資源提升系統(tǒng)的計(jì)算能力,雖然單機(jī)性能有所損耗。對計(jì)算框架的選型,由于實(shí)時(shí)性要求以及社區(qū)活躍度,我們選擇了流式計(jì)算框架jstorm。
SQL-like引擎最大作用是提供通用簡單的sql語法,完成復(fù)雜查詢和數(shù)據(jù)分析,一般用于數(shù)據(jù)倉庫。業(yè)內(nèi)有不少成熟的SQL-like引擎和工具,比如hive hql、spark sql 、wing等,但策略平臺的特殊性,接入現(xiàn)有工具的改造成本太大,于是我們定制化了自己的輕量級SQL-like引擎。
有人說知識條件并不復(fù)雜,用簡單的json就能表達(dá),無需sql,更沒必要單獨(dú)做了一個(gè)SQL-like引擎,而我們之所以這么做,理由包括:
- sql具有更直接明了的查詢表達(dá)能力,學(xué)習(xí)成本也低,還能做查詢優(yōu)化
- SQL-like在大數(shù)據(jù)領(lǐng)域很普遍,如果我們的SQL-like引擎無法適應(yīng)未來的發(fā)展,可以小成本遷移業(yè)內(nèi)成熟方案
- 作為一個(gè)獨(dú)立的無狀態(tài)組件,升級和擴(kuò)容縮容的風(fēng)險(xiǎn)變小
- 基于開源庫解析sql,開發(fā)成本不大
SQL-like引擎的設(shè)計(jì)借鑒了數(shù)據(jù)倉庫概念,比如知識條件相當(dāng)于維度(Dimention)、知識相當(dāng)于事實(shí)(Fact)。我們支持的sql語法如下:
select 知識類型 from 策略名 where 知識條件
// 支持的知識類型包括不限于:
// 數(shù)據(jù)流中任意一個(gè)field:語法是field,可用于慢請求定位等
// 總數(shù):語法是count(),可用于crash定位、流量控制、pv統(tǒng)計(jì)等
// 指定field的累加和:語法是sum(field),可用于累積流量統(tǒng)計(jì)等
// 拿3.1實(shí)例來說,就是select count() from crashMonitor where appId=100 and os=ios and module=abc
SQL-like引擎依賴JSqlParser完成詞法分析、語法分析、ast樹生成,對于一條簡單sql,JSqlParser大概耗時(shí)30ms,為了提升性能,可以將解析好的sql條件存入local緩存。SQL-like引擎定制化了自己的邏輯查詢計(jì)劃和物理查詢計(jì)劃,未來再展開講。
4、業(yè)內(nèi)比較

Sensors Data創(chuàng)始人對典型的數(shù)據(jù)流做了個(gè)總結(jié),如圖3。如果我們把持久化數(shù)據(jù)進(jìn)行可視化,就和圖3差不多了,這解釋了我們和業(yè)內(nèi)的相似之處,那區(qū)別在哪?
- 目標(biāo)不同:我們要實(shí)現(xiàn)app服務(wù)治理,業(yè)內(nèi)沒有成熟方案
- 手段不同:app服務(wù)治理分為知識和應(yīng)用兩個(gè)階段。知識階段對增量數(shù)據(jù)(實(shí)時(shí)數(shù)據(jù)流)和存量數(shù)據(jù)(redis中的知識數(shù)據(jù))做整合分析,而業(yè)內(nèi)一般對存量數(shù)據(jù)(全量原始數(shù)據(jù))做分析,也正由于知識數(shù)據(jù)量遠(yuǎn)比原始數(shù)據(jù)量小,因此可以做到s級低延遲。而應(yīng)用階段依賴知識數(shù)據(jù)做任意應(yīng)用,比如異常解決,應(yīng)用和業(yè)務(wù)強(qiáng)相關(guān),業(yè)務(wù)在業(yè)內(nèi)不一樣,應(yīng)用也就不一樣。
整個(gè)過程中,分析(或訓(xùn)練)的數(shù)據(jù)都擁有分類標(biāo)記,分析結(jié)果是對異常的預(yù)測,算是一種簡單的有監(jiān)督機(jī)器學(xué)習(xí)。
不過,如果未來需要用到離線的原始數(shù)據(jù)做治理,如何解決?這已經(jīng)涉及到傳統(tǒng)的數(shù)據(jù)倉庫OLAP甚至是data mining了,需求極度靈活,往往要用到任意原始數(shù)據(jù)項(xiàng)和任意時(shí)間點(diǎn)的數(shù)據(jù)。但我們目前更關(guān)注實(shí)時(shí)治理而非事后離線分析,況且原始數(shù)據(jù)存儲還有容量問題,要考慮引入Protobuf壓縮等方案,因此只是暫時(shí)緩存了3個(gè)月的原始數(shù)據(jù),以備不時(shí)之需。
5、如何開放
雖然開放和異常定位沒有直接關(guān)系,但app服務(wù)治理的使用者是上千個(gè)金融類app的維護(hù)者,所以一開始設(shè)計(jì)就要考慮如何開放,實(shí)現(xiàn)Strategy-as-a-Service。要實(shí)現(xiàn)開放,以下事情很重要。
- 接入數(shù)據(jù):提供統(tǒng)一接口,使用者按照標(biāo)準(zhǔn)格式傳遞數(shù)據(jù)到策略平臺
- 配置策略:提供統(tǒng)一的操作頁面
- 反饋:提供統(tǒng)一的可視化圖表,查看持久化數(shù)據(jù)和應(yīng)用策略命中情況
6、系統(tǒng)能力評估
首先,套用CAP原理來評估,即一致性和高可用間的權(quán)衡。一般來說,有狀態(tài)組件(比如redis和mongodb)是不一致問題之源,從圖2得知,知識數(shù)據(jù)要從redis向mongoDB持久化,只能由知識分析模塊或定時(shí)任務(wù)觸發(fā),而圖4場景中redis和mongoDB會(huì)有一段時(shí)間的不一致,我們無法做到強(qiáng)一致性,只能通過定時(shí)任務(wù)保證最終一致性,但定時(shí)任務(wù)會(huì)使很多數(shù)據(jù)在同一時(shí)間從redis中清除和寫mongoDB,降低了可用性。然而,為了追求某些場景的效率又偏向了可用性,比如我們將把sql解析結(jié)果緩存本地cache,修改sql后要同步所有機(jī)器清除本地cache,這里容忍同步期間的不一致。此外可用性方面,redis和mongDB都主從備,納入HA(High available)系統(tǒng),僅jstorm Nimbus是單點(diǎn)。

接著是性能。先是消息隊(duì)列中間件,一般考察TPS。通過Jason's Blog看到,kafka在3臺4核虛擬機(jī)broker下,如果數(shù)據(jù)流傳遞了策略平臺所有數(shù)據(jù)項(xiàng),即按100字節(jié)算,將達(dá)到330w records/s和322MB/s,而redis pub/sub benchmark表明單機(jī)tps在幾十萬。
再來分析一下jstorm內(nèi)組件的性能,重點(diǎn)考察TRT。假設(shè)命中的策略只設(shè)置了一個(gè)知識條件,按圖2的數(shù)據(jù)流,一次數(shù)據(jù)流請求最多包含apout和bolt間3次網(wǎng)絡(luò)消耗、3個(gè)bolt的邏輯處理耗時(shí)、讀寫mongoDB各1次、讀寫redis各1次、1次解析sql、1次http/dubbo應(yīng)用請求,大概50~200ms。
事實(shí)上,生產(chǎn)環(huán)境的情況更復(fù)雜,因?yàn)榭赡懿粫?huì)觸發(fā)寫mongoDB和http/dubbo請求、一個(gè)策略包含多個(gè)
知識條件等等,更詳實(shí)的壓測結(jié)果以后有機(jī)會(huì)再寫。