Spring Cloud的快速入門(mén)(一)

0.學(xué)習(xí)目標(biāo)

  • 了解系統(tǒng)架構(gòu)的演變
  • 了解RPC與Http的區(qū)別
  • 掌握HttpClient的簡(jiǎn)單使用
  • 知道什么是SpringCloud
  • 獨(dú)立搭建Eureka注冊(cè)中心
  • 獨(dú)立配置Robbin負(fù)載均衡

-Xms128m -Xmx128m

1.系統(tǒng)架構(gòu)演變

隨著互聯(lián)網(wǎng)的發(fā)展爆阶,網(wǎng)站應(yīng)用的規(guī)模不斷擴(kuò)大艳丛。需求的激增础嫡,帶來(lái)的是技術(shù)上的壓力琢岩。系統(tǒng)架構(gòu)也因此也不斷的演進(jìn)座柱、升級(jí)、迭代输吏。從單一應(yīng)用,到垂直拆分替蛉,到分布式服務(wù)贯溅,到SOA拄氯,以及現(xiàn)在火熱的微服務(wù)架構(gòu),還有在Google帶領(lǐng)下來(lái)勢(shì)洶涌的Service Mesh它浅。我們到底是該乘坐微服務(wù)的船只駛向遠(yuǎn)方译柏,還是偏安一隅得過(guò)且過(guò)?

其實(shí)生活不止眼前的茍且姐霍,還有詩(shī)和遠(yuǎn)方鄙麦。所以我們今天就回顧歷史,看一看系統(tǒng)架構(gòu)演變的歷程镊折;把握現(xiàn)在胯府,學(xué)習(xí)現(xiàn)在最火的技術(shù)架構(gòu);展望未來(lái)恨胚,爭(zhēng)取成為一名優(yōu)秀的Java工程師骂因。

1.1. 集中式架構(gòu)

當(dāng)網(wǎng)站流量很小時(shí),只需一個(gè)應(yīng)用赃泡,將所有功能都部署在一起寒波,以減少部署節(jié)點(diǎn)和成本。此時(shí)升熊,用于簡(jiǎn)化增刪改查工作量的數(shù)據(jù)訪問(wèn)框架(ORM)是影響項(xiàng)目開(kāi)發(fā)的關(guān)鍵俄烁。


1525529091749.png

存在的問(wèn)題:

  • 代碼耦合,開(kāi)發(fā)維護(hù)困難
  • 無(wú)法針對(duì)不同模塊進(jìn)行針對(duì)性優(yōu)化
  • 無(wú)法水平擴(kuò)展
  • 單點(diǎn)容錯(cuò)率低级野,并發(fā)能力差

1.2.垂直拆分

當(dāng)訪問(wèn)量逐漸增大猴娩,單一應(yīng)用無(wú)法滿足需求,此時(shí)為了應(yīng)對(duì)更高的并發(fā)和業(yè)務(wù)需求勺阐,我們根據(jù)業(yè)務(wù)功能對(duì)系統(tǒng)進(jìn)行拆分:

1525529671801.png

優(yōu)點(diǎn):

  • 系統(tǒng)拆分實(shí)現(xiàn)了流量分擔(dān)卷中,解決了并發(fā)問(wèn)題
  • 可以針對(duì)不同模塊進(jìn)行優(yōu)化
  • 方便水平擴(kuò)展,負(fù)載均衡渊抽,容錯(cuò)率提高

缺點(diǎn):

  • 系統(tǒng)間相互獨(dú)立蟆豫,會(huì)有很多重復(fù)開(kāi)發(fā)工作,影響開(kāi)發(fā)效率

1.3.分布式服務(wù)

當(dāng)垂直應(yīng)用越來(lái)越多懒闷,應(yīng)用之間交互不可避免十减,將核心業(yè)務(wù)抽取出來(lái),作為獨(dú)立的服務(wù)愤估,逐漸形成穩(wěn)定的服務(wù)中心帮辟,使前端應(yīng)用能更快速的響應(yīng)多變的市場(chǎng)需求。此時(shí)玩焰,用于提高業(yè)務(wù)復(fù)用及整合的分布式調(diào)用是關(guān)鍵由驹。

1525530657919.png

優(yōu)點(diǎn):

  • 將基礎(chǔ)服務(wù)進(jìn)行了抽取,系統(tǒng)間相互調(diào)用昔园,提高了代碼復(fù)用和開(kāi)發(fā)效率

缺點(diǎn):

  • 系統(tǒng)間耦合度變高蔓榄,調(diào)用關(guān)系錯(cuò)綜復(fù)雜并炮,難以維護(hù)

1.4.服務(wù)治理(SOA)

當(dāng)服務(wù)越來(lái)越多,容量的評(píng)估甥郑,小服務(wù)資源的浪費(fèi)等問(wèn)題逐漸顯現(xiàn)逃魄,此時(shí)需增加一個(gè)調(diào)度中心基于訪問(wèn)壓力實(shí)時(shí)管理集群容量,提高集群利用率澜搅。此時(shí)伍俘,用于提高機(jī)器利用率的資源調(diào)度和治理中心(SOA)是關(guān)鍵

1525530804753.png

以前出現(xiàn)了什么問(wèn)題?

  • 服務(wù)越來(lái)越多勉躺,需要管理每個(gè)服務(wù)的地址
  • 調(diào)用關(guān)系錯(cuò)綜復(fù)雜癌瘾,難以理清依賴關(guān)系
  • 服務(wù)過(guò)多,服務(wù)狀態(tài)難以管理赂蕴,無(wú)法根據(jù)服務(wù)情況動(dòng)態(tài)管理

服務(wù)治理要做什么柳弄?

  • 服務(wù)注冊(cè)中心,實(shí)現(xiàn)服務(wù)自動(dòng)注冊(cè)和發(fā)現(xiàn)概说,無(wú)需人為記錄服務(wù)地址
  • 服務(wù)自動(dòng)訂閱碧注,服務(wù)列表自動(dòng)推送,服務(wù)調(diào)用透明化糖赔,無(wú)需關(guān)心依賴關(guān)系
  • 動(dòng)態(tài)監(jiān)控服務(wù)狀態(tài)監(jiān)控報(bào)告萍丐,人為控制服務(wù)狀態(tài)

缺點(diǎn):

  • 服務(wù)間會(huì)有依賴關(guān)系,一旦某個(gè)環(huán)節(jié)出錯(cuò)會(huì)影響較大
  • 服務(wù)關(guān)系復(fù)雜放典,運(yùn)維逝变、測(cè)試部署困難,不符合DevOps思想

1.5.微服務(wù)

前面說(shuō)的SOA奋构,英文翻譯過(guò)來(lái)是面向服務(wù)壳影。微服務(wù),似乎也是服務(wù)弥臼,都是對(duì)系統(tǒng)進(jìn)行拆分宴咧。因此兩者非常容易混淆,但其實(shí)缺有一些差別:

1525532344817.png

微服務(wù)的特點(diǎn):

  • 單一職責(zé):微服務(wù)中每一個(gè)服務(wù)都對(duì)應(yīng)唯一的業(yè)務(wù)能力径缅,做到單一職責(zé)
  • 微:微服務(wù)的服務(wù)拆分粒度很小掺栅,例如一個(gè)用戶管理就可以作為一個(gè)服務(wù)。每個(gè)服務(wù)雖小纳猪,但“五臟俱全”氧卧。
  • 面向服務(wù):面向服務(wù)是說(shuō)每個(gè)服務(wù)都要對(duì)外暴露服務(wù)接口API。并不關(guān)心服務(wù)的技術(shù)實(shí)現(xiàn)氏堤,做到與平臺(tái)和語(yǔ)言無(wú)關(guān)沙绝,也不限定用什么技術(shù)實(shí)現(xiàn),只要提供Rest的接口即可。
  • 自治:自治是說(shuō)服務(wù)間互相獨(dú)立宿饱,互不干擾
    • 團(tuán)隊(duì)獨(dú)立:每個(gè)服務(wù)都是一個(gè)獨(dú)立的開(kāi)發(fā)團(tuán)隊(duì)熏瞄,人數(shù)不能過(guò)多脚祟。
    • 技術(shù)獨(dú)立:因?yàn)槭敲嫦蚍?wù)谬以,提供Rest接口,使用什么技術(shù)沒(méi)有別人干涉
    • 前后端分離:采用前后端分離開(kāi)發(fā)由桌,提供統(tǒng)一Rest接口为黎,后端不用再為PC、移動(dòng)段開(kāi)發(fā)不同接口
    • 數(shù)據(jù)庫(kù)分離:每個(gè)服務(wù)都使用自己的數(shù)據(jù)源
    • 部署獨(dú)立行您,服務(wù)間雖然有調(diào)用铭乾,但要做到服務(wù)重啟不影響其它服務(wù)。有利于持續(xù)集成和持續(xù)交付娃循。每個(gè)服務(wù)都是獨(dú)立的組件炕檩,可復(fù)用,可替換捌斧,降低耦合笛质,易維護(hù)

