序言
時間回到2008年,還在上海交通大學上學的張旭豪、康嘉等人在上海創(chuàng)辦了餓了么,從校園外賣場景出發(fā),餓了么一步一步發(fā)展壯大,成為外賣行業(yè)的領(lǐng)頭羊。2017年8月餓了么并購百度外賣,強強合并,繼續(xù)開疆擴土。2018年餓了么加入阿里巴巴大家庭,與口碑融合成立阿里巴巴本地生活公司。“愛什么,來什么”,是餓了么對用戶不變的承諾。
餓了么的技術(shù)也伴隨著業(yè)務(wù)的飛速增長也不斷突飛猛進。據(jù)公開報道,2014年5月的日訂單量只有10萬,但短短幾個月之后就沖到了日訂單百萬,到當今日訂單上千萬單。在短短幾年的技術(shù)發(fā)展歷程上,餓了么的技術(shù)體系、穩(wěn)定性建設(shè)、技術(shù)文化建設(shè)等都有長足的發(fā)展。各位可查看往期文章一探其中發(fā)展歷程,在此不再贅述:
而可觀測性作為技術(shù)體系的核心環(huán)節(jié)之一,也跟隨餓了么技術(shù)的飛速發(fā)展,不斷自我革新,從“全鏈路可觀測性ETrace”擴展到“多活下的可觀測性體系ETrace”,發(fā)展成目前“一站式可觀測性平臺EMonitor”。
EMonitor經(jīng)過5年的多次迭代,現(xiàn)在已經(jīng)建成了集指標數(shù)據(jù)、鏈路追蹤、可視化面板、報警與分析等多個可觀測性領(lǐng)域的平臺化產(chǎn)品。EMonitor每日處理約1200T的原始可觀測性數(shù)據(jù),覆蓋餓了么絕大多數(shù)中間件,可觀測超5萬臺機器實例,可觀測性數(shù)據(jù)時延在10秒左右。面向餓了么上千研發(fā)人員,EMonitor提供精準的報警服務(wù)和多樣化的觸達手段,同時運行約2萬的報警規(guī)則。本文就細數(shù)餓了么可觀測性的建設(shè)歷程,回顧下“餓了么可觀測性建設(shè)的那些年”。
1.0:混沌初開,萬物興起
翻看代碼提交記錄,ETrace項目的第一次提交在2015年10月24日。而2015年,正是餓了么發(fā)展的第七個年頭,也是餓了么業(yè)務(wù)、技術(shù)、人員開始蓬勃發(fā)展的年頭。彼時,餓了么的可觀測性系統(tǒng)依賴Zabbix、Statsd、Grafana等傳統(tǒng)的“輕量級”系統(tǒng)。而“全鏈路可觀測性”正是當時的微服務(wù)化技術(shù)改造、后端服務(wù)Java化等技術(shù)發(fā)展趨勢下的必行之勢。
我們可觀測性團隊,在調(diào)研業(yè)界主流的全鏈路可觀測性產(chǎn)品--包括著名的開源全鏈路可觀測性產(chǎn)品“CAT”后,吸取眾家之所長,在兩個多月的爆肝開發(fā)后,推出了初代ETrace。我們提供的Java版本ETrace-Agent隨著新版的餓了么SOA框架“Pylon”在餓了么研發(fā)團隊中的推廣和普及開來。ETrace-Agent能自動收集應(yīng)用的SOA調(diào)用信息、API調(diào)用信息、慢請求、慢SQL、異常信息、機器信息、依賴信息等。下圖為1.0版本的ETrace頁面截圖。
在經(jīng)歷了半年的爆肝開發(fā)和各中間件兄弟團隊的鼎力支持,我們又開發(fā)了Python版本的Agent,更能適應(yīng)餓了么當時各語言百花齊放的技術(shù)體系。并且,通過和餓了么DAL組件、緩存組件、消息組件的密切配合與埋點,用戶的應(yīng)用增加了多層次的訪問信息,鏈路更加完整,故障排查過程更加清晰。
整體架構(gòu)體系
ETrace整體架構(gòu)如下圖。通過SDK集成在用戶應(yīng)用中的Agent定期將Trace數(shù)據(jù)經(jīng)Thrift協(xié)議發(fā)送到Collector(Agent本地不落日志),Collector經(jīng)初步過濾后將數(shù)據(jù)打包壓縮發(fā)往Kafka。Kafka下游的Consumer消費這些Trace數(shù)據(jù),一方面將數(shù)據(jù)寫入Hbase+HDFS,一方面根據(jù)與各中間件約定好的埋點規(guī)則,將鏈路數(shù)據(jù)計算成指標存儲到時間序列數(shù)據(jù)庫-- LinDB中。在用戶端,Console服務(wù)提供UI及查詢指標與鏈路數(shù)據(jù)的API,供用戶使用。
全鏈路可觀測性的實現(xiàn)
所謂全鏈路可觀測性,即每次業(yè)務(wù)請求中都有唯一的能夠標記這次業(yè)務(wù)完整的調(diào)用鏈路,我們稱這個ID為RequestId。而每次鏈路上的調(diào)用關(guān)系,類似于樹形結(jié)構(gòu),我們將每個樹節(jié)點上用唯一的RpcId標記。
如圖,在入口應(yīng)用App1上會新建一個隨機RequestId(一個類似UUID的32位字符串,再加上生成時的時間戳)。因它為根節(jié)點,故RpcId為“1”。在后續(xù)的RPC調(diào)用中,RequestId通過SOA框架的Context傳遞到下一節(jié)點中,且下一節(jié)點的層級加1,變?yōu)樾稳纭?.1”、“1.2”。如此反復(fù),同一個RequestId的調(diào)用鏈就通過RpcId還原成一個調(diào)用樹。
也可以看到,“全鏈路可觀測性的實現(xiàn)”不僅依賴與ETrace系統(tǒng)自身的實現(xiàn),更依托與公司整體中間件層面的支持。如在請求入口的Gateway層,能對每個請求生成“自動”新的RequestId(或根據(jù)請求中特定的Header信息,復(fù)用RequestId與RpcId);RPC框架、Http框架、Dal層、Queue層等都要支持在Context中傳遞RequestId與RpcId。
ETrace Api示例
在Java或Python中提供鏈路埋點的API:
/*記錄一個調(diào)用鏈路/Transaction trasaction = Trace.newTransaction(String type, String name);// business codestransaction.complete();/*記錄調(diào)用中的一個事件/Trace.logEvent(String type, String name, Map<String,String> tags, String status, String data)/*記錄調(diào)用中的一個異常/Trace.logError(String msg, Exception e)
Consumer的設(shè)計細節(jié)
Consumer組件的核心任務(wù)就是將鏈路數(shù)據(jù)寫入存儲。主要思路是以RequestId+RpcId作為主鍵,對應(yīng)的Data數(shù)據(jù)寫入存儲的Payload。再考慮到可觀測性場景是寫多讀少,并且多為文本類型的Data數(shù)據(jù)可批量壓縮打包存儲,因此我們設(shè)計了基于HDFS+Hbase的兩層索引機制。
如圖,Consumer將Collector已壓縮好的Trace數(shù)據(jù)先寫入HDFS,并記錄寫入的文件Path與寫入的Offset,第二步將這些“索引信息”再寫入Hbase。特別的,構(gòu)建Hbase的Rowkey時,基于ReqeustId的Hashcode和Hbase Table的Region數(shù)量配置,來生成兩個Byte長度的ShardId字段作為Rowkey前綴,避免了某些固定RequestId格式可能造成的寫入熱點問題。(因RequestId在各調(diào)用源頭生成,如應(yīng)用自身、Nginx、餓了么網(wǎng)關(guān)層等。可能某應(yīng)用錯誤設(shè)置成以其AppId為前綴RequestId,若沒有ShardId來打散,則它所有RequestId都將落到同一個Hbase Region Server上。)
在查詢時,根據(jù)RequestId + RpcId作為查詢條件,依次去Hbase、HDFS查詢原始數(shù)據(jù),便能找到某次具體的調(diào)用鏈路數(shù)據(jù)。但有的需求場景是,只知道源頭的RequestId需要查看整條鏈路的信息,希望只排查鏈路中狀態(tài)異常的或某些指定RPC調(diào)用的數(shù)據(jù)。因此,我們在HBbase的Column Value上還額外寫了RPCInfo的信息,來記錄單次調(diào)用的簡要信息。如:調(diào)用狀態(tài)、耗時、上下游應(yīng)用名等。
此外,餓了么的場景下,研發(fā)團隊多以訂單號、運單號作為排障的輸入,因此我們和業(yè)務(wù)相關(guān)團隊約定特殊的埋點規(guī)則--在Transaction上記錄一個特殊的"orderId={實際訂單號}"的Tag--便會在Hbase中新寫一條“訂單表”的記錄。該表的設(shè)計也不復(fù)雜,Rowkey由ShardId與訂單號組成,Columne Value部分由對應(yīng)的RequestId+RpcId及訂單基本信息(類似上文的RPCInfo)三部分組成。
如此,從業(yè)務(wù)鏈路到全鏈路信息到詳細單個鏈路,形成了一個完整的全鏈路排查體系。
Consumer組件的另一個任務(wù)則是將鏈路數(shù)據(jù)計算成指標。實現(xiàn)方式是在寫入鏈路數(shù)據(jù)的同時,在內(nèi)存中將Transaction、Event等數(shù)據(jù)按照既定的計算邏輯,計算成SOA、DAL、Queue等中間件的指標,內(nèi)存稍加聚合后再寫入時序數(shù)據(jù)庫LinDB。
指標存儲:LinDB 1.0
應(yīng)用指標的存儲是一個典型的時間序列數(shù)據(jù)庫的使用場景。根據(jù)我們以前的經(jīng)驗,市面上主流的時間序列數(shù)據(jù)庫-- OpenTSDB、InfluxDB、Graphite--在擴展能力、集群化、讀寫效率等方面各有缺憾,所以我們選型使用RocksDB作為底層存儲引擎,借鑒Kafka的集群模式,開發(fā)了餓了么的時間序列數(shù)據(jù)庫--LinDB。
指標采用類似Prometheus的“指標名+鍵值對的Tags”的數(shù)據(jù)模型,每個指標只有一個支持Long或Double的Field。某個典型的指標如:
COUNTER: eleme_makeorder{city="shanghai",channel="app",status="success"} 45
我們主要做了一些設(shè)計實現(xiàn):
這套存儲方案在初期很好的支持了ETrace的指標存儲需求,為ETrace大規(guī)模接入與可觀測性數(shù)據(jù)的時效性提供了堅固的保障。有了ETrace,餓了么的技術(shù)人終于能從全鏈路的角度去排查問題、治理服務(wù),為之后的技術(shù)升級、架構(gòu)演進,提供了可觀測性層面的支持。
其中架構(gòu)的幾點說明
1. 是否保證所有可觀測性數(shù)據(jù)的可靠性?
不,我們承諾的是“盡可能不丟”,不保證100%的可靠性。基于這個前提,為我們設(shè)計架構(gòu)時提供了諸多便利。如,Agent與Collector若連接失敗,若干次重試后便丟棄數(shù)據(jù),直到Collector恢復(fù)可用;Kafka上下游的生產(chǎn)和消費也不必Ack,避免影響處理效率。
2. 為什么在SDK中的Agent將數(shù)據(jù)發(fā)給Collector,而不是直接發(fā)送到Kafka?
3. SDK中的Agent如何控制對業(yè)務(wù)應(yīng)用的影響?
4. 為什么選擇侵入性的Agent?
選擇寄生在業(yè)務(wù)應(yīng)用中的SDK模式,在當時看來更利于ETrace的普及與升級。而從現(xiàn)在的眼光看來,非侵入式的Agent對用戶的集成更加便利,并且可以通過Kubernates中SideCar的方式對用戶透明部署與升級。
5. 如何實現(xiàn)“盡量不丟數(shù)據(jù)”?
6. 可觀測性數(shù)據(jù)如何實現(xiàn)多語言支持?
Agent與Collector之間選擇Thrift RPC框架,并定制整個序列化方式。Java/Python/Go/PHP的Agent依數(shù)據(jù)規(guī)范開發(fā)即可。
2.0:異地多活,大勢初成
2016年底,餓了么為了迎接業(yè)務(wù)快速增長帶來的調(diào)整,開始推進“異地多活”項目。新的多數(shù)據(jù)中心架構(gòu)對既有的可觀測性架構(gòu)也帶來了調(diào)整,ETrace亦經(jīng)過了一年的開發(fā)演進,升級到多數(shù)據(jù)中心的新架構(gòu)、拆分出實時計算模塊、增加報警功能等,進入ETrace2.0時代。
異地多活的挑戰(zhàn)
隨著餓了么的異地多活的技術(shù)改造方案確定,對可觀測性平臺提出了新的挑戰(zhàn):如何設(shè)計多活架構(gòu)下的可觀測性系統(tǒng)?以及如何聚合多數(shù)據(jù)中心的可觀測性數(shù)據(jù)?
經(jīng)過一年多的推廣與接入,ETrace已覆蓋了餓了么絕大多數(shù)各語言的應(yīng)用,每日處理數(shù)據(jù)量已達到了數(shù)十T以上。在此數(shù)據(jù)規(guī)模下,決不可能將數(shù)據(jù)拉回到某個中心機房處理。因此“異地多活”架構(gòu)下的可觀測性設(shè)計的原則是:各機房處理各自的可觀測性數(shù)據(jù)。
我們開發(fā)一個Gateway模塊來代理與聚合各數(shù)據(jù)中心的返回結(jié)果,它會感知各機房間內(nèi)Console服務(wù)。圖中它處于某個中央的云上區(qū)域,實際上它可以部署在各機房中,通過域名的映射機制來做切換。
如此部署的架構(gòu)下,各機房中的應(yīng)用由與機房相綁定的環(huán)境變量控制將可觀測性數(shù)據(jù)發(fā)送到該機房內(nèi)的ETrace集群,收集、計算、存儲等過程都在同一機房內(nèi)完成。用戶通過前端Portal來訪問各機房內(nèi)的數(shù)據(jù),使用體驗和之前類似。
即使考慮極端情況下--某機房完全不可用(如斷網(wǎng)),“異地多活”架構(gòu)可將業(yè)務(wù)流量切換到存活的機房中,讓業(yè)務(wù)繼續(xù)運轉(zhuǎn)。而可觀測性上,通過將Portal域名與Gateway域名切換到存活的機房中,ETrace便能繼續(xù)工作(雖然會缺失故障機房的數(shù)據(jù))。在機房網(wǎng)絡(luò)恢復(fù)后,故障機房內(nèi)的可觀測性數(shù)據(jù)也能自動恢復(fù)(因為該機房內(nèi)的可觀測性數(shù)據(jù)處理流程在斷網(wǎng)時仍在正常運作)。
可觀測性數(shù)據(jù)實時處理的挑戰(zhàn)
在1.0版本中的Consumer組件,既負責將鏈路數(shù)據(jù)寫入到Hbase/HDFS中,又負責將鏈路數(shù)據(jù)計算成指標存儲到LinDB中。兩個流程可視為同步的流程,但前者可接受數(shù)分鐘的延遲,后者要求達到實時的時效性。當時Hbase集群受限于機器性能與規(guī)模,經(jīng)常在數(shù)據(jù)熱點時會寫入抖動,進而造成指標計算抖動,影響可用性。因此,我們迫切需要拆分鏈路寫入模塊與指標計算模塊。
在選型實時計算引擎時,我們考慮到需求場景是:
- 能靈活的配置鏈路數(shù)據(jù)的計算規(guī)則,最好能動態(tài)調(diào)整;
- 能水平擴展,以適應(yīng)業(yè)務(wù)的快速發(fā)展;
- 數(shù)據(jù)輸出與既有系統(tǒng)(如LinDB與Kafka)無縫銜接;
很遺憾的是,彼時業(yè)界無現(xiàn)成的拿來即用的大數(shù)據(jù)流處理產(chǎn)品。我們就基于復(fù)雜事件處理(CEP)引擎Esper實現(xiàn)了一個類SQL的實時數(shù)據(jù)計算平臺--Shaka。Shaka包括“Shaka Console”和“Shaka Container”兩個模塊。Shaka Console由用戶在圖形化界面上使用,來配置數(shù)據(jù)處理流程(Pipeline)、集群、數(shù)據(jù)源等信息。用戶完成Pipeline配置后,Shaka Console會將變更推送到Zookeeper上。無狀態(tài)的Shaka Container會監(jiān)聽Zookeeper上的配置變更,根據(jù)自己所屬的集群去更新內(nèi)部運行的Component組件。而各Component實現(xiàn)了各種數(shù)據(jù)的處理邏輯:消費Kafka數(shù)據(jù)、處理Trace/Metric數(shù)據(jù)、Metric聚合、運行Esper邏輯等。
Trace數(shù)據(jù)和Metric格式轉(zhuǎn)換成固定的格式后,剩下來按需編寫Esper語句就能生成所需的指標了。如下文所示的Esper語句,就能將類型為Transaction的Trace數(shù)據(jù)計算成以“{appId}.transaction”的指標(若Consumer中以編碼方式實現(xiàn),約需要近百行代碼)。經(jīng)過這次的架構(gòu)升級,Trace數(shù)據(jù)能快速的轉(zhuǎn)化為實時的Metric數(shù)據(jù),并且對于業(yè)務(wù)的可觀測性需求,只用改改SQL語句就能快速滿足,顯著降低了開發(fā)成本和提升了開發(fā)效率。
@Name('transaction')@Metric(name = '{appId}.transaction', tags = {'type', 'name', 'status', 'ezone', 'hostName'}, fields = {'timerCount', 'timerSum', 'timerMin', 'timerMax'}, sampling = 'sampling')select header.ezone as ezone, header.appId as appId, header.hostName as hostName, type as type, name as name, status as status, trunc_sec(timestamp, 10) as timestamp, f_sum(sum(duration)) as timerSum, f_sum(count(1)) as timerCount, f_max(max(duration)) as timerMax, f_min(min(duration)) as timerMin, sampling('Timer', duration, header.msg) as samplingfrom transactiongroup by header.appId, type, name, header.hostName, header.ezone, status, trunc_sec(timestamp, 10);
新的UI、更豐富的中間件數(shù)據(jù)
1.0版本的前端UI,是集成在Console項目中基于Angular V1開發(fā)的。我們迫切希望能做到前后端分離,各司其職。于是基于Angular V2的若干個月開發(fā),新的Portal組件登場。得益于Angular的數(shù)據(jù)綁定機制,新的ETrace UI各組件間聯(lián)動更自然,排查故障更方便。
餓了么自有中間件的研發(fā)進程也在不斷前行,在可觀測性的打通上也不斷深化。2.0階段,我們進一步集成了--Redis、Queue、ElasticSearch等等,覆蓋了餓了么所有的中間件,讓可觀測性無死角。
殺手級功能:指標查看與鏈路查看的無縫整合
傳統(tǒng)的可觀測性系統(tǒng)提供的排障方式大致是:接收報警(alert)--查看指標(Metrics)--登陸機器--搜索日志(Trace/Log),而ETrace通過Metric與Trace的整合,能讓用戶直接在UI上通過點擊就能定位絕大部分問題,顯著拔高了用戶的使用體驗與排障速度。
某個排查場景如:用戶發(fā)現(xiàn)總量異常突然增加,可在界面上篩選機房、異常類型等找到實際突增的某個異常,再在曲線上直接點擊數(shù)據(jù)點,就會彈出對應(yīng)時間段的異常鏈路信息。鏈路上有詳細的上下游信息,能幫助用戶定位故障。
它的實現(xiàn)原理如上圖所示。具體的,前文提到的實時計算模塊Shaka將Trace數(shù)據(jù)計算成Metric數(shù)據(jù)時,會額外以抽樣的方式將Trace上的RequsetId與RpcId也寫到Metric上(即上文Esper語句中,生成的Metric中的sampling字段)。這種Metric數(shù)據(jù)會被Consumer模塊消費并寫入到Hbase一張Sampling表中。
用戶在前端Portal的指標曲線上點擊某個點時,會構(gòu)建一個Sampling的查詢請求。該請求會帶上:該曲線的指標名、數(shù)據(jù)點的起止時間、用戶選擇過濾條件(即Tags)。Consumer基于這些信息構(gòu)建一個Hbase的RegexStringComparator的Scan查詢。查詢結(jié)果中可能會包含多個結(jié)果,對應(yīng)著該時間點內(nèi)數(shù)據(jù)點(Metric)上發(fā)生的多個調(diào)用鏈路(Trace),繼而拿著結(jié)果中的RequestId+RpcId再去查詢一次Hbase/HDFS存儲就能獲得鏈路原文。(注:實際構(gòu)建Hbase Rowkey時Tag部分存的是其Hashcode而不是原文String。)
眾多轉(zhuǎn)崗、離職的餓了么小伙伴,最念念不忘不完的就是這種“所見即所得”的可觀測性排障體驗。
報警Watchdog 1.0
在應(yīng)用可觀測性基本全覆蓋之后,報警的需求自然成了題中之義。技術(shù)選型上,根據(jù)我們在實時計算模塊Shaka上收獲的經(jīng)驗,決定再造一個基于實時數(shù)據(jù)的報警系統(tǒng)--Watchdog。
實時計算模塊Shaka中已經(jīng)將Trace數(shù)據(jù)計算成指標Metrics,報警模塊只需消費這些數(shù)據(jù),再結(jié)合用戶配置的報警規(guī)則產(chǎn)出報警事件即可。因此,我們選型使用Storm作為流式計算平臺,在Spount層次根據(jù)報警規(guī)則過濾和分流數(shù)據(jù),在Bolt層中Esper引擎運行著由用戶配置的報警規(guī)則轉(zhuǎn)化成Esper語句并處理分流后的Metric數(shù)據(jù)。若有符合Esper規(guī)則的數(shù)據(jù),即生成一個報警事件alert。Watchdog Portal模塊訂閱Kafka中的報警事件,再根據(jù)具體報警的觸達方式通知到用戶。默認Esper引擎中數(shù)據(jù)聚合時間窗口為1分鐘,所以整個數(shù)據(jù)處理流程的時延約為1分鐘左右。
Metrics API與LinDB 2.0:
在ETrace 1.0階段,我們只提供了Trace相關(guān)的API,LinDB僅供內(nèi)部存儲使用。用戶逐步的意識到如果能將“指標”與“鏈路”整合起來,就能發(fā)揮更大的功用。因此我們在ETrace-Agent中新增了Metrics相關(guān)的API:
// 計數(shù)器類型Trace.newCounter(String metricName).addTags(Map<String, String> tags).count(int value);// 耗時與次數(shù)Trace.newTimer(String metricName).addTags(Map<String, String> tags).value(int value);// 負載大小與次數(shù)Trace.newPayload(String metricName).addTags(Map<String, String> tags).value(int value);// 單值類型Trace.newGauge(String metricName).addTags(Map<String, String> tags).value(int value);
基于這些API,用戶可以在代碼中針對他的業(yè)務(wù)邏輯進行指標埋點,為后來可觀測性大一統(tǒng)提供了實現(xiàn)條件。在其他組件同步開發(fā)時,我們也針對LinDB做了若干優(yōu)化,提升了寫入性能與易用性:
- 增加Histogram、Gauge、Payload、Ratio多種指標數(shù)據(jù)類型;
- 從1.0版本的每條指標數(shù)據(jù)都調(diào)用一次RocksDB的API進行寫入,改成先在內(nèi)存中聚合一段時間,再通過RocksDB的API進行批量寫入文件。
3.0:推陳出新,融會貫通
可觀測性系統(tǒng)大一統(tǒng)
在2017年的餓了么,除了ETrace外還有多套可觀測性系統(tǒng):基于Statsd/Graphite的業(yè)務(wù)可觀測性系統(tǒng)、基于InfluxDB的基礎(chǔ)設(shè)施可觀測性系統(tǒng)。后兩者都集成Grafana上,用戶可以去查看他的業(yè)務(wù)或者機器的詳細指標。但實際排障場景中,用戶還是需要在多套系統(tǒng)間來回切換:根據(jù)Grafana上的業(yè)務(wù)指標感知業(yè)務(wù)故障,到ETrace上查看具體的SOA/DB故障,再到Grafana上去查看具體機器的網(wǎng)絡(luò)或磁盤IO指標。雖然,我們也開發(fā)了Grafana的插件來集成LinDB的數(shù)據(jù)源,但因本質(zhì)上差異巨大的系統(tǒng)架構(gòu),還是讓用戶“疲于奔命”式的來回切換系統(tǒng),用戶難以有統(tǒng)一的可觀測性體驗。因此2018年初,我們下定決心:將多套可觀測性系統(tǒng)合而為一,打通“業(yè)務(wù)可觀測性+應(yīng)用可觀測性+基礎(chǔ)設(shè)施可觀測性”,讓ETrace真正成為餓了么的一站式可觀測性平臺。
LinDB 3.0:
所謂“改造”未動,“存儲”先行。想要整合InfluxDB與Statsd,先要研究他們與LinDB的異同。我們發(fā)現(xiàn),InfluxDB是支持一個指標名(Measurement)上有多個Field Key的。如,InfluxDB可能有以下指標:
measurement=census, fields={butterfiles=12, honeybees=23}, tags={location=SH, scientist=jack}, timestamp=2015-08-18T00:06:00Z
若是LinDB 2.0的模式,則需要將上述指標轉(zhuǎn)換成兩個指標:
measurement=census, field={butterfiles=12}, tags={location=SH, scientist=jack}, timestamp=2015-08-18T00:06:00Zmeasurement=census, field={honeybees=23}, tags={location=SH, scientist=jack}, timestamp=2015-08-18T00:06:00Z
可以想見在數(shù)據(jù)存儲與計算效率上,單Field模式有著極大的浪費。但更改指標存儲的Schema,意味著整個數(shù)據(jù)處理鏈路都需要做適配和調(diào)整,工作量和改動極大。然而不改就意味著“將就”,我們不能忍受對自己要求的降低。因此又經(jīng)過了幾個月的爆肝研發(fā),LinDB 3.0開發(fā)完成。
這次改動,除了升級到指標多Fields模式外,還有以下優(yōu)化點:
經(jīng)過這次大規(guī)模優(yōu)化后,從最初的每日5T指標數(shù)據(jù)漲到如今的每日200T數(shù)據(jù),LinDB 3.0都經(jīng)受住了考驗。指標查詢的響應(yīng)時間的99分位線為200ms。詳細設(shè)計細節(jié)可參看文末的分布式時序數(shù)據(jù)庫 - LinDB。
將Statsd指標轉(zhuǎn)成LinDB指標
Statsd是餓了么廣泛使用的業(yè)務(wù)指標埋點方案,各機房有一個數(shù)十臺機器規(guī)模的Graphite集群。考慮到業(yè)務(wù)的核心指標都在Statsd上,并且各個AppId以ETrace Metrics API替換Statsd是一個漫長的過程(也確實是,前前后后替換完成就花了將近一年時間)。為了減少對用戶與NOC團隊的影響,我們決定:用戶更新代碼的同時,由ETrace同時“兼容”Statsd的數(shù)據(jù)。
得益于餓了么強大的中間件體系,業(yè)務(wù)在用Statsd API埋點的同時會“自動”記一條特殊的Trace數(shù)據(jù),攜帶上Statsd的Metric數(shù)據(jù)。那么只要處理Trace數(shù)據(jù)中的Statsd埋點,我們就能將大多數(shù)Statsd指標轉(zhuǎn)化為LinDB指標。如下圖:多個Statsd指標會轉(zhuǎn)為同一個LinDB指標。
// statsd:stats.app.myAppName.order.from_ios.success 32stats.app.myAppName.order.from_android.success 29stats.app.myAppName.order.from_pc.failure 10stats.app.myAppName.order.from_openapi.failure 5// lindb:MetricName: myAppName.orderTags: "tag1"=[from_ios, from_android,from_pc, from_openapi] "tag2"=[success, failure]
之前我們的實時計算模塊Shaka就在這里派上了大用場:只要再新增一路數(shù)據(jù)處理流程即可。如下圖,新增一條Statsd數(shù)據(jù)的處理Pipeline,并輸出結(jié)果到LinDB。在用戶的代碼全部從Statsd API遷移到ETrace API后,這一路處理邏輯即可移除。
將InfluxDB指標轉(zhuǎn)成LinDB指標
InfluxDB主要用于機器、網(wǎng)絡(luò)設(shè)備等基礎(chǔ)設(shè)施的可觀測性數(shù)據(jù)。餓了么每臺機器上,都部署了一個ESM-Agent。它負責采集機器的物理指標(CPU、網(wǎng)絡(luò)協(xié)議、磁盤、進程等),并在特定設(shè)備上進行網(wǎng)絡(luò)嗅探(Smoke Ping)等。這個數(shù)據(jù)采集Agent原由Python開發(fā),在不斷需求堆疊之后,已龐大到難以維護;并且每次更新可觀測邏輯,都需要全量發(fā)布每臺機器上的Agent,導(dǎo)致每次Agent的發(fā)布都令人心驚膽戰(zhàn)。
我們從0開始,以Golang重新開發(fā)了一套ESM-Agent,做了以下改進:
從ETrace到EMonitor,不斷升級的可觀測性體驗
2017年底,我們團隊終于迎來了一名正式的前端開發(fā)工程師,可觀測性團隊正式從后端開發(fā)寫前端的狀態(tài)中脫離出來。在之前的Angular的開發(fā)體驗中,我們深感“狀態(tài)轉(zhuǎn)換”的控制流程甚為繁瑣,并且開發(fā)的組件難以復(fù)用(雖然其后版本的Angular有了很大的改善)。在調(diào)用當時流行的前端框架后,我們在Vue與React之中選擇了后者,輔以Ant Design框架,開發(fā)出了媲美Grafana的指標看版與便利的鏈路看板,并且在PC版本之外還開發(fā)了移動端的定制版本。我們亦更名了整個可觀測性產(chǎn)品,從“ETrace”更新為“EMonitor”:不僅僅是鏈路可觀測性系統(tǒng),更是餓了么的一站式可觀測性平臺。
可觀測性數(shù)據(jù)的整合:業(yè)務(wù)指標 + 應(yīng)用鏈路 + 基礎(chǔ)設(shè)施指標 + 中間件指標
在指標系統(tǒng)都遷移到LinDB后,我們在EMonitor上集成了“業(yè)務(wù)指標 + 應(yīng)用鏈路 + 基礎(chǔ)設(shè)施指標 + 中間件指標”的多層次的可觀測性數(shù)據(jù),讓用戶能在一處觀測它的業(yè)務(wù)數(shù)據(jù)、排查業(yè)務(wù)故障、深挖底層基礎(chǔ)設(shè)施的數(shù)據(jù)。
可觀測性場景的整合:指標 + 鏈路 + 報警
在可觀測性場景上,“指標看板”用于日常業(yè)務(wù)盯屏與宏觀業(yè)務(wù)可觀測性,“鏈路”作為應(yīng)用排障與微觀業(yè)務(wù)邏輯透出,“報警”則實現(xiàn)可觀測性自動化,提高應(yīng)急響應(yīng)效率。
靈活的看板配置與業(yè)務(wù)大盤
在指標配置上,我們提供了多種圖表類型--線圖、面積圖、散點圖、柱狀圖、餅圖、表格、文本等,以及豐富的自定義圖表配置項,能滿足用戶不同數(shù)據(jù)展示需求。
在完成單個指標配置后,用戶需要將若干個指標組合成所需的指標看板。用戶在配置頁面中,先選擇待用的指標,再通過拖拽的方式,配置指標的布局便可實時預(yù)覽布局效果。一個指標可被多個看板引用,指標的更新也會自動同步到所有看板上。為避免指標配置錯誤而引起歧義,我們也開發(fā)了“配置歷史”的功能,指標、看板等配置都能回滾到任意歷史版本上。
看板配置是靜態(tài)圖表組合,而業(yè)務(wù)大盤提供了生動的業(yè)務(wù)邏輯視圖。用戶可以根據(jù)他的業(yè)務(wù)場景,將指標配置整合成一張宏觀的業(yè)務(wù)圖。
第三方系統(tǒng)整合:變更系統(tǒng) + SLS日志
因每條報警信息和指標配置信息都與AppId關(guān)聯(lián),那么在指標看板上可同步標記出報警的觸發(fā)時間。同理,我們拉取了餓了么變更系統(tǒng)的應(yīng)用變更數(shù)據(jù),將其標注到對應(yīng)AppId相關(guān)的指標上。在故障發(fā)生時,用戶查看指標數(shù)據(jù)時,能根據(jù)有無變更記錄、報警記錄來初步判斷故障原因。
餓了么的日志中間件能自動在記錄日志時加上對應(yīng)的ETrace的RequestId等鏈路信息。如此,用戶查看SLS日志服務(wù)時,能反查到整條鏈路的RequestId;而EMonitor也在鏈路查看頁面,拼接好了該應(yīng)用所屬的SLS鏈接信息,用戶點擊后能直達對應(yīng)的SLS查看日志上下文。
使用場景的整合:桌面版 + 移動版
除提供桌面版的EMonitor外,我們還開發(fā)了移動版的EMonitor,它也提供了大部分可觀測性系統(tǒng)的核心功能--業(yè)務(wù)指標、應(yīng)用指標、報警信息等。移動版EMonitor能內(nèi)嵌于釘釘之中,打通了用戶認證機制,幫助用戶隨時隨地掌握所有的可觀測性信息。
為了極致的體驗,精益求精
為了用戶的極致使用體驗,我們在EMonitor上各功能使用上細細打磨,這里僅舉幾個小例子:
- 我們?yōu)闃O客開發(fā)者實現(xiàn)了若干鍵盤快捷鍵。例如,“V”鍵就能展開查看指標大圖。
- 圖上多條曲線時,點擊圖例是默認單選,目的是讓用戶只看他關(guān)心的曲線。此外,若是“Ctrl+鼠標點擊”則是將其加選擇的曲線中。這個功能在一張圖幾十條曲線時,對比幾個關(guān)鍵曲線時尤為有用。
- 為了讓色弱開發(fā)者更容易區(qū)分成功或失敗的狀態(tài),我們針對性的調(diào)整了對應(yīng)顏色的對比度。
成為餓了么一站式可觀測性平臺
EMonitor開發(fā)完成后,憑借優(yōu)異的用戶體驗與產(chǎn)品集成度,很快在用戶中普及開來。但是,EMonitor要成為餓了么的一站式可觀測性平臺,還剩下最后一戰(zhàn)--NOC可觀測性大屏。
NOC可觀測性大屏替換
餓了么有一套完善的應(yīng)急處理與保障團隊,包括7*24值班的NOC(Network Operation Center)團隊。在NOC的辦公區(qū)域,有一整面墻上都是可觀測性大屏,上面顯示著餓了么的實時的各種業(yè)務(wù)曲線。下圖為網(wǎng)上找的一張示例圖,實際餓了么的NOC大屏比它更大、數(shù)據(jù)更多。
當時這個可觀測大屏是將Grafana的指標看版投影上去。我們希望將NOC大屏也替換成EMonitor的看版。如前文所說,我們逐步將用戶的Statsd指標數(shù)據(jù)轉(zhuǎn)換成了LinDB指標,在NOC團隊的協(xié)助下,一個一個將Grafana的可觀測性指標“搬”到EMonitor上。此外,在原來白色主題的EMonitor之上,我們開發(fā)了黑色主題以適配投屏上墻的效果(白色背景投屏太刺眼)。
終于趕在2018年的雙十一之前,EMonitor正式入駐NOC可觀測大屏。在雙十一當天,眾多研發(fā)擠在NOC室看著墻上的EMonitor看版上的業(yè)務(wù)曲線不斷飛漲,作為可觀測性團隊的一員,這份自豪之情由衷而生。經(jīng)此一役,EMonitor真正成為了餓了么的“一站式可觀測性平臺”,Grafana、Statsd、InfluxDB等都成了過去時。
報警Watchdog 2.0
同樣在EMonitor之前,亦有Statsd與InfluxDB對應(yīng)的多套報警系統(tǒng)。用戶若想要配置業(yè)務(wù)報警、鏈路報警、機器報警,需要輾轉(zhuǎn)多個報警系統(tǒng)之間。各系統(tǒng)的報警的配置規(guī)則、觸達體驗亦是千差萬別。Watchdog報警系統(tǒng)也面臨著統(tǒng)一融合的挑戰(zhàn)。
- 在調(diào)研其他系統(tǒng)的報警規(guī)則實現(xiàn)后,Watchdog中仍以LinDB的指標作為元數(shù)據(jù)實現(xiàn)。
- 針對其他報警系統(tǒng)的有顯著區(qū)別的訂閱模式,我們提出了"報警規(guī)則+一個規(guī)則多個訂閱標簽+一個用戶訂閱多個標簽"的方式,完美遷移了幾乎其他系統(tǒng)所有的報警規(guī)則與訂閱關(guān)系。
- 其他各系統(tǒng)在報警觸達與觸達內(nèi)容上也略有不同。我們統(tǒng)一整合成“郵件+短信+釘釘+語音外呼”四種通知方式,并且提供可參數(shù)化的自定義Markdown模板,讓用戶可自己定時報警信息。
經(jīng)過一番艱苦的報警配置與邏輯整合后,我們?yōu)橛脩簟白詣印边w移了上千個報警規(guī)則,并最終為他們提供了一個統(tǒng)一的報警平臺。
報警,更精準的報警
外賣行業(yè)的業(yè)務(wù)特性是業(yè)務(wù)的午高峰與晚高峰,在業(yè)務(wù)曲線上便是兩個波峰的形狀。這樣的可觀測數(shù)據(jù),自然難以簡單使用閾值或比率來做判斷。即使是根據(jù)歷史同環(huán)比、3-Sigma、移動平均等規(guī)則,也難以適應(yīng)餓了么的可觀測性場景。因為,餓了么的業(yè)務(wù)曲線并非一成不變,它受促銷、天氣因素、區(qū)域、壓測等因素影響。開發(fā)出一個自適應(yīng)業(yè)務(wù)曲線變化的報警算法,勢在必行。
我們經(jīng)過調(diào)研既有規(guī)則,與餓了么的業(yè)務(wù)場景,推出了全新的“趨勢”報警。簡要算法如下:
- 計算歷史10天的指標數(shù)據(jù)中值作為基線。其中這10天都取工作日或非工作日。不取10天的均值而取中值是為了減少壓測或機房流量切換造成的影響。
- 根據(jù)二階滑動平均算法,得到滑動平均值與當前實際值的差值。
- 將基線與差值相加作為預(yù)測值。
- 根據(jù)預(yù)測值的數(shù)量級,計算出波動的幅度(如上界與下界的數(shù)值)。
- 若當前值不在預(yù)測值與波動幅度確定的上下界之中,則觸發(fā)報警。
如上圖所示,22點01分的實際值因不在上下界所限定的區(qū)域之中,會觸發(fā)報警。但從后續(xù)趨勢來看,該下降趨勢符合預(yù)期,因此實際中還會輔以“偏離持續(xù)X分鐘”來修正誤報。(如該例中,可增加“持續(xù)3分鐘才報警”的規(guī)則,該點的數(shù)據(jù)便不會報警)算法中部分參數(shù)為經(jīng)驗值,而其中波動的閾值參數(shù)用戶可按照自己業(yè)務(wù)調(diào)整。用戶針對具備業(yè)務(wù)特征的曲線,再也不用費心的去調(diào)整參數(shù),配置上默認的“趨勢”規(guī)則就可以覆蓋大多數(shù)的可觀測性場景,目前“趨勢”報警在餓了么廣泛運用。
智能可觀測性:根因分析,大顯神威
作為AIOPS中重要的一環(huán),根因分析能幫助用戶快速定位故障,縮短故障響應(yīng)時間,減少故障造成的損失。2020年初,我們結(jié)合餓了么場景,攻堅克難,攻破“指標下鉆”、“根因分析”兩大難關(guān),在EMonitor上成功落地。
根因分析最大的難點在于:包含復(fù)雜維度的指標數(shù)據(jù)難以找到真正影響數(shù)據(jù)波動的具體維度;孤立的指標數(shù)據(jù)也難以分析出應(yīng)用上下游依賴引起的故障根因。例如,某個應(yīng)用的異常指標突增,當前我們只能知道突增的異常名、機房維度的異常分布、機器維度的異常分布等,只有用戶手工去點擊異常指標看來鏈路之后,才能大致判斷是哪個SOA方法/DB請求中的異常。繼而用戶根據(jù)異常鏈路的環(huán)節(jié),去追溯上游或下游的應(yīng)用,重復(fù)類似的排查過程,最后以人工經(jīng)驗判斷出故障點。
因此,在“指標下鉆”上,我們針對目標指標的曲線,細分成最精細的每個維度數(shù)據(jù)(指標group by待分析的tag維度),使用KMeans聚類找出故障數(shù)據(jù)的各維度的最大公共特征,依次計算找到最優(yōu)的公共特征,如此便能找到曲線波動對應(yīng)的維度信息。
其次,在鏈路數(shù)據(jù)計算時,我們就能將額外的上下游附加信息附加到對應(yīng)的指標之中。如,可在異常指標中追加一個維度來記錄產(chǎn)生異常的SOA方法名。這樣在根據(jù)異常指標分析時,能直接定位到是這個應(yīng)用的那個SOA方法拋出的異常,接下來“自動”分析是SOA下游故障還是自身故障(DB、Cache、GC等)。
在2020.3月在餓了么落地以來,在分析的上百例故障中,根因分析的準確率達到90%以上,顯著縮短的故障排查的時間,幫助各業(yè)務(wù)向穩(wěn)定性建設(shè)目標向前跨進了一大步。
4.0:繼往開來,乘勢而上
經(jīng)過4、5年的發(fā)展,風云變幻但團隊初心不改,為了讓用戶用好可觀測性系統(tǒng),EMonitor沒有停下腳步,自我革新,希望讓“天下沒有難用的可觀測性系統(tǒng)”。我們向集團的可觀測性團隊請教學習,結(jié)合本地生活自己的技術(shù)體系建設(shè),力爭百尺竿頭更進一步,規(guī)劃了以下的EMonitor 4.0的設(shè)計目標。
一、進行多租戶化改造,保障核心數(shù)據(jù)的時延和可靠性
在本地生活的技術(shù)體系與阿里巴巴集團技術(shù)體系的不斷深入的融合之中,單元化的部署環(huán)境以及對可觀測性數(shù)據(jù)不同程度的可靠性要求,催生了“多租戶化”的設(shè)計理念。我們可以根據(jù)應(yīng)用類型、數(shù)據(jù)類型、來源等,將可觀測性數(shù)據(jù)分流到不同的租戶中,再針對性配置數(shù)據(jù)處理流程及分配處理能力,實現(xiàn)差異化的可靠性保障能力。
初步我們可以劃分為兩個集群--核心應(yīng)用集群與非核心應(yīng)用集合,根據(jù)在應(yīng)用上標記的“應(yīng)用等級”將其數(shù)據(jù)自動發(fā)送到對應(yīng)集群中。兩套集群在資源配置上優(yōu)先側(cè)重核心集群,并且完全物理隔離。此外通過配置開關(guān)可動態(tài)控制某個應(yīng)用歸屬的租戶,實現(xiàn)業(yè)務(wù)的柔性降級,避免當下偶爾因個別應(yīng)用的不正確埋點方式會影響整體可觀測可用性的問題。
未來可根據(jù)業(yè)務(wù)發(fā)展進一步發(fā)展出業(yè)務(wù)相關(guān)的租戶,如到家業(yè)務(wù)集群、到店業(yè)務(wù)集群等。或者按照區(qū)域的劃分,如彈內(nèi)集群、彈外集群等。
二、打通集團彈內(nèi)、彈外的可觀測性數(shù)據(jù),成為本地生活的一站式可觀測性平臺
目前本地生活很多業(yè)務(wù)領(lǐng)域已經(jīng)遷入集團,在Trace鏈路可觀測方面,雖然在本地生活上云的項目中,EMonitor已經(jīng)通過中間件改造實現(xiàn)鷹眼TraceId在鏈路上的傳遞,并記錄了EMonitor RequestId與鷹眼TraceId的映射關(guān)系。但EMonitor與鷹眼在協(xié)議上的天然隔閡仍使得用戶需要在兩個平臺間跳轉(zhuǎn)查看同一條Trace鏈路。因此,我們接下來的目標是與鷹眼團隊合作,將鷹眼的Trace數(shù)據(jù)集成到EMonitor上,讓用戶能一站式的排查問題。
其次,本地生活上云后,眾多中間件已遷移到云上中間件,如云Redis、云Kafka、云Zookeeper等。對應(yīng)的可觀測性數(shù)據(jù)也需要額外登陸到阿里云控制臺去查看。云上中間的可觀測性數(shù)據(jù)大多已存儲到Prometheus之中,因此我們計劃在完成Prometheus協(xié)議兼容后,就與云上中間件團隊合作,將本地生活的云上可觀測性數(shù)據(jù)集成到EMonitor上。
三、擁抱云原生,兼容Prometheus、OpenTelemetry等開源協(xié)議。
云原生帶來的技術(shù)革新勢不可擋,本地生活的絕大多數(shù)應(yīng)用已遷移到集團的容器化平臺--ASI上,對應(yīng)帶來的新的可觀測環(huán)節(jié)也亟需補全。如,ASI上Prometheus協(xié)議的容器可觀測性數(shù)據(jù)、Envoy等本地生活PaaS平臺透出的可觀測性數(shù)據(jù)與Trace數(shù)據(jù)等。
因此,我們計劃在原先僅支持LinDB數(shù)據(jù)源的基礎(chǔ)上,增加對Prometheus數(shù)據(jù)源的支持;擴展OpenTelemetry的otel-collector exporter實現(xiàn),將Open Telemetry協(xié)議的Trace數(shù)據(jù)轉(zhuǎn)換成EMonitor的Trace格式。如此便可補全云原生技術(shù)升級引起的可觀測性數(shù)據(jù)缺失,并提供高度的適配性,滿足本地生活的可觀測性建設(shè)。
結(jié)語
縱觀各大互聯(lián)網(wǎng)公司的產(chǎn)品演進,技術(shù)產(chǎn)品的走向與命運都離不開公司業(yè)務(wù)的發(fā)展軌跡。我們餓了么的技術(shù)人是幸運的,能趕上這一波技術(shù)變革的大潮,能夠發(fā)揮聰明才智,打磨出一些為用戶津津樂道的技術(shù)產(chǎn)品。我們EMonitor可觀測性團隊也為能參與到這次技術(shù)變更中深感自豪,EMonitor能被大家認可, 離不開每位參與到餓了么可觀測性體系建設(shè)的同伴,也感謝各位對可觀測性系統(tǒng)提供幫助、支持、建議的伙伴!
作者簡介:柯圣,花名“炸天”,餓了么監(jiān)控技術(shù)組負責人。自2016年加入餓了么,長期深耕于可觀測性領(lǐng)域,全程參與了ETrace到EMonitor的餓了么可觀測性系統(tǒng)的發(fā)展歷程。
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。