微服務全流程分析
轉眼已經2020,距離微服務這個詞落地已經過去好多年!(我記得2017年就聽過這個詞)。然而今天我想想什麼是微服務,其實並沒有一個很好的定義。為什麼這樣說,按照微服務的定義:
微服務架構就是將一個龐大的業務系統按照業務模組拆分成若干個獨立的子系統,每個子系統都是一個獨立的應用,它是一種將應用構建成一系列按業務領域劃分模組的,小的自治服務的軟體架構方式,倡導將複雜的單體應用拆分成若干個功能單一、鬆偶合的服務,這樣可以降低開發難度、增強擴充套件性、便於敏捷開發,及持續整合與交付活動。
根據這個定義,不難看出其實就是對複雜的業務系統統一做邏輯拆分,保持邏輯上的獨立。那麼邏輯上獨立就是一個服務這樣做真的是好嗎,如何界定:小、獨,還有要做一個事情,完成單一的業務,單一的功能要拆分出來,為了獨立而獨立會不會導致拆的過細?不同人有不同的見解,我們今天一起探討微服務的過去和未來。
微服務緣起
在沒有微服務之前,我們最早的架構模式就是 MVC 模式,把業務邏輯分為:表示層,業務邏輯層,資料訪問層。MVC模式隨著大前端的發展,從一開始的前後端不分離,到現在的前後端分離逐漸演進。這種演進好的一點是剝離了不同開發語言的開發環境和部署環境,使得開發較為便利,部署更直接。然而問題是:這種模式仍然是單體應用模式,如果有一個改動需要上線,你不得不因為這個改動去考慮更多,因為你無法估量在這麼大體量的程式碼中你的一個改動會不會引發蝴蝶效應。
另外很重要的一點就是移動網際網路時代的到來,引發了使用者數幾何倍數暴增,傳統的單體應用模式已經無法支撐使用者量暴漲的流量衝擊,網際網路人不得不做出加機器的無賴之舉,然而發現有的時候加機器都無法搞定問題,因為邏輯呼叫過於耦合導致呼叫鏈複雜。繼而出現精簡呼叫流程,梳理呼叫路徑的舉措,於是演變出微服務這個概念。
其實在沒有微服務這個詞出現之前, 我們也是這樣乾的,只是乾的不徹底而已。比如說有一個信貸系統,信貸系統分為貸前,貸中,貸後三步:
在微服務未出現之前,我們大多是單體應用,基本上一個工程包含所有,無所不能,所以很臃腫上。述這些模組應該都是在一個工程中,但是按照業務做了程式碼上的拆分。另外就是 RPC 框架併為橫空出世,如果有服務上的拆分,比如不同部門之間呼叫對方提供的服務,那麼八九不離十肯定定義的是HTTP 介面,因為通用。但是某些時候大家又怕 HTTP 介面效能差,關鍵服務不敢用。
微服務出現之後,大家覺得按照模組分別部署好像是這麼回事,同時默默在心裡嘀咕,以前我只用釋出一個工程,現在倒好,可能有個改動涉及3個服務,我一個小小的改動就要釋出3次,是不是增加了工作量,另外我以前都不用調介面的,現在依賴了一堆別的系統,系統呼叫這麼複雜,萬一別的系統有問題,我豈不是就被耽擱了!
在這種質疑中大家雖有抱怨但是也沒有放棄趕時髦,微服務開展的如火如荼,使用者中心獨立部署,風控系統單獨成型,支付中心全公司統一獨立,財務系統不再各個業務各自為戰而是統籌公司各個業務線統一規劃。按照業務抽象獨立之後,大家發現好像是這麼回事,用起來真香。雖然每次需要別的模組的時候就需要找對應模組進行接入,但是業務邏輯上清晰了呀,如果出了問題,不是自己的,那就是別人的,甩鍋很方便的(笑)。
如何做微服務
因為微服務是功能粒度上的拆分,必然導致拆分之後的模組變多。針對模組與模組之間的通訊與維護,又演變出如下問題:
- 模組與模組之間如何通訊;
- 每個被拆分的微服務如何做負載均衡;
- 服務如何做註冊,如何做發現;
- 服務之間呼叫如何做限流,服務呼叫失敗如何做降級,流量異常如何做熔斷;
- 服務呼叫是否可以做統一的訪問控制;
針對這些問題,業界也在發展中慢慢演進出幾套通用的框架。理想中微服務框架應該具備這樣的能力:
基於上述微服務框架應該具備的能力,我們來分析目前可以落地的微服務框架的具體實現。
目前國內用的最多的無外乎是兩套框架:Dubbo,Spring Cloud。Dubbo大家都很熟悉,從開源到無人維護再到重新衝擊Apache頂級專案。但是Dubbo更加準確來說是一個分散式服務框架,致力於提供高效的RPC遠端服務呼叫方案以及SOA服務治理方案。說白了就是個分散式遠端服務呼叫框架。
Dubbo
從Dubbo官網給的圖來看Dubbo的整體架構:
模組註解:
- Provider: 暴露服務的服務提供方。
- Consumer: 呼叫遠端服務的服務消費方。
- Registry: 服務註冊與發現的註冊中心。
- Monitor: 統計服務的呼叫次調和呼叫時間的監控中心。
- Container: 服務執行容器。
從上圖中不難看出Dubbo功能還是很明確的:服務註冊於發現,服務監控。另外Dubbo也提供服務治理功能:
Dubbo提供了叢集容錯的能力,在管理後臺可以快速的摘除失敗的服務。
對於我們上面提到的一整套微服務應該提供的功能看,Dubbo只是提供了服務註冊與服務發現的功能。不可否認在這一項功能中,Dubbo做的是非常優秀的。
Spring Cloud
Spring Cloud 基於 Spring Boot,為微服務體系開發中的架構問題,提供了一整套的解決方案——服務註冊與發現,服務消費,服務保護與熔斷,閘道器,分散式呼叫追蹤,分散式配置管理等。
服務註冊與發現
目前Spring Cloud 支援的服務註冊元件有 Consul,Eureka。Consul 不是 Spring 官方的專案,需要單獨部署,Eureka 被 Spring 官方收錄,本身屬於 Spring Cloud 體系中。
下面列出可以被用作註冊中心的元件他們的特性對比:
特性 | Euerka | Consul | Zookeeper | etcd |
---|---|---|---|---|
服務健康檢查 | 可配支援 | 服務狀態,記憶體,硬碟等 | (弱)長連線,keepalive | 連線心跳 |
多資料中心 | — | 支援 | — | — |
kv 儲存服務 | — | 支援 | 支援 | 支援 |
一致性 | — | raft | paxos | raft |
cap | ap | cp | cp | cp |
使用介面(多語言能力) | http(sidecar) | 支援 http 和 dns | 客戶端 | http/grpc |
watch 支援 | 支援 long polling/大部分增量 | 全量/支援long polling | 支援 | 支援 long polling |
自身監控 | metrics | metrics | — | metrics |
安全 | — | acl /https | acl | https 支援(弱) |
spring cloud 整合 | 已支援 | 已支援 | 已支援 | 已支援 |
Consul
Consul 官網中介紹了 Consul 的以下幾個核心功能:
- 服務發現(Service Discovery):提供 HTTP 與DNS 兩種方式。
- 健康檢查(Health Checking):提供多種健康檢查方式,比如 HTTP 狀態碼、記憶體使用情況、硬碟等等。
- 鍵值儲存(KV Store):可以作為服務配置中心使用,類似 Spring Cloud Config。
- 加密服務通訊(Secure Service Communication)
- 多資料中心(Multi Datacenter):Consul 通過 WAN 的 Gossip 協議,完成跨資料中心的同步。
Consul 需要單獨部署,而不是與Spring整合的元件。
Eureka
Eureka 是 Spring Cloud NetFlix 預設的服務發現框架,但目前 2.0 版本已閉源,只剩下 1.9 版本的處於維護狀態。Eureka 使用盡力而為同步的方式提供提供弱一致的服務列表。當一個服務註冊時,Eureka 會嘗試將其同步到其他節點上,但不提供一致性的保證。 因此,Eureka 可以提供過時的或是已不存在的服務列表(在服務發現場景下,返回舊的總比什麼也不返回好)。
如果在 15分鐘內超過85%的客戶端節點都沒有正常的心跳,那麼 Eureka 就會認為客戶端與註冊中心出現了網路故障(出現網路分割槽),進入自我保護機制。
此時:
- Eureka Server 會保護服務登錄檔中的資訊,不再刪除服務。這是由於如果出現網路分割槽導致其他微服務和該 Eureka Server 無法通訊,Eureka Server 就會判定這些微服務失效,但很可能這些微服務都是健康的。
- Eureka Server 仍能接受新服務的註冊和查詢請求,但這些資料不會被同步到其他節點。
- 當網路恢復時,這個 Eureka Server 節點的資料會被同步到其他節點中。
優點:
Eureka Server 可以很好的應對因網路故障導致部分節點失聯的情況,而不會像 ZK 那樣如果有一半不可用的情況會導致整個叢集不可用。
服務閘道器
微服務的拆分導致服務分散,如果一個大的業務要對外提供輸出,每個服務單獨對外提供呼叫對接入方不友好並且呼叫也會很複雜。所以出現了閘道器,閘道器主要實現請求的路由轉發,負載均衡,統一校驗,請求過濾等功能。
目前社群主流的閘道器有三個:Zuul,Kong,Spring Cloud GateWay。
Zuul
Zuul 是 Netflix 公司的開源專案,Spring Cloud 在 Netflix 專案中也已經集成了 Zuul,依賴名叫:spring-cloud-starter-netflix-zuul。Zuul構建於 Servlet 2.5,相容 3.x,使用的是阻塞式的 API,不支援長連線,比如 websockets。我們現在說的 Zuul 指 Zuul 1.x,Netflix 最新的 Zuul 2.x一直跳票,所以 Spring Cloud 在Zuul 2.x沒有出的時候依靠社群的力量發展出了新的閘道器元件:Spring Cloud Gateway。
Zuul 的核心功能就是基於 Servlet 提供了一系列的過濾器:
- 身份認證與安全:識別每個資源的驗證要求,並拒絕那些與要求不符的請求。
- 審查與監控:在邊緣位置追蹤有意義的資料和統計結果,從而帶來精確的生產檢視。
- 動態路由:動態地將請求路由到不同的後端叢集。
- 壓力測試:逐漸增加指向叢集的流量,以瞭解效能。
- 負載分配:為每一種負載型別分配對應容量,並啟用超出限定值的請求。
- 靜態響應處理:在邊緣位置直接建立部分相應,從而避免其轉發到內部叢集。
Spring Cloud Gateway
Spring Cloud Gateway 構建於 Spring 5+,基於 Spring Boot 2.x 響應式的、非阻塞式的 API。同時,它支援 Websockets,和 Spring 框架緊密整合,開發體驗相對來說十分不錯。SpringCloud Gateway 是基於WebFlux框架實現的,而WebFlux框架底層則使用了高效能的Reactor模式通訊框架Netty。
總體來說 Spring Cloud Gateway 與Zuul 功能差別不大,最大的出入是在底層效能的提升上。
Zuul 本身是基於 Servlet 容器來實現的過濾,Servlet採用的是單例項多執行緒的處理方案,Servlet會為每一個Request分配一個執行緒,如果當前執行緒比較耗時那麼會一直等到執行緒處理完畢才會返回。所以說 Zuul 是基於servlet之上的一個阻塞式處理模型。
同步阻塞模型對於閘道器這種比較在意響應耗時和呼叫頻繁的元件來說,必然會引發一些效能問題,所以Zuul 2已經做出了改良,從Zuul 2開始已經使用Netty。但是不幸的是Spring 官方已經對它的更新頻率感到失望所以縱然更新了也沒有被選用。
Spring Cloud Gateway 底層基於Webflux。Webflux模式替換了舊的Servlet執行緒模型。用少量的執行緒處理request和response io操作,這些執行緒稱為Loop執行緒。Webflux的Loop執行緒,正好就是著名的Reactor 模式IO處理模型的Reactor執行緒,如果使用的是高效能的通訊框架Netty,這就是Netty的EventLoop執行緒。
所以整體來看,Spring Cloud Gateway 的效能要比目前在用的 Zuul 高。但是Webflux的程式設計方式可能大家不是很能接收。
服務降級
降級限流在微服務中屬於銀彈,一般不用,一旦用上那就是拯救宇宙般存在。
目前業界通用的降級限流工具主要有3款:Hystrix,Sentinel,Resilience4j。
Hystrix 的關注點在於以 隔離 和 熔斷 為主的容錯機制,超時或被熔斷的呼叫將會快速失敗,並可以提供 fallback 機制。Hystrix 是元老級別的存在,但是在18年11月 Netflix 官方宣佈停止更新(就是這麼不靠譜,說跳票就跳票)。雖然停止更新,但是社群又推出了新的替代工具:Resilience4j。
Resilience4j 的模組化做的比較好,將每個功能點(如熔斷、限速器、自動重試)都拆成了單獨的模組,這樣整體結構很清晰,使用者也只需要引入相應功能的依賴即可;另外resilience4j 是針對 Java 8 和函數語言程式設計設計的,API 比較簡潔優雅。同時與 Hystrix 相比,Resilience4j 增加了簡單的限速器和自動重試特性,使用場景更加豐富。
相比 Hystrix , Resilience4j的優勢在於:
- 針對 Java 8 和函數語言程式設計設計,提供函式式和響應式風格的 API;
- 增加了 rate limiting 和 automatic retrying 兩個模組。其中 rate limiting 引入了簡單的速率控制實現,補充了流量控制這一塊的功能;
- 而 automatic retrying 則是封裝了自動重試的邏輯,簡化了異常恢復的流程。
Resilience4j 屬於一個新興專案,社群也在蓬勃發展。總的來說,Resilience4j 是比較輕量的庫,在較小較新的專案中使用還是比較方便的,但是 Resilience4j 只包含限流降級的基本場景,對於非常複雜的企業級服務架構可能無法很好地 cover 住;同時 Resilience4j 缺乏生產級別的配套設施(如提供規則管理和實時監控能力的控制檯)。
Sentinel 一款面向分散式服務架構的輕量級流量控制組件,主要以流量為切入點,從流量控制、熔斷降級、系統自適應保護等多個維度來幫助使用者保障服務的穩定性。
Sentinel 的核心思想:根據對應資源配置的規則來為資源執行相應的流控/降級/系統保護策略。在 Sentinel 中資源定義和規則配置是分離的。使用者先通過 Sentinel API 給對應的業務邏輯定義資源,然後可以在需要的時候動態配置規則。
整體功能對比:
Sentinel | Hystrix | Resilience4j | |
---|---|---|---|
隔離策略 | 訊號量隔離(併發執行緒數限流) | 執行緒池隔離/訊號量隔離 | 訊號量隔離 |
熔斷降級策略 | 基於響應時間、異常比率、異常數 | 基於異常比率 | 基於異常比率、響應時間 |
實時統計實現 | 滑動視窗(LeapArray) | 滑動視窗(基於 RxJava) | Ring Bit Buffer |
動態規則配置 | 支援多種資料來源 | 支援多種資料來源 | 有限支援 |
擴充套件性 | 多個擴充套件點 | 外掛的形式 | 介面的形式 |
基於註解的支援 | 支援 | 支援 | 支援 |
限流 | 基於 QPS,支援基於呼叫關係的限流 | 有限的支援 | Rate Limiter |
流量整形 | 支援預熱模式、勻速器模式、預熱排隊模式 | 不支援 | 簡單的 Rate Limiter 模式 |
系統自適應保護 | 支援 | 不支援 | 不支援 |
控制檯 | 提供開箱即用的控制檯,可配置規則、檢視秒級監控、機器發現等 | 簡單的監控檢視 | 不提供控制檯,可對接其它監控系統 |
從上面的參照看,Sentinel 的功能相對要多一些,但是多並不意味著所有,合適的才是最好的,對於你用不到的功能,簡單才是美麗。
統一配置中心
統一配置中心概念的提出也是伴隨著微服務架構出現才出現,單體應用的時候所有的配置都可以整合在服務之中,多應用的時候如果每個應用都持有一份配置可能會有相同配置冗餘的情況;如果一共有2000臺機器,如果一個配置發生更改,是否要登入每一臺機器重新更改配置呢;另外,更多的配置必然會帶來管理上的混亂,如果沒有集中管理的地方必然會越來越亂。
分散式配置管理的本質基本上就是一種推送-訂閱模式的運用。配置的應用方是訂閱者,配置管理服務則是推送方。其中,客戶端包括管理人員publish資料到配置管理服務,可以理解為新增/更新資料;配置管理服務notify資料到訂閱者,可以理解為推送。配置管理服務往往會封裝一個客戶端庫,應用方則是基於該庫與配置管理服務進行互動。在實際實現時,客戶端庫可能是主動拉取(pull)資料,但對於應用方而言,一般是一種事件通知方式。
選型一個合格的配置中心,至少需要滿足如下4個核心需求:
- 非開發環境下應用配置的保密性,避免將關鍵配置寫入原始碼;
- 不同部署環境下應用配置的隔離性,比如非生產環境的配置不能用於生產環境;
- 同一部署環境下的伺服器應用配置的一致性,即所有伺服器使用同一份配置;
- 分散式環境下應用配置的可管理性,即提供遠端管理配置的能力。
Diamond
最開始我接觸過的配置中心是淘寶的 Diamond,Diamond中的資料是簡單的key-value結構。應用方訂閱資料則是基於key來訂閱,未訂閱的資料當然不會被推送。
Diamond是無單點架構,在做更新配置的時候只做三件事:
- 寫資料庫
- 寫本地
- 通知其他機器到資料庫拉更新
本地的設計就是為了快取,減少對資料庫的壓力。作為一個配置中心,高可用是最主要的需求。如何保持高可用,Diamond持有多層的資料儲存,資料被儲存在:資料庫,服務端磁碟,客戶端快取目錄,以及可以手工干預
的容災目錄。 客戶端通過API獲取配置資料按照固定的順序去不同的資料來源獲取資料:容災目錄,服務端磁碟,客戶端快取。
Diamond除了在容災上做了很多方案,在資料讀取方面也有很多特點。客戶端採用推拉結合的策略在長連線和短連線之間取得一個平衡,讓服務端不用太關注連線的管理,又可以獲得長連線的及時性。
使用Diamond的流程:
釋出配置:
讀取配置:
Diamond server是無中心節點的邏輯叢集,這樣就能避免單點故障。Diamond的同質節點之間會相互通訊以保證資料的一致性,每個節點都有其它節點的地址資訊,其中一個節點發生資料變更後會響應的通知其他節點,保證資料的一致性。
為了保證高可用,client還會在app端快取一個本地檔案,這樣即使server不可用也能保證app可用。Client不斷長輪詢server,獲取最新的配置推送,儘量保證本地資料的時效性。
Client預設啟動週期任務對server進行長輪詢感知server的配置變化,server感知到配置變化就傳送變更的資料編號,客戶端通過資料編號再去拉取最新配置資料;否則超時結束請求(預設10秒)。拉取到新配置後,client會通知監聽者(MessageListener)做相應處理,使用者可以通過Diamond#addListener監聽。
但是Diamond一般用途是做KV儲存,如果用來做配置中心,他提供的能力不是太符合。
可以看到早期的配置中心處理的東西還是比較簡單,那個時候業務沒有那麼複雜,讀取配置和更新配置沒有那麼多花樣,持久化儲存和本地快取,長連線更新就可以。但是現在的配置中心隨著技術的發展承擔的作用可能更多,
Spring Cloud Config
Spring Cloud Config 作為Spring官方提供的配置中心可能比較符合外國人的習慣:
Spring Cloud Config將不同環境的所有配置存放在git 倉庫中,服務啟動時通過介面拉取配置。遵循{ServiceID}-{profile}.properties的結構,按照profile拉取自己所需的配置。
當開發者修改了配置項之後,需要結合spring config bus將配置通知到對應的服務,實現配置的動態更新。
可以看到,Spring Cloud Config已經具備了一個配置中心的雛形,可以滿足小型專案對配置的管理,但仍然有著很多侷限性。配置使用git庫進行管理,那麼git庫的許可權如何來判斷?不同環境的安全性也得不到保障。配置的新增和刪除,配置項的彙總,也只能通過git命令來實現,對運維人員也並不友好。
Apollo
Apollo(阿波羅)是攜程框架部門研發的開源配置管理中心,能夠集中化管理應用不同環境、不同叢集的配置,配置修改後能夠實時推送到應用端,並且具備規範的許可權、流程治理等特性。
Apollo 支援4個維度管理 Key-Value 格式的配置:
- application(應用):實際使用配置的應用,Apollo客戶端在執行時需要知道當前應用是誰,從而可以去獲取對應的配置;每個應用都需要有唯一的身份標識 – appId,應用身份是跟著程式碼走的,所以需要在程式碼中配置。
- environment(環境):配置對應的環境,Apollo客戶端在執行時需要知道當前應用處於哪個環境,從而可以去獲取應用的配置。
- cluster(叢集):一個應用下不同例項的分組,比如典型的可以按照資料中心分,把上海機房的應用例項分為一個叢集,把北京機房的應用例項分為另一個叢集。對不同的cluster,同一個配置可以有不一樣的值,如ZooKeeper地址。
- namespace(名稱空間):一個應用下不同配置的分組,可以簡單地把namespace類比為檔案,不同型別的配置存放在不同的檔案中,如資料庫配置檔案,RPC配置檔案,應用自身的配置檔案等;應用可以直接讀取到公共元件的配置namespace,如DAL,RPC等;應用也可以通過繼承公共元件的配置namespace來對公共元件的配置做調整,如DAL的初始資料庫連線數。
Apollo配置中心包括:Config Service、Admin Service 和 Portal。
- Config Service:提供配置獲取介面、配置推送介面,服務於Apollo客戶端;
- Admin Service:提供配置管理介面、配置修改釋出介面,服務於管理介面Portal;
- Portal:配置管理介面,通過MetaServer獲取AdminService的服務列表,並使用客戶端軟負載SLB方式呼叫AdminService。
上圖簡要描述了Apollo的總體設計,我們可以從下往上看:
- Config Service提供配置的讀取、推送等功能,服務物件是Apollo客戶端;
- Admin Service提供配置的修改、釋出等功能,服務物件是Apollo Portal(管理介面);
- Config Service和Admin Service都是多例項、無狀態部署,所以需要將自己註冊到Eureka中並保持心跳;
- 在Eureka之上我們架了一層Meta Server用於封裝Eureka的服務發現介面;
- Client通過域名訪問Meta Server獲取Config Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Client側會做load balance、錯誤重試;
- Portal通過域名訪問Meta Server獲取Admin Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Portal側會做load balance、錯誤重試;
- 為了簡化部署,我們實際上會把Config Service、Eureka和Meta Server三個邏輯角色部署在同一個JVM程序中。
客戶端設計:
上圖簡要描述了Apollo客戶端的實現原理:
- 客戶端和服務端保持了一個長連線,從而能第一時間獲得配置更新的推送;
- 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置
- 這是一個fallback機制,為了防止推送機制失效導致配置不更新
- 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
- 定時頻率預設為每5分鐘拉取一次,客戶端也可以通過在執行時指定System Property:
apollo.refreshInterval
來覆蓋,單位為分鐘。
- 客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會儲存在記憶體中;
- 客戶端會把從服務端獲取到的配置在本地檔案系統快取一份;
- 在遇到服務不可用,或網路不通的時候,依然能從本地恢復配置
- 應用程式從Apollo客戶端獲取最新的配置、訂閱配置更新通知。
配置更新:
前面提到了Apollo客戶端和服務端保持了一個長連線,從而能第一時間獲得配置更新的推送。
長連線實際上是通過Http Long Polling實現的,具體而言:
- 客戶端發起一個Http請求到服務端
- 服務端會保持住這個連線60秒
- 如果在60秒內有客戶端關心的配置變化,被保持住的客戶端請求會立即返回,並告知客戶端有配置變化的namespace資訊,客戶端會據此拉取對應namespace的最新配置
- 如果在60秒內沒有客戶端關心的配置變化,那麼會返回Http狀態碼304給客戶端
- 客戶端在收到服務端請求後會立即重新發起連線,回到第一步
考慮到會有數萬客戶端向服務端發起長連,在服務端使用了async servlet(Spring DeferredResult)來服務Http Long Polling請求。
呼叫鏈路分析
服務呼叫鏈路分析在微服務中是幕後至關重要的使者,試想幾百個服務摻雜在一起,你想屢出誰先呼叫了誰,誰被誰呼叫,如果沒有一個可監控的路徑,光憑腦子跟蹤那的多累。基於這種需求,各路大神們集中腦汁展開遐想弄出一套分散式鏈路追蹤神器來。
在介紹呼叫鏈監控工具之前,我們首先需要知道在微服務架構系統中經常會遇到兩個問題:
- 跨服務呼叫發生異常,要求快速定位當前這次調用出問題在哪一步;
- 跨服務的呼叫發生效能瓶頸,要求迅速定位出系統瓶頸應該如何做。
打個比方說我們有兩個服務:訂單中心,庫存中心。使用者下單,先去查詢庫存系統,那麼呼叫鏈路分析系統對於一個下單查詢服務應該記錄什麼呢?我們造出如下一張呼叫鏈路請求記錄表,表字段如下:
表字段說明:
- id:自增id
- span_id:唯一id
- pspan_id:父級span_id
- service_name:服務名稱
- api:api路徑
- stage:階段/狀態
- timestamp:插入資料時的時間戳
id | span_id | p_span_id | service_name | api | stage | time_stamp |
---|---|---|---|---|---|---|
1 | uid1 | null | order-center | /shop/0001 | cs | t1 |
2 | uid2 | uid1 | shop-center | /getCount/0001 | sr | t2 |
3 | uid2 | uid1 | shop-center | /getCount/0001 | ss | t3 |
4 | uid3 | null | order-center | /shop/0001 | cr | t4 |
上表中的stage中的狀態解釋為:
- CS(Client Sent 客戶端傳送):客戶端傳送一個請求,表示span的開始;
- SR(Server Received 服務端接收):服務端接收請求並開始處理它。(SR - CS)等於網路的延遲;
- SS(Server Sent 服務端傳送):服務端處理請求完成,開始返回結束給服務端。(SR - SS)表示服務端處理請求的時間;
- CR(Client Received 客戶端接收):客戶端完成接受返回結果,此時span結束。(CR - CS)表示客戶端接收服務端資料的時間。
根據這個表我們就能很快的分析上面提到的兩個問題:
如果以上任何一步有問題,那麼當前呼叫就不是完整的,我們必然能追蹤出來;
通過每一步的呼叫時間進行分析,我們也必然知道阻塞在哪一步,從而對呼叫慢的地方進行優化。
現有的分散式Trace基本都是採用了google 的 Dapper 標準。
標準。
Dapper的思想很簡單,就是在每一次呼叫棧中,使用同一個TraceId將不同的server聯絡起來。
一次單獨的呼叫鏈也可以稱為一個span,dapper記錄的是span的名稱,以及每個span的ID和父ID,以重建在一次追蹤過程中不同span之間的關係。
對於一個特定的span,記錄從Start到End,首先經歷了客戶端傳送資料,然後server接收資料,然後server執行內部邏輯,這中間可能去訪問另一個應用。執行完了server將資料返回,然後客戶端接收到資料。
在整個過程中,TraceId和ParentId的生成至關重要。首先解釋下TraceId
和ParentId
。TraceId
是標識這個呼叫鏈的Id,整個呼叫鏈,從瀏覽器開始放完,到A到B到C,一直到呼叫結束,所有應用在這次呼叫中擁有同一個TraceId
,所以才能把這次呼叫鏈在一起。
既然知道了這次呼叫鏈的整個Id,那麼每次查詢問題的時候,只要知道某一個呼叫的TraceId
,就能把所有這個Id的呼叫全部查找出來,能夠清楚的知道本地呼叫鏈經過了哪些應用,產生了哪些呼叫。但是還缺一點,那就是鏈。
基於這種需求,目前各大廠商都做出了自己的分散式追蹤系統,目前國內開源的有阿里的鷹眼,美團的CAT,京東的Hydra,還有廣為人知的個人開源Apache頂級專案SkyWalking。國外的有Zipkin,Pinpoint。
Spring Cloud Sleuth + Zipkin
Spring Cloud Sleuth 實現了一種分散式的服務鏈路跟蹤解決方案,通過使用Sleuth可以讓我們快速定位某個服務的問題。簡單來說,Sleuth相當於呼叫鏈監控工具的客戶端,整合在各個微服務上,負責產生呼叫鏈監控資料。
通過Sleuth產生的呼叫鏈監控資訊,讓我們可以得知微服務之間的呼叫鏈路,但是監控資訊只輸出到控制檯始終不太方便檢視。所以我們需要一個圖形化的工具,這時候就輪到Zipkin出場了。Zipkin是Twitter開源的分散式跟蹤系統,主要用來收集系統的時序資料,從而追蹤系統的呼叫問題。
Spring Cloud Slueth 聚焦在鏈路追蹤和分析,將資訊傳送到Zipkin,利用 Zipkin的儲存來儲存資訊,當然,Zipkin也可以使用ELK來記錄日誌和展示,再通過收集伺服器效能的指令碼把資料儲存到ELK,則可以展示伺服器狀況資訊。
PinPoint
pinpoint資料分析非常完備的。提供程式碼級別的可見性以便輕鬆定位失敗點和瓶頸,對於執行的sql語句,都進行了記錄。還可以配置報警規則等,設定每個應用對應的負責人,根據配置的規則報警,支援的中介軟體和框架也比較完備。Pinpoint 是一個完整的效能監控解決方案:有從探針、收集器、儲存到 Web 介面等全套體系。
Pinpoint 提供有 Java Agent 探針,通過位元組碼注入的方式實現呼叫攔截和資料收集,可以做到真正的程式碼無侵入,只需要在啟動伺服器的時候新增一些引數,就可以完成探針的部署。
對於這一點,Zipkin 使用修改過的類庫和它自己的容器(Finagle)來提供分散式事務跟蹤的功能。但是,它要求在需要時修改程式碼。pinpoint是基於位元組碼增強的方式,開發人員不需要修改程式碼,並且可以收集到更多精確的資料因為有位元組碼中的更多資訊。
相對來說,pinpoint介面顯示的更加豐富,具體到呼叫的DB名,zipkin的拓撲侷限於服務於服務之間。
SkyWalking
SkyWalking 和 pinpoint有一種既生瑜何生亮的感嘆。SkyWalking 邏輯上分為四部分:
- 探針(SkyWalking-agent)
- 平臺後端(oap-server)
- 儲存(es)
- 使用者介面(apm-webapp)
探針基於不同的來源可能是不一樣的(原生代理, SDK 以及 Zipkin, Jaeger 和 OpenCensus )、但作用都是收集資料、將資料格式化為 SkyWalking 適用的格式。
平臺後端是一個支援叢集模式執行的後臺、用於資料聚合、資料分析以及驅動資料流從探針到使用者介面的流程、平臺後端還提供了各種可插拔的能力、如不同來源資料(如來自 Zipkin)格式化、不同儲存系統以及叢集管理、你甚至還可以使用觀測分析語言來進行自定義聚合分析。
儲存是開放式的、你可以選擇一個既有的儲存系統、如 ElasticSearch, H2 或 MySQL 叢集(Sharding-Sphere 管理)、也可以選擇自己實現一個儲存系統。
使用者介面對於 SkyWalking 的終端使用者來說非常炫酷且強大、同樣它也是可定製以匹配你已存在的後端的。
總結
以上是微服務過程全鏈路過程中需要經歷的階段,當然還不包括髮布系統的搭建,底層資料治理能力。所以提倡微服務可以,但是真的做起來不是所有公司都能做得到。小公司能做到服務拆分但是相應的配套設施不一定能跟上,大公司有人有錢有時間,才能提供這些基礎設施。微服務的路任重道遠,搭起來一套完整的設施並用於生產環境還是挺有挑戰,新的一年希望我能夠在踐行微服務的路上走下去,只有走的完整才是微服務,走的不完整對於開發人員來說,那就是過度開發,就是災難