在微服務中進行日誌跟蹤的理論基礎
在一個單體的應用程式中,我們只用簡單的將日誌記錄到日誌檔案裡面就能夠方便的檢視服務的執行狀態。但是在由眾多微服務構成的系統中,服務之間的依賴呼叫是比較常見的。

圖1 微服務呼叫鏈
如上圖所示,如果一個使用者呼叫了一個服務 A ,服務 A 同時呼叫了服務 B 和服務 C ,服務 B 又呼叫了服務 D 和服務 E,在這種情況下,如果我們還用原來的日誌記錄方法來跟蹤日誌,我們就要分別去服務 A、B、C、D、E 五個服務所在的伺服器上去分別檢視日誌。更加複雜的是,如果其中的一些服務時多個相同的服務例項形成一個小型的服務叢集來處理呼叫的請求,或者執行的例項執行在容器中,並且由 Kubernetes 這樣的排程系統來動態排程的,那麼我們很難直觀的瞭解是是哪個服務例項在這次呼叫中處理的呼叫請求,也不知道服務究竟是執行在叢集的那些機器上。所以也就很難直接找到日誌輸出的地方去進行日誌的檢視。
而且即使是在單體的應用中,多個使用者同時進行呼叫的時候,多個會話的日誌混雜在一起,我們也很難將一次請求的日誌給分離出來。所以,我們需要一個更合理和更高效的日誌記錄和跟蹤體系來解決這個問題。
Google 釋出的分散式系統跟蹤的論文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》 來處理這種情況。具體來說就是講上圖中前端使用者發起一個根請求,這個根請求形成的一個完整的呼叫鏈,把這個完整的呼叫鏈定義為為一個跟蹤樹(Trace tree),每次請求,即圖中自上而下的箭頭都定義為一個呼叫(span)。除了使用者直接發起的 span 沒有父 span 外,每個 span 都有一個上級 span的引用。這樣就形成了一個環環相扣的跟蹤鏈條。圖1 的呼叫鏈用上述的概念來描述,就形成了如圖2 所示的的跟蹤樹呼叫結構圖。

圖2 跟蹤樹呼叫結構圖
為了記錄一次呼叫(span)的詳細情況,我們在服務呼叫的過程中定義了一系列的事件並用日誌記錄下來。其中有四種系統定義的事件 Client Send ( CS ),代表呼叫者向被呼叫者發起了服務的呼叫請求, Client Receive( CR ) ,代表呼叫者接收到服務被呼叫者的迴應,Server Receive ( SR ),代表服務被呼叫者接收到服務呼叫者的請求, Server Send ( SS ),表示服務被呼叫者向服務呼叫者傳送迴應。還有使用者自定義的事件,用來記錄服務在一次呼叫執行過程中的一些重要狀態資訊。這個使用者自定義事件就是我們平時列印的日誌的資訊。一次跨服務的呼叫檢視如圖3 所示。

圖3 跨服務呼叫檢視
我們在將服務呼叫過程中的事件資料收集起來,通過服務呼叫跟蹤鏈和服務呼叫事件的呼叫過程資料,我們就可以我們能夠清晰的將一次呼叫中的所有事件按照樹形方式詳細的顯示出來。而且,根據CS、CR 、SS、SR的時間戳,我們還能分析服務呼叫過程中在各個網路等待和實際執行的耗時。
當然了,這裡只是將一個理論基礎,實際的過程中,還有很多問題需要解決。例如 traceId 和 spanId 的生成和傳遞是一個需要解決的問題。這個我們在後面再繼續說。