微服務(wù)結(jié)構(gòu)圖:

1526860071166.png

2.遠(yuǎn)程調(diào)用方式

無(wú)論是微服務(wù)還是SOA,都面臨著服務(wù)間的遠(yuǎn)程調(diào)用捞蚂。那么服務(wù)間的遠(yuǎn)程調(diào)用方式有哪些呢妇押?

常見(jiàn)的遠(yuǎn)程調(diào)用方式有以下幾種:

  • RPC:Remote Produce Call遠(yuǎn)程過(guò)程調(diào)用,類似的還有RMI姓迅。自定義數(shù)據(jù)格式敲霍,基于原生TCP通信,速度快丁存,效率高肩杈。早期的webservice,現(xiàn)在熱門(mén)的dubbo解寝,都是RPC的典型

  • Http:http其實(shí)是一種網(wǎng)絡(luò)傳輸協(xié)議扩然,基于TCP,規(guī)定了數(shù)據(jù)傳輸?shù)母袷健编丘,F(xiàn)在客戶端瀏覽器與服務(wù)端通信基本都是采用Http協(xié)議与学。也可以用來(lái)進(jìn)行遠(yuǎn)程服務(wù)調(diào)用。缺點(diǎn)是消息封裝臃腫嘉抓。

    現(xiàn)在熱門(mén)的Rest風(fēng)格索守,就可以通過(guò)http協(xié)議來(lái)實(shí)現(xiàn)。

2.1.認(rèn)識(shí)RPC

RPC抑片,即 Remote Procedure Call(遠(yuǎn)程過(guò)程調(diào)用)卵佛,是一個(gè)計(jì)算機(jī)通信協(xié)議。 該協(xié)議允許運(yùn)行于一臺(tái)計(jì)算機(jī)的程序調(diào)用另一臺(tái)計(jì)算機(jī)的子程序,而程序員無(wú)需額外地為這個(gè)交互作用編程截汪。說(shuō)得通俗一點(diǎn)就是:A計(jì)算機(jī)提供一個(gè)服務(wù)疾牲,B計(jì)算機(jī)可以像調(diào)用本地服務(wù)那樣調(diào)用A計(jì)算機(jī)的服務(wù)。

通過(guò)上面的概念衙解,我們可以知道阳柔,實(shí)現(xiàn)RPC主要是做到兩點(diǎn):

  • 實(shí)現(xiàn)遠(yuǎn)程調(diào)用其他計(jì)算機(jī)的服務(wù)
    • 要實(shí)現(xiàn)遠(yuǎn)程調(diào)用,肯定是通過(guò)網(wǎng)絡(luò)傳輸數(shù)據(jù)蚓峦。A程序提供服務(wù)舌剂,B程序通過(guò)網(wǎng)絡(luò)將請(qǐng)求參數(shù)傳遞給A,A本地執(zhí)行后得到結(jié)果暑椰,再將結(jié)果返回給B程序霍转。這里需要關(guān)注的有兩點(diǎn):
      • 1)采用何種網(wǎng)絡(luò)通訊協(xié)議虏肾?
        • 現(xiàn)在比較流行的RPC框架朋凉,都會(huì)采用TCP作為底層傳輸協(xié)議
      • 2)數(shù)據(jù)傳輸?shù)母袷皆鯓樱?
        • 兩個(gè)程序進(jìn)行通訊,必須約定好數(shù)據(jù)傳輸格式顽冶。就好比兩個(gè)人聊天召夹,要用同一種語(yǔ)言岩喷,否則無(wú)法溝通。所以戳鹅,我們必須定義好請(qǐng)求和響應(yīng)的格式均驶。另外,數(shù)據(jù)在網(wǎng)路中傳輸需要進(jìn)行序列化枫虏,所以還需要約定統(tǒng)一的序列化的方式妇穴。
  • 像調(diào)用本地服務(wù)一樣調(diào)用遠(yuǎn)程服務(wù)
    • 如果僅僅是遠(yuǎn)程調(diào)用,還不算是RPC隶债,因?yàn)镽PC強(qiáng)調(diào)的是過(guò)程調(diào)用腾它,調(diào)用的過(guò)程對(duì)用戶而言是應(yīng)該是透明的,用戶不應(yīng)該關(guān)心調(diào)用的細(xì)節(jié)死讹,可以像調(diào)用本地服務(wù)一樣調(diào)用遠(yuǎn)程服務(wù)瞒滴。所以RPC一定要對(duì)調(diào)用的過(guò)程進(jìn)行封裝

RPC調(diào)用流程圖:

1525568965976.png

想要了解詳細(xì)的RPC實(shí)現(xiàn),給大家推薦一篇文章:自己動(dòng)手實(shí)現(xiàn)RPC

2.2.認(rèn)識(shí)Http

Http協(xié)議:超文本傳輸協(xié)議赞警,是一種應(yīng)用層協(xié)議妓忍。規(guī)定了網(wǎng)絡(luò)傳輸?shù)恼?qǐng)求格式、響應(yīng)格式愧旦、資源定位和操作的方式等世剖。但是底層采用什么網(wǎng)絡(luò)傳輸協(xié)議,并沒(méi)有規(guī)定笤虫,不過(guò)現(xiàn)在都是采用TCP協(xié)議作為底層傳輸協(xié)議旁瘫。說(shuō)到這里祖凫,大家可能覺(jué)得,Http與RPC的遠(yuǎn)程調(diào)用非常像酬凳,都是按照某種規(guī)定好的數(shù)據(jù)格式進(jìn)行網(wǎng)絡(luò)通信惠况,有請(qǐng)求,有響應(yīng)宁仔。沒(méi)錯(cuò)稠屠,在這點(diǎn)來(lái)看,兩者非常相似台诗,但是還是有一些細(xì)微差別完箩。

  • RPC并沒(méi)有規(guī)定數(shù)據(jù)傳輸格式赐俗,這個(gè)格式可以任意指定拉队,不同的RPC協(xié)議,數(shù)據(jù)格式不一定相同阻逮。
  • Http中還定義了資源定位的路徑粱快,RPC中并不需要
  • 最重要的一點(diǎn):RPC需要滿足像調(diào)用本地服務(wù)一樣調(diào)用遠(yuǎn)程服務(wù),也就是對(duì)調(diào)用過(guò)程在API層面進(jìn)行封裝叔扼。Http協(xié)議沒(méi)有這樣的要求事哭,因此請(qǐng)求、響應(yīng)等細(xì)節(jié)需要我們自己去實(shí)現(xiàn)瓜富。
    • 優(yōu)點(diǎn):RPC方式更加透明鳍咱,對(duì)用戶更方便。Http方式更靈活与柑,沒(méi)有規(guī)定API和語(yǔ)言谤辜,跨語(yǔ)言、跨平臺(tái)
    • 缺點(diǎn):RPC方式需要在API層面進(jìn)行封裝价捧,限制了開(kāi)發(fā)的語(yǔ)言環(huán)境丑念。

例如我們通過(guò)瀏覽器訪問(wèn)網(wǎng)站,就是通過(guò)Http協(xié)議结蟋。只不過(guò)瀏覽器把請(qǐng)求封裝脯倚,發(fā)起請(qǐng)求以及接收響應(yīng),解析響應(yīng)的事情都幫我們做了嵌屎。如果是不通過(guò)瀏覽器推正,那么這些事情都需要自己去完成。

1525569352313.png

2.3.如何選擇宝惰?

既然兩種方式都可以實(shí)現(xiàn)遠(yuǎn)程調(diào)用植榕,我們?cè)撊绾芜x擇呢?

  • 速度來(lái)看掌测,RPC要比http更快内贮,雖然底層都是TCP产园,但是http協(xié)議的信息往往比較臃腫,不過(guò)可以采用gzip壓縮夜郁。
  • 難度來(lái)看什燕,RPC實(shí)現(xiàn)較為復(fù)雜,http相對(duì)比較簡(jiǎn)單
  • 靈活性來(lái)看竞端,http更勝一籌屎即,因?yàn)樗魂P(guān)心實(shí)現(xiàn)細(xì)節(jié),跨平臺(tái)事富、跨語(yǔ)言技俐。

