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)鍵俄烁。
存在的問(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)行拆分:
優(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)鍵由驹。
優(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)鍵
以前出現(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í)缺有一些差別:
微服務(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)圖:
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)一的序列化的方式妇穴。
- 1)采用何種網(wǎng)絡(luò)通訊協(xié)議虏肾?
- 要實(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):
- 像調(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)用流程圖:
想要了解詳細(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ò)瀏覽器推正,那么這些事情都需要自己去完成。
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
特點(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é)果:
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é)果:
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é)果:
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é)果:
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)行反序列化脑慧。
學(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)圖:
4.2.版本
SpringCloud的版本命名比較特殊育瓜,因?yàn)樗皇且粋€(gè)組件,而是許多組件的集合栽烂,它的命名是以A到Z的為首字母的一些單詞組成:
我們?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提供的快速搭建工具:
填寫(xiě)項(xiàng)目信息:
添加web依賴:
添加mybatis依賴:
填寫(xiě)項(xiàng)目位置:
生成的項(xiàng)目結(jié)構(gòu):
依賴也已經(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):
5.1.3.啟動(dòng)并測(cè)試:
啟動(dòng)項(xiàng)目叠骑,訪問(wèn)接口:http://localhost:8081/user/7
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
一個(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
流程如下:
存在什么問(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):
- 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提供的快速搭建工具:
選擇依賴:
完整的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
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è)面查看
我們發(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)行:
生成的URL:
訪問(wèn)結(jié)果:
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)器:
然后啟動(dòng)即可萍诱。
3)啟動(dòng)測(cè)試:
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è)信息:
在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ù)毛俏。
- 默認(rèn)格式是:
我們可以通過(guò)instance-id屬性來(lái)修改它的構(gòu)成:
eureka:
instance:
instance-id: ${spring.application.name}:${server.port}
重啟服務(wù)再試試看:
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面板看到一條警告:
這是觸發(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:
接下來(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先鱼。
Eureka監(jiān)控面板:
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é)果:
完美儿惫!
7.3.源碼跟蹤
為什么我們只輸入了service名稱就可以訪問(wèn)了呢?之前還要獲取ip和端口绷落。
顯然有人幫我們根據(jù)service名稱姥闪,獲取到了服務(wù)實(shí)例的ip和端口。它就是LoadBalancerInterceptor
我們進(jìn)行源碼跟蹤:
繼續(xù)跟入execute方法:發(fā)現(xiàn)獲取了8082端口的服務(wù)
再跟下一次砌烁,發(fā)現(xiàn)獲取的是8081:
7.4.負(fù)載均衡策略
Ribbon默認(rèn)的負(fù)載均衡策略是簡(jiǎn)單的輪詢,我們可以測(cè)試一下:
編寫(xiě)測(cè)試類催式,在剛才的源碼中我們看到攔截中是使用RibbonLoadBalanceClient來(lái)進(jìn)行負(fù)載均衡的函喉,其中有一個(gè)choose方法,是這樣介紹的:
現(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é)果:
符合了我們的預(yù)期推測(cè),確實(shí)是輪詢方式哺窄。
我們是否可以修改負(fù)載均衡的策略呢捐下?
繼續(xù)跟蹤源碼,發(fā)現(xiàn)這么一段代碼:
我們看看這個(gè)rule是誰(shuí):
這里的rule默認(rèn)值是一個(gè)RoundRobinRule
萌业,看類的介紹:
這不就是輪詢的意思嘛坷襟。
我們注意到,這個(gè)類其實(shí)是實(shí)現(xiàn)了接口IRule的生年,查看一下:
定義負(fù)載均衡的規(guī)則接口婴程。
它有以下實(shí)現(xiàn):
SpringBoot也幫我們提供了修改負(fù)載均衡規(guī)則的配置入口:
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
格式是:{服務(wù)名稱}.ribbon.NFLoadBalancerRuleClassName
,值就是IRule的實(shí)現(xiàn)類抱婉。
再次測(cè)試档叔,發(fā)現(xiàn)結(jié)果變成了隨機(jī):
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í)例:
因?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é)果!