服務(wù)注冊(cè)與發(fā)現(xiàn)——Netflix Eureka

(git上的源碼:https://gitee.com/rain7564/spring_microservices_study/tree/master/second-discovery-euraka)

何為服務(wù)發(fā)現(xiàn)

在許多分布式系統(tǒng)架構(gòu)中,都需要去獲取機(jī)器的物理地址(微服務(wù)實(shí)例部署的服務(wù)器地址及端口)。這一認(rèn)知在分布式系統(tǒng)架構(gòu)開始的時(shí)候就已經(jīng)存在焙矛,而等到分布式計(jì)算出現(xiàn)的時(shí)候抡秆,被正式稱為服務(wù)發(fā)現(xiàn)(service discovery)喊巍。

服務(wù)發(fā)現(xiàn)可以做一些簡(jiǎn)單的事情膊畴,比如維護(hù)一個(gè)帶有所有遠(yuǎn)程服務(wù)地址的屬性文件澄者,或一個(gè)UDDI(Universal Description, Discovery and Integration)存儲(chǔ)庫(kù)抚笔。

服務(wù)發(fā)現(xiàn)對(duì)微服務(wù)扶认、分布式應(yīng)用/基于云的應(yīng)用至關(guān)重要。有兩個(gè)主要原因:

  1. 它為應(yīng)用程序開發(fā)團(tuán)隊(duì)提供了快速擴(kuò)展的能力殊橙,以及縮減在一個(gè)環(huán)境中運(yùn)行的服務(wù)實(shí)例數(shù)量辐宾。
    通過服務(wù)發(fā)現(xiàn)狱从,服務(wù)消費(fèi)者(service consumers)可以從服務(wù)提供者(service provider)的物理地址中抽象出來。因?yàn)橄M(fèi)者并不知道提供者的實(shí)例的實(shí)際物理地址(多個(gè)實(shí)例就有多個(gè)地址)叠纹,新的服務(wù)實(shí)例也可以添加到"可用服務(wù)池"季研,失效的服務(wù)也會(huì)被移除,即消費(fèi)者根本沒辦法具體知道會(huì)消費(fèi)服務(wù)提供者哪個(gè)的實(shí)例誉察,服務(wù)提供者實(shí)例的物理地址對(duì)消費(fèi)者來說是透明的与涡。
    以往,龐大的單體應(yīng)用要擴(kuò)大業(yè)務(wù)處理能力持偏,只能通過將應(yīng)用部署到更大更好的機(jī)器上(縱向擴(kuò)展)驼卖,這種辦法取得的效果與成本的比值越來越低,而且也無法無限擴(kuò)展鸿秆;而服務(wù)發(fā)現(xiàn)能讓開發(fā)團(tuán)隊(duì)從這一困境走出酌畜,因?yàn)榉?wù)發(fā)現(xiàn)使將服務(wù)部署到更多的廉價(jià)機(jī)器上(橫向擴(kuò)展)成為可能。
  2. 服務(wù)發(fā)現(xiàn)使整體應(yīng)用更有彈性卿叽。當(dāng)機(jī)器出現(xiàn)異常使正在運(yùn)行的服務(wù)實(shí)例處理能力變?nèi)?頻繁報(bào)錯(cuò))或不可用桥胞,服務(wù)發(fā)現(xiàn)會(huì)將該實(shí)例從可用服務(wù)列表中移除,外部或應(yīng)用內(nèi)的其他微服務(wù)不會(huì)再"消費(fèi)"這個(gè)服務(wù)實(shí)例。這樣,因意外導(dǎo)致部分服務(wù)實(shí)例不可用蠢壹,服務(wù)發(fā)現(xiàn)會(huì)將對(duì)該服務(wù)的"消費(fèi)"路由到其他可用的服務(wù)實(shí)例绣张,繞過了已衰掉的實(shí)例,該服務(wù)還是能正常被"消費(fèi)",從而將影響降到了最低。

服務(wù)發(fā)現(xiàn)的實(shí)現(xiàn)方案

我們已經(jīng)初步了解服務(wù)發(fā)現(xiàn)帶來的好處。那么有沒有好的方案來實(shí)現(xiàn)"服務(wù)發(fā)現(xiàn)"屁使?例如DNS或負(fù)載均衡器(此處指服務(wù)端負(fù)載均衡,還有客戶端負(fù)載均衡奔则。注意:后文分析DNS+負(fù)載均衡模式蛮寂,都是指服務(wù)端負(fù)載均衡)。
開發(fā)過程中易茬,經(jīng)常會(huì)有一種情況——從第三方獲取資源酬蹋,在獲取的時(shí)候就必須定位這些資源所在的物理地址。若不是基于云架構(gòu)的應(yīng)用抽莱,大都采用DNS和負(fù)載均衡器實(shí)現(xiàn)范抓。大體架構(gòu)如下圖所示:


image.png

應(yīng)用調(diào)用第三方服務(wù)過程中,通過DNS將域名解析得到一個(gè)商業(yè)負(fù)載均衡器的物理地址食铐,然后將請(qǐng)求轉(zhuǎn)發(fā)到給負(fù)載均衡器匕垫,負(fù)載均衡器維護(hù)了一張路由表,然后根據(jù)這張路由表將請(qǐng)求路由給正確的服務(wù)虐呻。
為了獲得高可用象泵,會(huì)有一個(gè)處于空閑狀態(tài)的次負(fù)載均衡器一直發(fā)送ping請(qǐng)求來確認(rèn)主負(fù)載均衡器是否正常運(yùn)行寞秃,若已經(jīng)衰掉,次負(fù)載均衡器會(huì)被激活然后接管主負(fù)載均衡器偶惠。
然而這種解決方案并不適合基于云的微服務(wù)應(yīng)用春寿,原因如下:

  • 單點(diǎn)故障:負(fù)載均衡會(huì)成為整體架構(gòu)的一個(gè)單點(diǎn)故障。首先忽孽,如果負(fù)載均衡器衰掉绑改,那么所有依賴負(fù)載均衡器的服務(wù)都會(huì)受到牽連。雖然主次負(fù)載均衡器能實(shí)現(xiàn)高可用扒腕,但當(dāng)請(qǐng)求劇增時(shí),會(huì)成為整體架構(gòu)的性能瓶頸萤悴,單位時(shí)間內(nèi)能處理的請(qǐng)求數(shù)有限瘾腰。
  • 限制水平擴(kuò)展能力:通過負(fù)載均衡器將服務(wù)集中到單個(gè)集群,限制了負(fù)載均衡架構(gòu)水平擴(kuò)展的能力覆履。大多數(shù)商業(yè)負(fù)載平衡器使用冗余的熱交換模型蹋盆,所以只有一臺(tái)服務(wù)器來處理負(fù)載,而故障轉(zhuǎn)移的次負(fù)載平衡器只有在停機(jī)的情況下才成為主負(fù)載均衡器。
  • 靜態(tài)托管:許多商業(yè)負(fù)載均衡器并不支持為服務(wù)快速注冊(cè)/注銷硝全。他們將路由規(guī)則存儲(chǔ)在一個(gè)集中式數(shù)據(jù)庫(kù)中栖雾,而且添加新路由規(guī)則通常需要通過供應(yīng)商提供的專有API。
  • 復(fù)雜:因?yàn)樨?fù)載均衡器只是服務(wù)的一個(gè)代理伟众,服務(wù)消費(fèi)者的請(qǐng)求必須通過它才能定位到服務(wù)提供者的物理地址析藕。整個(gè)應(yīng)用架構(gòu)因?yàn)槎嗔素?fù)載均衡這一轉(zhuǎn)換層變得更復(fù)雜,因?yàn)樗新酚梢?guī)則都是通過人工定義和發(fā)布的凳厢。