因此,兩者都有不同的使用場(chǎng)景:

  • 如果對(duì)效率要求更高统台,并且開(kāi)發(fā)過(guò)程使用統(tǒng)一的技術(shù)棧雕擂,那么用RPC還是不錯(cuò)的。
  • 如果需要更加靈活贱勃,跨語(yǔ)言井赌、跨平臺(tái),顯然http更合適

那么我們?cè)撛趺催x擇呢贵扰?

微服務(wù)仇穗,更加強(qiáng)調(diào)的是獨(dú)立、自治戚绕、靈活纹坐。而RPC方式的限制較多,因此微服務(wù)框架中舞丛,一般都會(huì)采用基于Http的Rest風(fēng)格服務(wù)耘子。

3.Http客戶端工具

既然微服務(wù)選擇了Http,那么我們就需要考慮自己來(lái)實(shí)現(xiàn)對(duì)請(qǐng)求和響應(yīng)的處理瓷马。不過(guò)開(kāi)源世界已經(jīng)有很多的http客戶端工具拴还,能夠幫助我們做這些事情,例如:

  • HttpClient
  • OKHttp
  • URLConnection

接下來(lái)欧聘,我們就一起了解一款比較流行的客戶端工具:HttpClient

3.1.HttpClient

3.1.1.介紹

HttpClient是Apache公司的產(chǎn)品片林,是Http Components下的一個(gè)組件。

官網(wǎng)地址:http://hc.apache.org/index.html

1525570921966.png

特點(diǎn):

  • 基于標(biāo)準(zhǔn)怀骤、純凈的Java語(yǔ)言费封。實(shí)現(xiàn)了Http1.0和Http1.1
  • 以可擴(kuò)展的面向?qū)ο蟮慕Y(jié)構(gòu)實(shí)現(xiàn)了Http全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)
  • 支持HTTPS協(xié)議。
  • 通過(guò)Http代理建立透明的連接蒋伦。
  • 自動(dòng)處理Set-Cookie中的Cookie弓摘。

3.1.2.使用

我們導(dǎo)入課前資料提供的demo工程:《http-demo》

發(fā)起get請(qǐng)求:

    @Test
    public void testGet() throws IOException {
        HttpGet request = new HttpGet("http://www.baidu.com");
        String response = this.httpClient.execute(request, new BasicResponseHandler());
        System.out.println(response);
    }

發(fā)起Post請(qǐng)求:

@Test
public void testPost() throws IOException {
    HttpPost request = new HttpPost("http://www.oschina.net/");
    request.setHeader("User-Agent",
                      "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
    String response = this.httpClient.execute(request, new BasicResponseHandler());
    System.out.println(response);
}

嘗試訪問(wèn)昨天編寫(xiě)的接口:http://localhost/hello

這個(gè)接口返回一個(gè)User對(duì)象

@Test
public void testGetPojo() throws IOException {
    HttpGet request = new HttpGet("http://localhost/hello");
    String response = this.httpClient.execute(request, new BasicResponseHandler());
    System.out.println(response);
}

我們實(shí)際得到的是一個(gè)json字符串:

{
    "id": 8,
    "userName": "liuyan",
    "password": "123456",
    "name": "柳巖",
    "age": 21,
    "sex": 2,
    "birthday": "1995-08-07T16:00:00.000+0000",
    "created": "2014-09-20T03:41:15.000+0000",
    "updated": "2014-09-20T03:41:15.000+0000",
    "note": "柳巖同學(xué)在傳智播客學(xué)表演"
}

如果想要得到對(duì)象,我們還需要手動(dòng)進(jìn)行Json反序列化痕届,這一點(diǎn)比較麻煩韧献。

3.1.3.Json轉(zhuǎn)換工具

HttpClient請(qǐng)求數(shù)據(jù)后是json字符串末患,需要我們自己把Json字符串反序列化為對(duì)象,我們會(huì)使用JacksonJson工具來(lái)實(shí)現(xiàn)锤窑。

JacksonJson是SpringMVC內(nèi)置的json處理工具璧针,其中有一個(gè)ObjectMapper類,可以方便的實(shí)現(xiàn)對(duì)json的處理:

對(duì)象轉(zhuǎn)json

// json處理工具
    private ObjectMapper mapper = new ObjectMapper();
    @Test
    public void testJson() throws JsonProcessingException {
        User user = new User();
        user.setId(8L);
        user.setAge(21);
        user.setName("柳巖");
        user.setUserName("liuyan");
        // 序列化
        String json = mapper.writeValueAsString(user);
        System.out.println("json = " + json);
    }

結(jié)果:

1526877496885.png

json轉(zhuǎn)普通對(duì)象

// json處理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("柳巖");
    user.setUserName("liuyan");
    // 序列化
    String json = mapper.writeValueAsString(user);

    // 反序列化渊啰,接收兩個(gè)參數(shù):json數(shù)據(jù)探橱,反序列化的目標(biāo)類字節(jié)碼
    User result = mapper.readValue(json, User.class);
    System.out.println("result = " + result);
}

結(jié)果:

1526877647406.png

json轉(zhuǎn)集合

json轉(zhuǎn)集合比較麻煩,因?yàn)槟銦o(wú)法同時(shí)把集合的class和元素的class同時(shí)傳遞到一個(gè)參數(shù)绘证。

因此Jackson做了一個(gè)類型工廠隧膏,用來(lái)解決這個(gè)問(wèn)題:

// json處理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("柳巖");
    user.setUserName("liuyan");

    // 序列化,得到對(duì)象集合的json字符串
    String json = mapper.writeValueAsString(Arrays.asList(user, user));

    // 反序列化,接收兩個(gè)參數(shù):json數(shù)據(jù)嚷那,反序列化的目標(biāo)類字節(jié)碼
    List<User> users = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, User.class));
    for (User u : users) {
        System.out.println("u = " + u);
    }
}

結(jié)果:

1526877995530.png

json轉(zhuǎn)任意復(fù)雜類型

當(dāng)對(duì)象泛型關(guān)系復(fù)雜時(shí)胞枕,類型工廠也不好使了。這個(gè)時(shí)候Jackson提供了TypeReference來(lái)接收類型泛型车酣,然后底層通過(guò)反射來(lái)獲取泛型上的具體類型曲稼。實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)換。

// json處理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("柳巖");
    user.setUserName("liuyan");

    // 序列化,得到對(duì)象集合的json字符串
    String json = mapper.writeValueAsString(Arrays.asList(user, user));

    // 反序列化湖员,接收兩個(gè)參數(shù):json數(shù)據(jù),反序列化的目標(biāo)類字節(jié)碼
    List<User> users = mapper.readValue(json, new TypeReference<List<User>>(){});
    for (User u : users) {
        System.out.println("u = " + u);
    }
}

結(jié)果:

1526877988488.png

3.3.Spring的RestTemplate

Spring提供了一個(gè)RestTemplate模板工具類瑞驱,對(duì)基于Http的客戶端進(jìn)行了封裝娘摔,并且實(shí)現(xiàn)了對(duì)象與json的序列化和反序列化,非常方便唤反。RestTemplate并沒(méi)有限定Http的客戶端類型凳寺,而是進(jìn)行了抽象,目前常用的3種都有支持:

  • HttpClient
  • OkHttp
  • JDK原生的URLConnection(默認(rèn)的)

首先在項(xiàng)目中注冊(cè)一個(gè)RestTemplate對(duì)象彤侍,可以在啟動(dòng)類位置注冊(cè):

@SpringBootApplication
public class HttpDemoApplication {

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

    @Bean
    public RestTemplate restTemplate() {
        // 默認(rèn)的RestTemplate肠缨,底層是走JDK的URLConnection方式。
        return new RestTemplate();
    }
}

在測(cè)試類中直接@Autowired注入:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void httpGet() {
        User user = this.restTemplate.getForObject("http://localhost/hello", User.class);
        System.out.println(user);
    }
}
  • 通過(guò)RestTemplate的getForObject()方法盏阶,傳遞url地址及實(shí)體類的字節(jié)碼晒奕,RestTemplate會(huì)自動(dòng)發(fā)起請(qǐng)求,接收響應(yīng)名斟,并且?guī)臀覀儗?duì)響應(yīng)結(jié)果進(jìn)行反序列化脑慧。
1525573702492.png

