我的天,你們公司的“微服務(wù)”簡(jiǎn)直就是反人類…
轉(zhuǎn)眼已經(jīng) 2020,距離微服務(wù)這個(gè)詞落地已經(jīng)過去好多年!(我記得 2017 年就聽過這個(gè)詞)。然而今天我想想什么是微服務(wù),其實(shí)并沒有一個(gè)很好的定義。
圖片來自 Pexels
為什么這樣說?按照微服務(wù)的定義:
微服務(wù)架構(gòu)就是將一個(gè)龐大的業(yè)務(wù)系統(tǒng)按照業(yè)務(wù)模塊拆分成若干個(gè)獨(dú)立的子系統(tǒng),每個(gè)子系統(tǒng)都是一個(gè)獨(dú)立的應(yīng)用,它是一種將應(yīng)用構(gòu)建成一系列按業(yè)務(wù)領(lǐng)域劃分模塊的,小的自治服務(wù)的軟件架構(gòu)方式,倡導(dǎo)將復(fù)雜的單體應(yīng)用拆分成若干個(gè)功能單一、松偶合的服務(wù),這樣可以降低開發(fā)難度、增強(qiáng)擴(kuò)展性、便于敏捷開發(fā),及持續(xù)集成與交付活動(dòng)。
根據(jù)這個(gè)定義,不難看出其實(shí)就是對(duì)復(fù)雜的業(yè)務(wù)系統(tǒng)統(tǒng)一做邏輯拆分,保持邏輯上的獨(dú)立。
那么邏輯上獨(dú)立,一個(gè)服務(wù)這樣做真的就好嗎,如何界定:小、獨(dú),還有要做一個(gè)事情,完成單一的業(yè)務(wù),單一的功能要拆分出來,為了獨(dú)立而獨(dú)立會(huì)不會(huì)導(dǎo)致拆的過細(xì)?
不同人有不同的見解,我們今天一起探討微服務(wù)的過去和未來。
微服務(wù)緣起
在沒有微服務(wù)之前,我們最早的架構(gòu)模式就是 MVC 模式,把業(yè)務(wù)邏輯分為:
- 表示層
- 業(yè)務(wù)邏輯層
- 數(shù)據(jù)訪問層
MVC 模式隨著大前端的發(fā)展,從一開始的前后端不分離,到現(xiàn)在的前后端分離逐漸演進(jìn)。
這種演進(jìn)好的一點(diǎn)是剝離了不同開發(fā)語言的開發(fā)環(huán)境和部署環(huán)境,使得開發(fā)較為便利,部署更直接。
然而問題是:這種模式仍然是單體應(yīng)用模式,如果有一個(gè)改動(dòng)需要上線,你不得不因?yàn)檫@個(gè)改動(dòng)去考慮更多,因?yàn)槟銦o法估量在這么大體量的代碼中你的一個(gè)改動(dòng)會(huì)不會(huì)引發(fā)蝴蝶效應(yīng)。
另外很重要的一點(diǎn)就是移動(dòng)互聯(lián)網(wǎng)時(shí)代的到來,引發(fā)了用戶數(shù)幾何倍數(shù)暴增,傳統(tǒng)的單體應(yīng)用模式已經(jīng)無法支撐用戶量暴漲的流量沖擊,互聯(lián)網(wǎng)人不得不做出加機(jī)器的無奈之舉。
然而發(fā)現(xiàn)有的時(shí)候加機(jī)器都無法搞定問題,因?yàn)檫壿嬚{(diào)用過于耦合導(dǎo)致調(diào)用鏈復(fù)雜。
繼而出現(xiàn)精簡(jiǎn)調(diào)用流程,梳理調(diào)用路徑的舉措,于是演變出微服務(wù)這個(gè)概念。
其實(shí)在沒有微服務(wù)這個(gè)詞出現(xiàn)之前, 我們也是這樣干的,只是干的不徹底而已。
比如說有一個(gè)信貸系統(tǒng),分為貸前,貸中,貸后三步:
在微服務(wù)未出現(xiàn)之前,我們大多是單體應(yīng)用,基本上一個(gè)工程包含所有,無所不能,所以很臃腫。上述這些模塊應(yīng)該都是在一個(gè)工程中,但是按照業(yè)務(wù)做了代碼上的拆分。
另外就是 RPC 框架橫空出世,如果有服務(wù)上的拆分,比如不同部門之間調(diào)用對(duì)方提供的服務(wù),那么八九不離十肯定定義的是 HTTP 接口,因?yàn)橥ㄓ?。但是某些時(shí)候大家又怕 HTTP 接口性能差,關(guān)鍵服務(wù)不敢用。
微服務(wù)出現(xiàn)之后,大家覺得按照模塊分別部署好像是這么回事,同時(shí)默默在心里嘀咕,以前我只用發(fā)布一個(gè)工程,現(xiàn)在倒好,可能有個(gè)改動(dòng)涉及 3 個(gè)服務(wù),我一個(gè)小小的改動(dòng)就要發(fā)布 3 次,是不是增加了工作量。
另外我以前都不用調(diào)接口的,現(xiàn)在依賴了一堆別的系統(tǒng),系統(tǒng)調(diào)用這么復(fù)雜,萬一別的系統(tǒng)有問題,我豈不是就被耽擱了!
在這種質(zhì)疑中大家雖有抱怨但是也沒有放棄趕時(shí)髦,微服務(wù)開展的如火如荼,用戶中心獨(dú)立部署,風(fēng)控系統(tǒng)單獨(dú)成型,支付中心全公司統(tǒng)一獨(dú)立,財(cái)務(wù)系統(tǒng)不再各個(gè)業(yè)務(wù)各自為戰(zhàn)而是統(tǒng)籌公司各個(gè)業(yè)務(wù)線統(tǒng)一規(guī)劃。
按照業(yè)務(wù)抽象獨(dú)立之后,大家發(fā)現(xiàn)好像是這么回事,用起來真香。雖然每次需要?jiǎng)e的模塊的時(shí)候就需要找對(duì)應(yīng)模塊進(jìn)行接入,但是業(yè)務(wù)邏輯上清晰了呀,如果出了問題,不是自己的,那就是別人的,甩鍋很方便的(笑)。
如何做微服務(wù)
因?yàn)槲⒎?wù)是功能粒度上的拆分,必然導(dǎo)致拆分之后的模塊變多。
針對(duì)模塊與模塊之間的通信與維護(hù),又演變出如下問題:
- 模塊與模塊之間如何通信
- 每個(gè)被拆分的微服務(wù)如何做負(fù)載均衡
- 服務(wù)如何做注冊(cè),如何做發(fā)現(xiàn)
- 服務(wù)之間調(diào)用如何做限流,服務(wù)調(diào)用失敗如何做降級(jí),流量異常如何做熔斷
- 服務(wù)調(diào)用是否可以做統(tǒng)一的訪問控制
針對(duì)這些問題,業(yè)界也在發(fā)展中慢慢演進(jìn)出幾套通用的框架。
理想中微服務(wù)框架應(yīng)該具備這樣的能力:
基于上述微服務(wù)框架應(yīng)該具備的能力,我們來分析目前可以落地的微服務(wù)框架的具體實(shí)現(xiàn)。
目前國內(nèi)用的最多的無外乎是兩套框架:
- Dubbo
- Spring Cloud
Dubbo 大家都很熟悉,從開源到無人維護(hù)再到重新沖擊 Apache 頂級(jí)項(xiàng)目。
但是 Dubbo 更加準(zhǔn)確來說是一個(gè)分布式服務(wù)框架,致力于提供高效的 RPC 遠(yuǎn)程服務(wù)調(diào)用方案以及 SOA 服務(wù)治理方案。說白了就是個(gè)分布式遠(yuǎn)程服務(wù)調(diào)用框架。
Dubbo
從 Dubbo 官網(wǎng)給的圖來看 Dubbo 的整體架構(gòu):
模塊注解:
- Provider:暴露服務(wù)的服務(wù)提供方。
- Consumer:調(diào)用遠(yuǎn)程服務(wù)的服務(wù)消費(fèi)方。
- Registry:服務(wù)注冊(cè)與發(fā)現(xiàn)的注冊(cè)中心。
- Monitor:統(tǒng)計(jì)服務(wù)的調(diào)用次數(shù)和調(diào)用時(shí)間的監(jiān)控中心。
- Container:服務(wù)運(yùn)行容器。
從上圖中不難看出 Dubbo 功能還是很明確的:服務(wù)注冊(cè)于發(fā)現(xiàn),服務(wù)監(jiān)控。
另外 Dubbo 也提供服務(wù)治理功能:
Dubbo 提供了集群容錯(cuò)的能力,在管理后臺(tái)可以快速的摘除失敗的服務(wù)。
從我們上面提到的一整套微服務(wù)應(yīng)該提供的功能看,Dubbo 只是提供了服務(wù)注冊(cè)與服務(wù)發(fā)現(xiàn)的功能。不可否認(rèn)在這一項(xiàng)功能中,Dubbo 做的是非常優(yōu)秀的。
Spring Cloud
Spring Cloud 基于 Spring Boot,為微服務(wù)體系開發(fā)中的架構(gòu)問題,提供了一整套的解決方案:服務(wù)注冊(cè)與發(fā)現(xiàn),服務(wù)消費(fèi),服務(wù)保護(hù)與熔斷,網(wǎng)關(guān),分布式調(diào)用追蹤,分布式配置管理等。
①服務(wù)注冊(cè)與發(fā)現(xiàn)
目前 Spring Cloud 支持的服務(wù)注冊(cè)組件有:
- Consul
- Eureka
Consul 不是 Spring 官方的項(xiàng)目,需要單獨(dú)部署,Eureka 被 Spring 官方收錄,本身屬于 Spring Cloud 體系中。
下面列出可以被用作注冊(cè)中心的組件,他們的特性對(duì)比:
Consul 官網(wǎng)中介紹了 Consul 的以下幾個(gè)核心功能:
- 服務(wù)發(fā)現(xiàn)(Service Discovery):提供 HTTP 與DNS 兩種方式。
- 健康檢查(Health Checking):提供多種健康檢查方式,比如 HTTP 狀態(tài)碼、內(nèi)存使用情況、硬盤等等。
- 鍵值存儲(chǔ)(KV Store):可以作為服務(wù)配置中心使用,類似 Spring Cloud Config。
- 加密服務(wù)通信(Secure Service Communication)。
- 多數(shù)據(jù)中心(Multi Datacenter):Consul 通過 WAN 的 Gossip 協(xié)議,完成跨數(shù)據(jù)中心的同步。
Consul 需要單獨(dú)部署,而不是與 Spring 集成的組件。
Eureka 是 Spring Cloud NetFlix 默認(rèn)的服務(wù)發(fā)現(xiàn)框架,但目前 2.0 版本已閉源,只剩下 1.9 版本的處于維護(hù)狀態(tài)。
Eureka 使用盡力而為同步的方式提供弱一致的服務(wù)列表。當(dāng)一個(gè)服務(wù)注冊(cè)時(shí),Eureka 會(huì)嘗試將其同步到其他節(jié)點(diǎn)上,但不提供一致性的保證。
因此,Eureka 可以提供過時(shí)的或是已不存在的服務(wù)列表(在服務(wù)發(fā)現(xiàn)場(chǎng)景下,返回舊的總比什么也不返回好)。
如果在 15 分鐘內(nèi)超過 85% 的客戶端節(jié)點(diǎn)都沒有正常的心跳,那么 Eureka 就會(huì)認(rèn)為客戶端與注冊(cè)中心出現(xiàn)了網(wǎng)絡(luò)故障(出現(xiàn)網(wǎng)絡(luò)分區(qū)),進(jìn)入自我保護(hù)機(jī)制。
此時(shí):
- Eureka Server 會(huì)保護(hù)服務(wù)注冊(cè)表中的信息,不再刪除服務(wù)。這是由于如果出現(xiàn)網(wǎng)絡(luò)分區(qū)導(dǎo)致其他微服務(wù)和該 Eureka Server 無法通信,Eureka Server 就會(huì)判定這些微服務(wù)失效,但很可能這些微服務(wù)都是健康的。
- Eureka Server 仍能接受新服務(wù)的注冊(cè)和查詢請(qǐng)求,但這些數(shù)據(jù)不會(huì)被同步到其他節(jié)點(diǎn)。
- 當(dāng)網(wǎng)絡(luò)恢復(fù)時(shí),這個(gè) Eureka Server 節(jié)點(diǎn)的數(shù)據(jù)會(huì)被同步到其他節(jié)點(diǎn)中。
優(yōu)點(diǎn):Eureka Server 可以很好的應(yīng)對(duì)因網(wǎng)絡(luò)故障導(dǎo)致部分節(jié)點(diǎn)失聯(lián)的情況,而不會(huì)像 ZK 那樣如果有一半不可用的情況會(huì)導(dǎo)致整個(gè)集群不可用。
②服務(wù)網(wǎng)關(guān)
微服務(wù)的拆分導(dǎo)致服務(wù)分散,如果一個(gè)大的業(yè)務(wù)要對(duì)外提供輸出,每個(gè)服務(wù)單獨(dú)對(duì)外提供調(diào)用對(duì)接入方不友好并且調(diào)用也會(huì)很復(fù)雜。
所以出現(xiàn)了網(wǎng)關(guān),網(wǎng)關(guān)主要實(shí)現(xiàn)請(qǐng)求的路由轉(zhuǎn)發(fā),負(fù)載均衡,統(tǒng)一校驗(yàn),請(qǐng)求過濾等功能。
目前社區(qū)主流的網(wǎng)關(guān)有三個(gè):
- Zuul
- Kong
- Spring Cloud GateWay
Zuul:是 Netflix 公司的開源項(xiàng)目,Spring Cloud 在 Netflix 項(xiàng)目中也已經(jīng)集成了 Zuul,依賴名叫:spring-cloud-starter-netflix-zuul。
Zuul 構(gòu)建于 Servlet 2.5,兼容 3.x,使用的是阻塞式的 API,不支持長連接,比如 Websockets。
我們現(xiàn)在說的 Zuul 指 Zuul 1.x,Netflix 最新的 Zuul 2.x一直跳票,所以 Spring Cloud 在 Zuul 2.x 沒有出的時(shí)候依靠社區(qū)的力量發(fā)展出了新的網(wǎng)關(guān)組件:Spring Cloud Gateway。
Zuul 的核心功能就是基于 Servlet 提供了一系列的過濾器:
- 身份認(rèn)證與安全:識(shí)別每個(gè)資源的驗(yàn)證要求,并拒絕那些與要求不符的請(qǐng)求。
- 審查與監(jiān)控:在邊緣位置追蹤有意義的數(shù)據(jù)和統(tǒng)計(jì)結(jié)果,從而帶來精確的生產(chǎn)視圖。
- 動(dòng)態(tài)路由:動(dòng)態(tài)地將請(qǐng)求路由到不同的后端集群。
- 壓力測(cè)試:逐漸增加指向集群的流量,以了解性能。
- 負(fù)載分配:為每一種負(fù)載類型分配對(duì)應(yīng)容量,并啟用超出限定值的請(qǐng)求。
- 靜態(tài)響應(yīng)處理:在邊緣位置直接建立部分響應(yīng),從而避免其轉(zhuǎn)發(fā)到內(nèi)部集群。
Spring Cloud Gateway:構(gòu)建于 Spring 5+,基于 Spring Boot 2.x 響應(yīng)式的、非阻塞式的 API。
同時(shí),它支持 Websockets,和 Spring 框架緊密集成,開發(fā)體驗(yàn)相對(duì)來說十分不錯(cuò)。
Spring Cloud Gateway 是基于 WebFlux 框架實(shí)現(xiàn)的,而 WebFlux 框架底層則使用了高性能的 Reactor 模式通信框架 Netty。
總體來說,Spring Cloud Gateway 與 Zuul 功能差別不大,最大的出入是在底層性能的提升上。
Zuul 本身是基于 Servlet 容器來實(shí)現(xiàn)的過濾,Servlet 采用的是單實(shí)例多線程的處理方案,Servlet 會(huì)為每一個(gè) Request 分配一個(gè)線程。
如果當(dāng)前線程比較耗時(shí)那么會(huì)一直等到線程處理完畢才會(huì)返回。所以說 Zuul 是基于 Servlet 之上的一個(gè)阻塞式處理模型。
同步阻塞模型對(duì)于網(wǎng)關(guān)這種比較在意響應(yīng)耗時(shí)和調(diào)用頻繁的組件來說,必然會(huì)引發(fā)一些性能問題,所以 Zuul 2 已經(jīng)做出了改良,從 Zuul 2 開始已經(jīng)使用 Netty。
但是不幸的是 Spring 官方已經(jīng)對(duì)它的更新頻率感到失望,所以縱然更新了也沒有被選用。
Spring Cloud Gateway 底層基于 Webflux。Webflux 模式替換了舊的 Servlet 線程模型。
用少量的線程處理 request 和 response io 操作,這些線程稱為 Loop 線程。
Webflux 的 Loop 線程,正好就是著名的 Reactor 模式 IO 處理模型的 Reactor 線程,如果使用的是高性能的通信框架 Netty,這就是 Netty 的 EventLoop 線程。
所以整體來看,Spring Cloud Gateway 的性能要比目前在用的 Zuul 高。但是 Webflux 的編程方式可能大家不是很能接收。
③服務(wù)降級(jí)
降級(jí)限流在微服務(wù)中屬于銀彈,一般不用,一旦用上那就是拯救宇宙般存在。
目前業(yè)界通用的降級(jí)限流工具主要有三款:
- Hystrix
- Sentinel
- Resilience4j
Hystrix 的關(guān)注點(diǎn)在于以隔離和熔斷為主的容錯(cuò)機(jī)制,超時(shí)或被熔斷的調(diào)用將會(huì)快速失敗,并可以提供 Fallback 機(jī)制。
Hystrix 是元老級(jí)別的存在,但是在 2018 年 11 月 Netflix 官方宣布停止更新(就是這么不靠譜,說跳票就跳票)。雖然停止更新,但是社區(qū)又推出了新的替代工具:Resilience4j。
Resilience4j 的模塊化做的比較好,將每個(gè)功能點(diǎn)(如熔斷、限速器、自動(dòng)重試)都拆成了單獨(dú)的模塊。
這樣整體結(jié)構(gòu)很清晰,用戶也只需要引入相應(yīng)功能的依賴即可;另外 Resilience4j 是針對(duì) Java 8 和函數(shù)式編程設(shè)計(jì)的,API 比較簡(jiǎn)潔優(yōu)雅。
同時(shí)與 Hystrix 相比,Resilience4j 增加了簡(jiǎn)單的限速器和自動(dòng)重試特性,使用場(chǎng)景更加豐富。
相比 Hystrix , Resilience4j 的優(yōu)勢(shì)在于:
- 針對(duì) Java 8 和函數(shù)式編程設(shè)計(jì),提供函數(shù)式和響應(yīng)式風(fēng)格的 API。
- 增加了 rate limiting 和 automatic retrying 兩個(gè)模塊。其中 rate limiting 引入了簡(jiǎn)單的速率控制實(shí)現(xiàn),補(bǔ)充了流量控制這一塊的功能。
- 而 automatic retrying 則是封裝了自動(dòng)重試的邏輯,簡(jiǎn)化了異?;謴?fù)的流程。
Resilience4j 屬于一個(gè)新興項(xiàng)目,社區(qū)也在蓬勃發(fā)展??偟膩碚f,Resilience4j 是比較輕量的庫,在較小較新的項(xiàng)目中使用還是比較方便的。
但是 Resilience4j 只包含限流降級(jí)的基本場(chǎng)景,對(duì)于非常復(fù)雜的企業(yè)級(jí)服務(wù)架構(gòu)可能無法很好地 cover 住。
同時(shí) Resilience4j 缺乏生產(chǎn)級(jí)別的配套設(shè)施(如提供規(guī)則管理和實(shí)時(shí)監(jiān)控能力的控制臺(tái))。
Sentinel 是一款面向分布式服務(wù)架構(gòu)的輕量級(jí)流量控制組件,主要以流量為切入點(diǎn),從流量控制、熔斷降級(jí)、系統(tǒng)自適應(yīng)保護(hù)等多個(gè)維度來幫助用戶保障服務(wù)的穩(wěn)定性。
Sentinel 的核心思想:根據(jù)對(duì)應(yīng)資源配置的規(guī)則來為資源執(zhí)行相應(yīng)的流控/降級(jí)/系統(tǒng)保護(hù)策略。在 Sentinel 中資源定義和規(guī)則配置是分離的。
用戶先通過 Sentinel API 給對(duì)應(yīng)的業(yè)務(wù)邏輯定義資源,然后可以在需要的時(shí)候動(dòng)態(tài)配置規(guī)則。
整體功能對(duì)比:
從上面的參照看,Sentinel 的功能相對(duì)要多一些,但是多并不意味著所有,合適的才是最好的,對(duì)于你用不到的功能,簡(jiǎn)單才是美麗。
④統(tǒng)一配置中心
統(tǒng)一配置中心概念的提出也是伴隨著微服務(wù)架構(gòu)出現(xiàn)才出現(xiàn),單體應(yīng)用的時(shí)候所有的配置都可以集成在服務(wù)之中,多應(yīng)用的時(shí)候如果每個(gè)應(yīng)用都持有一份配置可能會(huì)有相同配置冗余的情況。
如果一共有 2000 臺(tái)機(jī)器,其中一個(gè)配置發(fā)生更改,是否要登錄每一臺(tái)機(jī)器重新更改配置呢。
另外,更多的配置必然會(huì)帶來管理上的混亂,如果沒有集中管理的地方必然會(huì)越來越亂。
分布式配置管理的本質(zhì)基本上就是一種推送-訂閱模式的運(yùn)用。配置的應(yīng)用方是訂閱者,配置管理服務(wù)則是推送方。
其中,客戶端包括管理人員 Publish 數(shù)據(jù)到配置管理服務(wù),可以理解為添加/更新數(shù)據(jù);配置管理服務(wù) Notify 數(shù)據(jù)到訂閱者,可以理解為推送。
配置管理服務(wù)往往會(huì)封裝一個(gè)客戶端庫,應(yīng)用方則是基于該庫與配置管理服務(wù)進(jìn)行交互。
在實(shí)際實(shí)現(xiàn)時(shí),客戶端庫可能是主動(dòng)拉取(pull)數(shù)據(jù),但對(duì)于應(yīng)用方而言,一般是一種事件通知方式。
選型一個(gè)合格的配置中心,至少需要滿足如下四個(gè)核心需求:
- 非開發(fā)環(huán)境下應(yīng)用配置的保密性,避免將關(guān)鍵配置寫入源代碼。
- 不同部署環(huán)境下應(yīng)用配置的隔離性,比如非生產(chǎn)環(huán)境的配置不能用于生產(chǎn)環(huán)境。
- 同一部署環(huán)境下的服務(wù)器應(yīng)用配置的一致性,即所有服務(wù)器使用同一份配置。
- 分布式環(huán)境下應(yīng)用配置的可管理性,即提供遠(yuǎn)程管理配置的能力。
Diamond:最開始我接觸過的配置中心是淘寶的 Diamond,Diamond 中的數(shù)據(jù)是簡(jiǎn)單的 Key-Value 結(jié)構(gòu)。應(yīng)用方訂閱數(shù)據(jù)則是基于 Key 來訂閱,未訂閱的數(shù)據(jù)當(dāng)然不會(huì)被推送。
Diamond 是無單點(diǎn)架構(gòu),在做更新配置的時(shí)候只做三件事:
- 寫數(shù)據(jù)庫
- 寫本地
- 通知其他機(jī)器到數(shù)據(jù)庫拉更新
本地的設(shè)計(jì)就是為了緩存,減少對(duì)數(shù)據(jù)庫的壓力。作為一個(gè)配置中心,高可用是最主要的需求。
如何保持高可用,Diamond 持有多層的數(shù)據(jù)存儲(chǔ),數(shù)據(jù)被存儲(chǔ)在:數(shù)據(jù)庫,服務(wù)端磁盤,客戶端緩存目錄,以及可以手工干預(yù)的容災(zāi)目錄。
客戶端通過 API 獲取配置數(shù)據(jù),按照固定的順序去不同的數(shù)據(jù)源獲取數(shù)據(jù):容災(zāi)目錄,服務(wù)端磁盤,客戶端緩存。
Diamond 除了在容災(zāi)上做了很多方案,在數(shù)據(jù)讀取方面也有很多特點(diǎn)。客戶端采用推拉結(jié)合的策略在長連接和短連接之間取得一個(gè)平衡,讓服務(wù)端不用太關(guān)注連接的管理,又可以獲得長連接的及時(shí)性。
使用 Diamond 的流程如下:
發(fā)布配置
讀取配置
Diamond Server 是無中心節(jié)點(diǎn)的邏輯集群,這樣就能避免單點(diǎn)故障。
Diamond 的同質(zhì)節(jié)點(diǎn)之間會(huì)相互通信以保證數(shù)據(jù)的一致性,每個(gè)節(jié)點(diǎn)都有其他節(jié)點(diǎn)的地址信息,其中一個(gè)節(jié)點(diǎn)發(fā)生數(shù)據(jù)變更后會(huì)響應(yīng)的通知其他節(jié)點(diǎn),保證數(shù)據(jù)的一致性。
為了保證高可用,Client 還會(huì)在 App 端緩存一個(gè)本地文件,這樣即使 Server 不可用也能保證 App 可用。
Client 不斷長輪詢 Server,獲取最新的配置推送,盡量保證本地?cái)?shù)據(jù)的時(shí)效性。
Client 默認(rèn)啟動(dòng)周期任務(wù)對(duì) Server 進(jìn)行長輪詢感知 Server 的配置變化,Server 感知到配置變化就發(fā)送變更的數(shù)據(jù)編號(hào),客戶端通過數(shù)據(jù)編號(hào)再去拉取最新配置數(shù)據(jù);否則超時(shí)結(jié)束請(qǐng)求(默認(rèn) 10 秒)。
拉取到新配置后,Client 會(huì)通知監(jiān)聽者(Message Listener)做相應(yīng)處理,用戶可以通過 Diamond#addListener 監(jiān)聽。
但是 Diamond 一般用途是做 KV 存儲(chǔ),如果用來做配置中心,他提供的能力不是太符合。
可以看到早期的配置中心處理的東西還是比較簡(jiǎn)單,那個(gè)時(shí)候業(yè)務(wù)沒有那么復(fù)雜,讀取配置和更新配置沒有那么多花樣,持久化存儲(chǔ)和本地緩存,長連接更新就可以。但是現(xiàn)在的配置中心隨著技術(shù)的發(fā)展承擔(dān)的作用可能更多。
Spring Cloud Config:作為 Spring 官方提供的配置中心可能比較符合外國人的習(xí)慣。
Spring Cloud Config 將不同環(huán)境的所有配置存放在 Git 倉庫中,服務(wù)啟動(dòng)時(shí)通過接口拉取配置。
遵循 {ServiceID}-{profile}.properties 的結(jié)構(gòu),按照 Profile 拉取自己所需的配置。
當(dāng)開發(fā)者修改了配置項(xiàng)之后,需要結(jié)合 Spring Config Bus 將配置通知到對(duì)應(yīng)的服務(wù),實(shí)現(xiàn)配置的動(dòng)態(tài)更新。
可以看到,Spring Cloud Config 已經(jīng)具備了一個(gè)配置中心的雛形,可以滿足小型項(xiàng)目對(duì)配置的管理,但仍然有著很多局限性。
配置使用 Git 庫進(jìn)行管理,那么 Git 庫的權(quán)限如何來判斷?不同環(huán)境的安全性也得不到保障。
配置的添加和刪除,配置項(xiàng)的匯總,也只能通過 Git 命令來實(shí)現(xiàn),對(duì)運(yùn)維人員也并不友好。
Apollo(阿波羅):是攜程框架部門研發(fā)的開源配置管理中心,能夠集中化管理應(yīng)用不同環(huán)境、不同集群的配置,配置修改后能夠?qū)崟r(shí)推送到應(yīng)用端,并且具備規(guī)范的權(quán)限、流程治理等特性。
Apollo 支持四個(gè)維度管理 Key-Value 格式的配置:
- Application(應(yīng)用):實(shí)際使用配置的應(yīng)用,Apollo 客戶端在運(yùn)行時(shí)需要知道當(dāng)前應(yīng)用是誰,從而可以去獲取對(duì)應(yīng)的配置;每個(gè)應(yīng)用都需要有唯一的身份標(biāo)識(shí) – appId,應(yīng)用身份是跟著代碼走的,所以需要在代碼中配置。
- Environment(環(huán)境):配置對(duì)應(yīng)的環(huán)境,Apollo 客戶端在運(yùn)行時(shí)需要知道當(dāng)前應(yīng)用處于哪個(gè)環(huán)境,從而可以去獲取應(yīng)用的配置。
- Cluster(集群):一個(gè)應(yīng)用下不同實(shí)例的分組,比如典型的可以按照數(shù)據(jù)中心分,把上海機(jī)房的應(yīng)用實(shí)例分為一個(gè)集群,把北京機(jī)房的應(yīng)用實(shí)例分為另一個(gè)集群。
對(duì)不同的 Cluster,同一個(gè)配置可以有不一樣的值,如 ZooKeeper 地址。
- Namespace(命名空間):一個(gè)應(yīng)用下不同配置的分組,可以簡(jiǎn)單地把 Namespace 類比為文件,不同類型的配置存放在不同的文件中,如數(shù)據(jù)庫配置文件,RPC 配置文件,應(yīng)用自身的配置文件等。
應(yīng)用可以直接讀取到公共組件的配置 Namespace,如 DAL,RPC 等;應(yīng)用也可以通過繼承公共組件的配置 Namespace 來對(duì)公共組件的配置做調(diào)整,如 DAL 的初始數(shù)據(jù)庫連接數(shù)。
Apollo 配置中心包括:
- Config Service:提供配置獲取接口、配置推送接口,服務(wù)于 Apollo 客戶端。
- Admin Service:提供配置管理接口、配置修改發(fā)布接口,服務(wù)于管理界面 Portal。
- Portal:配置管理界面,通過 MetaServer 獲取 Admin Service 的服務(wù)列表,并使用客戶端軟負(fù)載 SLB 方式調(diào)用 Admin Service。
上圖簡(jiǎn)要描述了 Apollo 的總體設(shè)計(jì),我們可以從下往上看:
- Config Service 提供配置的讀取、推送等功能,服務(wù)對(duì)象是 Apollo 客戶端。
- Admin Service 提供配置的修改、發(fā)布等功能,服務(wù)對(duì)象是 Apollo Portal(管理界面)。
- Config Service 和 Admin Service 都是多實(shí)例、無狀態(tài)部署,所以需要將自己注冊(cè)到 Eureka 中并保持心跳。
- 在 Eureka 之上我們架了一層 Meta Server 用于封裝 Eureka 的服務(wù)發(fā)現(xiàn)接口。
- Client 通過域名訪問 Meta Server 獲取 Config Service 服務(wù)列表(IP+Port),而后直接通過 IP+Port 訪問服務(wù),同時(shí)在 Client 側(cè)會(huì)做 Load Balance、錯(cuò)誤重試。
- Portal 通過域名訪問 Meta Server 獲取 Admin Service 服務(wù)列表(IP+Port),而后直接通過 IP+Port 訪問服務(wù),同時(shí)在 Portal 側(cè)會(huì)做Load Balance、錯(cuò)誤重試。
- 為了簡(jiǎn)化部署,我們實(shí)際上會(huì)把 Config Service、Eureka 和 Meta Server 三個(gè)邏輯角色部署在同一個(gè) JVM 進(jìn)程中。
客戶端設(shè)計(jì):
上圖簡(jiǎn)要描述了 Apollo 客戶端的實(shí)現(xiàn)原理:
- 客戶端和服務(wù)端保持了一個(gè)長連接,從而能第一時(shí)間獲得配置更新的推送。
- 客戶端還會(huì)定時(shí)從 Apollo 配置中心服務(wù)端拉取應(yīng)用的最新配置。這是一個(gè) Fallback 機(jī)制,為了防止推送機(jī)制失效導(dǎo)致配置不更新。
客戶端定時(shí)拉取會(huì)上報(bào)本地版本,所以一般情況下,對(duì)于定時(shí)拉取的操作,服務(wù)端都會(huì)返回 304 - Not Modified。
定時(shí)頻率默認(rèn)為每 5 分鐘拉取一次,客戶端也可以通過在運(yùn)行時(shí)指定 System Property: apollo.refreshInterval 來覆蓋,單位為分鐘。
- 客戶端從 Apollo 配置中心服務(wù)端獲取到應(yīng)用的最新配置后,會(huì)保存在內(nèi)存中。
- 客戶端會(huì)把從服務(wù)端獲取到的配置在本地文件系統(tǒng)緩存一份,在遇到服務(wù)不可用,或網(wǎng)絡(luò)不通的時(shí)候,依然能從本地恢復(fù)配置。
- 應(yīng)用程序從 Apollo 客戶端獲取最新的配置、訂閱配置更新通知。
配置更新:前面提到了 Apollo 客戶端和服務(wù)端保持了一個(gè)長連接,從而能第一時(shí)間獲得配置更新的推送。
長連接實(shí)際上是通過 Http Long Polling 實(shí)現(xiàn)的,具體而言:
- 客戶端發(fā)起一個(gè) Http 請(qǐng)求到服務(wù)端。
- 服務(wù)端會(huì)保持住這個(gè)連接 60 秒,如果在 60 秒內(nèi)有客戶端關(guān)心的配置變化,被保持住的客戶端請(qǐng)求會(huì)立即返回,并告知客戶端有配置變化的 Namespace 信息,客戶端會(huì)據(jù)此拉取對(duì)應(yīng) Namespace 的最新配置。
如果在 60 秒內(nèi)沒有客戶端關(guān)心的配置變化,那么會(huì)返回 Http 狀態(tài)碼 304 給客戶端。
- 客戶端在收到服務(wù)端請(qǐng)求后會(huì)立即重新發(fā)起連接,回到第一步。
考慮到會(huì)有數(shù)萬客戶端向服務(wù)端發(fā)起長連,在服務(wù)端使用了 async servlet(Spring DeferredResult)來服務(wù) Http Long Polling 請(qǐng)求。
⑤調(diào)用鏈路分析
服務(wù)調(diào)用鏈路分析在微服務(wù)中是幕后至關(guān)重要的使者,試想幾百個(gè)服務(wù)摻雜在一起,你想捋出誰先調(diào)用了誰,誰被誰調(diào)用,如果沒有一個(gè)可監(jiān)控的路徑,光憑腦子跟蹤那得多累。
基于這種需求,各路大神們集中腦汁展開遐想弄出一套分布式鏈路追蹤神器來。
在介紹調(diào)用鏈監(jiān)控工具之前,我們首先需要知道在微服務(wù)架構(gòu)系統(tǒng)中經(jīng)常會(huì)遇到兩個(gè)問題:
- 跨服務(wù)調(diào)用發(fā)生異常,要求快速定位當(dāng)前這次調(diào)用出問題在哪一步。
- 跨服務(wù)的調(diào)用發(fā)生性能瓶頸,要求迅速定位出系統(tǒng)瓶頸應(yīng)該如何做。
打個(gè)比方說我們有兩個(gè)服務(wù):訂單中心,庫存中心。用戶下單,先去查詢庫存系統(tǒng),那么調(diào)用鏈路分析系統(tǒng)對(duì)于一個(gè)下單查詢服務(wù)應(yīng)該記錄什么呢?
我們?cè)斐鋈缦乱粡堈{(diào)用鏈路請(qǐng)求記錄表,表字段如下:
表字段說明:
- id:自增 id
- span_id:唯一 id
- pspan_id:父級(jí) span_id
- service_name:服務(wù)名稱
- api:api 路徑
- stage:階段/狀態(tài)
- timestamp:插入數(shù)據(jù)時(shí)的時(shí)間戳
上表中的 stage 中的狀態(tài)解釋為:
- CS(Client Sent 客戶端發(fā)送):客戶端發(fā)送一個(gè)請(qǐng)求,表示 Span 的開始。
- SR(Server Received 服務(wù)端接收):服務(wù)端接收請(qǐng)求并開始處理它。(SR - CS)等于網(wǎng)絡(luò)的延遲。
- SS(Server Sent 服務(wù)端發(fā)送):服務(wù)端處理請(qǐng)求完成,開始返回結(jié)束給服務(wù)端。(SR - SS)表示服務(wù)端處理請(qǐng)求的時(shí)間。
- CR(Client Received 客戶端接收):客戶端完成接受返回結(jié)果,此時(shí) Span 結(jié)束。(CR - CS)表示客戶端接收服務(wù)端數(shù)據(jù)的時(shí)間。
根據(jù)這個(gè)表我們就能很快的分析上面提到的兩個(gè)問題:
如果以上任何一步有問題,那么當(dāng)前調(diào)用就不是完整的,我們必然能追蹤出來。
通過每一步的調(diào)用時(shí)間進(jìn)行分析,我們也必然知道阻塞在哪一步,從而對(duì)調(diào)用慢的地方進(jìn)行優(yōu)化。
現(xiàn)有的分布式 Trace 基本都是采用了 Google 的 Dapper 標(biāo)準(zhǔn)。
Dapper 的思想很簡(jiǎn)單,就是在每一次調(diào)用棧中,使用同一個(gè) TraceId 將不同的 Server 聯(lián)系起來。
一次單獨(dú)的調(diào)用鏈也可以稱為一個(gè) Span,Dapper 記錄的是 Span 的名稱,以及每個(gè) Span 的 ID 和父 ID,以重建在一次追蹤過程中不同 Span 之間的關(guān)系。
對(duì)于一個(gè)特定的 Span,記錄從 Start 到 End,首先經(jīng)歷了客戶端發(fā)送數(shù)據(jù),然后 Server 接收數(shù)據(jù),然后 Server 執(zhí)行內(nèi)部邏輯,這中間可能去訪問另一個(gè)應(yīng)用。執(zhí)行完了 Server 將數(shù)據(jù)返回,然后客戶端接收到數(shù)據(jù)。
在整個(gè)過程中,TraceId 和 ParentId 的生成至關(guān)重要。首先解釋下 TraceId 和 ParentId。
TraceId 是標(biāo)識(shí)這個(gè)調(diào)用鏈的 Id,整個(gè)調(diào)用鏈,從瀏覽器開始放完,到 A 到 B 到 C,一直到調(diào)用結(jié)束,所有應(yīng)用在這次調(diào)用中擁有同一個(gè) TraceId,所以才能把這次調(diào)用鏈在一起。
既然知道了這次調(diào)用鏈的整個(gè) Id,那么每次查找問題的時(shí)候,只要知道某一個(gè)調(diào)用的 TraceId,就能把所有這個(gè) Id 的調(diào)用全部查找出來,能夠清楚的知道本地調(diào)用鏈經(jīng)過了哪些應(yīng)用,產(chǎn)生了哪些調(diào)用。但是還缺一點(diǎn),那就是鏈。
基于這種需求,目前各大廠商都做出了自己的分布式追蹤系統(tǒng):
- 目前國內(nèi)開源的有:阿里的鷹眼,美團(tuán)的 CAT,京東的 Hydra,還有廣為人知的個(gè)人開源 Apache 頂級(jí)項(xiàng)目 SkyWalking。
- 國外的有:Zipkin,Pinpoint。
Spring Cloud Sleuth+Zipkin:Spring Cloud Sleuth 實(shí)現(xiàn)了一種分布式的服務(wù)鏈路跟蹤解決方案,通過使用 Sleuth 可以讓我們快速定位某個(gè)服務(wù)的問題。
簡(jiǎn)單來說,Sleuth 相當(dāng)于調(diào)用鏈監(jiān)控工具的客戶端,集成在各個(gè)微服務(wù)上,負(fù)責(zé)產(chǎn)生調(diào)用鏈監(jiān)控?cái)?shù)據(jù)。
通過 Sleuth 產(chǎn)生的調(diào)用鏈監(jiān)控信息,讓我們可以得知微服務(wù)之間的調(diào)用鏈路,但是監(jiān)控信息只輸出到控制臺(tái)始終不太方便查看。
所以我們需要一個(gè)圖形化的工具,這時(shí)候就輪到 Zipkin 出場(chǎng)了。Zipkin 是 Twitter 開源的分布式跟蹤系統(tǒng),主要用來收集系統(tǒng)的時(shí)序數(shù)據(jù),從而追蹤系統(tǒng)的調(diào)用問題。
Spring Cloud Slueth 聚焦在鏈路追蹤和分析,將信息發(fā)送到 Zipkin,利用 Zipkin 的存儲(chǔ)來存儲(chǔ)信息。
當(dāng)然,Zipkin 也可以使用 ELK 來記錄日志和展示,再通過收集服務(wù)器性能的腳本把數(shù)據(jù)存儲(chǔ)到 ELK,則可以展示服務(wù)器狀況信息。
Pinpoint:數(shù)據(jù)分析非常完備。提供代碼級(jí)別的可見性以便輕松定位失敗點(diǎn)和瓶頸,對(duì)于執(zhí)行的 SQL 語句,都進(jìn)行了記錄。
還可以配置報(bào)警規(guī)則等,設(shè)置每個(gè)應(yīng)用對(duì)應(yīng)的負(fù)責(zé)人,根據(jù)配置的規(guī)則報(bào)警,支持的中間件和框架也比較完備。
Pinpoint 是一個(gè)完整的性能監(jiān)控解決方案:有從探針、收集器、存儲(chǔ)到 Web 界面等全套體系。
Pinpoint 提供有 Java Agent 探針,通過字節(jié)碼注入的方式實(shí)現(xiàn)調(diào)用攔截和數(shù)據(jù)收集,可以做到真正的代碼無侵入,只需要在啟動(dòng)服務(wù)器的時(shí)候添加一些參數(shù),就可以完成探針的部署。
對(duì)于這一點(diǎn),Zipkin 使用修改過的類庫和它自己的容器(Finagle)來提供分布式事務(wù)跟蹤的功能。
但是,它要求在需要時(shí)修改代碼。Pinpoint 是基于字節(jié)碼增強(qiáng)的方式,開發(fā)人員不需要修改代碼,并且可以收集到更多精確的數(shù)據(jù)因?yàn)橛凶止?jié)碼中的更多信息。
相對(duì)來說,Pinpoint 界面顯示的更加豐富,具體到調(diào)用的 DB 名,Zipkin 的拓?fù)渚窒抻诜?wù)于服務(wù)之間。
SkyWalking:和 Pinpoint 有一種既生瑜何生亮的感嘆。
SkyWalking 邏輯上分為四部分:
- 探針(SkyWalking-agent)
- 平臺(tái)后端(oap-server)
- 存儲(chǔ)(es)
- 用戶界面(apm-webapp)
探針基于不同的來源可能是不一樣的(原生代理,SDK 以及 Zipkin,Jaeger 和 OpenCensus ),但作用都是收集數(shù)據(jù)、將數(shù)據(jù)格式化為 SkyWalking 適用的格式。
平臺(tái)后端是一個(gè)支持集群模式運(yùn)行的后臺(tái)、用于數(shù)據(jù)聚合、數(shù)據(jù)分析以及驅(qū)動(dòng)數(shù)據(jù)流從探針到用戶界面的流程。
平臺(tái)后端還提供了各種可插拔的能力,如不同來源數(shù)據(jù)(如來自 Zipkin)格式化、不同存儲(chǔ)系統(tǒng)以及集群管理,你甚至還可以使用觀測(cè)分析語言來進(jìn)行自定義聚合分析。
存儲(chǔ)是開放式的,你可以選擇一個(gè)既有的存儲(chǔ)系統(tǒng),如 ElasticSearch,H2 或 MySQL 集群(Sharding-Sphere 管理)、也可以選擇自己實(shí)現(xiàn)一個(gè)存儲(chǔ)系統(tǒng)。
用戶界面對(duì)于 SkyWalking 的最終用戶來說非常炫酷且強(qiáng)大、同樣它也是可定制以匹配你已存在的后端的。
總結(jié)
以上是微服務(wù)全鏈路過程中需要經(jīng)歷的階段,當(dāng)然還不包括發(fā)布系統(tǒng)的搭建,底層數(shù)據(jù)治理能力。
所以提倡微服務(wù)可以,但是真的做起來不是所有公司都能做得到。小公司能做到服務(wù)拆分但是相應(yīng)的配套設(shè)施不一定能跟上,大公司有人有錢有時(shí)間,才能提供這些基礎(chǔ)設(shè)施。
微服務(wù)的路任重道遠(yuǎn),搭起來一套完整的設(shè)施并用于生產(chǎn)環(huán)境還是挺有挑戰(zhàn),新的一年希望我能夠在踐行微服務(wù)的路上走下去,只有走的完整才是微服務(wù),走的不完整對(duì)于開發(fā)人員來說,那就是過度開發(fā),就是災(zāi)難。