以上四個(gè)原因并不是想說明服務(wù)端負(fù)載均衡架構(gòu)不好账胧,而是為了說明不適合基于云的微服務(wù)應(yīng)用。因?yàn)榉?wù)端負(fù)載均衡能很好的作用于應(yīng)用的大小先紫、規(guī)模能通過集中式網(wǎng)絡(luò)架構(gòu)支撐的情況治泥。
然而,基于云的應(yīng)用中遮精,需要處理大量的事務(wù)和數(shù)據(jù)信息居夹,集中式網(wǎng)絡(luò)架構(gòu)并不能很好支撐這種情況,因?yàn)檫@種架構(gòu)擴(kuò)展能力弱本冲,擴(kuò)展成本效益低准脂。下面開始介紹如何實(shí)現(xiàn)一個(gè)健壯的擴(kuò)展性強(qiáng)的適用于云微服務(wù)應(yīng)用的服務(wù)發(fā)現(xiàn)。

適用于云的服務(wù)發(fā)現(xiàn)

基于云的微服務(wù)環(huán)境的服務(wù)發(fā)現(xiàn)解決方案檬洞,必須具備以下特征:

  • 高可用:服務(wù)發(fā)現(xiàn)需要具備支持"熱"集群環(huán)境的能力意狠。即服務(wù)的信息可以在服務(wù)發(fā)現(xiàn)集群的多個(gè)節(jié)點(diǎn)中共享。當(dāng)集群中的某個(gè)節(jié)點(diǎn)不可用疮胖,其它節(jié)點(diǎn)可以完全接管环戈。
  • 所有節(jié)點(diǎn)對(duì)等:集群中的所有節(jié)點(diǎn)共享所有注冊(cè)到服務(wù)發(fā)現(xiàn)的服務(wù)實(shí)例的狀態(tài)信息闷板。
  • 負(fù)載均衡:服務(wù)發(fā)現(xiàn)需要?jiǎng)討B(tài)地、均衡地將請(qǐng)求分配到所有注冊(cè)到服務(wù)發(fā)現(xiàn)的服務(wù)實(shí)例院塞。當(dāng)然遮晚,服務(wù)發(fā)現(xiàn)可以根據(jù)不同的分配策略進(jìn)行分配,比如輪詢拦止、隨機(jī)等县遣。服務(wù)發(fā)現(xiàn)取代了類似上文提及的靜態(tài)的、需要人工配置的負(fù)載均衡器汹族。
  • 彈性:服務(wù)發(fā)現(xiàn)的客戶端(服務(wù)發(fā)現(xiàn)分服務(wù)端和客戶端萧求,后文會(huì)詳細(xì)說明)應(yīng)該在本地緩存服務(wù)實(shí)例信息。這樣做有很多好處顶瞒,比如:不用每次遠(yuǎn)程請(qǐng)求都去服務(wù)發(fā)現(xiàn)的服務(wù)端獲取目標(biāo)服務(wù)的信息夸政、當(dāng)服務(wù)發(fā)現(xiàn)服務(wù)端衰掉了,短時(shí)間內(nèi)還可以依賴本地緩存繼續(xù)正常運(yùn)行等榴徐。
  • 容錯(cuò):服務(wù)發(fā)現(xiàn)需要及時(shí)發(fā)現(xiàn)那些不可用的服務(wù)實(shí)例并將其從可用服務(wù)列表中移除守问。即可以自動(dòng)發(fā)現(xiàn)并處理而不用認(rèn)為干預(yù)。

至此坑资,應(yīng)該對(duì)適用于云的服務(wù)發(fā)現(xiàn)有一定的了解耗帕,接下來將會(huì)對(duì)如下幾個(gè)方面進(jìn)行分析:

  • 基于云的服務(wù)發(fā)現(xiàn)代理是如何運(yùn)作
  • 當(dāng)服務(wù)發(fā)現(xiàn)代理不可用時(shí),客戶端負(fù)載均衡是如何能繼續(xù)運(yùn)作
  • 如何使用Spring Cloud和Netflix Eureka實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)代理
服務(wù)發(fā)現(xiàn)架構(gòu)

在討論服務(wù)發(fā)現(xiàn)架構(gòu)之前袱贮,需要了解四個(gè)概念仿便,這四個(gè)概念在所有服務(wù)發(fā)現(xiàn)架構(gòu)都會(huì)涉及到的,如下:

  • 服務(wù)注冊(cè):服務(wù)是怎樣把自己注冊(cè)到服務(wù)發(fā)現(xiàn)代理攒巍?
  • 客戶端獲取服務(wù)地址:客戶端是怎樣從服務(wù)發(fā)現(xiàn)代理獲取其他同樣注冊(cè)到服務(wù)發(fā)現(xiàn)代理的服務(wù)的信息探越?
  • 信息共享:服務(wù)發(fā)現(xiàn)代理集群之間是如何共享服務(wù)注冊(cè)信息?
  • 健康監(jiān)控:服務(wù)客戶端是怎樣通知服務(wù)發(fā)現(xiàn)代理自己的狀態(tài)信息窑业?

下圖中序號(hào)1-4分別對(duì)應(yīng)上面所說的四個(gè)概念钦幔。

image.png

當(dāng)服務(wù)啟動(dòng)后,它們會(huì)將自己注冊(cè)到一個(gè)或多個(gè)服務(wù)發(fā)現(xiàn)代理常柄,即將物理地址鲤氢、端口等信息發(fā)送。雖然某個(gè)服務(wù)的所有實(shí)例有不同的IP和端口號(hào)西潘,但它們都會(huì)注冊(cè)在同一個(gè)服務(wù)ID(上文已經(jīng)提到的邏輯名稱卷玉,application name)下,這個(gè)服務(wù)ID只是被服務(wù)發(fā)現(xiàn)代理用來給不同服務(wù)分組而已喷市,即相同服務(wù)的不同實(shí)例相种,它們的服務(wù)ID是相同的。