學(xué)習(xí)完了Http客戶端工具,接下來(lái)就可以正式學(xué)習(xí)微服務(wù)了砰盐。

4.初始SpringCloud

微服務(wù)是一種架構(gòu)方式闷袒,最終肯定需要技術(shù)架構(gòu)去實(shí)施。

微服務(wù)的實(shí)現(xiàn)方式很多岩梳,但是最火的莫過(guò)于Spring Cloud了囊骤。為什么晃择?

  • 后臺(tái)硬:作為Spring家族的一員,有整個(gè)Spring全家桶靠山也物,背景十分強(qiáng)大藕各。
  • 技術(shù)強(qiáng):Spring作為Java領(lǐng)域的前輩,可以說(shuō)是功力深厚焦除。有強(qiáng)力的技術(shù)團(tuán)隊(duì)支撐激况,一般人還真比不了
  • 群眾基礎(chǔ)好:可以說(shuō)大多數(shù)程序員的成長(zhǎng)都伴隨著Spring框架髓考,試問(wèn):現(xiàn)在有幾家公司開(kāi)發(fā)不用Spring徘禁?SpringCloud與Spring的各個(gè)框架無(wú)縫整合叼风,對(duì)大家來(lái)說(shuō)一切都是熟悉的配方眠屎,熟悉的味道竹揍。
  • 使用方便:相信大家都體會(huì)到了SpringBoot給我們開(kāi)發(fā)帶來(lái)的便利呕寝,而SpringCloud完全支持SpringBoot的開(kāi)發(fā)达舒,用很少的配置就能完成微服務(wù)框架的搭建

4.1.簡(jiǎn)介

SpringCloud是Spring旗下的項(xiàng)目之一只嚣,官網(wǎng)地址:http://projects.spring.io/spring-cloud/

Spring最擅長(zhǎng)的就是集成灿渴,把世界上最好的框架拿過(guò)來(lái)洛波,集成到自己的項(xiàng)目中。

SpringCloud也是一樣骚露,它將現(xiàn)在非常流行的一些技術(shù)整合到一起蹬挤,實(shí)現(xiàn)了諸如:配置管理,服務(wù)發(fā)現(xiàn)棘幸,智能路由焰扳,負(fù)載均衡,熔斷器误续,控制總線吨悍,集群狀態(tài)等等功能。其主要涉及的組件包括:

netflix

  • Eureka:注冊(cè)中心
  • Zuul:服務(wù)網(wǎng)關(guān)
  • Ribbon:負(fù)載均衡
  • Feign:服務(wù)調(diào)用
  • Hystix:熔斷器

以上只是其中一部分蹋嵌,架構(gòu)圖:

1525575656796.png

4.2.版本

SpringCloud的版本命名比較特殊育瓜,因?yàn)樗皇且粋€(gè)組件,而是許多組件的集合栽烂,它的命名是以A到Z的為首字母的一些單詞組成:

1525575903675.png

我們?cè)陧?xiàng)目中躏仇,會(huì)是以Finchley的版本。

其中包含的組件愕鼓,也都有各自的版本钙态,如下表:

Component Edgware.SR3 Finchley.RC1 Finchley.BUILD-SNAPSHOT
spring-cloud-aws 1.2.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-bus 1.3.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-cli 1.4.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-commons 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-contract 1.2.4.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-config 1.4.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-netflix 1.4.4.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-security 1.2.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-cloudfoundry 1.1.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-consul 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-sleuth 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-stream Ditmars.SR3 Elmhurst.RELEASE Elmhurst.BUILD-SNAPSHOT
spring-cloud-zookeeper 1.2.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-boot 1.5.10.RELEASE 2.0.1.RELEASE 2.0.0.BUILD-SNAPSHOT
spring-cloud-task 1.2.2.RELEASE 2.0.0.RC1 2.0.0.RELEASE
spring-cloud-vault 1.1.0.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-gateway 1.0.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-openfeign 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT

接下來(lái),我們就一一學(xué)習(xí)SpringCloud中的重要組件菇晃。

5.微服務(wù)場(chǎng)景模擬

首先册倒,我們需要模擬一個(gè)服務(wù)調(diào)用的場(chǎng)景。方便后面學(xué)習(xí)微服務(wù)架構(gòu)

5.1.服務(wù)提供者

我們新建一個(gè)項(xiàng)目磺送,對(duì)外提供查詢用戶的服務(wù)驻子。

5.1.1.Spring腳手架創(chuàng)建工程

借助于Spring提供的快速搭建工具:

1525576816916.png

填寫(xiě)項(xiàng)目信息:

1525576909381.png

添加web依賴:

1525576950842.png

添加mybatis依賴:

1525576999052.png

填寫(xiě)項(xiàng)目位置:

1525577029150.png

生成的項(xiàng)目結(jié)構(gòu):

1525577106711.png

依賴也已經(jīng)全部自動(dòng)引入:

<?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>com.leyou.demo</groupId>
    <artifactId>user-service-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>user-service-demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

當(dāng)然灿意,因?yàn)橐褂猛ㄓ胢apper,所以我們需要手動(dòng)加一條依賴:

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

非吵绾牵快捷扮途纭!

5.1.2.編寫(xiě)代碼

添加一個(gè)對(duì)外查詢的接口:

@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id) {
        return this.userService.queryById(id);
    }
}

Service:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User queryById(Long id) {
        return this.userMapper.selectByPrimaryKey(id);
    }
}

mapper:

@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User>{
}

實(shí)體類:

@Table(name = "tb_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 用戶名
    private String userName;

    // 密碼
    private String password;

    // 姓名
    private String name;

    // 年齡
    private Integer age;

    // 性別域慷,1男性荒辕,2女性
    private Integer sex;

    // 出生日期
    private Date birthday;

    // 創(chuàng)建時(shí)間
    private Date created;

    // 更新時(shí)間
    private Date updated;

    // 備注
    private String note;

   // 。犹褒。抵窒。省略getters和setters
}

屬性文件,這里我們采用了yaml語(yǔ)法,而不是properties:

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb01
    username: root
    password: 123
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
mybatis:
  type-aliases-package: com.leyou.userservice.pojo

項(xiàng)目結(jié)構(gòu):

1525577911331.png

5.1.3.啟動(dòng)并測(cè)試:

啟動(dòng)項(xiàng)目叠骑,訪問(wèn)接口:http://localhost:8081/user/7

1525593139364.png

5.2.服務(wù)調(diào)用者

5.2.1.創(chuàng)建工程

與上面類似李皇,這里不再贅述,需要注意的是宙枷,我們調(diào)用user-service的功能掉房,因此不需要mybatis相關(guān)依賴了。

pom:

<?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>com.leyou.demo</groupId>
    <artifactId>user-consumer-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>user-consumer-demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 添加OkHttp支持 -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.9.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

5.2.2.編寫(xiě)代碼

首先在啟動(dòng)類中注冊(cè)RestTemplate

@SpringBootApplication
public class UserConsumerDemoApplication {

    @Bean
    public RestTemplate restTemplate() {
        // 這次我們使用了OkHttp客戶端,只需要注入工廠即可
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }

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

然后編寫(xiě)UserDao慰丛,注意卓囚,這里不是調(diào)用mapper查數(shù)據(jù)庫(kù),而是通過(guò)RestTemplate遠(yuǎn)程查詢user-service-demo中的接口:

@Component
public class UserDao {

    @Autowired
    private RestTemplate restTemplate;

    public User queryUserById(Long id){
        String url = "http://localhost:8081/user/" + id;
        return this.restTemplate.getForObject(url, User.class);
    }
}

然后編寫(xiě)user-service璧帝,循環(huán)查詢UserDAO信息:

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public List<User> querUserByIds(List<Long> ids){
        List<User> users = new ArrayList<>();
        for (Long id : ids) {
            User user = this.userDao.queryUserById(id);
            users.add(user);
        }
        return users;
    }
}

編寫(xiě)controller:

@RestController
@RequestMapping("consume")
public class ConsumerController {

    @Autowired
    private UserService userService;

    @GetMapping
    public List<User> consume(@RequestParam("ids") List<Long> ids) {
        return this.userService.queryUserByIds(ids);
    }
}

5.2.3.啟動(dòng)測(cè)試:

因?yàn)槲覀儧](méi)有配置端口捍岳,那么默認(rèn)就是8080,我們?cè)L問(wèn):http://localhost:8080/consume?ids=6,7,8

1525594222408.png

一個(gè)簡(jiǎn)單的遠(yuǎn)程服務(wù)調(diào)用案例就實(shí)現(xiàn)了睬隶。

