01.08 我的天,你們公司的“微服務”簡直就是反人類…

轉眼已經 2020,距離微服務這個詞落地已經過去好多年!(我記得 2017 年就聽過這個詞)。然而今天我想想什麼是微服務,其實並沒有一個很好的定義。

我的天,你們公司的“微服務”簡直就是反人類…

圖片來自 Pexels

為什麼這樣說?按照微服務的定義:

微服務架構就是將一個龐大的業務系統按照業務模塊拆分成若干個獨立的子系統,每個子系統都是一個獨立的應用,它是一種將應用構建成一系列按業務領域劃分模塊的,小的自治服務的軟件架構方式,倡導將複雜的單體應用拆分成若干個功能單一、松偶合的服務,這樣可以降低開發難度、增強擴展性、便於敏捷開發,及持續集成與交付活動。

根據這個定義,不難看出其實就是對複雜的業務系統統一做邏輯拆分,保持邏輯上的獨立。

那麼邏輯上獨立,一個服務這樣做真的就好嗎,如何界定:小、獨,還有要做一個事情,完成單一的業務,單一的功能要拆分出來,為了獨立而獨立會不會導致拆的過細?

不同人有不同的見解,我們今天一起探討微服務的過去和未來。

微服務緣起

在沒有微服務之前,我們最早的架構模式就是 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 體系中。

下面列出可以被用作註冊中心的組件,他們的特性對比:

我的天,你們公司的“微服務”簡直就是反人類…

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 是 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:是 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 5+,基於 Spring Boot 2.x 響應式的、非阻塞式的 API。

同時,它支持 Websockets,和 Spring 框架緊密集成,開發體驗相對來說十分不錯。

Spring Cloud 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 的編程方式可能大家不是很能接收。

③服務降級

降級限流在微服務中屬於銀彈,一般不用,一旦用上那就是拯救宇宙般存在。

目前業界通用的降級限流工具主要有三款:

  • Hystrix
  • Sentinel
  • Resilience4j

Hystrix 的關注點在於以隔離和熔斷為主的容錯機制,超時或被熔斷的調用將會快速失敗,並可以提供 Fallback 機制。

Hystrix 是元老級別的存在,但是在 2018 年 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 的功能相對要多一些,但是多並不意味著所有,合適的才是最好的,對於你用不到的功能,簡單才是美麗。

④統一配置中心

統一配置中心概念的提出也是伴隨著微服務架構出現才出現,單體應用的時候所有的配置都可以集成在服務之中,多應用的時候如果每個應用都持有一份配置可能會有相同配置冗餘的情況。

如果一共有 2000 臺機器,其中一個配置發生更改,是否要登錄每一臺機器重新更改配置呢。

另外,更多的配置必然會帶來管理上的混亂,如果沒有集中管理的地方必然會越來越亂。

分佈式配置管理的本質基本上就是一種推送-訂閱模式的運用。配置的應用方是訂閱者,配置管理服務則是推送方。

其中,客戶端包括管理人員 Publish 數據到配置管理服務,可以理解為添加/更新數據;配置管理服務 Notify 數據到訂閱者,可以理解為推送。

配置管理服務往往會封裝一個客戶端庫,應用方則是基於該庫與配置管理服務進行交互。

在實際實現時,客戶端庫可能是主動拉取(pull)數據,但對於應用方而言,一般是一種事件通知方式。

選型一個合格的配置中心,至少需要滿足如下四個核心需求:

  • 非開發環境下應用配置的保密性,避免將關鍵配置寫入源代碼。
  • 不同部署環境下應用配置的隔離性,比如非生產環境的配置不能用於生產環境。
  • 同一部署環境下的服務器應用配置的一致性,即所有服務器使用同一份配置。
  • 分佈式環境下應用配置的可管理性,即提供遠程管理配置的能力。

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 會通知監聽者(Message Listener)做相應處理,用戶可以通過 Diamond#addListener 監聽。

但是 Diamond 一般用途是做 KV 存儲,如果用來做配置中心,他提供的能力不是太符合。

可以看到早期的配置中心處理的東西還是比較簡單,那個時候業務沒有那麼複雜,讀取配置和更新配置沒有那麼多花樣,持久化存儲和本地緩存,長連接更新就可以。但是現在的配置中心隨著技術的發展承擔的作用可能更多。

Spring Cloud Config:作為 Spring 官方提供的配置中心可能比較符合外國人的習慣。

我的天,你們公司的“微服務”簡直就是反人類…

Spring Cloud Config 將不同環境的所有配置存放在 Git 倉庫中,服務啟動時通過接口拉取配置。

遵循 {ServiceID}-{profile}.properties 的結構,按照 Profile 拉取自己所需的配置。

當開發者修改了配置項之後,需要結合 Spring Config Bus 將配置通知到對應的服務,實現配置的動態更新。

可以看到,Spring Cloud Config 已經具備了一個配置中心的雛形,可以滿足小型項目對配置的管理,但仍然有著很多侷限性。