一般情況下品姓,一個(gè)服務(wù)只需要注冊(cè)到一個(gè)服務(wù)發(fā)現(xiàn)代理寝并。因?yàn)榇蠖喾?wù)發(fā)現(xiàn)實(shí)現(xiàn)都采用數(shù)據(jù)共享模型箫措,即一個(gè)服務(wù)發(fā)現(xiàn)集群的所有節(jié)點(diǎn)會(huì)共享它們維護(hù)的數(shù)據(jù)。

當(dāng)一個(gè)服務(wù)實(shí)例注冊(cè)到服務(wù)發(fā)現(xiàn)代理衬潦,就意味著它隨時(shí)可以被其他應(yīng)用或服務(wù)調(diào)用斤蔓,注冊(cè)到服務(wù)發(fā)現(xiàn)的所有服務(wù)之間的調(diào)用是相互的《频海客戶端服務(wù)在"發(fā)現(xiàn)"服務(wù)上有多種模式弦牡。比如:客戶端服務(wù)的每次遠(yuǎn)程調(diào)用,都依靠服務(wù)發(fā)現(xiàn)引擎去解析得到目標(biāo)服務(wù)的地址漂羊。這種模式(服務(wù)端服務(wù)發(fā)現(xiàn)驾锰,如DNS+負(fù)載均衡器)是非常脆弱的,因?yàn)榭蛻舳朔?wù)的每一個(gè)遠(yuǎn)程調(diào)用完全依賴于服務(wù)發(fā)現(xiàn)引擎走越,所以需要有一個(gè)更好椭豫、更健壯的模式——客戶端負(fù)載均衡。下圖說明了何為客戶端負(fù)載均衡:


image.png

另外买喧,當(dāng)服務(wù)調(diào)用失敗捻悯,客戶端會(huì)讓本地緩存失效匆赃,然后重新從服務(wù)發(fā)現(xiàn)代理獲取新的服務(wù)注冊(cè)信息淤毛。

使用Netflix Eureka實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)

接下來,我們會(huì)創(chuàng)建一個(gè)服務(wù)發(fā)現(xiàn)代理并向該代理注冊(cè)兩個(gè)服務(wù)算柳。然后其中的一個(gè)服務(wù)使用從服務(wù)發(fā)現(xiàn)代理獲取的服務(wù)信息去調(diào)用另一個(gè)服務(wù)低淡。Netflix Eureka的服務(wù)發(fā)現(xiàn)引擎可以實(shí)現(xiàn)服務(wù)發(fā)現(xiàn),而客戶端負(fù)載均衡的實(shí)現(xiàn)則使用Netflix的Ribbon庫(kù)瞬项。

當(dāng)然蔗蹋,Spring Cloud為實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)提供了多種解決方案。下文也會(huì)分析各自的優(yōu)劣勢(shì)囱淋。

在上一節(jié)猪杭,我們已經(jīng)創(chuàng)建了一個(gè)微服務(wù)應(yīng)用,該應(yīng)用中只有l(wèi)icense一個(gè)微服務(wù)⊥滓拢現(xiàn)在我們會(huì)在現(xiàn)有應(yīng)用的基礎(chǔ)上皂吮,再創(chuàng)建一個(gè)organization服務(wù)和一個(gè)服務(wù)發(fā)現(xiàn)代理,并將license服務(wù)和organization服務(wù)注冊(cè)到服務(wù)發(fā)現(xiàn)代理税手。

假設(shè)當(dāng)license服務(wù)被調(diào)用蜂筹,它會(huì)去調(diào)用organization服務(wù),organization服務(wù)再根據(jù)organization ID獲取對(duì)應(yīng)的organization信息并返回。在從organization服務(wù)獲取數(shù)據(jù)前芦倒,必須先知道organization服務(wù)可用的實(shí)例信息艺挪,這些信息是organization服務(wù)啟動(dòng)的時(shí)候就注冊(cè)到服務(wù)發(fā)現(xiàn)代理并由它維護(hù)的。而license服務(wù)在調(diào)用organization服務(wù)時(shí)兵扬,會(huì)從本地獲取organization服務(wù)實(shí)例的信息麻裳。下圖說明了這個(gè)過程:

image.png

在上一節(jié)創(chuàng)建的應(yīng)用的基礎(chǔ)上口蝠,再創(chuàng)建一個(gè)organization微服務(wù),由于創(chuàng)建步驟與創(chuàng)建license服務(wù)時(shí)大同小異掂器,這里就不貼代碼出來亚皂,需要的可以去git上查看。(由于需要將license服務(wù)和organization服務(wù)注冊(cè)到Eureka上国瓮,實(shí)現(xiàn)這一功能的代碼會(huì)在下文貼出)

接下來我們開始進(jìn)入本節(jié)的重頭戲灭必,服務(wù)發(fā)現(xiàn)代理的搭建。

搭建Spring Eureka服務(wù)

我們會(huì)使用Spring Boot搭建Eureka服務(wù)乃摹,因?yàn)榉?wù)發(fā)現(xiàn)本質(zhì)上也是一個(gè)微服務(wù)禁漓。

  1. pom文件
    首先,把注意力放在pom.xml文件上孵睬,代碼如下:
    eureka服務(wù):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.study.microservice</groupId>
    <artifactId>discovery-eureka</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>discovery-eureka</name>
    <description></description>

    <parent>
        <groupId>cn.study.microservice</groupId>
        <artifactId>second-discovery-euraka</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>

可以看到播歼,pom文件中的依賴很少,只有一個(gè)"spring-cloud-starter-eureka-server"掰读,這是因?yàn)镾pring Cloud的Eureka server啟動(dòng)依賴秘狞,其中包含多個(gè)必須的jar包,如"spring-cloud-netflix-eureka-server"蹈集、"spring-cloud-starter-ribbon"等烁试。

同樣的,因?yàn)閘icense服務(wù)和organization服務(wù)會(huì)注冊(cè)到Eureka上拢肆,所以對(duì)應(yīng)的pom文件也需要作出修改减响,如下:
license服務(wù):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.study.microservice</groupId>
    <artifactId>license-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>license-service</name>
    <description></description>

    <parent>
        <groupId>cn.study.microservice</groupId>
        <artifactId>second-discovery-euraka</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
    </dependencies>

</project>