5.3.有沒(méi)有問(wèn)題?

簡(jiǎn)單回顧一下页徐,剛才我們寫(xiě)了什么:

  • use-service-demo:一個(gè)提供根據(jù)id查詢用戶的微服務(wù)
  • consumer-demo:一個(gè)服務(wù)調(diào)用者苏潜,通過(guò)RestTemplate遠(yuǎn)程調(diào)用user-service-demo

流程如下:

1525595012668.png

存在什么問(wèn)題?

  • 在consumer中变勇,我們把url地址硬編碼到了代碼中恤左,不方便后期維護(hù)
  • consumer需要記憶user-service的地址,如果出現(xiàn)變更搀绣,可能得不到通知飞袋,地址將失效
  • consumer不清楚user-service的狀態(tài),服務(wù)宕機(jī)也不知道
  • user-service只有1臺(tái)服務(wù)链患,不具備高可用性
  • 即便user-service形成集群巧鸭,consumer還需自己實(shí)現(xiàn)負(fù)載均衡

其實(shí)上面說(shuō)的問(wèn)題,概括一下就是分布式服務(wù)必然要面臨的問(wèn)題:

  • 服務(wù)管理
    • 如何自動(dòng)注冊(cè)和發(fā)現(xiàn)
    • 如何實(shí)現(xiàn)狀態(tài)監(jiān)管
    • 如何實(shí)現(xiàn)動(dòng)態(tài)路由
  • 服務(wù)如何實(shí)現(xiàn)負(fù)載均衡
  • 服務(wù)如何解決容災(zāi)問(wèn)題
  • 服務(wù)如何實(shí)現(xiàn)統(tǒng)一配置

以上的問(wèn)題麻捻,我們都將在SpringCloud中得到答案纲仍。

6.Eureka注冊(cè)中心

6.1.認(rèn)識(shí)Eureka

首先我們來(lái)解決第一問(wèn)題呀袱,服務(wù)的管理。

問(wèn)題分析

在剛才的案例中郑叠,user-service對(duì)外提供服務(wù)夜赵,需要對(duì)外暴露自己的地址。而consumer(調(diào)用者)需要記錄服務(wù)提供者的地址乡革。將來(lái)地址出現(xiàn)變更寇僧,還需要及時(shí)更新。這在服務(wù)較少的時(shí)候并不覺(jué)得有什么沸版,但是在現(xiàn)在日益復(fù)雜的互聯(lián)網(wǎng)環(huán)境嘁傀,一個(gè)項(xiàng)目肯定會(huì)拆分出十幾,甚至數(shù)十個(gè)微服務(wù)推穷。此時(shí)如果還人為管理地址心包,不僅開(kāi)發(fā)困難,將來(lái)測(cè)試馒铃、發(fā)布上線都會(huì)非常麻煩蟹腾,這與DevOps的思想是背道而馳的。

網(wǎng)約車

這就好比是 網(wǎng)約車出現(xiàn)以前区宇,人們出門(mén)叫車只能叫出租車娃殖。一些私家車想做出租卻沒(méi)有資格,被稱為黑車议谷。而很多人想要約車炉爆,但是無(wú)奈出租車太少,不方便卧晓。私家車很多卻不敢攔芬首,而且滿大街的車,誰(shuí)知道哪個(gè)才是愿意載人的逼裆。一個(gè)想要郁稍,一個(gè)愿意給,就是缺少引子胜宇,缺乏管理啊耀怜。

此時(shí)滴滴這樣的網(wǎng)約車平臺(tái)出現(xiàn)了,所有想載客的私家車全部到滴滴注冊(cè)桐愉,記錄你的車型(服務(wù)類型)财破,身份信息(聯(lián)系方式)。這樣提供服務(wù)的私家車从诲,在滴滴那里都能找到左痢,一目了然。

此時(shí)要叫車的人,只需要打開(kāi)APP抖锥,輸入你的目的地亿眠,選擇車型(服務(wù)類型),滴滴自動(dòng)安排一個(gè)符合需求的車到你面前磅废,為你服務(wù)纳像,完美!

Eureka做什么拯勉?

Eureka就好比是滴滴竟趾,負(fù)責(zé)管理、記錄服務(wù)提供者的信息宫峦。服務(wù)調(diào)用者無(wú)需自己尋找服務(wù)岔帽,而是把自己的需求告訴Eureka,然后Eureka會(huì)把符合你需求的服務(wù)告訴你导绷。

同時(shí)犀勒,服務(wù)提供方與Eureka之間通過(guò)“心跳”機(jī)制進(jìn)行監(jiān)控,當(dāng)某個(gè)服務(wù)提供方出現(xiàn)問(wèn)題妥曲,Eureka自然會(huì)把它從服務(wù)列表中剔除贾费。

這就實(shí)現(xiàn)了服務(wù)的自動(dòng)注冊(cè)、發(fā)現(xiàn)檐盟、狀態(tài)監(jiān)控褂萧。

6.2.原理圖

基本架構(gòu):

1525597885059.png
  • Eureka:就是服務(wù)注冊(cè)中心(可以是一個(gè)集群),對(duì)外暴露自己的地址
  • 提供者:?jiǎn)?dòng)后向Eureka注冊(cè)自己信息(地址葵萎,提供什么服務(wù))
  • 消費(fèi)者:向Eureka訂閱服務(wù)导犹,Eureka會(huì)將對(duì)應(yīng)服務(wù)的所有提供者地址列表發(fā)送給消費(fèi)者,并且定期更新
  • 心跳(續(xù)約):提供者定期通過(guò)http方式向Eureka刷新自己的狀態(tài)

6.3.入門(mén)案例

6.3.1.編寫(xiě)EurekaServer

接下來(lái)我們創(chuàng)建一個(gè)項(xiàng)目羡忘,啟動(dòng)一個(gè)EurekaServer:

依然使用spring提供的快速搭建工具:

1525598231170.png

選擇依賴:

1525598312368.png

完整的Pom文件:

<?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>com.leyou.demo</groupId>
    <artifactId>eureka-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <!-- SpringCloud版本谎痢,是最新的F系列 -->
        <spring-cloud.version>Finchley.RC1</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- Eureka服務(wù)端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!-- SpringCloud依賴,一定要放到dependencyManagement中卷雕,起到管理版本的作用即可 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

編寫(xiě)啟動(dòng)類:

@SpringBootApplication
@EnableEurekaServer // 聲明這個(gè)應(yīng)用是一個(gè)EurekaServer
public class EurekaDemoApplication {

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

編寫(xiě)配置:

server:
  port: 10086 # 端口
spring:
  application:
    name: eureka-server # 應(yīng)用名稱舶得,會(huì)在Eureka中顯示
eureka:
  client:
    register-with-eureka: false # 是否注冊(cè)自己的信息到EurekaServer,默認(rèn)是true
    fetch-registry: false # 是否拉取其它服務(wù)的信息爽蝴,默認(rèn)是true
    service-url: # EurekaServer的地址,現(xiàn)在是自己的地址纫骑,如果是集群蝎亚,需要加上其它Server的地址。
      defaultZone: http://127.0.0.1:${server.port}/eureka

啟動(dòng)服務(wù)先馆,并訪問(wèn):http://127.0.0.1:10086/eureka

1525604959508.png
1525605081129.png

6.3.2.將user-service注冊(cè)到Eureka

注冊(cè)服務(wù)发框,就是在服務(wù)上添加Eureka的客戶端依賴,客戶端代碼會(huì)自動(dòng)把服務(wù)注冊(cè)到EurekaServer中煤墙。

我們?cè)趗ser-service-demo中添加Eureka客戶端依賴:

先添加SpringCloud依賴:

<!-- SpringCloud的依賴 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.RC1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- Spring的倉(cāng)庫(kù)地址 -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

然后是Eureka客戶端:

<!-- Eureka客戶端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

在啟動(dòng)類上開(kāi)啟Eureka客戶端功能

通過(guò)添加@EnableDiscoveryClient來(lái)開(kāi)啟Eureka客戶端功能

@SpringBootApplication
@EnableDiscoveryClient // 開(kāi)啟EurekaClient功能
public class UserServiceDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceDemoApplication.class, args);
    }
}

編寫(xiě)配置

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb01
    username: root
    password: 123
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
  application:
    name: user-service # 應(yīng)用名稱
mybatis:
  type-aliases-package: com.leyou.userservice.pojo
eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true # 當(dāng)調(diào)用getHostname獲取實(shí)例的hostname時(shí)梅惯,返回ip而不是host名稱
    ip-address: 127.0.0.1 # 指定自己的ip信息宪拥,不指定的話會(huì)自己尋找

注意:

  • 這里我們添加了spring.application.name屬性來(lái)指定應(yīng)用名稱,將來(lái)會(huì)作為應(yīng)用的id使用铣减。
  • 不用指定register-with-eureka和fetch-registry她君,因?yàn)槟J(rèn)是true

重啟項(xiàng)目,訪問(wèn)Eureka監(jiān)控頁(yè)面查看

1525609225152.png

我們發(fā)現(xiàn)user-service服務(wù)已經(jīng)注冊(cè)成功了

6.3.3.消費(fèi)者從Eureka獲取服務(wù)

接下來(lái)我們修改consumer-demo葫哗,嘗試從EurekaServer獲取服務(wù)缔刹。

方法與消費(fèi)者類似,只需要在項(xiàng)目中添加EurekaClient依賴劣针,就可以通過(guò)服務(wù)名稱來(lái)獲取信息了校镐!

1)添加依賴:

先添加SpringCloud依賴:

<!-- SpringCloud的依賴 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.RC1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- Spring的倉(cāng)庫(kù)地址 -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

然后是Eureka客戶端:

<!-- Eureka客戶端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2)在啟動(dòng)類開(kāi)啟Eureka客戶端

@SpringBootApplication
@EnableDiscoveryClient // 開(kāi)啟Eureka客戶端
public class UserConsumerDemoApplication {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerDemoApplication.class, args);
    }
}

3)修改配置:

server:
  port: 8080
spring:
  application:
    name: consumer # 應(yīng)用名稱
eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true # 當(dāng)其它服務(wù)獲取地址時(shí)提供ip而不是hostname
    ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的話會(huì)自己尋找

4)修改代碼捺典,用DiscoveryClient類的方法鸟廓,根據(jù)服務(wù)名稱,獲取服務(wù)實(shí)例:

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;// Eureka客戶端襟己,可以獲取到服務(wù)實(shí)例信息

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        // String baseUrl = "http://localhost:8081/user/";
        // 根據(jù)服務(wù)名稱争群,獲取服務(wù)實(shí)例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因?yàn)橹挥幸粋€(gè)UserService,因此我們直接get(0)獲取
        ServiceInstance instance = instances.get(0);
        // 獲取ip和端口信息
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/";
        ids.forEach(id -> {
            // 我們測(cè)試多次查詢,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 每次間隔500毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
}

5)Debug跟蹤運(yùn)行:

1525613025086.png

生成的URL:

1525613051210.png

訪問(wèn)結(jié)果:

1525613160920.png

6.4.Eureka詳解

接下來(lái)我們?cè)敿?xì)講解Eureka的原理及配置饲梭。

6.4.1.基礎(chǔ)架構(gòu)

Eureka架構(gòu)中的三個(gè)核心角色:

  • 服務(wù)注冊(cè)中心

    Eureka的服務(wù)端應(yīng)用苛白,提供服務(wù)注冊(cè)和發(fā)現(xiàn)功能,就是剛剛我們建立的eureka-demo

  • 服務(wù)提供者

    提供服務(wù)的應(yīng)用退客,可以是SpringBoot應(yīng)用骏融,也可以是其它任意技術(shù)實(shí)現(xiàn),只要對(duì)外提供的是Rest風(fēng)格服務(wù)即可萌狂。本例中就是我們實(shí)現(xiàn)的user-service-demo

  • 服務(wù)消費(fèi)者

    消費(fèi)應(yīng)用從注冊(cè)中心獲取服務(wù)列表档玻,從而得知每個(gè)服務(wù)方的信息,知道去哪里調(diào)用服務(wù)方茫藏。本例中就是我們實(shí)現(xiàn)的consumer-demo

6.4.2.高可用的Eureka Server

Eureka Server即服務(wù)的注冊(cè)中心误趴,在剛才的案例中,我們只有一個(gè)EurekaServer务傲,事實(shí)上EurekaServer也可以是一個(gè)集群凉当,形成高可用的Eureka中心。

服務(wù)同步

多個(gè)Eureka Server之間也會(huì)互相注冊(cè)為服務(wù)售葡,當(dāng)服務(wù)提供者注冊(cè)到Eureka Server集群中的某個(gè)節(jié)點(diǎn)時(shí)看杭,該節(jié)點(diǎn)會(huì)把服務(wù)的信息同步給集群中的每個(gè)節(jié)點(diǎn),從而實(shí)現(xiàn)數(shù)據(jù)同步挟伙。因此楼雹,無(wú)論客戶端訪問(wèn)到Eureka Server集群中的任意一個(gè)節(jié)點(diǎn),都可以獲取到完整的服務(wù)列表信息。

動(dòng)手搭建高可用的EurekaServer

我們假設(shè)要搭建兩條EurekaServer的集群贮缅,端口分別為:10086和10087

1)我們修改原來(lái)的EurekaServer配置:

server:
  port: 10086 # 端口
spring:
  application:
    name: eureka-server # 應(yīng)用名稱榨咐,會(huì)在Eureka中顯示
eureka:
  client:
    service-url: # 配置其他Eureka服務(wù)的地址,而不是自己谴供,比如10087
      defaultZone: http://127.0.0.1:10087/eureka

所謂的高可用注冊(cè)中心块茁,其實(shí)就是把EurekaServer自己也作為一個(gè)服務(wù)進(jìn)行注冊(cè),這樣多個(gè)EurekaServer之間就能互相發(fā)現(xiàn)對(duì)方憔鬼,從而形成集群龟劲。因此我們做了以下修改:

  • 刪除了register-with-eureka=false和fetch-registry=false兩個(gè)配置。因?yàn)槟J(rèn)值是true轴或,這樣就會(huì)吧自己注冊(cè)到注冊(cè)中心了昌跌。
  • 把service-url的值改成了另外一臺(tái)EurekaServer的地址,而不是自己

2)另外一臺(tái)配置恰好相反:

server:
  port: 10087 # 端口
spring:
  application:
    name: eureka-server # 應(yīng)用名稱照雁,會(huì)在Eureka中顯示
eureka:
  client:
    service-url: # 配置其他Eureka服務(wù)的地址蚕愤,而不是自己,比如10087
      defaultZone: http://127.0.0.1:10086/eureka

注意:idea中一個(gè)應(yīng)用不能啟動(dòng)兩次饺蚊,我們需要重新配置一個(gè)啟動(dòng)器:

1525615070033.png
1525615095693.png
1525615026937.png

然后啟動(dòng)即可萍诱。

3)啟動(dòng)測(cè)試:

1525615165157.png

4)客戶端注冊(cè)服務(wù)到集群

因?yàn)镋urekaServer不止一個(gè),因此注冊(cè)服務(wù)的時(shí)候污呼,service-url參數(shù)需要變化:

eureka:
  client:
    service-url: # EurekaServer地址,多個(gè)地址以','隔開(kāi)
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

6.4.3.服務(wù)提供者

服務(wù)提供者要向EurekaServer注冊(cè)服務(wù)裕坊,并且完成服務(wù)續(xù)約等工作。

服務(wù)注冊(cè)

服務(wù)提供者在啟動(dòng)時(shí)燕酷,會(huì)檢測(cè)配置屬性中的:eureka.client.register-with-erueka=true參數(shù)是否正確籍凝,事實(shí)上默認(rèn)就是true。如果值確實(shí)為true苗缩,則會(huì)向EurekaServer發(fā)起一個(gè)Rest請(qǐng)求饵蒂,并攜帶自己的元數(shù)據(jù)信息,Eureka Server會(huì)把這些信息保存到一個(gè)雙層Map結(jié)構(gòu)中酱讶。第一層Map的Key就是服務(wù)名稱退盯,第二層Map的key是服務(wù)的實(shí)例id。

服務(wù)續(xù)約

在注冊(cè)服務(wù)完成以后泻肯,服務(wù)提供者會(huì)維持一個(gè)心跳(定時(shí)向EurekaServer發(fā)起Rest請(qǐng)求)渊迁,告訴EurekaServer:“我還活著”。這個(gè)我們稱為服務(wù)的續(xù)約(renew)灶挟;

有兩個(gè)重要參數(shù)可以修改服務(wù)續(xù)約的行為:

eureka:
  instance:
    lease-expiration-duration-in-seconds: 90
    lease-renewal-interval-in-seconds: 30
  • lease-renewal-interval-in-seconds:服務(wù)續(xù)約(renew)的間隔宫纬,默認(rèn)為30秒
  • lease-expiration-duration-in-seconds:服務(wù)失效時(shí)間,默認(rèn)值90秒

也就是說(shuō)膏萧,默認(rèn)情況下每個(gè)30秒服務(wù)會(huì)向注冊(cè)中心發(fā)送一次心跳,證明自己還活著。如果超過(guò)90秒沒(méi)有發(fā)送心跳榛泛,EurekaServer就會(huì)認(rèn)為該服務(wù)宕機(jī)蝌蹂,會(huì)從服務(wù)列表中移除,這兩個(gè)值在生產(chǎn)環(huán)境不要修改曹锨,默認(rèn)即可孤个。

但是在開(kāi)發(fā)時(shí),這個(gè)值有點(diǎn)太長(zhǎng)了沛简,經(jīng)常我們關(guān)掉一個(gè)服務(wù)齐鲤,會(huì)發(fā)現(xiàn)Eureka依然認(rèn)為服務(wù)在活著。所以我們?cè)陂_(kāi)發(fā)階段可以適當(dāng)調(diào)小椒楣。

eureka:
  instance:
    lease-expiration-duration-in-seconds: 10 # 10秒即過(guò)期
    lease-renewal-interval-in-seconds: 5 # 5秒一次心跳

實(shí)例id

先來(lái)看一下服務(wù)狀態(tài)信息:

在Eureka監(jiān)控頁(yè)面给郊,查看服務(wù)注冊(cè)信息:

1525617060656.png

在status一列中,顯示以下信息:

  • UP(1):代表現(xiàn)在是啟動(dòng)了1個(gè)示例捧灰,沒(méi)有集群
  • DESKTOP-2MVEC12:user-service:8081:是示例的名稱(instance-id)淆九,
    • 默認(rèn)格式是:${hostname} + ${spring.application.name} + ${server.port}
    • instance-id是區(qū)分同一服務(wù)的不同實(shí)例的唯一標(biāo)準(zhǔn),因此不能重復(fù)毛俏。

我們可以通過(guò)instance-id屬性來(lái)修改它的構(gòu)成:

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}

重啟服務(wù)再試試看:

1525617542081.png

6.4.4.服務(wù)消費(fèi)者

獲取服務(wù)列表

當(dāng)服務(wù)消費(fèi)者啟動(dòng)是炭庙,會(huì)檢測(cè)eureka.client.fetch-registry=true參數(shù)的值,如果為true煌寇,則會(huì)從Eureka Server服務(wù)的列表只讀備份焕蹄,然后緩存在本地。并且每隔30秒會(huì)重新獲取并更新數(shù)據(jù)阀溶。我們可以通過(guò)下面的參數(shù)來(lái)修改:

eureka:
  client:
    registry-fetch-interval-seconds: 5

生產(chǎn)環(huán)境中腻脏,我們不需要修改這個(gè)值。

但是為了開(kāi)發(fā)環(huán)境下淌哟,能夠快速得到服務(wù)的最新?tīng)顟B(tài)迹卢,我們可以將其設(shè)置小一點(diǎn)。

6.4.5.失效剔除和自我保護(hù)

失效剔除

有些時(shí)候徒仓,我們的服務(wù)提供方并不一定會(huì)正常下線腐碱,可能因?yàn)閮?nèi)存溢出、網(wǎng)絡(luò)故障等原因?qū)е路?wù)無(wú)法正常工作掉弛。Eureka Server需要將這樣的服務(wù)剔除出服務(wù)列表症见。因此它會(huì)開(kāi)啟一個(gè)定時(shí)任務(wù),每隔60秒對(duì)所有失效的服務(wù)(超過(guò)90秒未響應(yīng))進(jìn)行剔除殃饿。

可以通過(guò)eureka.server.eviction-interval-timer-in-ms參數(shù)對(duì)其進(jìn)行修改谋作,單位是毫秒,生成環(huán)境不要修改乎芳。

這個(gè)會(huì)對(duì)我們開(kāi)發(fā)帶來(lái)極大的不變遵蚜,你對(duì)服務(wù)重啟帖池,隔了60秒Eureka才反應(yīng)過(guò)來(lái)。開(kāi)發(fā)階段可以適當(dāng)調(diào)整吭净,比如10S

自我保護(hù)

我們關(guān)停一個(gè)服務(wù)睡汹,就會(huì)在Eureka面板看到一條警告:

1525618396076.png

這是觸發(fā)了Eureka的自我保護(hù)機(jī)制。當(dāng)一個(gè)服務(wù)未按時(shí)進(jìn)行心跳續(xù)約時(shí)寂殉,Eureka會(huì)統(tǒng)計(jì)最近15分鐘心跳失敗的服務(wù)實(shí)例的比例是否超過(guò)了85%囚巴。在生產(chǎn)環(huán)境下,因?yàn)榫W(wǎng)絡(luò)延遲等原因友扰,心跳失敗實(shí)例的比例很有可能超標(biāo)彤叉,但是此時(shí)就把服務(wù)剔除列表并不妥當(dāng),因?yàn)榉?wù)可能沒(méi)有宕機(jī)村怪。Eureka就會(huì)把當(dāng)前實(shí)例的注冊(cè)信息保護(hù)起來(lái)秽浇,不予剔除。生產(chǎn)環(huán)境下這很有效实愚,保證了大多數(shù)服務(wù)依然可用兼呵。

但是這給我們的開(kāi)發(fā)帶來(lái)了麻煩, 因此開(kāi)發(fā)階段我們都會(huì)關(guān)閉自我保護(hù)模式:

eureka:
  server:
    enable-self-preservation: false # 關(guān)閉自我保護(hù)模式(缺省為打開(kāi))
    eviction-interval-timer-in-ms: 1000 # 掃描失效服務(wù)的間隔時(shí)間(缺省為60*1000ms)

7.負(fù)載均衡Robbin

在剛才的案例中腊敲,我們啟動(dòng)了一個(gè)user-service击喂,然后通過(guò)DiscoveryClient來(lái)獲取服務(wù)實(shí)例信息,然后獲取ip和端口來(lái)訪問(wèn)碰辅。

但是實(shí)際環(huán)境中懂昂,我們往往會(huì)開(kāi)啟很多個(gè)user-service的集群。此時(shí)我們獲取的服務(wù)列表中就會(huì)有多個(gè)没宾,到底該訪問(wèn)哪一個(gè)呢凌彬?

一般這種情況下我們就需要編寫(xiě)負(fù)載均衡算法,在多個(gè)實(shí)例列表中進(jìn)行選擇循衰。

不過(guò)Eureka中已經(jīng)幫我們集成了負(fù)載均衡組件:Ribbon铲敛,簡(jiǎn)單修改代碼即可使用。

什么是Ribbon:

1525619257397.png

接下來(lái)会钝,我們就來(lái)使用Ribbon實(shí)現(xiàn)負(fù)載均衡伐蒋。

7.1.啟動(dòng)兩個(gè)服務(wù)實(shí)例

首先我們啟動(dòng)兩個(gè)user-service實(shí)例,一個(gè)8081迁酸,一個(gè)8082先鱼。

1525619515586.png

Eureka監(jiān)控面板:

1525619546904.png

7.2.開(kāi)啟負(fù)載均衡

因?yàn)镋ureka中已經(jīng)集成了Ribbon,所以我們無(wú)需引入新的依賴奸鬓。直接修改代碼:

在RestTemplate的配置方法上添加@LoadBalanced注解:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

修改調(diào)用方式焙畔,不再手動(dòng)獲取ip和端口,而是直接通過(guò)服務(wù)名稱調(diào)用:

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        // 地址直接寫(xiě)服務(wù)名稱即可
        String baseUrl = "http://user-service/user/";
        ids.forEach(id -> {
            // 我們測(cè)試多次查詢串远,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 每次間隔500毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
}

訪問(wèn)頁(yè)面宏多,查看結(jié)果:

1525620305704.png

完美儿惫!

7.3.源碼跟蹤

為什么我們只輸入了service名稱就可以訪問(wèn)了呢?之前還要獲取ip和端口绷落。

顯然有人幫我們根據(jù)service名稱姥闪,獲取到了服務(wù)實(shí)例的ip和端口。它就是LoadBalancerInterceptor

我們進(jìn)行源碼跟蹤:

1525620483637.png

繼續(xù)跟入execute方法:發(fā)現(xiàn)獲取了8082端口的服務(wù)

1525620787090.png

再跟下一次砌烁,發(fā)現(xiàn)獲取的是8081:

1525620835911.png

7.4.負(fù)載均衡策略

Ribbon默認(rèn)的負(fù)載均衡策略是簡(jiǎn)單的輪詢,我們可以測(cè)試一下:

編寫(xiě)測(cè)試類催式,在剛才的源碼中我們看到攔截中是使用RibbonLoadBalanceClient來(lái)進(jìn)行負(fù)載均衡的函喉,其中有一個(gè)choose方法,是這樣介紹的:

1525622320277.png

現(xiàn)在這個(gè)就是負(fù)載均衡獲取實(shí)例的方法荣月。

我們對(duì)注入這個(gè)類的對(duì)象管呵,然后對(duì)其測(cè)試:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserConsumerDemoApplication.class)
public class LoadBalanceTest {

