1 為什么需要服務(wù)發(fā)現(xiàn)
簡單來說,服務(wù)化的核心就是將傳統(tǒng)的一站式應(yīng)用根據(jù)業(yè)務(wù)拆分成一個(gè)一個(gè)的服務(wù),而微服務(wù)在這個(gè)基礎(chǔ)上要更徹底地去耦合(不再共享DB、KV,去掉重量級(jí)ESB),并且強(qiáng)調(diào)DevOps和快速演化。這就要求我們必須采用與一站式時(shí)代、泛SOA時(shí)代不同的技術(shù)棧,而Spring Cloud就是其中的佼佼者。
DevOps是英文Development和Operations的合體,他要求開發(fā)、測(cè)試、運(yùn)維進(jìn)行一體化的合作,進(jìn)行更小、更頻繁、更自動(dòng)化的應(yīng)用發(fā)布,以及圍繞應(yīng)用架構(gòu)來構(gòu)建基礎(chǔ)設(shè)施的架構(gòu)。這就要求應(yīng)用充分的內(nèi)聚,也方便運(yùn)維和管理。這個(gè)理念與微服務(wù)理念不謀而合。
接下來我們從服務(wù)化架構(gòu)演進(jìn)的角度來看看為什么Spring Cloud更適應(yīng)微服務(wù)架構(gòu)。
1.1 從使用nginx說起
最初的服務(wù)化解決方案是給提供相同服務(wù)提供一個(gè)統(tǒng)一的域名,然后服務(wù)調(diào)用者向這個(gè)域名發(fā)送HTTP請(qǐng)求,由Nginx負(fù)責(zé)請(qǐng)求的分發(fā)和跳轉(zhuǎn)。
這種架構(gòu)存在很多問題:
- Nginx作為中間層,在配置文件中耦合了服務(wù)調(diào)用的邏輯,這削弱了微服務(wù)的完整性,也使得Nginx在一定程度上變成了一個(gè)重量級(jí)的ESB。
- 服務(wù)的信息分散在各個(gè)系統(tǒng),無法統(tǒng)一管理和維護(hù)。每一次的服務(wù)調(diào)用都是一次嘗試,服務(wù)消費(fèi)者并不知道有哪些實(shí)例在給他們提供服務(wù)。這不符合DevOps的理念。
- 無法直觀的看到服務(wù)提供者和服務(wù)消費(fèi)者當(dāng)前的運(yùn)行狀況和通信頻率。這也不符合DevOps的理念。
- 消費(fèi)者的失敗重發(fā),負(fù)載均衡等都沒有統(tǒng)一策略,這加大了開發(fā)每個(gè)服務(wù)的難度,不利于快速演化。
為了解決上面的問題,我們需要一個(gè)現(xiàn)成的中心組件對(duì)服務(wù)進(jìn)行整合,將每個(gè)服務(wù)的信息匯總,包括服務(wù)的組件名稱、地址、數(shù)量等。服務(wù)的調(diào)用方在請(qǐng)求某項(xiàng)服務(wù)時(shí)首先通過中心組件獲取提供這項(xiàng)服務(wù)的實(shí)例的信息(IP、端口等),再通過默認(rèn)或自定義的策略選擇該服務(wù)的某一提供者直接進(jìn)行訪問。所以,我們引入了Dubbo。
1.2 基于Dubbo實(shí)現(xiàn)微服務(wù)
Dubbo是阿里開源的一個(gè)SOA服務(wù)治理解決方案,文檔豐富,在國內(nèi)的使用度非常高。
使用Dubbo構(gòu)建的微服務(wù),已經(jīng)可以比較好地解決上面提到的問題:
調(diào)用中間層變成了可選組件,消費(fèi)者可以直接訪問服務(wù)提供者。
服務(wù)信息被集中到Registry中,形成了服務(wù)治理的中心組件。
通過Monitor監(jiān)控系統(tǒng),可以直觀地展示服務(wù)調(diào)用的統(tǒng)計(jì)信息。
Consumer可以進(jìn)行負(fù)載均衡、服務(wù)降級(jí)的選擇。
但是對(duì)于微服務(wù)架構(gòu)而言,Dubbo也并不是十全十美的:
Registry嚴(yán)重依賴第三方組件(zookeeper或者redis),當(dāng)這些組件出現(xiàn)問題時(shí),服務(wù)調(diào)用很快就會(huì)中斷。
DUBBO只支持RPC調(diào)用。使得服務(wù)提供方與調(diào)用方在代碼上產(chǎn)生了強(qiáng)依賴,服務(wù)提供者需要不斷將包含公共代碼的jar包打包出來供消費(fèi)者使用。一旦打包出現(xiàn)問題,就會(huì)導(dǎo)致服務(wù)調(diào)用出錯(cuò)。
最為重要的是,DUBBO現(xiàn)在已經(jīng)停止維護(hù)了,對(duì)于技術(shù)發(fā)展的新需求,需要由開發(fā)者自行拓展升級(jí)。這對(duì)于很多想要采用微服務(wù)架構(gòu)的中小軟件組織,顯然是不太合適的。
目前Github社區(qū)上有一個(gè)DUBBO的升級(jí)版,叫DUBBOX,提供了更高效的RPC序列化方式和REST調(diào)用方式。但是該項(xiàng)目也基本停止維護(hù)了。
1.3 新的選擇——Spring Cloud
作為新一代的服務(wù)框架,Spring Cloud提出的口號(hào)是開發(fā)“面向云環(huán)境的應(yīng)用程序”,它為微服務(wù)架構(gòu)提供了更加全面的技術(shù)支持。
結(jié)合我們一開始提到的微服務(wù)的訴求,我們把Spring Cloud與DUBBO進(jìn)行一番對(duì)比:
微服務(wù)需要的功能 | Dubbo | Spring Cloud |
---|---|---|
服務(wù)注冊(cè)和發(fā)現(xiàn) | Zookeeper | Eureka |
服務(wù)調(diào)用方式 | RPC | RESTful API |
斷路器 | 有 | 有 |
負(fù)載均衡 | 有 | 有 |
服務(wù)路由和過濾 | 有 | 有 |
分布式配置 | 無 | 有 |
分布式鎖 | 無 | 計(jì)劃開發(fā) |
集群選主 | 無 | 有 |
分布式消息 | 無 | 有 |
Spring Cloud拋棄了Dubbo的RPC通信,采用的是基于HTTP的REST方式。嚴(yán)格來說,這兩種方式各有優(yōu)劣。雖然從一定程度上來說,后者犧牲了服務(wù)調(diào)用的性能,但也避免了上面提到的原生RPC帶來的問題。而且REST相比RPC更為靈活,服務(wù)提供方和調(diào)用方的依賴只依靠一紙契約,不存在代碼級(jí)別的強(qiáng)依賴,這在強(qiáng)調(diào)快速演化的微服務(wù)環(huán)境下,顯得更加合適。
很明顯,Spring Cloud的功能比DUBBO更加強(qiáng)大,涵蓋面更廣,而且作為Spring的拳頭項(xiàng)目,它也能夠與Spring Framework、Spring Boot、Spring Data、Spring Batch等其他Spring項(xiàng)目完美融合,這些對(duì)于微服務(wù)而言是至關(guān)重要的。前面提到,微服務(wù)背后一個(gè)重要的理念就是持續(xù)集成、快速交付,而在服務(wù)內(nèi)部使用一個(gè)統(tǒng)一的技術(shù)框架,顯然比把分散的技術(shù)組合到一起更有效率。更重要的是,相比于Dubbo,它是一個(gè)正在持續(xù)維護(hù)的、社區(qū)更加火熱的開源項(xiàng)目,這就保證使用它構(gòu)建的系統(tǒng),可以持續(xù)地得到開源力量的支持。
2. Spring Cloud Netflix 組件
Netflix和Spring Cloud是什么關(guān)系呢?Netflix是一家成功實(shí)踐微服務(wù)架構(gòu)的互聯(lián)網(wǎng)公司,幾年前,Netflix就把它的幾乎整個(gè)微服務(wù)框架棧開源貢獻(xiàn)給了社區(qū)。Spring背后的Pivotal在2015年推出的Spring Cloud開源產(chǎn)品,主要對(duì)Netflix開源組件的進(jìn)一步封裝,方便Spring開發(fā)人員構(gòu)建微服務(wù)基礎(chǔ)框架。
對(duì)于微服務(wù)的治理而言,核心就是服務(wù)的注冊(cè)和發(fā)現(xiàn)。所以選擇哪個(gè)組件,很大程度上要看它對(duì)于服務(wù)注冊(cè)與發(fā)現(xiàn)的解決方案。在這個(gè)領(lǐng)域,開源架構(gòu)很多,最常見的是Zookeeper,但這并不是一個(gè)最佳選擇。
在分布式系統(tǒng)領(lǐng)域有個(gè)著名的CAP定理:C——數(shù)據(jù)一致性,A——服務(wù)可用性,P——服務(wù)對(duì)網(wǎng)絡(luò)分區(qū)故障的容錯(cuò)性。這三個(gè)特性在任何分布式系統(tǒng)中不能同時(shí)滿足,最多同時(shí)滿足兩個(gè)。
Zookeeper是著名Hadoop的一個(gè)子項(xiàng)目,很多場景下Zookeeper也作為Service發(fā)現(xiàn)服務(wù)解決方案。Zookeeper保證的是CP,即任何時(shí)刻對(duì)
Zookeeper的訪問請(qǐng)求能得到一致的數(shù)據(jù)結(jié)果,同時(shí)系統(tǒng)對(duì)網(wǎng)絡(luò)分割具備容錯(cuò)性,但是它不能保證每次服務(wù)請(qǐng)求的可用性。從實(shí)際情況來分析,在使用Zookeeper獲取服務(wù)列表時(shí),如果zookeeper正在選主,或者Zookeeper集群中半數(shù)以上機(jī)器不可用,那么將就無法獲得數(shù)據(jù)了。所以說,Zookeeper不能保證服務(wù)可用性。
誠然,對(duì)于大多數(shù)分布式環(huán)境,尤其是涉及到數(shù)據(jù)存儲(chǔ)的場景,數(shù)據(jù)一致性應(yīng)該是首先被保證的,這也是zookeeper設(shè)計(jì)成CP的原因。但是對(duì)于服務(wù)發(fā)現(xiàn)場景來說,情況就不太一樣了:針對(duì)同一個(gè)服務(wù),即使注冊(cè)中心的不同節(jié)點(diǎn)保存的服務(wù)提供者信息不盡相同,也并不會(huì)造成災(zāi)難性的后果。因?yàn)閷?duì)于服務(wù)消費(fèi)者來說,能消費(fèi)才是最重要的——拿到可能不正確的服務(wù)實(shí)例信息后嘗試消費(fèi)一下,也好過因?yàn)闊o法獲取實(shí)例信息而不去消費(fèi)。所以,對(duì)于服務(wù)發(fā)現(xiàn)而言,可用性比數(shù)據(jù)一致性更加重要——AP勝過CP。而Spring Cloud Netflix在設(shè)計(jì)Eureka時(shí)遵守的就是AP原則。
Eureka本身是Netflix開源的一款提供服務(wù)注冊(cè)和發(fā)現(xiàn)的產(chǎn)品,并且提供了相應(yīng)的Java封裝。在它的實(shí)現(xiàn)中,節(jié)點(diǎn)之間是相互平等的,部分注冊(cè)中心的節(jié)點(diǎn)掛掉也不會(huì)對(duì)集群造成影響,即使集群只剩一個(gè)節(jié)點(diǎn)存活,也可以正常提供發(fā)現(xiàn)服務(wù)。哪怕是所有的服務(wù)注冊(cè)節(jié)點(diǎn)都掛了,Eureka Clients上也會(huì)緩存服務(wù)調(diào)用的信息。這就保證了我們微服務(wù)之間的互相調(diào)用是足夠健壯的。
除此之外,Spring Cloud Netflix背后強(qiáng)大的開源力量,也促使我們選擇了Spring Cloud Netflix:
- 前文提到過,Spring Cloud的社區(qū)十分活躍,其在業(yè)界的應(yīng)用也十分廣泛(尤其是國外),而且整個(gè)框架也經(jīng)受住了Netflix嚴(yán)酷生產(chǎn)環(huán)境的考驗(yàn)。
- 除了服務(wù)注冊(cè)和發(fā)現(xiàn),Spring Cloud Netflix的其他功能也十分強(qiáng)大,包括Ribbon,hystrix,F(xiàn)eign,Zuul等組件,結(jié)合到一起,讓服務(wù)的調(diào)用、路由也變得異常容易。
- Spring Cloud Netflix作為Spring的重量級(jí)整合框架,使用它也意味著我們能從Spring獲取到巨大的便利。Spring Cloud的其他子項(xiàng)目,比如Spring Cloud Stream、Spring Cloud Config等等,都為微服務(wù)的各種需求提供了一站式的解決方案。
3. Spring Cloud 服務(wù)發(fā)現(xiàn)
Spring Cloud Netflix的核心是用于服務(wù)注冊(cè)與發(fā)現(xiàn)的Eureka,接下來我們將以Eureka為線索,介紹Eureka、Ribbon、Hystrix、Feign這些Spring Cloud Netflix主要組件。
3.1 服務(wù)注冊(cè)與發(fā)現(xiàn)——Eureka
Eureka這個(gè)詞來源于古希臘語,意為“我找到了!我發(fā)現(xiàn)了!”,據(jù)傳,阿基米德在洗澡時(shí)發(fā)現(xiàn)浮力原理,高興得來不及穿上褲子,跑到街上大喊:“Eureka(我找到了)!"。
Eureka由多個(gè)instance(服務(wù)實(shí)例)組成,這些服務(wù)實(shí)例可以分為兩種:Eureka Server和Eureka Client。為了便于理解,我們將Eureka client再分為Service Provider和Service Consumer。如下圖所示:
- Eureka Server:服務(wù)的注冊(cè)中心,負(fù)責(zé)維護(hù)注冊(cè)的服務(wù)列表。
- Service Provider:服務(wù)提供方,作為一個(gè)Eureka Client,向Eureka Server做服務(wù)注冊(cè)、續(xù)約和下線等操作,注冊(cè)的主要數(shù)據(jù)包括服務(wù)名、機(jī)器ip、端口號(hào)、域名等等。
- Service Consumer:服務(wù)消費(fèi)方,作為一個(gè)Eureka Client,向Eureka Server獲取Service Provider的注冊(cè)信息,并通過遠(yuǎn)程調(diào)用與Service Provider進(jìn)行通信。
Service Provider和Service Consumer不是嚴(yán)格的概念,Service Consumer也可以隨時(shí)向Eureka Server注冊(cè),來讓自己變成一個(gè)Service Provider。
Spring Cloud針對(duì)服務(wù)注冊(cè)與發(fā)現(xiàn),進(jìn)行了一層抽象,并提供了三種實(shí)現(xiàn):Eureka、Consul、Zookeeper。目前支持得最好的就是Eureka,其次是Consul,最后是Zookeeper。
3.1.1 Eureka Server
Eureka Server作為一個(gè)獨(dú)立的部署單元,以REST API的形式為服務(wù)實(shí)例提供了注冊(cè)、管理和查詢等操作。同時(shí),Eureka Server也為我們提供了可視化的監(jiān)控頁面,可以直觀地看到各個(gè)Eureka Server當(dāng)前的運(yùn)行狀態(tài)和所有已注冊(cè)服務(wù)的情況。
3.1.1.1 Eureka Server的高可用集群
Eureka Server可以運(yùn)行多個(gè)實(shí)例來構(gòu)建集群,解決單點(diǎn)問題,但不同于ZooKeeper的選舉leader的過程,Eureka Server采用的是Peer to Peer對(duì)等通信。這是一種去中心化的架構(gòu),無master/slave區(qū)分,每一個(gè)Peer都是對(duì)等的。在這種架構(gòu)中,節(jié)點(diǎn)通過彼此互相注冊(cè)來提高可用性,每個(gè)節(jié)點(diǎn)需要添加一個(gè)或多個(gè)有效的serviceUrl指向其他節(jié)點(diǎn)。每個(gè)節(jié)點(diǎn)都可被視為其他節(jié)點(diǎn)的副本。
如果某臺(tái)Eureka Server宕機(jī),Eureka Client的請(qǐng)求會(huì)自動(dòng)切換到新的Eureka Server節(jié)點(diǎn),當(dāng)宕機(jī)的服務(wù)器重新恢復(fù)后,Eureka會(huì)再次將其納入到服務(wù)器集群管理之中。當(dāng)節(jié)點(diǎn)開始接受客戶端請(qǐng)求時(shí),所有的操作都會(huì)進(jìn)行replicateToPeer(節(jié)點(diǎn)間復(fù)制)操作,將請(qǐng)求復(fù)制到其他Eureka Server當(dāng)前所知的所有節(jié)點(diǎn)中。
一個(gè)新的Eureka Server節(jié)點(diǎn)啟動(dòng)后,會(huì)首先嘗試從鄰近節(jié)點(diǎn)獲取所有實(shí)例注冊(cè)表信息,完成初始化。Eureka Server通過getEurekaServiceUrls()方法獲取所有的節(jié)點(diǎn),并且會(huì)通過心跳續(xù)約的方式定期更新。默認(rèn)配置下,如果Eureka Server在一定時(shí)間內(nèi)沒有接收到某個(gè)服務(wù)實(shí)例的心跳,Eureka Server將會(huì)注銷該實(shí)例(默認(rèn)為90秒,通過eureka.instance.lease-expiration-duration-in-seconds配置)。當(dāng)Eureka Server節(jié)點(diǎn)在短時(shí)間內(nèi)丟失過多的心跳時(shí)(比如發(fā)生了網(wǎng)絡(luò)分區(qū)故障),那么這個(gè)節(jié)點(diǎn)就會(huì)進(jìn)入自我保護(hù)模式。下圖為Eureka官網(wǎng)的架構(gòu)圖
什么是自我保護(hù)模式?默認(rèn)配置下,如果Eureka Server每分鐘收到心跳續(xù)約的數(shù)量低于一個(gè)閾值(instance的數(shù)量*(60/每個(gè)instance的心跳間隔秒數(shù))*自我保護(hù)系數(shù)),就會(huì)觸發(fā)自我保護(hù)。在自我保護(hù)模式中,Eureka Server會(huì)保護(hù)服務(wù)注冊(cè)表中的信息,不再注銷任何服務(wù)實(shí)例。當(dāng)它收到的心跳數(shù)重新恢復(fù)到閾值以上時(shí),該Eureka Server節(jié)點(diǎn)就會(huì)自動(dòng)退出自我保護(hù)模式。它的設(shè)計(jì)哲學(xué)前面提到過,那就是寧可保留錯(cuò)誤的服務(wù)注冊(cè)信息,也不盲目注銷任何可能健康的服務(wù)實(shí)例。該模式可以通過eureka.server.enable-self-preservation = false來禁用,同時(shí)eureka.instance.lease-renewal-interval-in-seconds可以用來更改心跳間隔,eureka.server.renewal-percent-threshold可以用來修改自我保護(hù)系數(shù)(默認(rèn)0.85)。
3.1.1.2 Eureka Server的Region、Zone
Eureka的官方文檔對(duì)Regin、Zone幾乎沒有提及,由于概念抽象,新手很難理解。因此,我們先來了解一下Region、Zone、Eureka集群三者的關(guān)系,如下圖所示:
region和zone(或者Availability Zone)均是AWS的概念。在非AWS環(huán)境下,我們可以先簡單地將region理解為Eureka集群,zone理解成機(jī)房。上圖就可以理解為一個(gè)Eureka集群被部署在了zone1機(jī)房和zone2機(jī)房中。
3.1.2 Service Provider
3.1.2.1 服務(wù)注冊(cè)
Service Provider本質(zhì)上是一個(gè)Eureka Client。它啟動(dòng)時(shí),會(huì)調(diào)用服務(wù)注冊(cè)方法,向Eureka Server注冊(cè)自己的信息。Eureka Server會(huì)維護(hù)一個(gè)已注冊(cè)服務(wù)的列表,這個(gè)列表為一個(gè)嵌套的hash map:
- 第一層,application name和對(duì)應(yīng)的服務(wù)實(shí)例。
- 第二層,服務(wù)實(shí)例及其對(duì)應(yīng)的注冊(cè)信息,包括IP,端口號(hào)等。
當(dāng)實(shí)例狀態(tài)發(fā)生變化時(shí)(如自身檢測(cè)認(rèn)為Down的時(shí)候),也會(huì)向Eureka Server更新自己的服務(wù)狀態(tài),同時(shí)用replicateToPeers()向其它Eureka Server節(jié)點(diǎn)做狀態(tài)同步。
3.1.2.2 續(xù)約與剔除
前面提到過,服務(wù)實(shí)例啟動(dòng)后,會(huì)周期性地向Eureka Server發(fā)送心跳以續(xù)約自己的信息,避免自己的注冊(cè)信息被剔除。續(xù)約的方式與服務(wù)注冊(cè)基本一致:首先更新自身狀態(tài),再同步到其它Peer。
如果Eureka Server在一段時(shí)間內(nèi)沒有接收到某個(gè)微服務(wù)節(jié)點(diǎn)的心跳,Eureka Server將會(huì)注銷該微服務(wù)節(jié)點(diǎn)(自我保護(hù)模式除外)。
3.1.3 Service Consumer
Service Consumer本質(zhì)上也是一個(gè)Eureka Client(它也會(huì)向Eureka Server注冊(cè),只是這個(gè)注冊(cè)信息無關(guān)緊要罷了)。它啟動(dòng)后,會(huì)從Eureka Server上獲取所有實(shí)例的注冊(cè)信息,包括IP地址、端口等,并緩存到本地。這些信息默認(rèn)每30秒更新一次。前文提到過,如果與Eureka Server通信中斷,Service Consumer仍然可以通過本地緩存與Service Provider通信。
實(shí)際開發(fā)Eureka的過程中,有時(shí)會(huì)遇見Service Consumer獲取到Server Provider的信息有延遲,在Eureka Wiki中有這么一段話:
All operations from Eureka client may take some time to reflect in the Eureka servers and subsequently in other Eureka clients. This is because of the caching of the payload on the eureka server which is refreshed periodically to reflect new information. Eureka clients also fetch deltas periodically. Hence, it may take up to 2 mins for changes to propagate to all Eureka clients.
最后一句話提到,服務(wù)端的更改可能需要2分鐘才能傳播到所有客戶端,至于原因并沒有介紹。這是因?yàn)镋ureka有三處緩存和一處延遲造成的。
- Eureka Server對(duì)注冊(cè)列表進(jìn)行緩存,默認(rèn)時(shí)間為30s。
- Eureka Client對(duì)獲取到的注冊(cè)信息進(jìn)行緩存,默認(rèn)時(shí)間為30s。
- Ribbon會(huì)從上面提到的Eureka Client獲取服務(wù)列表,將負(fù)載均衡后的結(jié)果緩存30s。
- 如果不是在Spring Cloud環(huán)境下使用這些組件(Eureka, Ribbon),服務(wù)啟動(dòng)后并不會(huì)馬上向Eureka注冊(cè),而是需要等到第一次發(fā)送心跳請(qǐng)求時(shí)才會(huì)注冊(cè)。心跳請(qǐng)求的發(fā)送間隔默認(rèn)是30s。Spring Cloud對(duì)此做了修改,服務(wù)啟動(dòng)后會(huì)馬上注冊(cè)。
基于Service Consumer獲取到的服務(wù)實(shí)例信息,我們就可以進(jìn)行服務(wù)調(diào)用了。而Spring Cloud也為Service Consumer提供了豐富的服務(wù)調(diào)用工具:
- Ribbon,實(shí)現(xiàn)客戶端的負(fù)載均衡。
- Hystrix,斷路器。
- Feign,RESTful Web Service客戶端,整合了Ribbon和Hystrix。
接下來我們就一一介紹。
3.2 服務(wù)調(diào)用端負(fù)載均衡——Ribbon
Ribbon是Netflix發(fā)布的開源項(xiàng)目,主要功能是為REST客戶端實(shí)現(xiàn)負(fù)載均衡。它主要包括六個(gè)組件:
- ServerList,負(fù)載均衡使用的服務(wù)器列表。這個(gè)列表會(huì)緩存在負(fù)載均衡器中,并定期更新。當(dāng)Ribbon與Eureka結(jié)合使用時(shí),ServerList的實(shí)現(xiàn)類就是DiscoveryEnabledNIWSServerList,它會(huì)保存Eureka Server中注冊(cè)的服務(wù)實(shí)例表。
- ServerListFilter,服務(wù)器列表過濾器。這是一個(gè)接口,主要用于對(duì)Service Consumer獲取到的服務(wù)器列表進(jìn)行預(yù)過濾,過濾的結(jié)果也是ServerList。Ribbon提供了多種過濾器的實(shí)現(xiàn)。
- IPing,探測(cè)服務(wù)實(shí)例是否存活的策略。
- IRule,負(fù)載均衡策略,其實(shí)現(xiàn)類表述的策略包括:輪詢、隨機(jī)、根據(jù)響應(yīng)時(shí)間加權(quán)等。
我們也可以自己定義負(fù)載均衡策略,比如我們就利用自己實(shí)現(xiàn)的策略,實(shí)現(xiàn)了服務(wù)的版本控制和直連配置。實(shí)現(xiàn)好之后,將實(shí)現(xiàn)類重新注入到Ribbon中即可。
- ILoadBalancer,負(fù)載均衡器。這也是一個(gè)接口,Ribbon為其提供了多個(gè)實(shí)現(xiàn),比如ZoneAwareLoadBalancer。而上層代碼通過調(diào)用其API進(jìn)行服務(wù)調(diào)用的負(fù)載均衡選擇。一般ILoadBalancer的實(shí)現(xiàn)類中會(huì)引用一個(gè)IRule。
- RestClient,服務(wù)調(diào)用器。顧名思義,這就是負(fù)載均衡后,Ribbon向Service Provider發(fā)起REST請(qǐng)求的工具。
Ribbon工作時(shí)會(huì)做四件事情:
- 優(yōu)先選擇在同一個(gè)Zone且負(fù)載較少的Eureka Server;
- 定期從Eureka更新并過濾服務(wù)實(shí)例列表;
- 根據(jù)用戶指定的策略,在從Server取到的服務(wù)注冊(cè)列表中選擇一個(gè)實(shí)例的地址;
- 通過RestClient進(jìn)行服務(wù)調(diào)用。
3.3 服務(wù)調(diào)用端熔斷——Hystrix
Netflix創(chuàng)建了一個(gè)名為Hystrix的庫,實(shí)現(xiàn)了斷路器的模式?!皵嗦菲鳌北旧硎且环N開關(guān)裝置,當(dāng)某個(gè)服務(wù)單元發(fā)生故障之后,通過斷路器的故障監(jiān)控(類似熔斷保險(xiǎn)絲),向調(diào)用方返回一個(gè)符合預(yù)期的、可處理的備選響應(yīng)(FallBack),而不是長時(shí)間的等待或者拋出調(diào)用方無法處理的異常,這樣就保證了服務(wù)調(diào)用方的線程不會(huì)被長時(shí)間、不必要地占用,從而避免了故障在分布式系統(tǒng)中的蔓延,乃至雪崩。
當(dāng)然,在請(qǐng)求失敗頻率較低的情況下,Hystrix還是會(huì)直接把故障返回給客戶端。只有當(dāng)失敗次數(shù)達(dá)到閾值(默認(rèn)在20秒內(nèi)失敗5次)時(shí),斷路器打開并且不進(jìn)行后續(xù)通信,而是直接返回備選響應(yīng)。當(dāng)然,Hystrix的備選響應(yīng)也是可以由開發(fā)者定制的。
除了隔離依賴服務(wù)的調(diào)用以外,Hystrix還提供了準(zhǔn)實(shí)時(shí)的調(diào)用監(jiān)控(Hystrix Dashboard),Hystrix會(huì)持續(xù)地記錄所有通過Hystrix發(fā)起的請(qǐng)求的執(zhí)行信息,并以統(tǒng)計(jì)報(bào)表和圖形的形式展示給用戶,包括每秒執(zhí)行多少請(qǐng)求多少成功,多少失敗等。Netflix通過hystrix-metrics-event-stream項(xiàng)目實(shí)現(xiàn)了對(duì)以上指標(biāo)的監(jiān)控。Spring Cloud也提供了Hystrix Dashboard的整合,對(duì)監(jiān)控內(nèi)容轉(zhuǎn)化成可視化界面,Hystrix Dashboard Wiki上詳細(xì)說明了圖上每個(gè)指標(biāo)的含義。
3.4 服務(wù)調(diào)用端代碼抽象和封裝——Feign
Feign是一個(gè)聲明式的Web Service客戶端,它的目的就是讓W(xué)eb Service調(diào)用更加簡單。它整合了Ribbon和Hystrix,從而讓我們不再需要顯式地使用這兩個(gè)組件。Feign還提供了HTTP請(qǐng)求的模板,通過編寫簡單的接口和插入注解,我們就可以定義好HTTP請(qǐng)求的參數(shù)、格式、地址等信息。接下來,F(xiàn)eign會(huì)完全代理HTTP的請(qǐng)求,我們只需要像調(diào)用方法一樣調(diào)用它就可以完成服務(wù)請(qǐng)求。
Feign具有如下特性:
- 可插拔的注解支持,包括Feign注解和JAX-RS注解
- 支持可插拔的HTTP編碼器和解碼器
- 支持Hystrix和它的Fallback
- 支持Ribbon的負(fù)載均衡
- 支持HTTP請(qǐng)求和響應(yīng)的壓縮
以下是一個(gè)Feign的簡單示例:
@SpringBootApplication
@EnableDiscoveryClient //啟用Feign
@EnableFeignClients
public class Application
{
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);
}
}
@FeignClient(name = "elements", fallback = ElementsFallback.class) //指定feign調(diào)用的服務(wù)和Hystrix Fallback(name即eureka的application name)
public interface Elements
{
@RequestMapping(value = "/index")
String index();
}
//Hystrix Fallback
@Component
public class ElementsFallback implements Elements
{
@Override
public String index()
{
return "**************";
}
}
//測(cè)試類
@Component
public class TestController {
@Autowired
Elements elements;
@RequestMapping(value = "/testEureka", method = RequestMethod.GET)
public String testeureka()
{
return elements.index();
}
}
4. 參考文獻(xiàn)
http://cloud.spring.io/spring-cloud-static/Brixton.SR7
https://github.com/Netflix/eureka/wiki
http://itmuch.com/spring-cloud-sum-eureka