organization服務(wù):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.study.microservice</groupId>
    <artifactId>organization-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>organization-service</name>
    <description></description>

    <parent>
        <groupId>cn.study.microservice</groupId>
        <artifactId>second-discovery-euraka</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
    </dependencies>

</project>

上面兩個(gè)pom文件中,可以看到都引入了一個(gè)與eureka相關(guān)的依賴——"spring-cloud-starter-eureka"郭怪,與服務(wù)發(fā)現(xiàn)引入的依賴——"spring-cloud-starter-eureka-server"略有不同支示,多了一個(gè)"-server",那么不同在哪里呢鄙才?可以打開idea提供的"Maven Project"視圖颂鸿,打開方法如下:


image.png

打開后可以看到類似如下圖所示:

image.png

可以看到,"spring-cloud-starter-eureka-server"會(huì)引入"spring-cloud-netflix-eureka-server"攒庵,而"spring-cloud-starter-eureka"會(huì)引入"spring-cloud-netflix-eureka-client"嘴纺,分別代表eureka服務(wù)端和客戶端。

另外叙甸,license颖医、organization兩個(gè)服務(wù)還添加"spring-boot-starter-data-jpa"、"com.h2database.h2"裆蒸。即數(shù)據(jù)庫(kù)使用的是h2熔萧,因?yàn)橐_保教程的代碼能開箱即用,如果使用mysql等其他開源數(shù)據(jù)庫(kù),讀者還要去新建數(shù)據(jù)庫(kù)佛致,如果數(shù)據(jù)庫(kù)賬戶密碼不同贮缕,還需要去改配置文件;數(shù)據(jù)訪問層框架使用的是"spring data jpa"俺榆,"spring-boot-starter-data-jpa"是spring boot提供的啟動(dòng)依賴感昼,里邊包含spring data jpa和hibernate-core等jar包,若之前未了解過spring data jpa罐脊,建議先去熟悉下定嗓。

最后,license服務(wù)pom文件中還多出了一個(gè)依賴:"spring-cloud-starter-feign"萍桌。該依賴是spring cloud提供的啟動(dòng)依賴宵溅,主要包含了Netflix的一個(gè)開源項(xiàng)目feign項(xiàng)目的jar包,feign主要功能是對(duì)上文提及的實(shí)現(xiàn)了客戶端負(fù)載均衡的Ribbon的封裝上炎,下文會(huì)詳細(xì)說明恃逻。

  1. 配置文件

eureka服務(wù)的application.yml配置文件:

#默認(rèn)端口號(hào)為8761
server:
  port: 8761

eureka:
  client:
    #由于該應(yīng)用為注冊(cè)中心,所以設(shè)置為false藕施,代表不向注冊(cè)中心注冊(cè)自己
    registerWithEureka: false
    #由于注冊(cè)中心的職責(zé)就是維護(hù)服務(wù)實(shí)例寇损,它并不需要去檢索服務(wù),所以也設(shè)置為false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  server:
    #關(guān)閉自我保護(hù)模式裳食。自我保護(hù)模式是指矛市,出現(xiàn)網(wǎng)絡(luò)分區(qū)、eureka在短時(shí)間內(nèi)丟失過多客戶端時(shí)胞谈,會(huì)進(jìn)入自我保護(hù)模式尘盼。
    #自我保護(hù):一個(gè)服務(wù)長(zhǎng)時(shí)間沒有發(fā)送心跳包憨愉,eureka也不會(huì)將其刪除烦绳,默認(rèn)為true。
    enable-self-preservation: false
    #在Eureka服務(wù)器獲取不到集群里對(duì)等服務(wù)器上的實(shí)例時(shí)配紫,需要等待的時(shí)間径密,單位為毫秒,默認(rèn)為1000 * 60 * 5
    wait-time-in-ms-when-sync-empty: 0

license服務(wù)的application.yml配置文件:

server:
  port: 10000

#服務(wù)發(fā)現(xiàn)客戶端
eureka:
  instance:
    #向Eureka注冊(cè)時(shí)躺孝,是否使用IP地址+端口號(hào)作為服務(wù)實(shí)例的唯一標(biāo)識(shí)享扔。推薦設(shè)置為true
    prefer-ip-address: true

    #============分界線================
    #以下為基本不需要配置的屬性,屬性的值為默認(rèn)值

    #服務(wù)續(xù)約的調(diào)用時(shí)間間隔植袍,默認(rèn)30秒
    lease-renewal-interval-in-seconds: 30
    #服務(wù)失效的時(shí)間惧眠,默認(rèn)90秒
    lease-expiration-duration-in-seconds: 90
    #非安全的通信端口號(hào)
    non-secure-port: 80
    #安全的通信端口號(hào)
    secure-port: 443
    #是否啟用非安全的通信端口號(hào)
    non-secure-port-enabled: true
    #是否啟用安全的通信端口號(hào)
    secure-port-enabled: false

  client:
    #是否將自身的實(shí)例信息注冊(cè)到Eureka服務(wù)端
    register-with-eureka: true
    #是否拉取并緩存其他服務(wù)注冊(cè)表副本到本地
    fetch-registry: true
    #注冊(cè)到哪個(gè)Eureka服務(wù)實(shí)例
    service-url:
      defaultZone: http://localhost:8761/eureka/

    #============分界線================
    #以下為基本不需要配置的屬性,屬性的值為默認(rèn)值

    #更新其他服務(wù)注冊(cè)表時(shí)間間隔于个,默認(rèn)30秒
    registry-fetch-interval-seconds: 30
    #更新實(shí)例信息的變化到Eureka服務(wù)端的間隔時(shí)間氛魁,單位為秒
    instance-info-replication-interval-seconds: 30
    #初始化實(shí)例信息到Eureka服務(wù)端的間隔時(shí)間,單位為秒
    initial-instance-info-replication-interval-seconds: 40
    #輪詢Eureka服務(wù)端地址更改的間隔時(shí)間,單位為秒秀存。
    #當(dāng)我們與Sping Cloud Config配合捶码,動(dòng)態(tài)刷新Eureka的service url地址時(shí)需要關(guān)注該參數(shù)
    eureka-service-url-poll-interval-seconds: 300
    #讀取Eureka Server信息的超時(shí)時(shí)間,單位為秒
    eureka-server-read-timeout-seconds: 8
    #連接Eureka Server的超時(shí)時(shí)間或链,單位為秒
    eureka-server-connect-timeout-seconds: 5
    #從Eureka客戶端到所有Eureka服務(wù)端的連接總數(shù)
    eureka-server-total-connections: 200
    #從Eureka客戶端到每個(gè)Eureka服務(wù)端主機(jī)的連接總數(shù)
    eureka-server-total-connections-per-host: 50
    #Eureka服務(wù)連接的空閑關(guān)閉時(shí)間惫恼,單位為秒
    eureka-connection-idle-timeout-seconds: 30
    #心跳連接池的初始化線程數(shù)
    heartbeat-executor-thread-pool-size: 2
    #心跳超時(shí)重試延遲時(shí)間的最大乘數(shù)值
    heartbeat-executor-exponential-back-off-bound: 10
    #緩存刷新線程池的初始化線程數(shù)
    cache-refresh-executor-thread-pool-size: 2
    #緩存刷新重試延遲時(shí)間的最大乘數(shù)值
    cache-refresh-executor-exponential-back-off-bound: 10
    #使用DNS來獲取Eureka服務(wù)端的service url
    use-dns-for-fetching-service-urls: false
    #是否優(yōu)先使用處于相同Zone的Eureka服務(wù)端
    perfer-same-zone-eureka: true
    #獲取實(shí)例時(shí)是否過濾,僅保留UP狀態(tài)的實(shí)例
    filter-only-up-instances: true

