淺談可觀測架構模式

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可觀測性( Observability )主要是指了解程序內部運行情況的能力。我們不希望應用發佈上線後,對應用的內部一無所知。對於我們來說,整個應用就是一個黑盒子。即便應用出現錯誤或者發生崩潰,我們也可以得到崩潰前的所有相關數據,這也是飛機黑匣子( Flight Recorder )設計的出發點,如 圖1 所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/f6\/f6fbb59293fccec9d8394e7b27d64ac0.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"圖1 飛行記錄儀之日誌、度量和追蹤"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前,關於可觀測性的架構設計主要涉及三個部分:日誌(logging)、度量(Metrics)和追蹤(Tracing)。下面就從這三個方面詳細闡述可觀測性架構的設計。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"日誌"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要想了解系統的運行情況,最簡單的方法就是查看日誌。爲此,我們創造了非常多的日誌框架、工具和系統,如日誌文件打印、日誌文件採集工具、日誌分析系統等。但是,在實際運維中,我們不能將所有信息事無鉅細地全部記錄下來,這樣做反而沒有意義。我們需要爲日誌設置不同的級別,如 debug、error、info 等,在開發、測試、生產等不同環境下開啓不同的日誌級別,並保證在系統運行時能夠實時調控這些日誌級別。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常,我們不用考慮日誌處理的問題,畢竟日誌處理技術經過長時間的發展,目前已經非常成熟,幾乎所有的編程語言都有對應的日誌框架。目前,雲廠商基本上都會提供日誌服務,對接非常簡單,或者自行安裝成熟的日誌處理系統,如 ElasticStack 等。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"度量"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"度量不僅包括 CPU 負載、內存使用量等技術指標的度量,還包括非常多的業務度量( Business Metrics ),如每分鐘的交易額、每分鐘會員登錄數等。對於這些業務度量參數,我們在做架構設計的時候,需要以參考指標的方式全部羅列出來,以便於觀測上線後的數據,並做出相應的業務決策。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏可能會有讀者產生疑問,我們已經使用日誌記錄了相關的數據,數據庫中也保存了最終的數據,爲什麼還要增加對數據的記錄?爲了解答這個問題,我們首先看一下如下區別。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一,日誌記錄的是發生在某個時間點的事情,其中包含非常多的細節,可以說是事無鉅細的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二,數據庫記錄的是當前數據的最新快照,我們通常不會關注中間的過程,如電商網站的商品價格可能經過多次調整,但數據庫通常只會記錄商品的最新價格。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三,度量統計的是一個窗口期的聚合數據,可以是平均值,也可以是累計值。如果是 CPU 負載,就統計一段時間的平均值;如果是 1 分鐘內交易的訂單數,就需要統計累計值。還有一類比較特殊,就是那些沒有時間區間的情況,如計數器等,在應用啓動後的整個運行期間,它的值會不停地累加,在應用重啓後它會被重新計算。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然日誌可以計算出一些數據,如訂單數、訂單金額等,但這裏需要考慮數據分析的成本和實時性,以更好地實現計算資源、存儲節約和快速查詢等。而度量統計的是窗口期的數據,所以不需要再次計算,從而節約了計算資源;同時也不需要保存窗口期中每一條具體的數據,因此可以節約存儲資源;從用戶角度來說,由於數據經過了窗口期的預處理,因此查詢響應的速度也會更快。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總體來說,度量部分處理的是可觀測性數據中的垂直場景。當我們更關注某一窗口期的聚合數據,同時關注點主要聚焦於數據的趨勢和對比時,度量剛好能夠滿足這類需求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"典型的度量指標主要由以下 5 個部分組成:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)名稱:因爲度量指標的名稱要表達其代表的意思,所以最好採用命名空間級聯的方式,可以使用類似域名的“ . ”分隔,或者使用 Prometheus 中採用的“ _”分隔。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)時間點:採集度量的時間點,通常由度量框架自動設置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)數字值:度量值只能爲數字值,不能爲字符串等其他值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)類型:典型的類型分別爲計數器、直方圖、平均比率、計時器、計量表等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5)標籤:主要包括一些元信息,如來源服務器標識、應用名稱、分組信息、運行環境等。標籤是爲了方便後續的度量查詢和再聚合處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當以上信息保存到 Prometheus 等度量系統後,我們可以根據上述結構進行查詢。PromQL 是 Prometheus 提供的度量查詢語言。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後爲大家介紹基於度量系統的一些預警規則。預警規則非常豐富,下面列舉幾條以方便大家參考:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)閾值預警:當某一度量指標的值低於或高於某一預設值時,就會觸發警報。例如,CPU 的負載、業務上的度量值跌至零,這些都會觸發預警。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)同期數據對比:在某些場景下,通過絕對值判斷是不能發現系統問題的,比如,一個電商網站每天不同時段的交易額是有差別的,所以比對每週同一天同一時段的數據來判斷問題會更加精確。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)趨勢預警:主要是針對計數器類型設置的預警,如果度量數值出現激增或驟降,或者遊離在正常的曲線趨勢之外,就需要引起我們的注意。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回到實際的應用開發,大多數雲廠商也提供了度量集成化服務,如阿里雲的Prometheus 服務。在程序中,我們基本上只需要直接對接即可,諸如度量指標的採集、存儲、監控、告警、圖表展現等數據監控服務。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"追蹤"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服務架構後基本上是分佈式的架構設計。一個簡單的 HTTP 請求可能涉及 5 個以上應用,一旦出現問題,就會很難快速定位。例如,用戶反饋會員登錄非常慢,基本要花費 5 秒以上的時間,這種情況該如何定位問題所在?定位問題涉及登錄的 Web應用、賬號驗證服務、會員信息服務、登錄的安全監控系統,還涉及 Redis、數據庫等。如果沒有一個高效的追蹤系統,排查定位問題的複雜度可想而知。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,讓我們看一下追蹤系統的基本元素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.  traceId"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"traceId 用來標識一個追蹤鏈,如一個 64 位或 128 位長度的字符串。不同追蹤鏈的 traceId 不同。但在某一個追蹤鏈中,traceId 始終保持不變。traceId 通常在請求的入口處生成。如對於 HTTP 請求,traceId 基本上在網關層生成,也可以延後到具體的 Web 應用中生成。在產品環境中,並不是所有的請求都要啓動追蹤。我們只會採樣部分請求,如只會追蹤 2% 的請求,這樣做主要是考慮到追蹤對整個系統會造成額外的開銷。當然,在測試環境中,爲便於排查問題,建議所有請求都開啓追蹤。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.  spanId"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"spanId 用於在一個追蹤鏈中記錄一個跨時間段的操作。例如,我們訪問數據庫或者進行 RPC 調用的過程,就對應於一個 spanId。在一個區間(span)中,ID 的作用是便於識別。ID 通常是一個 64 位的 long 型數值,名稱的作用是便於用戶瞭解是什麼操作,起始時間和結束時間的作用是便於瞭解操作時長。另外,區間還可以包含其他元信息。總的來說,一個追蹤鏈是由多個區間組成的。區間提供具體的操作信息。區間的生成會涉及應用中的代碼,我們稱之爲區間的埋點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.  parentId"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在追蹤鏈中,我們可能需要對一些區間進行分組,如將某一應用內部的多個區間歸在一起,這樣就可以瞭解該應用在整個調用鏈中消耗的時間。其解決方案是爲區間添加parentId,將不同類別的區間歸在一起。通常,我們在進入一個應用時會進行 parentId 的設置。例如,進入會員登錄應用時會設置一個 parentId,在進入賬號驗證服務時會設置一個 parentId ,這樣我們就能根據不同的應用對區間進行歸類。在同一個應用內部,我們還可以基於應用的 parentId 設置子 parentId。如果想要歸類數據庫相關的操作,則將操作全部列在數據庫的 parentId 下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"追蹤鏈可以將整個請求在不同應用和系統中的操作信息串聯起來。我們只要輸入traceId ,就可以在追蹤系統中瞭解整個調用鏈的詳細信息。那麼,在不同的應用和系統中,路徑和區間信息又是如何採集的呢?Zipkin 是一款知名的路徑跟蹤產品,其中 Brave SDK 可以實現路徑和區間信息的採集。Brave SDK 負責創建路徑和區間,同時將這些信息異步上報給 Zipkin,完成追蹤鏈的數據採集工作。由於路徑和區間信息的採集是通過遠程調用實現的,因此這個採集過程一定要是異步實現的,只有這樣,才能確保不會影響到正常的業務操作。最典型的採集方法就是對接 gRPC、Kafka 和 RSocket 等異步協議或系統,以確保數據的採集全部是異步的。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"事件流訂閱"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"日誌記錄、度量和路徑追蹤是實現可觀測性架構模式的三大保證。但是在一些場景中,還存在其他非常精巧的設計,如 Java 飛行記錄器(Java Flight Recorder,JFR)。與前三者不太一樣的是,這是一種基於事件流(Event  Stream)的推送設計。我們可以在應用中定義各種 JFR 事件,並在業務流程中觸發這些事件。與日誌記錄不太一樣的是,JFR 事件可能並不需要像 CPU 負荷那樣被持久化記錄下來並保存到日誌文件中,而是在用戶對這些事件感興趣時才通過訂閱來開啓這些事件的採集,我們暫且稱之爲事件流訂閱。與日誌分析相比,這種方式更靈活,隨時開啓、隨時分析、隨時退出,而且完全是實時的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於 JFR 開啓實時事件流訂閱的好處是,我們不需要關心額外的開銷對系統性能的影響,因爲 JFR 的設計對系統額外開銷的影響已經降到了非常低,只有不到 1%,比日誌對系統的影響還要小。這就意味着在生產環境中,我們可以隨時快速開啓事件流監控。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Java14 中,JFR 有了進一步的提升和改進,包括性能優化、自定義事件 API 和流式訂閱等,這些都使得 JFR 的使用變得更加容易。在最新的 JDK15 中,JFR 的事件類型數量高達157個,如 CPU 負載(jdk.CPULoad)、Thread 啓動(jdk.ThreadStart)、文件讀取(jdk. FileRead)、Socket 讀取(jdk.SocketRead)等。這些都有事件記錄,對監控的幫助也非常大。但 JFR 只針對 Java 平臺,如果某個項目是基於 Java 的,那麼 JFR 就可以很好地提升系統的可觀測性。最新的 JUnit5.7 版本也已經默認支持 JFR 的特性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文轉載自:阿里巴巴中間件(ID:Aliware_2018)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/tR47edTcpI46pLI869oxIw","title":"xxx","type":null},"content":[{"type":"text","text":"淺談可觀測架構模式"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章