餓了麼EMonitor演進史

序言

時間回到2008年,還在上海交通大學上學的張旭豪、康嘉等人在上海創辦了餓了麼,從校園外賣場景出發,餓了麼一步一步發展壯大,成爲外賣行業的領頭羊。2017年8月餓了麼併購百度外賣,強強合併,繼續開疆擴土。2018年餓了麼加入阿里巴巴你們庭,與口碑融合成立阿里巴巴本地生活公司。「愛什麼,來什麼」,是餓了麼對用戶不變的承諾。前端

餓了麼的技術也伴隨着業務的飛速增加也不斷日新月異。據公開報道,2014年5月的日訂單量只有10萬,但短短几個月以後就衝到了日訂單百萬,到當今日訂單上千萬單。在短短几年的技術發展歷程上,餓了麼的技術體系、穩定性建設、技術文化建設等都有長足的發展。各位可查看《餓了麼技術往事》一探其中發展歷程,在此再也不贅述:android

《餓了麼技術往事(上)》ios

《餓了麼技術往事(中)》算法

《餓了麼技術往事(下)》數據庫

而可觀測性做爲技術體系的核心環節之一,也跟隨餓了麼技術的飛速發展,不斷自我革新,從「全鏈路可觀測性ETrace」擴展到「多活下的可觀測性體系ETrace」,發展成目前「一站式可觀測性平臺EMonitor」。後端

EMonitor通過5年的屢次迭代,如今已經建成了集指標數據、鏈路追蹤、可視化面板、報警與分析等多個可觀測性領域的平臺化產品。EMonitor每日處理約1200T的原始可觀測性數據,覆蓋餓了麼絕大多數中間件,可觀測超5萬臺機器實例,可觀測性數據時延在10秒左右。面向餓了麼上千研發人員,EMonitor提供精準的報警服務和多樣化的觸達手段,同時運行約2萬的報警規則。本文就細數餓了麼可觀測性的建設歷程,回顧下「餓了麼可觀測性建設的那些年」。api

1.0:混沌初開,萬物興起

翻看代碼提交記錄,ETrace項目的第一次提交在2015年10月24日。而2015年,正是餓了麼發展的第七個年頭,也是餓了麼業務、技術、人員開始蓬勃發展的年頭。彼時,餓了麼的可觀測性系統依賴Zabbix、Statsd、Grafana等傳統的「輕量級」系統。而「全鏈路可觀測性」正是當時的微服務化技術改造、後端服務Java化等技術發展趨勢下的必行之勢。緩存

咱們可觀測性團隊,在調研業界主流的全鏈路可觀測性產品--包括著名的開源全鏈路可觀測性產品「CAT」後,吸收衆家之所長,在兩個多月的爆肝開發後,推出了初代ETrace。咱們提供的Java版本ETrace-Agent隨着新版的餓了麼SOA框架「Pylon」在餓了麼研發團隊中的推廣和普及開來。ETrace-Agent能自動收集應用的SOA調用信息、API調用信息、慢請求、慢SQL、異常信息、機器信息、依賴信息等。下圖爲1.0版本的ETrace頁面截圖。前端框架

在經歷了半年的爆肝開發和各中間件兄弟團隊的鼎力支持,咱們又開發了Python版本的Agent,更能適應餓了麼當時各語言百花齊放的技術體系。而且,經過和餓了麼DAL組件、緩存組件、消息組件的密切配合與埋點,用戶的應用增長了多層次的訪問信息,鏈路更加完整,故障排查過程更加清晰。網絡

總體架構體系

ETrace總體架構以下圖。經過SDK集成在用戶應用中的Agent按期將Trace數據經Thrift協議發送到Collector(Agent本地不落日誌),Collector經初步過濾後將數據打包壓縮發往Kafka。Kafka下游的Consumer消費這些Trace數據,一方面將數據寫入HBase+HDFS,一方面根據與各中間件約定好的埋點規則,將鏈路數據計算成指標存儲到時間序列數據庫-- LinDB中。在用戶端,Console服務提供UI及查詢指標與鏈路數據的API,供用戶使用。

全鏈路可觀測性的實現

所謂全鏈路可觀測性,即每次業務請求中都有惟一的可以標記此次業務完整的調用鏈路,咱們稱這個ID爲RequestId。而每次鏈路上的調用關係,相似於樹形結構,咱們將每一個樹節點上用惟一的RpcId標記。

如圖,在入口應用App1上會新建一個隨機RequestId(一個相似UUID的32位字符串,再加上生成時的時間戳)。因它爲根節點,故RpcId爲「1」。在後續的RPC調用中,RequestId經過SOA框架的Context傳遞到下一節點中,且下一節點的層級加1,變爲形如「1.1」、「1.2」。如此反覆,同一個RequestId的調用鏈就經過RpcId還原成一個調用樹。

也能夠看到,「全鏈路可觀測性的實現」不只依賴與ETrace系統自身的實現,更依託與公司總體中間件層面的支持。如在請求入口的Gateway層,能對每一個請求生成「自動」新的RequestId(或根據請求中特定的Header信息,複用RequestId與RpcId);RPC框架、Http框架、Dal層、Queue層等都要支持在Context中傳遞RequestId與RpcId。