#數(shù)據(jù)源的配置
spring:
  datasource:
    platform: h2
    schema: classpath:schema.sql
    data: classpath:data.sql
    driver-class-name: org.h2.Driver
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: none

organization服務(wù)的application.yml配置文件:

server:
  port: 11000

eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

spring:
  datasource:
    platform: h2
    schema: classpath:schema.sql
    data: classpath:data.sql
    driver-class-name: org.h2.Driver
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: none

每一個(gè)注冊(cè)到eureka的服務(wù)實(shí)例澳盐,eureka中都有兩個(gè)屬性與它關(guān)聯(lián)起來祈纯,分別為:application ID、instance ID叼耙。application ID是用來標(biāo)識(shí)某一個(gè)服務(wù)的實(shí)例集合盆繁,即同一個(gè)服務(wù)的不同實(shí)例,它們的application ID都是相同的旬蟋;基于Spring Boot的微服務(wù)油昂,可以通過設(shè)置屬性spring.application.name來定義application ID(一般在bootstrap.yml中配置),上面的配置,license服務(wù)的application ID為licensingservice倾贰,organization服務(wù)的為organizationservice冕碟;instance ID默認(rèn)是一個(gè)隨機(jī)數(shù),用來定位服務(wù)的實(shí)例集合中某一實(shí)例匆浙,通常會(huì)配置eureka.instance.instance-id安寺,如${spring.cloud.client.ipAddress}:${server.port},上面給出的配置并沒有配置該屬性首尼,讀者可自行配置挑庶。

下面介紹幾個(gè)主要的屬性的作用:

  • 屬性eureka.instance.preferIpAddress:會(huì)通知eureka使用服務(wù)實(shí)例的IP地址進(jìn)行注冊(cè),而不是它的主機(jī)名(hostname)软能。一般情況下迎捺,該屬性會(huì)被設(shè)置為true,其中一個(gè)原因是:基于云的微服務(wù)的生存周期很短暫且是無狀態(tài)的查排,這些微服務(wù)可以隨意啟動(dòng)和關(guān)閉凳枝。因此使用IP地址更適合這種微服務(wù)。

  • 屬性eureka.client.registerWithEureka:代表該服務(wù)是否將自己注冊(cè)到eureka中跋核;屬性eureka.client.fetchRegistry表明該服務(wù)是否從eureka服務(wù)端獲取其他服務(wù)的注冊(cè)信息到本地岖瑰,將該屬性設(shè)置為true,就不用在每一次調(diào)用其他服務(wù)的接口時(shí)都要去eureka獲取目標(biāo)服務(wù)的信息砂代。另外蹋订,若eureka.client.fetchRegistry設(shè)置為true,服務(wù)每隔30s會(huì)從eureka刷新服務(wù)信息到本地刻伊。

  • 屬性eureka.client.serviceUrl.defaultZone:代表服務(wù)會(huì)注冊(cè)到哪個(gè)eureka服務(wù)端實(shí)例露戒。實(shí)現(xiàn)eureka的高可用需要關(guān)注這個(gè)屬性难礼,這里不過多說明,需要了解更多關(guān)于eureka高可用玫锋,請(qǐng)自行Google或百度蛾茉。

配置文件中的大多數(shù)屬性的作用都已在文件中給出注釋,就不過多贅述了撩鹿。

另外license谦炬、organization兩個(gè)服務(wù)還多出一個(gè)bootstrap.yml文件,該文件主要配置微服務(wù)的application.name和profiles.active节沦,該文件會(huì)在application.yml之前被加載键思。

  1. 啟動(dòng)類

eureka服務(wù):

@SpringBootApplication
@EnableEurekaServer
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

觀察上面的代碼,可以看出相比普通的微服務(wù)啟動(dòng)類甫贯,只多出了一個(gè)注解——@EnableEurekaServer吼鳞,該注解表明該微服務(wù)會(huì)成為一個(gè)Eureka服務(wù),即服務(wù)發(fā)現(xiàn)的服務(wù)端叫搁。
啟動(dòng)該服務(wù)赔桌,可以看到控制臺(tái)會(huì)打印如下字符串:

Started Eureka Server
Tomcat started on port(s): 8761 (http)

表明eureka服務(wù)已成功啟動(dòng),并且在端口8761啟動(dòng)渴逻,之前在配置文件中配置的server.port: 8761已經(jīng)起作用了疾党。

接著在瀏覽器訪問: http://localhost:8761/,如下圖所示:

image.png

上圖圈中的區(qū)域是一個(gè)注冊(cè)到該eureka服務(wù)實(shí)例的所有服務(wù)實(shí)例列表惨奕,因?yàn)榇藭r(shí)尚未有任何服務(wù)注冊(cè)雪位,所以列表為空。

organization服務(wù):

//該注解表明該類是項(xiàng)目(微服務(wù))的啟動(dòng)類
@SpringBootApplication
@EnableEurekaClient
public class Application {
    //運(yùn)行該方法梨撞,會(huì)啟動(dòng)整個(gè)Spring Boot服務(wù)
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

觀察上面organization服務(wù)啟動(dòng)類的代碼雹洗,與eureka服務(wù)的相比,@EnableEurekaServer注解變成了@EnableEurekaClient卧波,使用該注解的服務(wù)时肿,會(huì)在服務(wù)啟動(dòng)的時(shí)候?qū)⒆约鹤?cè)到eureka服務(wù)端。如果查看@EnableEurekaClient的源碼幽勒,如下:

@EnableDiscoveryClient
public @interface EnableEurekaClient {
}