    @Autowired
    RibbonLoadBalancerClient client;

    @Test
    public void test(){
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("user-service");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }
}

結(jié)果:

1525622357371.png

符合了我們的預(yù)期推測(cè),確實(shí)是輪詢方式哺窄。

我們是否可以修改負(fù)載均衡的策略呢捐下?

繼續(xù)跟蹤源碼,發(fā)現(xiàn)這么一段代碼:

1525622652849.png

我們看看這個(gè)rule是誰(shuí):

1525622699666.png

這里的rule默認(rèn)值是一個(gè)RoundRobinRule萌业,看類的介紹:

1525622754316.png

這不就是輪詢的意思嘛坷襟。

我們注意到,這個(gè)類其實(shí)是實(shí)現(xiàn)了接口IRule的生年,查看一下:

1525622817451.png

定義負(fù)載均衡的規(guī)則接口婴程。

它有以下實(shí)現(xiàn):

1525622876842.png

SpringBoot也幫我們提供了修改負(fù)載均衡規(guī)則的配置入口:

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式是:{服務(wù)名稱}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的實(shí)現(xiàn)類抱婉。

再次測(cè)試档叔,發(fā)現(xiàn)結(jié)果變成了隨機(jī):

1525623193949.png

7.5.重試機(jī)制

Eureka的服務(wù)治理強(qiáng)調(diào)了CAP原則中的AP,即可用性和可靠性蒸绩。它與Zookeeper這一類強(qiáng)調(diào)CP(一致性衙四,可靠性)的服務(wù)治理框架最大的區(qū)別在于:Eureka為了實(shí)現(xiàn)更高的服務(wù)可用性,犧牲了一定的一致性患亿,極端情況下它寧愿接收故障實(shí)例也不愿丟掉健康實(shí)例传蹈,正如我們上面所說(shuō)的自我保護(hù)機(jī)制。

但是窍育,此時(shí)如果我們調(diào)用了這些不正常的服務(wù)卡睦,調(diào)用就會(huì)失敗,從而導(dǎo)致其它服務(wù)不能正常工作漱抓!這顯然不是我們?cè)敢饪吹降摹?/p>

我們現(xiàn)在關(guān)閉一個(gè)user-service實(shí)例:

1525653565855.png

因?yàn)榉?wù)剔除的延遲表锻,consumer并不會(huì)立即得到最新的服務(wù)列表,此時(shí)再次訪問(wèn)你會(huì)得到錯(cuò)誤提示:

但是此時(shí)乞娄,8081服務(wù)其實(shí)是正常的瞬逊。

因此Spring Cloud 整合了Spring Retry 來(lái)增強(qiáng)RestTemplate的重試能力显歧,當(dāng)一次服務(wù)調(diào)用失敗后,不會(huì)立即拋出一次确镊,而是再次重試另一個(gè)服務(wù)士骤。

只需要簡(jiǎn)單配置即可實(shí)現(xiàn)Ribbon的重試:

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true # 開(kāi)啟Spring Cloud的重試功能
user-service:
  ribbon:
    ConnectTimeout: 250 # Ribbon的連接超時(shí)時(shí)間
    ReadTimeout: 1000 # Ribbon的數(shù)據(jù)讀取超時(shí)時(shí)間
    OkToRetryOnAllOperations: true # 是否對(duì)所有操作都進(jìn)行重試
    MaxAutoRetriesNextServer: 1 # 切換實(shí)例的重試次數(shù)
    MaxAutoRetries: 1 # 對(duì)當(dāng)前實(shí)例的重試次數(shù)

根據(jù)如上配置,當(dāng)訪問(wèn)到某個(gè)服務(wù)超時(shí)后蕾域,它會(huì)再次嘗試訪問(wèn)下一個(gè)服務(wù)實(shí)例拷肌,如果不行就再換一個(gè)實(shí)例,如果不行旨巷,則返回失敗巨缘。切換次數(shù)取決于MaxAutoRetriesNextServer參數(shù)的值

引入spring-retry依賴

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

我們重啟user-consumer-demo,測(cè)試采呐,發(fā)現(xiàn)即使user-service2宕機(jī)若锁,也能通過(guò)另一臺(tái)服務(wù)實(shí)例獲取到結(jié)果!

1525658269456.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斧吐,一起剝皮案震驚了整個(gè)濱河市又固,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌煤率,老刑警劉巖仰冠,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異涕侈,居然都是意外死亡沪停,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)裳涛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)木张,“玉大人,你說(shuō)我怎么就攤上這事端三∠侠瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵郊闯,是天一觀的道長(zhǎng)妻献。 經(jīng)常有香客問(wèn)我,道長(zhǎng)团赁,這世上最難降的妖魔是什么育拨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮欢摄,結(jié)果婚禮上瞬沦,老公的妹妹穿的比我還像新娘扶歪。我一直安慰自己震檩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布害捕。 她就那樣靜靜地躺著,像睡著了一般闷畸。 火紅的嫁衣襯著肌膚如雪尝盼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天佑菩,我揣著相機(jī)與錄音盾沫,去河邊找鬼。 笑死殿漠,一個(gè)胖子當(dāng)著我的面吹牛疮跑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凸舵,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼失尖!你這毒婦竟也來(lái)了啊奄?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掀潮,失蹤者是張志新(化名)和其女友劉穎菇夸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體仪吧,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庄新,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了薯鼠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片择诈。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖出皇,靈堂內(nèi)的尸體忽然破棺而出羞芍,到底是詐尸還是另有隱情,我是刑警寧澤郊艘,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布荷科,位于F島的核電站,受9級(jí)特大地震影響纱注,放射性物質(zhì)發(fā)生泄漏畏浆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一狞贱、第九天 我趴在偏房一處隱蔽的房頂上張望刻获。 院中可真熱鬧,春花似錦斥滤、人聲如沸将鸵。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)顶掉。三九已至草娜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間痒筒,已是汗流浹背宰闰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留簿透,地道東北人移袍。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像老充,于是被迫代替她去往敵國(guó)和親葡盗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • 微服務(wù)架構(gòu)模式的核心在于如何識(shí)別服務(wù)的邊界啡浊,設(shè)計(jì)出合理的微服務(wù)觅够。但如果要將微服務(wù)架構(gòu)運(yùn)用到生產(chǎn)項(xiàng)目上,并且能夠發(fā)揮...
    java菜閱讀 2,949評(píng)論 0 6
  • Demo 源碼下載 本案例為源碼分支的 eureka 分支 服務(wù)發(fā)現(xiàn)概述 服務(wù)發(fā)現(xiàn)機(jī)制是為了解決硬網(wǎng)絡(luò)編碼問(wèn)題巷嚣,服...
    聰明的奇瑞閱讀 5,190評(píng)論 0 13
  • 一廷粒、Eureka服務(wù)治理體系 1.1 服務(wù)治理 ??服務(wù)治理是微服務(wù)架構(gòu)中最為核心和基礎(chǔ)的模塊窘拯,它主要用來(lái)實(shí)現(xiàn)各個(gè)...
    CarlosBen閱讀 1,956評(píng)論 0 2
  • 今天的你和昨天的你一樣 重復(fù)著同樣的下落姿勢(shì) 連周身的光暈都不曾偷懶 我被你迷了眼 淚眼婆娑的盯著你 蒲公英終于有...
    等愛(ài)的貓閱讀 591評(píng)論 6 17
  • Swift 是 iOS 和 OS X 應(yīng)用開(kāi)發(fā)的一門(mén)新語(yǔ)言,是在 C 和 Objective-C 的基礎(chǔ)上提出的坝茎。...
    天空中的球閱讀 421評(píng)論 0 1