轉眼已經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 的最終用戶來說非常炫酷且強大、同樣它也是可定制以匹配你已存在的后端的。
總結
以上是微服務過程全鏈路過程中需要經歷的階段,當然還不包括發布系統的搭建,底層數據治理能力。所以提倡微服務可以,但是真的做起來不是所有公司都能做得到。小公司能做到服務拆分但是相應的配套設施不一定能跟上,大公司有人有錢有時間,才能提供這些基礎設施。微服務的路任重道遠,搭起來一套完整的設施並用於生產環境還是挺有挑戰,新的一年希望我能夠在踐行微服務的路上走下去,只有走的完整才是微服務,走的不完整對於開發人員來說,那就是過度開發,就是災難。