可以看到嗜侮,該注解其實(shí)是Netflix Eureka對(duì)注解@EnableDiscoveryClient的封裝港令,注解@EnableDiscoveryClient可以說是服務(wù)可以使用DiscoveryClient和Ribbon庫(kù)的觸發(fā)器啥容,并將自己注冊(cè)到服務(wù)發(fā)現(xiàn)代理。所以你也可以選擇使用@EnableDiscoveryClient代替@EnableEurekaClient顷霹。

啟動(dòng)organization服務(wù)咪惠,可以看到控制臺(tái)有類似下圖的輸出:

image.png

此時(shí)若再次訪問http://localhost:8761/,可以看到類似如下圖的界面:

image.png

若出現(xiàn)的界面與上圖類似淋淀,說明到此為止應(yīng)用的搭建還算順利遥昧。可以看到上圖的注冊(cè)到eureka服務(wù)的所有服務(wù)實(shí)例列表不為空,多出了一行炭臭,這一行列出的信息代表有一個(gè)application Id為"ORGANIZATIONSERVICE"永脓,instance ID為"10.10.1.216:organizationservice:11000"的服務(wù)實(shí)例注冊(cè)到eureka,"UP(1)"則代表該服務(wù)只有一個(gè)實(shí)例正在運(yùn)行鞋仍。

license服務(wù):
此時(shí)先不考慮license服務(wù)會(huì)消費(fèi)organization服務(wù)常摧,所以啟動(dòng)類的代碼跟organization服務(wù)的相同,這里就不貼出來威创。

啟動(dòng)license服務(wù)落午,然后刷新http://localhost:8761/,可以看到向eureka服務(wù)注冊(cè)的服務(wù)列表中多出了一行肚豺,說明剛剛啟動(dòng)的license服務(wù)也注冊(cè)成功溃斋。

image.png
使用服務(wù)發(fā)現(xiàn)

接下來我們會(huì)通過三種方式來實(shí)現(xiàn)服務(wù)的發(fā)現(xiàn)。分別如下:

  • Spring Discovery client
  • Netflix Ribbon client
  • Netflix Feign client

首先吸申,把license和organization服務(wù)關(guān)掉梗劫,eureka無所謂;然后把organization服務(wù)的其他相關(guān)代碼加上截碴,如controller包在跳、repository包、service包隐岛,詳細(xì)代碼請(qǐng)參考git上的代碼猫妙。我們把重點(diǎn)放在license服務(wù)代碼編寫上。下面開始進(jìn)入正題聚凹。

(License和Organization實(shí)體類請(qǐng)讀者自行加上)

  • Spring Discovery client
    首先創(chuàng)建一個(gè)服務(wù)發(fā)現(xiàn)客戶端類——OrganizationDiscoveryClient割坠,路徑:cn.study.microservice.license.client.OrganizationDiscoveryClient,如下:
@Component
public class OrganizationDiscoveryClient {

    //當(dāng)服務(wù)啟動(dòng)類加上@EnableEurekaClient或@EnableDiscoveryClient注解后妒牙,會(huì)自動(dòng)注入實(shí)現(xiàn)DiscoveryClient接口的對(duì)象彼哼。此處會(huì)注入EurekaDiscoveryClient對(duì)象
    @Autowired
    private DiscoveryClient discoveryClient;

    public Organization getOrganization(String organizationId) {
        RestTemplate restTemplate = new RestTemplate();
        //會(huì)從eureka服務(wù)端獲取organizationservice服務(wù)的所有實(shí)例集合
        List<ServiceInstance> instances = discoveryClient.getInstances("organizationservice");

        if (instances.size()==0) return null;
        String serviceUri = String.format("%s/v1/organizations/%s",instances.get(0).getUri().toString(), organizationId);
        System.out.println("!!!! SERVICE URI:  " + serviceUri);

        ResponseEntity< Organization > restExchange =
                restTemplate.exchange(
                        serviceUri,
                        HttpMethod.GET,
                        null, Organization.class, organizationId);

        return restExchange.getBody();
    }
}

上面代碼中,涉及到一個(gè)類:RestTemplate湘今,該類是Spring用于客戶端http同步訪問的主要類敢朱。具體用法,請(qǐng)讀者自行了解摩瞎。

接著拴签,創(chuàng)建cn.study.microservice.license.service.LicenseService,如下:

@Service
public class LicenseService {

    @Autowired
    private LicenseRepository licenseRepository;

    @Autowired
    private OrganizationDiscoveryClient organizationDiscoveryClient;

    private Organization retrieveOrgInfo(String organizationId, String clientType){
        Organization organization = null;

        switch (clientType) {
            case "discovery":
                System.out.println("I am using the discovery client");
                organization = organizationDiscoveryClient.getOrganization(organizationId);
                break;
            default:
                organization = organizationDiscoveryClient.getOrganization(organizationId);
        }

        return organization;
    }

    public License getLicense(String organizationId, String licenseId, String clientType) {
        License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId);

        Organization org = retrieveOrgInfo(organizationId, clientType);

        return license
                .withOrganizationName( org.getName())
                .withContactName( org.getContactName())
                .withContactEmail( org.getContactEmail() )
                .withContactPhone( org.getContactPhone() )
                .withComment("");
    }

}

最后旗们,編寫controller類蚓哩,如下:

@RestController
@RequestMapping(value="v1/organizations/{organizationId}/licenses")
public class LicenseServiceController {
    @Autowired
    private LicenseService licenseService;

    @RequestMapping(value="/{licenseId}/{clientType}",method = RequestMethod.GET)
    public License getLicensesWithClient( @PathVariable("organizationId") String organizationId,
                                          @PathVariable("licenseId") String licenseId,
                                          @PathVariable("clientType") String clientType) {

        return licenseService.getLicense(organizationId,licenseId, clientType);
    }
}

到此,代碼編寫完畢上渴,啟動(dòng)服務(wù)岸梨,然后使用postman訪問http://localhost:10000/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a/discovery喜颁,結(jié)果如下:

image.png

可以看到,返回的結(jié)果是licenseId為"f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a"的License對(duì)象的json字符串曹阔,其中"organizationName"半开、"contactName"、"contactPhone"和"contactEmail"都是根據(jù)organizationId訪問organization服務(wù)提供的接口:/v1/organizations/{organizationId}赃份,然后返回一個(gè)Organization對(duì)象稿茉,最后將對(duì)象中的信息賦值到License對(duì)象中。實(shí)現(xiàn)這一功能的核心代碼如下:

...
ResponseEntity< Organization > restExchange =
        restTemplate.exchange(
                serviceUri,
                HttpMethod.GET,
                null, Organization.class, organizationId);

return restExchange.getBody();
...

exchange方法的方法簽名為:

public <T> ResponseEntity<T> exchange(
    String url, //需要訪問的資源的url
    HttpMethod method, //HTTPMethod枚舉芥炭,分別代表http定義的各種安全方法漓库,如:GET、POST园蝠、PUT渺蒿、DELETE等。
    HttpEntity<?> requestEntity, //請(qǐng)求實(shí)體彪薛,即請(qǐng)求攜帶的數(shù)據(jù)茂装。為空時(shí),賦null
    Class<T> responseType, //返回對(duì)象的Class類型
    Object... uriVariables //url路徑上變量對(duì)應(yīng)的值
)

此處善延,可以看成是license服務(wù)調(diào)用了organization服務(wù)的接口少态。該接口在organization服務(wù)的OrganizationServiceController類中被定義。讀者可以自行驗(yàn)證易遣,在控制臺(tái)會(huì)答應(yīng)出變量"serviceUri"的值:http://10.10.1.216:11000/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a彼妻,請(qǐng)讀者將"10.10.1.216"換成自己的IP或"localhost",然后用postman訪問豆茫。

至此侨歉,第一種實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)方式完成。原理是:使用DiscoveryClient從服務(wù)端獲取對(duì)應(yīng)服務(wù)的所有實(shí)例列表揩魂,然后取其中的一個(gè)實(shí)例幽邓,并獲取該實(shí)例的物理地址,最后調(diào)用該服務(wù)的接口火脉∏6妫看到這里,讀者應(yīng)該對(duì)服務(wù)發(fā)現(xiàn)有更深的理解倦挂。license服務(wù)在調(diào)用organization服務(wù)的接口時(shí)畸颅,沒有使用硬編碼也行不通,因?yàn)樵诖a編寫階段妒峦,根本不知道服務(wù)會(huì)被部署到哪個(gè)服務(wù)器的哪個(gè)端口重斑,而通過服務(wù)發(fā)現(xiàn),則可以動(dòng)態(tài)地獲取目標(biāo)服務(wù)實(shí)例的物理地址(前提是目標(biāo)服務(wù)也注冊(cè)到eureka)肯骇,進(jìn)而調(diào)用該服務(wù)的接口窥浪。

  • Netflix Ribbon client
    在編寫服務(wù)發(fā)現(xiàn)客戶端類前,先修改啟動(dòng)類笛丙,添加一下代碼:
//該注解告知Spring Cloud創(chuàng)建一個(gè)基于Ribbon的RestTemplate漾脂,才可以實(shí)現(xiàn)客戶端負(fù)載均衡
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
    return new RestTemplate();
}

接著,編寫服務(wù)發(fā)現(xiàn)客戶端類——OrganizationRestTemplateClient胚鸯,如下:

@Component
public class OrganizationRestTemplateClient {
    @Autowired
    RestTemplate restTemplate;

    public Organization getOrganization(String organizationId){
        ResponseEntity<Organization> restExchange =
                restTemplate.exchange(
                        "http://organizationservice/v1/organizations/{organizationId}",
                        HttpMethod.GET,
                        null, Organization.class, organizationId);

        return restExchange.getBody();
    }
}

然后骨稿,修改LicenseService類,注入OrganizationRestTemplateClient姜钳、修改方法retrieveOrgInfo坦冠,如下:

...
@Autowired
private OrganizationRestTemplateClient organizationRestClient;
...
switch (clientType) {
    case "discovery":
        System.out.println("I am using the discovery client");
        organization = organizationDiscoveryClient.getOrganization(organizationId);
        break;
    case "rest":
        System.out.println("I am using the rest client");
        organization = organizationRestClient.getOrganization(organizationId);
        break;
    default:
        organization = organizationDiscoveryClient.getOrganization(organizationId);
}
...

至此,代碼修改完畢哥桥,啟動(dòng)服務(wù)辙浑。使用postman調(diào)用:http://localhost:10000/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a/rest。正確返回后拟糕,可以發(fā)現(xiàn)返回結(jié)果與第一種方式返回的結(jié)果相同判呕。接下來,開始分析第二種方式是如何內(nèi)部工作的:

查看服務(wù)發(fā)現(xiàn)客戶端類OrganizationRestTemplateClient送滞,可以發(fā)現(xiàn)同樣是使用RestTemplate的exchange方法來調(diào)用organization服務(wù)的接口侠草,唯一不同的是方法的第一個(gè)參數(shù)值,這里硬編碼為:"http://organizationservice/v1/organizations/{organizationId}"犁嗅。為什么這里可以這樣直接寫死在程序里边涕,而且是使用"organizationservice"代替常規(guī)的服務(wù)實(shí)例物理地址。還記得在啟動(dòng)類中注入的RestTemplate嗎褂微,我們?cè)谧⑷氲臅r(shí)候另外加了一個(gè)注解@LoadBalanced奥吩,答案就在這里。

加了注解@LoadBalanced后蕊梧,代表在注入RestTemplate時(shí)霞赫,Ribbon會(huì)對(duì)其進(jìn)行加工,加工后的RestTemplate的能力有:

  1. 在調(diào)用其他服務(wù)的接口時(shí)肥矢,會(huì)從訪問的url中截取得到目標(biāo)服務(wù)的application ID端衰,此處為"organizationservice",然后根據(jù)該服務(wù)獲取對(duì)應(yīng)的實(shí)例甘改,最后正常訪問旅东;
  2. 實(shí)現(xiàn)客戶端負(fù)載均衡

讀者可以自行驗(yàn)證,把注解去掉十艾,然后重啟服務(wù)抵代,再訪問一次,結(jié)果肯定是報(bào)錯(cuò)忘嫉。如下:

{
  "timestamp": 1509695097081,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "org.springframework.web.client.ResourceAccessException",
  "message": "I/O error on GET request for \"http://organizationservice/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a\": organizationservice; nested exception is java.net.UnknownHostException: organizationservice",
  "path": "/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a/rest"
}

可以看出荤牍,報(bào)的錯(cuò)是未知主機(jī)名:organizationservice案腺。如果將"organizationservice"換成"localhost:11000"就能正常訪問。

最后,我們來對(duì)可能是大家最關(guān)心的問題進(jìn)行驗(yàn)證,客戶端負(fù)載均衡吠谢。

  • 打包項(xiàng)目
    打開idea的Maven Projects,如下:
image.png
  • 啟動(dòng)多個(gè)organization服務(wù)
    使用快捷鍵組合:Alt + F12同辣,打開終端控制臺(tái),然后進(jìn)入organization項(xiàng)目的target目錄惭载,最后使用命令:
java -jar organization-service-0.0.1-SNAPSHOT.jar --server.port=11000

執(zhí)行該命令后旱函,會(huì)在端口11000啟動(dòng)一個(gè)organization服務(wù)。

接著按照同樣的方式在不同端口啟動(dòng)一個(gè)或多個(gè)服務(wù)描滔。首先點(diǎn)擊終端控制臺(tái)左上角的"+"圖標(biāo)棒妨,創(chuàng)建一個(gè)新的終端,然后進(jìn)入target目錄伴挚,使用命令:

java -jar organization-service-0.0.1-SNAPSHOT.jar --server.port=11001

啟動(dòng)多個(gè)organization服務(wù)后靶衍,多次訪問:http://localhost:10000/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a/rest

觀察兩個(gè)終端控制臺(tái),會(huì)出現(xiàn)如下圖所示的輸出:

image.png
image.png

由上圖可知客戶端負(fù)載均衡驗(yàn)證成功茎芋。license服務(wù)多次調(diào)用organization服務(wù)的接口颅眶,會(huì)隨機(jī)的訪問organization服務(wù)的某一實(shí)例,從而避免頻繁訪問某一個(gè)實(shí)例田弥,達(dá)到負(fù)載均衡的目的涛酗。

使用Netflix Ribbon client可以很方便地實(shí)現(xiàn)對(duì)其它服務(wù)接口的調(diào)用,但每個(gè)接口的調(diào)用都要寫類似OrganizationRestTemplateClient.getOrganization方法的代碼偷厦,雖然不多商叹,但有沒有更方便、簡(jiǎn)介的實(shí)現(xiàn)方法呢只泼?下面開始講如何使用"Netflix Feign client"實(shí)現(xiàn)剖笙。

  • Netflix Feign client

在編寫服務(wù)發(fā)現(xiàn)客戶端類前,向啟動(dòng)類添加一個(gè)類注解:@EnableFeignClients请唱。

image.png

接著弥咪,編寫服務(wù)發(fā)現(xiàn)客戶端類——OrganizationFeignClient,其實(shí)是一個(gè)接口:

@FeignClient("organizationservice")
public interface OrganizationFeignClient {
    @RequestMapping(
            method= RequestMethod.GET,
            value="/v1/organizations/{organizationId}",
            consumes="application/json")
    Organization getOrganization(@PathVariable("organizationId") String organizationId);
}

最后修改十绑,LicenseService類聚至,注入OrganizationRestTemplateClient、修改方法retrieveOrgInfo本橙。如下:

...
@Autowired
OrganizationFeignClient organizationFeignClient;
...
switch (clientType) {
    case "discovery":
        System.out.println("I am using the discovery client");
        organization = organizationDiscoveryClient.getOrganization(organizationId);
        break;
    case "rest":
        System.out.println("I am using the rest client");
        organization = organizationRestClient.getOrganization(organizationId);
        break;
    case "feign":
        System.out.println("I am using the feign client");
        organization = organizationFeignClient.getOrganization(organizationId);
        break;
    default:
        organization = organizationDiscoveryClient.getOrganization(organizationId);
}
...

代碼修改完成扳躬,重啟license服務(wù)。然后訪問http://localhost:10000/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a/feign。返回的結(jié)果與之前兩種方式一樣贷币。
下面分析接口OrganizationFeignClient:
可以看到該接口上方添加了一個(gè)注解——FeignClient击胜,注解的value值為:organizationservice,看到這里片择,應(yīng)該能猜出organizationservice代表的是需要調(diào)用的接口所屬的服務(wù)的application ID潜的,而該接口的作用是:用于通知Feign組件對(duì)該接口進(jìn)行代理骚揍,而不需要編寫任何邏輯代碼字管。在服務(wù)啟動(dòng)時(shí),F(xiàn)eign會(huì)掃描標(biāo)有@FeignClient注解的接口信不,生成代理嘲叔,并注冊(cè)到Spring容器中,因此可通過@Autowired注入抽活。
接著就是接口中的方法的編寫硫戈。可以看到方法getOrganization添加了一個(gè)@RequestMapping注解下硕,注解的value值就是目標(biāo)服務(wù)的接口丁逝。如果與第二種方式調(diào)用RestTemplate.exchange方法做對(duì)比,服務(wù)application ID+@RequestMapping的value值與exchange的第一個(gè)參數(shù)url對(duì)應(yīng)梭姓;@RequestMapping的method與第二個(gè) 參數(shù)對(duì)應(yīng)霜幼;接口方法的返回值與第四個(gè)參數(shù)對(duì)應(yīng);接口方法的所有帶@PathVariable注解的參數(shù)合并成一個(gè)數(shù)組與第五個(gè)參數(shù)對(duì)應(yīng)誉尖;至于第三個(gè)參數(shù)罪既,由于該接口的請(qǐng)求體為空,所以在方法getOrganization的簽名中沒體現(xiàn)出來铡恕。

假設(shè)啟動(dòng)多個(gè)organization服務(wù)實(shí)例琢感,那么多次訪問http://localhost:10000/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a/feign,也會(huì)負(fù)載均衡探熔, 其負(fù)載均衡的默認(rèn)實(shí)現(xiàn)是基于 Netflix Ribbon驹针。

完!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诀艰,一起剝皮案震驚了整個(gè)濱河市柬甥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涡驮,老刑警劉巖暗甥,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異捉捅,居然都是意外死亡撤防,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門棒口,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寄月,“玉大人辜膝,你說我怎么就攤上這事⊙梗” “怎么了厂抖?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)克懊。 經(jīng)常有香客問我忱辅,道長(zhǎng),這世上最難降的妖魔是什么谭溉? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任墙懂,我火速辦了婚禮,結(jié)果婚禮上扮念,老公的妹妹穿的比我還像新娘损搬。我一直安慰自己,他們只是感情好柜与,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布巧勤。 她就那樣靜靜地躺著,像睡著了一般弄匕。 火紅的嫁衣襯著肌膚如雪颅悉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天粘茄,我揣著相機(jī)與錄音签舞,去河邊找鬼。 笑死柒瓣,一個(gè)胖子當(dāng)著我的面吹牛儒搭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芙贫,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼搂鲫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了磺平?” 一聲冷哼從身側(cè)響起魂仍,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拣挪,沒想到半個(gè)月后擦酌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菠劝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年赊舶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笼平,死狀恐怖园骆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寓调,我是刑警寧澤锌唾,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站夺英,受9級(jí)特大地震影響晌涕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秋麸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一渐排、第九天 我趴在偏房一處隱蔽的房頂上張望炬太。 院中可真熱鬧灸蟆,春花似錦、人聲如沸亲族。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霎迫。三九已至斋枢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間知给,已是汗流浹背瓤帚。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涩赢,地道東北人戈次。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像筒扒,于是被迫代替她去往敵國(guó)和親怯邪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容