ETrace Api示例

在Java或Python中提供鏈路埋點的API:

/* 記錄一個調用鏈路 / Transaction trasaction = Trace.newTransaction(String type, String name); // business codes transaction.complete();

/* 記錄調用中的一個事件 / Trace.logEvent(String type, String name, Map<String,String> tags, String status, String data)

/* 記錄調用中的一個異常 / Trace.logError(String msg, Exception e)

Consumer的設計細節

Consumer組件的核心任務就是將鏈路數據寫入存儲。主要思路是以RequestId+RpcId做爲主鍵,對應的Data數據寫入存儲的Payload。再考慮到可觀測性場景是寫多讀少,而且多爲文本類型的Data數據可批量壓縮打包存儲,所以咱們設計了基於HDFS+HBase的兩層索引機制。

如圖,Consumer將Collector已壓縮好的Trace數據先寫入HDFS,並記錄寫入的文件Path與寫入的Offset,第二步將這些「索引信息」再寫入HBase。特別的,構建HBase的Rowkey時,基於ReqeustId的Hashcode和HBase Table的Region數量配置,來生成兩個Byte長度的ShardId字段做爲Rowkey前綴,避免了某些固定RequestId格式可能形成的寫入熱點問題。(因RequestId在各調用源頭生成,如應用自身、Nginx、餓了麼網關層等。可能某應用錯誤設置成以其AppId爲前綴RequestId,若沒有ShardId來打散,則它全部RequestId都將落到同一個HBase Region Server上。)

在查詢時,根據RequestId + RpcId做爲查詢條件,依次去HBase、HDFS查詢原始數據,便能找到某次具體的調用鏈路數據。但有的需求場景是,只知道源頭的RequestId須要查看整條鏈路的信息,但願只排查鏈路中狀態異常的或某些指定RPC調用的數據。所以,咱們在HBbase的Column Value上還額外寫了RPCInfo的信息,來記錄單次調用的簡要信息。如:調用狀態、耗時、上下游應用名等。

此外,餓了麼的場景下,研發團隊多以訂單號、運單號做爲排障的輸入,所以咱們和業務相關團隊約定特殊的埋點規則--在Transaction上記錄一個特殊的"orderId={實際訂單號}"的Tag--便會在HBase中新寫一條「訂單表」的記錄。該表的設計也不復雜,Rowkey由ShardId與訂單號組成,Columne Value部分由對應的RequestId+RpcId及訂單基本信息(相似上文的RPCInfo)三部分組成。

如此,從業務鏈路到全鏈路信息到詳細單個鏈路,造成了一個完整的全鏈路排查體系。

Consumer組件的另外一個任務則是將鏈路數據計算成指標。實現方式是在寫入鏈路數據的同時,在內存中將Transaction、Event等數據按照既定的計算邏輯,計算成SOA、DAL、Queue等中間件的指標,內存稍加聚合後再寫入時序數據庫LinDB。

指標存儲:LinDB 1.0

應用指標的存儲是一個典型的時間序列數據庫的使用場景。根據咱們之前的經驗,市面上主流的時間序列數據庫-- OpenTSDB、InfluxDB、Graphite--在擴展能力、集羣化、讀寫效率等方面各有缺憾,因此咱們選型使用RocksDB做爲底層存儲引擎,借鑑Kafka的集羣模式,開發了餓了麼的時間序列數據庫--LinDB。

指標採用相似Prometheus的「指標名+鍵值對的Tags」的數據模型,每一個指標只有一個支持Long或Double的Field。某個典型的指標如:

COUNTER: eleme_makeorder{city="shanghai",channel="app",status="success"} 45

咱們主要作了一些設計實現:

• 指標寫入時根據「指標名+Tags」進行Hash寫入到LinDB的Leader上,由Leader負責同步給他的Follower。

• 借鑑OpenTSDB的存儲設計,將「指標名」、TagKey、TagValue都轉化爲Integer,放入映射表中以節省存儲資源。

• RocksDB的存儲設計爲:以"指標名+TagKeyId + TagValueId+時間(小時粒度)「做爲Key,以該小時時間線內的指標數值做爲Value。

• 爲實現Counter、Timer類型數據聚合邏輯,開發了C++版本RocksDB插件。

這套存儲方案在初期很好的支持了ETrace的指標存儲需求,爲ETrace大規模接入與可觀測性數據的時效性提供了堅固的保障。有了ETrace,餓了麼的技術人終於能從全鏈路的角度去排查問題、治理服務,爲以後的技術升級、架構演進,提供了可觀測性層面的支持。

其中架構的幾點說明

  1. 是否保證全部可觀測性數據的可靠性? 不,咱們承諾的是「儘量不丟」,不保證100%的可靠性。基於這個前提,爲咱們設計架構時提供了諸多便利。如,Agent與Collector若鏈接失敗,若干次重試後便丟棄數據,直到Collector恢復可用;Kafka上下游的生產和消費也沒必要Ack,避免影響處理效率。

  2. 爲何在SDK中的Agent將數據發給Collector,而不是直接發送到Kafka?

  • 避免Agent與Kafka版本強綁定,並避免引入Kafka Client的依賴。
  • 在Collector層能夠作數據的分流、過濾等操做,增長了數據處理的靈活性。而且Collector會將數據壓縮後再發送到Kafka,有效減小Kafka的帶寬壓力。
  • Collector機器會有大量TCP鏈接,可針對性使用高性能機器。
  1. SDK中的Agent如何控制對業務應用的影響?
  • 純異步的API,內部採用隊列處理,隊列滿了就丟棄。
  • Agent不會寫本地日誌,避免佔用磁盤IO、磁盤存儲而影響業務應用。
  • Agent會定時從Collector拉取配置信息,以獲取後端Collector具體IP,並可實時配置來開關是否執行埋點。
  1. 爲何選擇侵入性的Agent?

選擇寄生在業務應用中的SDK模式,在當時看來更利於ETrace的普及與升級。而從如今的眼光看來,非侵入式的Agent對用戶的集成更加便利,而且能夠經過Kubernates中SideCar的方式對用戶透明部署與升級。

  1. 如何實現「儘可能不丟數據」?
  • Agent中根據得到的Collector IP週期性數據發送,若失敗則重試3次。並按期(5分鐘)獲取Collector集羣的IP列表,隨機選取可用的IP發送數據。
  • Collector中實現了基於本地磁盤的Queue,在後端的Kafka不可用時,會將可觀測性數據寫入到本地磁盤中。待Kafak恢復後,又會將磁盤上的數據,繼續寫入Kafka。
  1. 可觀測性數據如何實現多語言支持?

Agent與Collector之間選擇Thrift RPC框架,並定製整個序列化方式。Java/Python/Go/PHP的Agent依數據規範開發便可。

2.0:異地多活,大勢初成

2016年末,餓了麼爲了迎接業務快速增加帶來的調整,開始推動「異地多活」項目。新的多數據中心架構對既有的可觀測性架構也帶來了調整,ETrace亦通過了一年的開發演進,升級到多數據中心的新架構、拆分出實時計算模塊、增長報警功能等,進入ETrace2.0時代。

異地多活的挑戰

隨着餓了麼的異地多活的技術改造方案肯定,對可觀測性平臺提出了新的挑戰:如何設計多活架構下的可觀測性系統?以及如何聚合多數據中心的可觀測性數據?

通過一年多的推廣與接入,ETrace已覆蓋了餓了麼絕大多數各語言的應用,每日處理數據量已達到了數十T以上。在此數據規模下,決不可能將數據拉回到某個中心機房處理。所以「異地多活」架構下的可觀測性設計的原則是:各機房處理各自的可觀測性數據。

咱們開發一個Gateway模塊來代理與聚合各數據中心的返回結果,它會感知各機房間內Console服務。圖中它處於某個中央的雲上區域,實際上它能夠部署在各機房中,經過域名的映射機制來作切換。

如此部署的架構下,各機房中的應用由與機房相綁定的環境變量控制將可觀測性數據發送到該機房內的ETrace集羣,收集、計算、存儲等過程都在同一機房內完成。用戶經過前端Portal來訪問各機房內的數據,使用體驗和以前相似。

即便考慮極端狀況下--某機房徹底不可用(如斷網),「異地多活」架構可將業務流量切換到存活的機房中,讓業務繼續運轉。而可觀測性上,經過將Portal域名與Gateway域名切換到存活的機房中,ETrace便能繼續工做(雖然會缺失故障機房的數據)。在機房網絡恢復後,故障機房內的可觀測性數據也能自動恢復(由於該機房內的可觀測性數據處理流程在斷網時仍在正常運做)。

可觀測性數據實時處理的挑戰

在1.0版本中的Consumer組件,既負責將鏈路數據寫入到HBase/HDFS中,又負責將鏈路數據計算成指標存儲到LinDB中。兩個流程可視爲同步的流程,但前者可接受數分鐘的延遲,後者要求達到實時的時效性。當時HBase集羣受限於機器性能與規模,常常在數據熱點時會寫入抖動,進而形成指標計算抖動,影響可用性。所以,咱們迫切須要拆分鏈路寫入模塊與指標計算模塊。

在選型實時計算引擎時,咱們考慮到需求場景是:

  1. 能靈活的配置鏈路數據的計算規則,最好能動態調整;
  2. 能水平擴展,以適應業務的快速發展;
  3. 數據輸出與既有系統(如LinDB與Kafka)無縫銜接;

很遺憾的是,彼時業界無現成的拿來即用的大數據流處理產品。咱們就基於復瑣事件處理(CEP)引擎Esper實現了一個類SQL的實時數據計算平臺--Shaka。Shaka包括「Shaka Console」和「Shaka Container」兩個模塊。Shaka Console由用戶在圖形化界面上使用,來配置數據處理流程(Pipeline)、集羣、數據源等信息。用戶完成Pipeline配置後,Shaka Console會將變動推送到Zookeeper上。無狀態的Shaka Container會監聽Zookeeper上的配置變動,根據本身所屬的集羣去更新內部運行的Component組件。而各Component實現了各類數據的處理邏輯:消費Kafka數據、處理Trace/Metric數據、Metric聚合、運行Esper邏輯等。

Trace數據和Metric格式轉換成固定的格式後,剩下來按需編寫Esper語句就能生成所需的指標了。以下文所示的Esper語句,就能將類型爲Transaction的Trace數據計算成以「{appId}.transaction」的指標(若Consumer中以編碼方式實現,約須要近百行代碼)。通過此次的架構升級,Trace數據能快速的轉化爲實時的Metric數據,而且對於業務的可觀測性需求,只用改改SQL語句就能快速知足,顯著下降了開發成本和提高了開發效率。

@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 sampling

from transaction group by header.appId, type, name, header.hostName, header.ezone, status, trunc_sec(timestamp, 10);

新的UI、更豐富的中間件數據

1.0版本的前端UI,是集成在Console項目中基於Angular V1開發的。咱們迫切但願能作到先後端分離,各司其職。因而基於Angular V2的若干個月開發,新的Portal組件登場。得益於Angular的數據綁定機制,新的ETrace UI各組件間聯動更天然,排查故障更方便。

餓了麼自有中間件的研發進程也在不斷前行,在可觀測性的打通上也不斷深化。2.0階段,咱們進一步集成了--Redis、Queue、ElasticSearch等等,覆蓋了餓了麼全部的中間件,讓可觀測性無死角。

殺手級功能:指標查看與鏈路查看的無縫整合

傳統的可觀測性系統提供的排障方式大體是:接收報警(Alert)--查看指標(Metrics)--登錄機器--搜索日誌(Trace/Log),而ETrace經過Metric與Trace的整合,能讓用戶直接在UI上經過點擊就能定位絕大部分問題,顯著拔高了用戶的使用體驗與排障速度。

某個排查場景如:用戶發現總量異常忽然增長,可在界面上篩選機房、異常類型等找到實際突增的某個異常,再在曲線上直接點擊數據點,就會彈出對應時間段的異常鏈路信息。鏈路上有詳細的上下游信息,能幫助用戶定位故障。

它的實現原理如上圖所示。具體的,前文提到的實時計算模塊Shaka將Trace數據計算成Metric數據時,會額外以抽樣的方式將Trace上的RequsetId與RpcId也寫到Metric上(即上文Esper語句中,生成的Metric中的sampling字段)。這種Metric數據會被Consumer模塊消費並寫入到HBase一張Sampling表中。

用戶在前端Portal的指標曲線上點擊某個點時,會構建一個Sampling的查詢請求。該請求會帶上:該曲線的指標名、數據點的起止時間、用戶選擇過濾條件(即Tags)。Consumer基於這些信息構建一個HBase的RegexStringComparator的Scan查詢。查詢結果中可能會包含多個結果,對應着該時間點內數據點(Metric)上發生的多個調用鏈路(Trace),繼而拿着結果中的RequestId+RpcId再去查詢一次HBase/HDFS存儲就能得到鏈路原文。(注:實際構建HBase Rowkey時Tag部分存的是其Hashcode而不是原文String。)

衆多轉崗、離職的餓了麼小夥伴,最念念不忘不完的就是這種「所見即所得」的可觀測性排障體驗。

報警Watchdog 1.0

在應用可觀測性基本全覆蓋以後,報警的需求天然成了題中之義。技術選型上,根據咱們在實時計算模塊Shaka上收穫的經驗,決定再造一個基於實時數據的報警系統--Watchdog。

實時計算模塊Shaka中已經將Trace數據計算成指標Metrics,報警模塊只需消費這些數據,再結合用戶配置的報警規則產出報警事件便可。所以,咱們選型使用Storm做爲流式計算平臺,在Spount層次根據報警規則過濾和分流數據,在Bolt層中Esper引擎運行着由用戶配置的報警規則轉化成Esper語句並處理分流後的Metric數據。如有符合Esper規則的數據,即生成一個報警事件Alert。Watchdog Portal模塊訂閱Kakfa中的報警事件,再根據具體報警的觸達方式通知到用戶。默認Esper引擎中數據聚合時間窗口爲1分鐘,因此整個數據處理流程的時延約爲1分鐘左右。

Metrics API與LinDB 2.0

在ETrace 1.0階段,咱們只提供了Trace相關的API,LinDB僅供內部存儲使用。用戶逐步的意識到若是能將「指標」與「鏈路」整合起來,就能發揮更大的功用。所以咱們在ETrace-Agent中新增了Metrics相關的API:

// 計數器類型

Trace.newCounter(String metricName).addTags(Map<String, String> tags).count(int value);

// 耗時與次數

Trace.newTimer(String metricName).addTags(Map<String, String> tags).value(int value);

// 負載大小與次數

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,用戶能夠在代碼中針對他的業務邏輯進行指標埋點,爲後來可觀測性大一統提供了實現條件。在其餘組件同步開發時,咱們也針對LinDB作了若干優化,提高了寫入性能與易用性:

  1. 增長Histogram、Gauge、Payload、Ratio多種指標數據類型;
  2. 從1.0版本的每條指標數據都調用一次RocksDB的API進行寫入,改爲先在內存中聚合一段時間,再經過RocksDB的API進行批量寫入文件。

3.0:推陳出新,融會貫通

可觀測性系統大一統

在2017年的餓了麼,除了ETrace外還有多套可觀測性系統:基於Statsd/Graphite的業務可觀測性系統、基於InfluxDB的基礎設施可觀測性系統。後二者都集成Grafana上,用戶能夠去查看他的業務或者機器的詳細指標。但實際排障場景中,用戶仍是須要在多套系統間來回切換:根據Grafana上的業務指標感知業務故障,到ETrace上查看具體的SOA/DB故障,再到Grafana上去查看具體機器的網絡或磁盤IO指標。雖然,咱們也開發了Grafana的插件來集成LinDB的數據源,但因本質上差別巨大的系統架構,仍是讓用戶「疲於奔命」式的來回切換系統,用戶難以有統一的可觀測性體驗。所以2018年初,咱們下定決心:將多套可觀測性系統合而爲一,打通「業務可觀測性+應用可觀測性+基礎設施可觀測性」,讓ETrace真正成爲餓了麼的一站式可觀測性平臺。

LinDB 3.0

所謂「改造」未動,「存儲」先行。想要整合InfluxDB與Statsd,先要研究他們與LinDB的異同。咱們發現,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的模式,則須要將上述指標轉換成兩個指標:

measurement=census, field={butterfiles=12}, tags={location=SH, scientist=jack}, timestamp=2015-08-18T00:06:00Z

measurement=census, field={honeybees=23}, tags={location=SH, scientist=jack}, timestamp=2015-08-18T00:06:00Z

能夠想見在數據存儲與計算效率上,單Field模式有着極大的浪費。但更改指標存儲的Schema,意味着整個數據處理鏈路都須要作適配和調整,工做量和改動極大。然而不改就意味着「將就」,咱們不能忍受對本身要求的下降。所以又通過了幾個月的爆肝研發,LinDB 3.0開發完成。

此次改動,除了升級到指標多Fields模式外,還有如下優化點:

• 集羣方面引入Kafka的ISR設計,解決了以前機器故障時查詢數據缺失的問題。 • 存儲層面支持更加通用的多Field模式,而且支持對多Field之間的表達式運算。 • 引入了倒排索引,顯著提升了對於任意Tag組合的過濾查詢的性能。 • 支持了自動化的Rollup操做,對於任意時間範圍的查詢自動選取合適的粒度來聚合。

通過此次大規模優化後,從最初的每日5T指標數據漲到現在的每日200T數據,LinDB 3.0都經受住了考驗。指標查詢的響應時間的99分位線爲200ms。詳細設計細節可參看分佈式時序數據庫 - LinDB。

將Statsd指標轉成LinDB指標

Statsd是餓了麼普遍使用的業務指標埋點方案,各機房有一個數十臺機器規模的Graphite集羣。考慮到業務的核心指標都在Statsd上,而且各個AppId以ETrace Metrics API替換Statsd是一個漫長的過程(也確實是,前先後後替換完成就花了將近一年時間)。爲了減小對用戶與NOC團隊的影響,咱們決定:用戶更新代碼的同時,由ETrace同時「兼容」Statsd的數據。

得益於餓了麼強大的中間件體系,業務在用Statsd API埋點的同時會「自動」記一條特殊的Trace數據,攜帶上Statsd的Metric數據。那麼只要處理Trace數據中的Statsd埋點,咱們就能將大多數Statsd指標轉化爲LinDB指標。以下圖:多個Statsd指標會轉爲同一個LinDB指標。

// statsd: stats.app.myAppName.order.from_ios.success 32 stats.app.myAppName.order.from_android.success 29 stats.app.myAppName.order.from_pc.failure 10 stats.app.myAppName.order.from_openapi.failure 5 // lindb: MetricName: myAppName.order Tags: "tag1"=[from_ios, from_android,from_pc, from_openapi] "tag2"=[success, failure]

以前咱們的實時計算模塊Shaka就在這裏派上了大用場:只要再新增一路數據處理流程便可。以下圖,新增一條Statsd數據的處理Pipeline,並輸出結果到LinDB。在用戶的代碼所有從Statsd API遷移到ETrace API後,這一路處理邏輯便可移除。

將InfluxDB指標轉成LinDB指標

InfluxDB主要用於機器、網絡設備等基礎設施的可觀測性數據。餓了麼每臺機器上,都部署了一個ESM-Agent。它負責採集機器的物理指標(CPU、網絡協議、磁盤、進程等),並在特定設備上進行網絡嗅探(Smoke Ping)等。這個數據採集Agent起因Python開發,在不斷需求堆疊以後,已龐大到難以維護;而且每次更新可觀測邏輯,都須要全量發佈每臺機器上的Agent,致使每次Agent的發佈都使人心驚膽戰。

咱們從0開始,以Golang從新開發了一套ESM-Agent,作了如下改進:

• 可觀測性邏輯以插件的形式,推送到各宿主機上。不一樣的設備、不一樣應用的機器,其上運行的插件能夠定製化部署。 • 制定插件的交互接口,讓中間件團隊可定製本身的數據採集實現,解放了生產力。 • 移除了etcd,使用MySql作配置數據存儲,減輕了系統的複雜度。 • 開發了便利的發佈界面,可灰度、全量的推送與發佈Agent,運維工做變得輕鬆。 • 最重要的,收集到的數據以LinDB多Fields的格式發送到Collector組件,由其發送到後續的處理與存儲流程上。

從ETrace到EMonitor,不斷升級的可觀測性體驗

2017年末,咱們團隊終於迎來了一名正式的前端開發工程師,可觀測性團隊正式從後端開發寫前端的狀態中脫離出來。在以前的Angular的開發體驗中,咱們深感「狀態轉換」的控制流程甚爲繁瑣,而且開發的組件難以複用(雖然其後版本的Angular有了很大的改善)。在調用當時流行的前端框架後,咱們在Vue與React之中選擇了後者,輔以Ant Design框架,開發出了媲美Grafana的指標看版與便利的鏈路看板,而且在PC版本以外還開發了移動端的定製版本。咱們亦改名了整個可觀測性產品,從「ETrace」更新爲「EMonitor」:不只僅是鏈路可觀測性系統,更是餓了麼的一站式可觀測性平臺。

可觀測性數據的整合:業務指標 + 應用鏈路 + 基礎設施指標 + 中間件指標

在指標系統都遷移到LinDB後,咱們在EMonitor上集成了「業務指標 + 應用鏈路 + 基礎設施指標 + 中間件指標」的多層次的可觀測性數據,讓用戶能在一處觀測它的業務數據、排查業務故障、深挖底層基礎設施的數據。

可觀測性場景的整合:指標 + 鏈路 + 報警

在可觀測性場景上,「指標看板」用於平常業務盯屏與宏觀業務可觀測性,「鏈路」做爲應用排障與微觀業務邏輯透出,「報警」則實現可觀測性自動化,提升應急響應效率。

靈活的看板配置與業務大盤

在指標配置上,咱們提供了多種圖表類型--線圖、面積圖、散點圖、柱狀圖、餅圖、表格、文本等,以及豐富的自定義圖表配置項,能知足用戶不一樣數據展現需求。

在完成單個指標配置後,用戶須要將若干個指標組合成所需的指標看板。用戶在配置頁面中,先選擇待用的指標,再經過拖拽的方式,配置指標的佈局即可實時預覽佈局效果。一個指標可被多個看板引用,指標的更新也會自動同步到全部看板上。爲避免指標配置錯誤而引發歧義,咱們也開發了「配置歷史」的功能,指標、看板等配置都能回滾到任意歷史版本上。

看板配置是靜態圖表組合,而業務大盤提供了生動的業務邏輯視圖。用戶能夠根據他的業務場景,將指標配置整合成一張宏觀的業務圖。

第三方系統整合:變動系統 + SLS日誌

因每條報警信息和指標配置信息都與AppId關聯,那麼在指標看板上可同步標記出報警的觸發時間。同理,咱們拉取了餓了麼變動系統的應用變動數據,將其標註到對應AppId相關的指標上。在故障發生時,用戶查看指標數據時,能根據有無變動記錄、報警記錄來初步判斷故障緣由。

餓了麼的日誌中間件能自動在記錄日誌時加上對應的ETrace的RequestId等鏈路信息。如此,用戶查看SLS日誌服務時,能反查到整條鏈路的RequestId;而EMonitor也在鏈路查看頁面,拼接好了該應用所屬的SLS連接信息,用戶點擊後能直達對應的SLS查看日誌上下文。

使用場景的整合:桌面版 + 移動版

除提供桌面版的EMonitor外,咱們還開發了移動版的EMonitor,它也提供了大部分可觀測性系統的核心功能--業務指標、應用指標、報警信息等。移動版EMonitor能內嵌於釘釘之中,打通了用戶認證機制,幫助用戶隨時隨地掌握全部的可觀測性信息。

爲了極致的體驗,精益求精

爲了用戶的極導致用體驗,咱們在EMonitor上各功能使用上細細打磨,這裏僅舉幾個小例子:

  1. 咱們爲極客開發者實現了若干鍵盤快捷鍵。例如,「V」鍵就能展開查看指標大圖。
  2. 圖上多條曲線時,點擊圖例是默認單選,目的是讓用戶只看他關心的曲線。此外,如果「Ctrl+鼠標點擊」則是將其加選擇的曲線中。這個功能在一張圖幾十條曲線時,對比幾個關鍵曲線時尤其有用。
  3. 爲了讓色弱開發者更容易區分紅功或失敗的狀態,咱們針對性的調整了對應顏色的對比度。

成爲餓了麼一站式可觀測性平臺

EMonitor開發完成後,憑藉優異的用戶體驗與產品集成度,很快在用戶中普及開來。可是,EMonitor要成爲餓了麼的一站式可觀測性平臺,還剩下最後一戰--NOC可觀測性大屏。

- NOC可觀測性大屏替換

餓了麼有一套完善的應急處理與保障團隊,包括7X24值班的NOC(Network Operation Center)團隊。在NOC的辦公區域,有一整面牆上都是可觀測性大屏,上面顯示着餓了麼的實時的各類業務曲線。下圖爲網上找的一張示例圖,實際餓了麼的NOC大屏比它更大、數據更多。

當時這個可觀測大屏是將Grafana的指標看版投影上去。咱們但願將NOC大屏也替換成EMonitor的看版。如前文所說,咱們逐步將用戶的Statsd指標數據轉換成了LinDB指標,在NOC團隊的協助下,一個一個將Grafana的可觀測性指標「搬」到EMonitor上。此外,在原來白色主題的EMonitor之上,咱們開發了黑色主題以適配投屏上牆的效果(白色背景投屏太刺眼)。

終於趕在2018年的雙十一以前,EMonitor正式入駐NOC可觀測大屏。在雙十一當天,衆多研發擠在NOC室看着牆上的EMonitor看版上的業務曲線不斷飛漲,做爲可觀測性團隊的一員,這份自豪之情由衷而生。經此一役,EMonitor真正成爲了餓了麼的「一站式可觀測性平臺」,Grafana、Statsd、InfluxDB等都成了過去時。

報警Watchdog 2.0

一樣在EMonitor以前,亦有Statsd與InfluxDB對應的多套報警系統。用戶若想要配置業務報警、鏈路報警、機器報警,須要展轉多個報警系統之間。各系統的報警的配置規則、觸達體驗亦是千差萬別。Watchdog報警系統也面臨着統一融合的挑戰。

  1. 在調研其餘系統的報警規則實現後,Watchdog中仍以LinDB的指標做爲元數據實現。
  2. 針對其餘報警系統的有顯著區別的訂閱模式,咱們提出了"報警規則+一個規則多個訂閱標籤+一個用戶訂閱多個標籤"的方式,完美遷移了幾乎其餘系統全部的報警規則與訂閱關係。
  3. 其餘各系統在報警觸達與觸達內容上也略有不一樣。咱們統一整合成「郵件+短信+釘釘+語音外呼」四種通知方式,而且提供可參數化的自定義Markdown模板,讓用戶可本身定時報警信息。

通過一番艱苦的報警配置與邏輯整合後,咱們爲用戶「自動」遷移了上千個報警規則,並最終爲他們提供了一個統一的報警平臺。

報警,更精準的報警

外賣行業的業務特性是業務的午高峯與晚高峯,在業務曲線上即是兩個波峯的形狀。這樣的可觀測數據,天然難以簡單使用閾值或比率來作判斷。即便是根據歷史同環比、3-Sigma、移動平均等規則,也難以適應餓了麼的可觀測性場景。由於,餓了麼的業務曲線並不是一成不變,它受促銷、天氣因素、區域、壓測等因素影響。開發出一個自適應業務曲線變化的報警算法,勢在必行。

咱們通過調研既有規則,與餓了麼的業務場景,推出了全新的「趨勢」報警。簡要算法以下:

  1. 計算曆史10天的指標數據中值做爲基線。其中這10天都取工做日或非工做日。不取10天的均值而取中值是爲了減小壓測或機房流量切換形成的影響。
  2. 根據二階滑動平均算法,獲得滑動平均值與當前實際值的差值。
  3. 將基線與差值相加做爲預測值。
  4. 根據預測值的數量級,計算出波動的幅度(如上界與下界的數值)。
  5. 若當前值不在預測值與波動幅度肯定的上下界之中,則觸發報警。

如上圖所示,22點01分的實際值因不在上下界所限定的區域之中,會觸發報警。但從後續趨勢來看,該降低趨勢符合預期,所以實際中還會輔以「偏離持續X分鐘」來修正誤報。(如該例中,可增長「持續3分鐘才報警」的規則,該點的數據便不會報警)算法中部分參數爲經驗值,而其中波動的閾值參數用戶可按照本身業務調整。用戶針對具有業務特徵的曲線,不再用費心的去調整參數,配置上默認的「趨勢」規則就能夠覆蓋大多數的可觀測性場景,目前「趨勢」報警在餓了麼普遍運用。

智能可觀測性:根因分析,大顯神威

做爲AIOPS中重要的一環,根因分析能幫助用戶快速定位故障,縮短故障響應時間,減小故障形成的損失。2020年初,咱們結合餓了麼場景,攻堅克難,攻破「指標下鑽」、「根因分析」兩大難關,在EMonitor上成功落地。

根因分析最大的難點在於:包含複雜維度的指標數據難以找到真正影響數據波動的具體維度;孤立的指標數據也難以分析出應用上下游依賴引發的故障根因。 例如,某個應用的異常指標突增,當前咱們只能知道突增的異常名、機房維度的異常分佈、機器維度的異常分佈等,只有用戶手工去點擊異常指標看來鏈路以後,才能大體判斷是哪一個SOA方法/DB請求中的異常。繼而用戶根據異常鏈路的環節,去追溯上游或下游的應用,重複相似的排查過程,最後以人工經驗判斷出故障點。

所以,在「指標下鑽」上,咱們針對目標指標的曲線,細分紅最精細的每一個維度數據(指標group by待分析的tag維度),使用KMeans聚類找出故障數據的各維度的最大公共特徵,依次計算找到最優的公共特徵,如此便能找到曲線波動對應的維度信息。

其次,在鏈路數據計算時,咱們就能將額外的上下游附加信息附加到對應的指標之中。如,可在異常指標中追加一個維度來記錄產生異常的SOA方法名。這樣在根據異常指標分析時,能直接定位到是這個應用的那個SOA方法拋出的異常,接下來「自動」分析是SOA下游故障仍是自身故障(DB、Cache、GC等)。

在2020.3月在餓了麼落地以來,在分析的上百例故障中,根因分析的準確率達到90%以上,顯著縮短的故障排查的時間,幫助各業務向穩定性建設目標向前跨進了一大步。

4.0:繼往開來,乘勢而上

通過四、5年的發展,風雲變幻但團隊初心不改,爲了讓用戶用好可觀測性系統,EMonitor沒有停下腳步,自我革新,但願讓「天下沒有難用的可觀測性系統」。咱們向集團的可觀測性團隊請教學習,結合本地生活本身的技術體系建設,力爭百尺竿頭更進一步,規劃瞭如下的EMonitor 4.0的設計目標。

1、進行多租戶化改造,保障核心數據的時延和可靠性

在本地生活的技術體系與阿里巴巴集團技術體系的不斷深刻的融合之中,單元化的部署環境以及對可觀測性數據不一樣程度的可靠性要求,催生了「多租戶化」的設計理念。咱們能夠根據應用類型、數據類型、來源等,將可觀測性數據分流到不一樣的租戶中,再針對性配置數據處理流程及分配處理能力,實現差別化的可靠性保障能力。

初步咱們能夠劃分爲兩個集羣--核心應用集羣與非核心應用集合,根據在應用上標記的「應用等級」將其數據自動發送到對應集羣中。兩套集羣在資源配置上優先側重核心集羣,而且徹底物理隔離。此外經過配置開關可動態控制某個應用歸屬的租戶,實現業務的柔性降級,避免當下偶爾因個別應用的不正確埋點方式會影響總體可觀測可用性的問題。

將來可根據業務發展進一步發展出業務相關的租戶,如到家業務集羣、到店業務集羣等。或者按照區域的劃分,如彈內集羣、彈外集羣等。

2、打通集團彈內、彈外的可觀測性數據,成爲本地生活的一站式可觀測性平臺

目前本地生活不少業務領域已經遷入集團,在Trace鏈路可觀測方面,雖然在本地生活上雲的項目中,EMonitor已經經過中間件改造實現鷹眼TraceId在鏈路上的傳遞,並記錄了EMonitor RequestId與鷹眼TraceId的映射關係。但EMonitor與鷹眼在協議上的自然隔閡仍使得用戶須要在兩個平臺間跳轉查看同一條Trace鏈路。所以,咱們接下來的目標是與鷹眼團隊合做,將鷹眼的Trace數據集成到EMonitor上,讓用戶能一站式的排查問題。

其次,本地生活上雲後,衆多中間件已遷移到雲上中間件,如雲Redis、雲Kafka、雲Zookeeper等。對應的可觀測性數據也須要額外登錄到阿里雲控制檯去查看。雲上中間的可觀測性數據大多已存儲到Prometheus之中,所以咱們計劃在完成Prometheus協議兼容後,就與雲上中間件團隊合做,將本地生活的雲上可觀測性數據集成到EMonitor上。

3、擁抱雲原生,兼容Prometheus、OpenTelemetry等開源協議。

雲原生帶來的技術革新勢不可擋,本地生活的絕大多數應用已遷移到集團的容器化平臺--ASI上,對應帶來的新的可觀測環節也亟需補全。如,ASI上Prometheus協議的容器可觀測性數據、Envoy等本地生活PaaS平臺透出的可觀測性數據與Trace數據等。

所以,咱們計劃在原先僅支持LinDB數據源的基礎上,增長對Prometheus數據源的支持;擴展OpenTelemetry的otel-collector exporter實現,將Open Telemetry協議的Trace數據轉換成EMonitor的Trace格式。如此即可補全雲原生技術升級引發的可觀測性數據缺失,並提供高度的適配性,知足本地生活的可觀測性建設。

結語

縱觀各大互聯網公司的產品演進,技術產品的走向與命運都離不開公司業務的發展軌跡。咱們餓了麼的技術人是幸運的,能遇上這一波技術變革的大潮,可以發揮聰明才智,打磨出一些爲用戶津津樂道的技術產品。咱們EMonitor可觀測性團隊也爲能參與到此次技術變動中深感自豪,EMonitor能被你們承認, 離不開每位參與到餓了麼可觀測性體系建設的同伴,也感謝各位對可觀測性系統提供幫助、支持、建議的夥伴!

做者簡介: 柯聖,花名「炸天」,餓了麼監控技術組負責人。自2016年加入餓了麼,長期深耕於可觀測性領域,全程參與了ETrace到EMonitor的餓了麼可觀測性系統的發展歷程。

點擊閱讀原文

相關文章
相關標籤/搜索