配置使用 Git 庫進行管理,那麼 Git 庫的權限如何來判斷?不同環境的安全性也得不到保障。

配置的添加和刪除,配置項的彙總,也只能通過 Git 命令來實現,對運維人員也並不友好。

Apollo(阿波羅):是攜程框架部門研發的開源配置管理中心,能夠集中化管理應用不同環境、不同集群的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性。

Apollo 支持四個維度管理 Key-Value 格式的配置:

  • Application(應用):實際使用配置的應用,Apollo 客戶端在運行時需要知道當前應用是誰,從而可以去獲取對應的配置;每個應用都需要有唯一的身份標識 – appId,應用身份是跟著代碼走的,所以需要在代碼中配置。
  • Environment(環境):配置對應的環境,Apollo 客戶端在運行時需要知道當前應用處於哪個環境,從而可以去獲取應用的配置。
  • Cluster(集群):一個應用下不同實例的分組,比如典型的可以按照數據中心分,把上海機房的應用實例分為一個集群,把北京機房的應用實例分為另一個集群。

對不同的 Cluster,同一個配置可以有不一樣的值,如 ZooKeeper 地址。

  • Namespace(命名空間):一個應用下不同配置的分組,可以簡單地把 Namespace 類比為文件,不同類型的配置存放在不同的文件中,如數據庫配置文件,RPC 配置文件,應用自身的配置文件等。

應用可以直接讀取到公共組件的配置 Namespace,如 DAL,RPC 等;應用也可以通過繼承公共組件的配置 Namespace 來對公共組件的配置做調整,如 DAL 的初始數據庫連接數。

Apollo 配置中心包括:

  • Config Service:提供配置獲取接口、配置推送接口,服務於 Apollo 客戶端。
  • Admin Service:提供配置管理接口、配置修改發佈接口,服務於管理界面 Portal。
  • Portal:配置管理界面,通過 MetaServer 獲取 Admin Service 的服務列表,並使用客戶端軟負載 SLB 方式調用 Admin Service。
我的天,你們公司的“微服務”簡直就是反人類…

上圖簡要描述了 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:插入數據時的時間戳

上表中的 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:數據分析非常完備。提供代碼級別的可見性以便輕鬆定位失敗點和瓶頸,對於執行的 SQL 語句,都進行了記錄。

還可以配置報警規則等,設置每個應用對應的負責人,根據配置的規則報警,支持的中間件和框架也比較完備。

Pinpoint 是一個完整的性能監控解決方案:有從探針、收集器、存儲到 Web 界面等全套體系。

Pinpoint 提供有 Java Agent 探針,通過字節碼注入的方式實現調用攔截和數據收集,可以做到真正的代碼無侵入,只需要在啟動服務器的時候添加一些參數,就可以完成探針的部署。

對於這一點,Zipkin 使用修改過的類庫和它自己的容器(Finagle)來提供分佈式事務跟蹤的功能。

但是,它要求在需要時修改代碼。Pinpoint 是基於字節碼增強的方式,開發人員不需要修改代碼,並且可以收集到更多精確的數據因為有字節碼中的更多信息。

相對來說,Pinpoint 界面顯示的更加豐富,具體到調用的 DB 名,Zipkin 的拓撲侷限於服務於服務之間。

SkyWalking:和 Pinpoint 有一種既生瑜何生亮的感嘆。

SkyWalking 邏輯上分為四部分:

  • 探針(SkyWalking-agent)
  • 平臺後端(oap-server)
  • 存儲(es)
  • 用戶界面(apm-webapp)

探針基於不同的來源可能是不一樣的(原生代理,SDK 以及 Zipkin,Jaeger 和 OpenCensus ),但作用都是收集數據、將數據格式化為 SkyWalking 適用的格式。

平臺後端是一個支持集群模式運行的後臺、用於數據聚合、數據分析以及驅動數據流從探針到用戶界面的流程。

平臺後端還提供了各種可插拔的能力,如不同來源數據(如來自 Zipkin)格式化、不同存儲系統以及集群管理,你甚至還可以使用觀測分析語言來進行自定義聚合分析。

存儲是開放式的,你可以選擇一個既有的存儲系統,如 ElasticSearch,H2 或 MySQL 集群(Sharding-Sphere 管理)、也可以選擇自己實現一個存儲系統。

用戶界面對於 SkyWalking 的最終用戶來說非常炫酷且強大、同樣它也是可定製以匹配你已存在的後端的。

總結

以上是微服務全鏈路過程中需要經歷的階段,當然還不包括髮布系統的搭建,底層數據治理能力。

所以提倡微服務可以,但是真的做起來不是所有公司都能做得到。小公司能做到服務拆分但是相應的配套設施不一定能跟上,大公司有人有錢有時間,才能提供這些基礎設施。

微服務的路任重道遠,搭起來一套完整的設施並用於生產環境還是挺有挑戰,新的一年希望我能夠在踐行微服務的路上走下去,只有走的完整才是微服務,走的不完整對於開發人員來說,那就是過度開發,就是災難。


分享到:


相關文章: