javascript
Spring Cloud入门,看这篇就够了!
點(diǎn)擊▲關(guān)注 “中生代技術(shù)”? ?給公眾號(hào)標(biāo)星置頂
更多精彩 第一時(shí)間直達(dá)
概述??
首先我給大家看一張圖,如果大家對(duì)這張圖有些地方不太理解的話,我希望你們看完我這篇文章會(huì)恍然大悟。
什么是 Spring cloud
構(gòu)建分布式系統(tǒng)不需要復(fù)雜和容易出錯(cuò)。Spring Cloud 為最常見的分布式系統(tǒng)模式提供了一種簡(jiǎn)單且易于接受的編程模型,幫助開發(fā)人員構(gòu)建有彈性的、可靠的、協(xié)調(diào)的應(yīng)用程序。Spring Cloud 構(gòu)建于 Spring Boot 之上,使得開發(fā)者很容易入手并快速應(yīng)用于生產(chǎn)中。
官方果然官方,介紹都這么有板有眼的。
我所理解的Spring Cloud就是微服務(wù)系統(tǒng)架構(gòu)的一站式解決方案,在平時(shí)我們構(gòu)建微服務(wù)的過程中需要做如服務(wù)發(fā)現(xiàn)注冊(cè)、配置中心、消息總線、負(fù)載均衡、斷路器、數(shù)據(jù)監(jiān)控等操作,而 Spring Cloud 為我們提供了一套簡(jiǎn)易的編程模型,使我們能在 Spring Boot 的基礎(chǔ)上輕松地實(shí)現(xiàn)微服務(wù)項(xiàng)目的構(gòu)建。
Spring Cloud 的版本
當(dāng)然這個(gè)只是個(gè)題外話。
Spring Cloud 的版本號(hào)并不是我們通常見的數(shù)字版本號(hào),而是一些很奇怪的單詞。這些單詞均為英國倫敦地鐵站的站名。同時(shí)根據(jù)字母表的順序來對(duì)應(yīng)版本時(shí)間順序,比如:最早 的 Release 版本 Angel,第二個(gè) Release 版本 Brixton(英國地名),然后是 Camden、 Dalston、Edgware、Finchley、Greenwich、Hoxton。
Spring Cloud 的服務(wù)發(fā)現(xiàn)框架——Eureka
Eureka是基于REST(代表性狀態(tài)轉(zhuǎn)移)的服務(wù),主要在AWS云中用于定位服務(wù),以實(shí)現(xiàn)負(fù)載均衡和中間層服務(wù)器的故障轉(zhuǎn)移。我們稱此服務(wù)為Eureka服務(wù)器。Eureka還帶有一個(gè)基于Java的客戶端組件Eureka Client,它使與服務(wù)的交互變得更加容易。客戶端還具有一個(gè)內(nèi)置的負(fù)載平衡器,可以執(zhí)行基本的循環(huán)負(fù)載平衡。在Netflix,更復(fù)雜的負(fù)載均衡器將Eureka包裝起來,以基于流量,資源使用,錯(cuò)誤條件等多種因素提供加權(quán)負(fù)載均衡,以提供出色的彈性。
總的來說,Eureka 就是一個(gè)服務(wù)發(fā)現(xiàn)框架。何為服務(wù),何又為發(fā)現(xiàn)呢?
舉一個(gè)生活中的例子,就比如我們平時(shí)租房子找中介的事情。
在沒有中介的時(shí)候我們需要一個(gè)一個(gè)去尋找是否有房屋要出租的房東,這顯然會(huì)非常的費(fèi)力,一你找憑一個(gè)人的能力是找不到很多房源供你選擇,再者你也懶得這么找下去(找了這么久,沒有合適的只能將就)。這里的我們就相當(dāng)于微服務(wù)中的Consumer,而那些房東就相當(dāng)于微服務(wù)中的Provider。消費(fèi)者Consumer需要調(diào)用提供者Provider提供的一些服務(wù),就像我們現(xiàn)在需要租他們的房子一樣。
但是如果只是租客和房東之間進(jìn)行尋找的話,他們的效率是很低的,房東找不到租客賺不到錢,租客找不到房東住不了房。所以,后來房東肯定就想到了廣播自己的房源信息(比如在街邊貼貼小廣告),這樣對(duì)于房東來說已經(jīng)完成他的任務(wù)(將房源公布出去),但是有兩個(gè)問題就出現(xiàn)了。
第一、其他不是租客的都能收到這種租房消息,這在現(xiàn)實(shí)世界沒什么,但是在計(jì)算機(jī)的世界中就會(huì)出現(xiàn)資源消耗的問題了。第二、租客這樣還是很難找到你,試想一下我需要租房,我還需要東一個(gè)西一個(gè)地去找街邊小廣告,麻不麻煩?
那怎么辦呢?我們當(dāng)然不會(huì)那么傻乎乎的,第一時(shí)間就是去找中介呀,它為我們提供了統(tǒng)一房源的地方,我們消費(fèi)者只需要跑到它那里去找就行了。而對(duì)于房東來說,他們也只需要把房源在中介那里發(fā)布就行了。
那么現(xiàn)在,我們的模式就是這樣的了。
但是,這個(gè)時(shí)候還會(huì)出現(xiàn)一些問題。
房東注冊(cè)之后如果不想賣房子了怎么辦?我們是不是需要讓房東定期續(xù)約?如果房東不進(jìn)行續(xù)約是不是要將他們從中介那里的注冊(cè)列表中移除。
租客是不是也要進(jìn)行注冊(cè)呢?不然合同乙方怎么來呢?
中介可不可以做連鎖店呢?如果這一個(gè)店因?yàn)槟承┎豢煽沽σ蛩囟鵁o法使用,那么我們是否可以換一個(gè)連鎖店呢?
針對(duì)上面的問題我們來重新構(gòu)建一下上面的模式圖。
好了,舉完這個(gè)例子我們就可以來看關(guān)于 Eureka 的一些基礎(chǔ)概念了,你會(huì)發(fā)現(xiàn)這東西理解起來怎么這么簡(jiǎn)單。
服務(wù)發(fā)現(xiàn):其實(shí)就是一個(gè)“中介”,整個(gè)過程中有三個(gè)角色:服務(wù)提供者(出租房子的)、服務(wù)消費(fèi)者(租客)、服務(wù)中介(房屋中介)。
服務(wù)提供者:就是提供一些自己能夠執(zhí)行的一些服務(wù)給外界。
服務(wù)消費(fèi)者:就是需要使用一些服務(wù)的“用戶”。
服務(wù)中介:其實(shí)就是服務(wù)提供者和服務(wù)消費(fèi)者之間的“橋梁”,服務(wù)提供者可以把自己注冊(cè)到服務(wù)中介那里,而服務(wù)消費(fèi)者如需要消費(fèi)一些服務(wù)(使用一些功能)就可以在服務(wù)中介中尋找注冊(cè)在服務(wù)中介的服務(wù)提供者。
服務(wù)注冊(cè) Register:
官方解釋:當(dāng) Eureka 客戶端向[Eureka] Server注冊(cè)時(shí),它提供自身的元數(shù)據(jù),比如IP地址、端口,運(yùn)行狀況指示符URL,主頁等。
結(jié)合中介理解:房東 (提供者[Eureka] Client Provider)在中介 (服務(wù)器[Eureka] Server) 那里登記房屋的信息,比如面積,價(jià)格,地段等等(元數(shù)據(jù)metaData)。
服務(wù)續(xù)約 Renew:
官方解釋:Eureka 客戶會(huì)每隔30秒(默認(rèn)情況下)發(fā)送一次心跳來續(xù)約。通過續(xù)約來告知[Eureka] Server該 Eureka 客戶仍然存在,沒有出現(xiàn)問題。正常情況下,如果[Eureka] Server在90秒沒有收到 Eureka 客戶的續(xù)約,它會(huì)將實(shí)例從其注冊(cè)表中刪除。
結(jié)合中介理解:房東 (提供者[Eureka] Client Provider) 定期告訴中介 (服務(wù)器[Eureka] Server) 我的房子還租(續(xù)約) ,中介 (服務(wù)器[Eureka] Server) 收到之后繼續(xù)保留房屋的信息。
獲取注冊(cè)列表信息 Fetch Registries:
官方解釋:Eureka 客戶端從服務(wù)器獲取注冊(cè)表信息,并將其緩存在本地。客戶端會(huì)使用該信息查找其他服務(wù),從而進(jìn)行遠(yuǎn)程調(diào)用。該注冊(cè)列表信息定期(每30秒鐘)更新一次。每次返回注冊(cè)列表信息可能與 Eureka 客戶端的緩存信息不同, Eureka 客戶端自動(dòng)處理。如果由于某種原因?qū)е伦?cè)列表信息不能及時(shí)匹配,Eureka 客戶端則會(huì)重新獲取整個(gè)注冊(cè)表信息。
Eureka 服務(wù)器緩存注冊(cè)列表信息,整個(gè)注冊(cè)表以及每個(gè)應(yīng)用程序的信息進(jìn)行了壓縮,壓縮內(nèi)容和沒有壓縮的內(nèi)容完全相同。Eureka 客戶端和 Eureka 服務(wù)器可以使用JSON / XML格式進(jìn)行通訊。在默認(rèn)的情況下 Eureka 客戶端使用壓縮JSON格式來獲取注冊(cè)列表的信息。
結(jié)合中介理解:租客(消費(fèi)者[Eureka] Client Consumer) 去中介 (服務(wù)器[Eureka] Server) 那里獲取所有的房屋信息列表 (客戶端列表[Eureka] Client List) ,而且租客為了獲取最新的信息會(huì)定期向中介 (服務(wù)器[Eureka] Server) 那里獲取并更新本地列表。
服務(wù)下線 Cancel:
官方解釋:Eureka客戶端在程序關(guān)閉時(shí)向Eureka服務(wù)器發(fā)送取消請(qǐng)求。發(fā)送請(qǐng)求后,該客戶端實(shí)例信息將從服務(wù)器的實(shí)例注冊(cè)表中刪除。該下線請(qǐng)求不會(huì)自動(dòng)完成,它需要調(diào)用以下內(nèi)容:DiscoveryManager.getInstance().shutdownComponent();
結(jié)合中介理解:房東 (提供者[Eureka] Client Provider) 告訴中介 ?(服務(wù)器[Eureka] Server) 我的房子不租了,中介之后就將注冊(cè)的房屋信息從列表中剔除。
服務(wù)剔除 Eviction:
官方解釋:在默認(rèn)的情況下,當(dāng)Eureka客戶端連續(xù)90秒(3個(gè)續(xù)約周期)沒有向Eureka服務(wù)器發(fā)送服務(wù)續(xù)約,即心跳,Eureka服務(wù)器會(huì)將該服務(wù)實(shí)例從服務(wù)注冊(cè)列表刪除,即服務(wù)剔除。
結(jié)合中介理解:房東(提供者[Eureka] Client Provider) 會(huì)定期聯(lián)系 中介 ?(服務(wù)器[Eureka] Server) 告訴他我的房子還租(續(xù)約),如果中介 ?(服務(wù)器[Eureka] Server) 長(zhǎng)時(shí)間沒收到提供者的信息,那么中介會(huì)將他的房屋信息給下架(服務(wù)剔除)。
下面就是Netflix官方給出的 Eureka 架構(gòu)圖,你會(huì)發(fā)現(xiàn)和我們前面畫的中介圖別無二致。
當(dāng)然,可以充當(dāng)服務(wù)發(fā)現(xiàn)的組件有很多:Zookeeper,Consul, Eureka 等。
更多關(guān)于 Eureka 的知識(shí)(自我保護(hù),初始注冊(cè)策略等等)可以自己去官網(wǎng)查看,或者查看我的另一篇文章 [深入理解 Eureka]
負(fù)載均衡之 Ribbon
什么是 RestTemplate?
不是講Ribbon么?怎么扯到了RestTemplate了?你先別急,聽我慢慢道來。更多的關(guān)于設(shè)計(jì),原理知識(shí)點(diǎn)的問題。
我就說一句!RestTemplate是Spring提供的一個(gè)訪問Http服務(wù)的客戶端類,怎么說呢?就是微服務(wù)之間的調(diào)用是使用的RestTemplate。比如這個(gè)時(shí)候我們 消費(fèi)者B 需要調(diào)用 提供者A 所提供的服務(wù)我們就需要這么寫。如我下面的偽代碼
@Autowired?? private?RestTemplate?restTemplate;?? //?這里是提供者A的ip地址,但是如果使用了?Eureka?那么就應(yīng)該是提供者A的名稱?? private?static?final?String?SERVICE_PROVIDER_A?=?"http://localhost:8081";??@PostMapping("/judge")?? public?boolean?judge(@RequestBody?Request?request)?{??String?url?=?SERVICE_PROVIDER_A?+?"/service1";??return?restTemplate.postForObject(url,?request,?Boolean.class);?? }??如果你對(duì)源碼感興趣的話,你會(huì)發(fā)現(xiàn)上面我們所講的 Eureka 框架中的注冊(cè)、續(xù)約等,底層都是使用的RestTemplate。
為什么需要 Ribbon?
Ribbon 是Netflix公司的一個(gè)開源的負(fù)載均衡 項(xiàng)目,是一個(gè)客戶端/進(jìn)程內(nèi)負(fù)載均衡器,運(yùn)行在消費(fèi)者端。
我們?cè)倥e個(gè)例子,比如我們?cè)O(shè)計(jì)了一個(gè)秒殺系統(tǒng),但是為了整個(gè)系統(tǒng)的高可用,我們需要將這個(gè)系統(tǒng)做一個(gè)集群,而這個(gè)時(shí)候我們消費(fèi)者就可以擁有多個(gè)秒殺系統(tǒng)的調(diào)用途徑了,如下圖。
如果這個(gè)時(shí)候我們沒有進(jìn)行一些均衡操作,如果我們對(duì)秒殺系統(tǒng)1進(jìn)行大量的調(diào)用,而另外兩個(gè)基本不請(qǐng)求,就會(huì)導(dǎo)致秒殺系統(tǒng)1崩潰,而另外兩個(gè)就變成了傀儡,那么我們?yōu)槭裁催€要做集群,我們高可用體現(xiàn)的意義又在哪呢?
推薦閱讀:分布式與集群的區(qū)別到底是什么?
所以Ribbon出現(xiàn)了,注意我們上面加粗的幾個(gè)字——運(yùn)行在消費(fèi)者端。指的是,Ribbon是運(yùn)行在消費(fèi)者端的負(fù)載均衡器,如下圖。
其工作原理就是Consumer端獲取到了所有的服務(wù)列表之后,在其內(nèi)部使用負(fù)載均衡算法,進(jìn)行對(duì)多個(gè)系統(tǒng)的調(diào)用。
Nginx 和 Ribbon 的對(duì)比
提到負(fù)載均衡就不得不提到大名鼎鼎的Nignx了,而和Ribbon不同的是,它是一種集中式的負(fù)載均衡器。
何為集中式呢?簡(jiǎn)單理解就是將所有請(qǐng)求都集中起來,然后再進(jìn)行負(fù)載均衡。如下圖。
我們可以看到Nginx是接收了所有的請(qǐng)求進(jìn)行負(fù)載均衡的,而對(duì)于Ribbon來說它是在消費(fèi)者端進(jìn)行的負(fù)載均衡。如下圖。
請(qǐng)注意Request的位置,在Nginx中請(qǐng)求是先進(jìn)入負(fù)載均衡器,而在Ribbon中是先在客戶端進(jìn)行負(fù)載均衡才進(jìn)行請(qǐng)求的。
Ribbon 的幾種負(fù)載均衡算法
負(fù)載均衡,不管Nginx還是Ribbon都需要其算法的支持,如果我沒記錯(cuò)的話Nginx使用的是 輪詢和加權(quán)輪詢算法。而在Ribbon中有更多的負(fù)載均衡調(diào)度算法,其默認(rèn)是使用的RoundRobinRule輪詢策略。
RoundRobinRule:輪詢策略。Ribbon默認(rèn)采用的策略。若經(jīng)過一輪輪詢沒有找到可用的provider,其最多輪詢 10 輪。若最終還沒有找到,則返回 null。
RandomRule: 隨機(jī)策略,從所有可用的 provider 中隨機(jī)選擇一個(gè)。
RetryRule: 重試策略。先按照 RoundRobinRule 策略獲取 provider,若獲取失敗,則在指定的時(shí)限內(nèi)重試。默認(rèn)的時(shí)限為 500 毫秒。
還有很多,這里不一一舉例子了,你最需要知道的是默認(rèn)輪詢算法,并且可以更換默認(rèn)的負(fù)載均衡算法,只需要在配置文件中做出修改就行。
providerName:??ribbon:??NFLoadBalancerRuleClassName:?com.netflix.loadbalancer.RandomRule??當(dāng)然,在Ribbon中你還可以自定義負(fù)載均衡算法,你只需要實(shí)現(xiàn)IRule接口,然后修改配置文件或者自定義Java Config類。
什么是 Open Feign
有了 Eureka,RestTemplate,Ribbon我們就可以愉快地進(jìn)行服務(wù)間的調(diào)用了,但是使用RestTemplate還是不方便,我們每次都要進(jìn)行這樣的調(diào)用。
@Autowired?? private?RestTemplate?restTemplate;?? //?這里是提供者A的ip地址,但是如果使用了?Eureka?那么就應(yīng)該是提供者A的名稱?? private?static?final?String?SERVICE_PROVIDER_A?=?"http://localhost:8081";??@PostMapping("/judge")?? public?boolean?judge(@RequestBody?Request?request)?{??String?url?=?SERVICE_PROVIDER_A?+?"/service1";??//?是不是太麻煩了???每次都要?url、請(qǐng)求、返回類型的??return?restTemplate.postForObject(url,?request,?Boolean.class);?? }??這樣每次都調(diào)用RestRemplate的API是否太麻煩,我能不能像調(diào)用原來代碼一樣進(jìn)行各個(gè)服務(wù)間的調(diào)用呢?
聰明的小朋友肯定想到了,那就用映射呀,就像域名和IP地址的映射。我們可以將被調(diào)用的服務(wù)代碼映射到消費(fèi)者端,這樣我們就可以“無縫開發(fā)”啦。
OpenFeign 也是運(yùn)行在消費(fèi)者端的,使用 Ribbon 進(jìn)行負(fù)載均衡,所以 OpenFeign 直接內(nèi)置了 Ribbon。
在導(dǎo)入了Open Feign之后我們就可以進(jìn)行愉快編寫 ?Consumer端代碼了。
//?使用?@FeignClient?注解來指定提供者的名字?? @FeignClient(value?=?"eureka-client-provider")?? public?interface?TestClient?{??//?這里一定要注意需要使用的是提供者那端的請(qǐng)求相對(duì)路徑,這里就相當(dāng)于映射了??@RequestMapping(value?=?"/provider/xxx",??method?=?RequestMethod.POST)??CommonResponse<List<Plan>>?getPlans(@RequestBody?planGetRequest?request);?? }??然后我們?cè)贑ontroller就可以像原來調(diào)用Service層代碼一樣調(diào)用它了。
@RestController?? public?class?TestController?{??//?這里就相當(dāng)于原來自動(dòng)注入的?Service??@Autowired??private?TestClient?testClient;??//?controller?調(diào)用?service?層代碼??@RequestMapping(value?=?"/test",?method?=?RequestMethod.POST)??public?CommonResponse<List<Plan>>?get(@RequestBody?planGetRequest?request)?{??return?testClient.getPlans(request);??}?? }??必不可少的 Hystrix
什么是 Hystrix之熔斷和降級(jí)
在分布式環(huán)境中,不可避免地會(huì)有許多服務(wù)依賴項(xiàng)中的某些失敗。Hystrix是一個(gè)庫,可通過添加等待時(shí)間容限和容錯(cuò)邏輯來幫助您控制這些分布式服務(wù)之間的交互。Hystrix通過隔離服務(wù)之間的訪問點(diǎn),停止服務(wù)之間的級(jí)聯(lián)故障并提供后備選項(xiàng)來實(shí)現(xiàn)此目的,所有這些都可以提高系統(tǒng)的整體彈性。
總體來說[Hystrix]就是一個(gè)能進(jìn)行熔斷和降級(jí)的庫,通過使用它能提高整個(gè)系統(tǒng)的彈性。
那么什么是 熔斷和降級(jí) 呢?再舉個(gè)例子,此時(shí)我們整個(gè)微服務(wù)系統(tǒng)是這樣的。服務(wù)A調(diào)用了服務(wù)B,服務(wù)B再調(diào)用了服務(wù)C,但是因?yàn)槟承┰?#xff0c;服務(wù)C頂不住了,這個(gè)時(shí)候大量請(qǐng)求會(huì)在服務(wù)C阻塞。
服務(wù)C阻塞了還好,畢竟只是一個(gè)系統(tǒng)崩潰了。但是請(qǐng)注意這個(gè)時(shí)候因?yàn)榉?wù)C不能返回響應(yīng),那么服務(wù)B調(diào)用服務(wù)C的的請(qǐng)求就會(huì)阻塞,同理服務(wù)B阻塞了,那么服務(wù)A也會(huì)阻塞崩潰。
請(qǐng)注意,為什么阻塞會(huì)崩潰。因?yàn)檫@些請(qǐng)求會(huì)消耗占用系統(tǒng)的線程、IO 等資源,消耗完你這個(gè)系統(tǒng)服務(wù)器不就崩了么。
這就叫服務(wù)雪崩。媽耶,上面兩個(gè)熔斷和降級(jí)你都沒給我解釋清楚,你現(xiàn)在又給我扯什么服務(wù)雪崩?
不聽我也得講下去!
所謂熔斷就是服務(wù)雪崩的一種有效解決方案。當(dāng)指定時(shí)間窗內(nèi)的請(qǐng)求失敗率達(dá)到設(shè)定閾值時(shí),系統(tǒng)將通過斷路器直接將此請(qǐng)求鏈路斷開。
也就是我們上面服務(wù)B調(diào)用服務(wù)C在指定時(shí)間窗內(nèi),調(diào)用的失敗率到達(dá)了一定的值,那么[Hystrix]則會(huì)自動(dòng)將 服務(wù)B與C 之間的請(qǐng)求都斷了,以免導(dǎo)致服務(wù)雪崩現(xiàn)象。
其實(shí)這里所講的熔斷就是指的[Hystrix]中的斷路器模式,你可以使用簡(jiǎn)單的@[Hystrix]Command注解來標(biāo)注某個(gè)方法,這樣[Hystrix]就會(huì)使用斷路器來“包裝”這個(gè)方法,每當(dāng)調(diào)用時(shí)間超過指定時(shí)間時(shí)(默認(rèn)為1000ms),斷路器將會(huì)中斷對(duì)這個(gè)方法的調(diào)用。
當(dāng)然你可以對(duì)這個(gè)注解的很多屬性進(jìn)行設(shè)置,比如設(shè)置超時(shí)時(shí)間,像這樣。
@HystrixCommand(??commandProperties?=?{@HystrixProperty(name?=?"execution.isolation.thread.timeoutInMilliseconds",value?=?"1200")}?? )?? public?List<Xxx>?getXxxx()?{??//?...省略代碼邏輯?? }??但是,我查閱了一些博客,發(fā)現(xiàn)他們都將熔斷和降級(jí)的概念混淆了,以我的理解,降級(jí)是為了更好的用戶體驗(yàn),當(dāng)一個(gè)方法調(diào)用異常時(shí),通過執(zhí)行另一種代碼邏輯來給用戶友好的回復(fù)。這也就對(duì)應(yīng)著[Hystrix]的后備處理模式。你可以通過設(shè)置fallbackMethod來給一個(gè)方法設(shè)置備用的代碼邏輯。比如這個(gè)時(shí)候有一個(gè)熱點(diǎn)新聞出現(xiàn)了,我們會(huì)推薦給用戶查看詳情,然后用戶會(huì)通過id去查詢新聞的詳情,但是因?yàn)檫@條新聞太火了(比如最近什么*易對(duì)吧),大量用戶同時(shí)訪問可能會(huì)導(dǎo)致系統(tǒng)崩潰,那么我們就進(jìn)行服務(wù)降級(jí),一些請(qǐng)求會(huì)做一些降級(jí)處理比如當(dāng)前人數(shù)太多請(qǐng)稍后查看等等。
//?指定了后備方法調(diào)用?? @HystrixCommand(fallbackMethod?=?"getHystrixNews")?? @GetMapping("/get/news")?? public?News?getNews(@PathVariable("id")?int?id)?{??//?調(diào)用新聞系統(tǒng)的獲取新聞api?代碼邏輯省略?? }?? //?? public?News?getHystrixNews(@PathVariable("id")?int?id)?{??//?做服務(wù)降級(jí)??//?返回當(dāng)前人數(shù)太多,請(qǐng)稍后查看?? }??什么是Hystrix之其他
我在閱讀 《Spring微服務(wù)實(shí)戰(zhàn)》這本書的時(shí)候還接觸到了一個(gè)艙壁模式的概念。在不使用艙壁模式的情況下,服務(wù)A調(diào)用服務(wù)B,這種調(diào)用默認(rèn)的是使用同一批線程來執(zhí)行的,而在一個(gè)服務(wù)出現(xiàn)性能問題的時(shí)候,就會(huì)出現(xiàn)所有線程被刷爆并等待處理工作,同時(shí)阻塞新請(qǐng)求,最終導(dǎo)致程序崩潰。而艙壁模式會(huì)將遠(yuǎn)程資源調(diào)用隔離在他們自己的線程池中,以便可以控制單個(gè)表現(xiàn)不佳的服務(wù),而不會(huì)使該程序崩潰。
具體其原理我推薦大家自己去了解一下,本篇文章中對(duì)艙壁模式不做過多解釋。當(dāng)然還有[Hystrix]儀表盤,它是用來實(shí)時(shí)監(jiān)控****[Hystrix]的各項(xiàng)指標(biāo)信息的,這里我將這個(gè)問題也拋出去,希望有不了解的可以自己去搜索一下。
微服務(wù)網(wǎng)關(guān)——Zuul
ZUUL 是從設(shè)備和 web 站點(diǎn)到 Netflix 流應(yīng)用后端的所有請(qǐng)求的前門。作為邊界服務(wù)應(yīng)用,ZUUL 是為了實(shí)現(xiàn)動(dòng)態(tài)路由、監(jiān)視、彈性和安全性而構(gòu)建的。它還具有根據(jù)情況將請(qǐng)求路由到多個(gè) Amazon Auto Scaling Groups(亞馬遜自動(dòng)縮放組,亞馬遜的一種云計(jì)算方式) 的能力
在上面我們學(xué)習(xí)了 Eureka 之后我們知道了服務(wù)提供者是消費(fèi)者通過[Eureka] Server進(jìn)行訪問的,即[Eureka] Server是服務(wù)提供者的統(tǒng)一入口。那么整個(gè)應(yīng)用中存在那么多消費(fèi)者需要用戶進(jìn)行調(diào)用,這個(gè)時(shí)候用戶該怎樣訪問這些消費(fèi)者工程呢?當(dāng)然可以像之前那樣直接訪問這些工程。但這種方式?jīng)]有統(tǒng)一的消費(fèi)者工程調(diào)用入口,不便于訪問與管理,而 Zuul 就是這樣的一個(gè)對(duì)于消費(fèi)者的統(tǒng)一入口。
如果學(xué)過前端的肯定都知道 Router 吧,比如 Flutter 中的路由,Vue,React中的路由,用了 Zuul 你會(huì)發(fā)現(xiàn)在路由功能方面和前端配置路由基本是一個(gè)理,我偶爾擼擼 Flutter。關(guān)注微信公眾號(hào):Java技術(shù)棧,在后臺(tái)回復(fù):cloud,可以獲取 Spring Cloud 系列教程。
大家對(duì)網(wǎng)關(guān)應(yīng)該很熟吧,簡(jiǎn)單來講網(wǎng)關(guān)是系統(tǒng)唯一對(duì)外的入口,介于客戶端與服務(wù)器端之間,用于對(duì)請(qǐng)求進(jìn)行鑒權(quán)、限流、路由、監(jiān)控等功能。
沒錯(cuò),網(wǎng)關(guān)有的功能,Zuul基本都有。而Zuul中最關(guān)鍵的就是路由和過濾器了,在官方文檔中Zuul的標(biāo)題就是
Router and Filter : Zuul
Zuul 的路由功能
簡(jiǎn)單配置
本來想給你們復(fù)制一些代碼,但是想了想,因?yàn)楦鱾€(gè)代碼配置比較零散,看起來也比較零散,我決定還是給你們畫個(gè)圖來解釋吧。
比如這個(gè)時(shí)候我們已經(jīng)向[Eureka] Server注冊(cè)了兩個(gè)Consumer、三個(gè)Provicer,這個(gè)時(shí)候我們?cè)偌觽€(gè)Zuul網(wǎng)關(guān)應(yīng)該變成這樣子了。
emmm,信息量有點(diǎn)大,我來解釋一下。關(guān)于前面的知識(shí)我就不解釋了 。
首先,Zuul需要向 Eureka 進(jìn)行注冊(cè),注冊(cè)有啥好處呢?
你傻呀,Consumer都向[Eureka] Server進(jìn)行注冊(cè)了,我網(wǎng)關(guān)是不是只要注冊(cè)就能拿到所有Consumer的信息了?
拿到信息有什么好處呢?
我拿到信息我是不是可以獲取所有的Consumer的元數(shù)據(jù)(名稱,ip,端口)?
拿到這些元數(shù)據(jù)有什么好處呢?拿到了我們是不是直接可以做路由映射?比如原來用戶調(diào)用Consumer1的接口localhost:8001/studentInfo/update這個(gè)請(qǐng)求,我們是不是可以這樣進(jìn)行調(diào)用了呢?localhost:9000/consumer1/studentInfo/update呢?你這樣是不是恍然大悟了?
這里的url為了讓更多人看懂所以沒有使用 restful 風(fēng)格。
上面的你理解了,那么就能理解關(guān)于Zuul最基本的配置了,看下面。
server:??port:?9000?? eureka:??client:??service-url:??#?這里只要注冊(cè)?Eureka?就行了??defaultZone:?http://localhost:9997/eureka??然后在啟動(dòng)類上加入@EnableZuulProxy注解就行了。沒錯(cuò),就是那么簡(jiǎn)單。
統(tǒng)一前綴
這個(gè)很簡(jiǎn)單,就是我們可以在前面加一個(gè)統(tǒng)一的前綴,比如我們剛剛調(diào)用的是localhost:9000/consumer1/studentInfo/update,這個(gè)時(shí)候我們?cè)趛aml配置文件中添加如下。
zuul:??prefix:?/zuul??這樣我們就需要通過localhost:9000/zuul/consumer1/studentInfo/update來進(jìn)行訪問了。
路由策略配置
你會(huì)發(fā)現(xiàn)前面的訪問方式(直接使用服務(wù)名),需要將微服務(wù)名稱暴露給用戶,會(huì)存在安全性問題。所以,可以自定義路徑來替代微服務(wù)名稱,即自定義路由策略。
zuul:??routes:??consumer1:?/FrancisQ1/**??consumer2:?/FrancisQ2/**??這個(gè)時(shí)候你就可以使用localhost:9000/zuul/FrancisQ1/studentInfo/update進(jìn)行訪問了。
服務(wù)名屏蔽
這個(gè)時(shí)候你別以為你好了,你可以試試,在你配置完路由策略之后使用微服務(wù)名稱還是可以訪問的,這個(gè)時(shí)候你需要將服務(wù)名屏蔽。
zuul:??ignore-services:?"*"??路徑屏蔽
Zuul還可以指定屏蔽掉的路徑 URI,即只要用戶請(qǐng)求中包含指定的 URI 路徑,那么該請(qǐng)求將無法訪問到指定的服務(wù)。通過該方式可以限制用戶的權(quán)限。
zuul:??ignore-patterns:?**/auto/**??這樣關(guān)于 auto 的請(qǐng)求我們就可以過濾掉了。
** 代表匹配多級(jí)任意路徑
*代表匹配一級(jí)任意路徑
敏感請(qǐng)求頭屏蔽
默認(rèn)情況下,像 Cookie、Set-Cookie 等敏感請(qǐng)求頭信息會(huì)被 zuul 屏蔽掉,我們可以將這些默認(rèn)屏蔽去掉,當(dāng)然,也可以添加要屏蔽的請(qǐng)求頭。
Zuul 的過濾功能
如果說,路由功能是Zuul的基操的話,那么過濾器就是Zuul的利器了。畢竟所有請(qǐng)求都經(jīng)過網(wǎng)關(guān)(Zuul),那么我們可以進(jìn)行各種過濾,這樣我們就能實(shí)現(xiàn)限流,灰度發(fā)布,權(quán)限控制等等。
簡(jiǎn)單實(shí)現(xiàn)一個(gè)請(qǐng)求時(shí)間日志打印
要實(shí)現(xiàn)自己定義的Filter我們只需要繼承ZuulFilter然后將這個(gè)過濾器類以@Component注解加入 Spring 容器中就行了。
在給你們看代碼之前我先給你們解釋一下關(guān)于過濾器的一些注意點(diǎn)。
過濾器類型:Pre、Routing、Post。前置Pre就是在請(qǐng)求之前進(jìn)行過濾,Routing路由過濾器就是我們上面所講的路由策略,而Post后置過濾器就是在Response之前進(jìn)行過濾的過濾器。你可以觀察上圖結(jié)合著理解,并且下面我會(huì)給出相應(yīng)的注釋。
//?加入Spring容器?? @Component?? public?class?PreRequestFilter?extends?ZuulFilter?{??//?返回過濾器類型?這里是前置過濾器??@Override??public?String?filterType()?{??return?FilterConstants.PRE_TYPE;??}??//?指定過濾順序?越小越先執(zhí)行,這里第一個(gè)執(zhí)行??//?當(dāng)然不是只真正第一個(gè)?在Zuul內(nèi)置中有其他過濾器會(huì)先執(zhí)行??//?那是寫死的?比如?SERVLET_DETECTION_FILTER_ORDER?=?-3??@Override??public?int?filterOrder()?{??return?0;??}??//?什么時(shí)候該進(jìn)行過濾??//?這里我們可以進(jìn)行一些判斷,這樣我們就可以過濾掉一些不符合規(guī)定的請(qǐng)求等等??@Override??public?boolean?shouldFilter()?{??return?true;??}??//?如果過濾器允許通過則怎么進(jìn)行處理??@Override??public?Object?run()?throws?ZuulException?{??//?這里我設(shè)置了全局的RequestContext并記錄了請(qǐng)求開始時(shí)間??RequestContext?ctx?=?RequestContext.getCurrentContext();??ctx.set("startTime",?System.currentTimeMillis());??return?null;??}?? }??//?lombok的日志?? @Slf4j?? //?加入?Spring?容器?? @Component?? public?class?AccessLogFilter?extends?ZuulFilter?{??//?指定該過濾器的過濾類型??//?此時(shí)是后置過濾器??@Override??public?String?filterType()?{??return?FilterConstants.POST_TYPE;??}??//?SEND_RESPONSE_FILTER_ORDER?是最后一個(gè)過濾器??//?我們此過濾器在它之前執(zhí)行??@Override??public?int?filterOrder()?{??return?FilterConstants.SEND_RESPONSE_FILTER_ORDER?-?1;??}??@Override??public?boolean?shouldFilter()?{??return?true;??}??//?過濾時(shí)執(zhí)行的策略??@Override??public?Object?run()?throws?ZuulException?{??RequestContext?context?=?RequestContext.getCurrentContext();??HttpServletRequest?request?=?context.getRequest();??//?從RequestContext獲取原先的開始時(shí)間?并通過它計(jì)算整個(gè)時(shí)間間隔??Long?startTime?=?(Long)?context.get("startTime");??//?這里我可以獲取HttpServletRequest來獲取URI并且打印出來??String?uri?=?request.getRequestURI();??long?duration?=?System.currentTimeMillis()?-?startTime;??log.info("uri:?"?+?uri?+?",?duration:?"?+?duration?/?100?+?"ms");??return?null;??}?? }??上面就簡(jiǎn)單實(shí)現(xiàn)了請(qǐng)求時(shí)間日志打印功能,你有沒有感受到Zuul過濾功能的強(qiáng)大了呢?Spring Cloud Gateway VS Zuul 比較,怎么選?
沒有?好的、那我們?cè)賮怼?/p>
令牌桶限流
當(dāng)然不僅僅是令牌桶限流方式,Zuul只要是限流的活它都能干,這里我只是簡(jiǎn)單舉個(gè)例子。
我先來解釋一下什么是令牌桶限流吧。
首先我們會(huì)有個(gè)桶,如果里面沒有滿那么就會(huì)以一定固定的速率會(huì)往里面放令牌,一個(gè)請(qǐng)求過來首先要從桶中獲取令牌,如果沒有獲取到,那么這個(gè)請(qǐng)求就拒絕,如果獲取到那么就放行。很簡(jiǎn)單吧,啊哈哈、
下面我們就通過Zuul的前置過濾器來實(shí)現(xiàn)一下令牌桶限流。
@Component?? @Slf4j?? public?class?RouteFilter?extends?ZuulFilter?{??//?定義一個(gè)令牌桶,每秒產(chǎn)生2個(gè)令牌,即每秒最多處理2個(gè)請(qǐng)求??private?static?final?RateLimiter?RATE_LIMITER?=?RateLimiter.create(2);??@Override??public?String?filterType()?{??return?FilterConstants.PRE_TYPE;??}??@Override??public?int?filterOrder()?{??return?-5;??}??@Override??public?Object?run()?throws?ZuulException?{??log.info("放行");??return?null;??}??@Override??public?boolean?shouldFilter()?{??RequestContext?context?=?RequestContext.getCurrentContext();??if(!RATE_LIMITER.tryAcquire())?{??log.warn("訪問量超載");??//?指定當(dāng)前請(qǐng)求未通過過濾??context.setSendZuulResponse(false);??//?向客戶端返回響應(yīng)碼429,請(qǐng)求數(shù)量過多??context.setResponseStatusCode(429);??return?false;??}??return?true;??}?? }??這樣我們就能將請(qǐng)求數(shù)量控制在一秒兩個(gè),有沒有覺得很酷?
關(guān)于 Zuul ?的其他
Zuul的過濾器的功能肯定不止上面我所實(shí)現(xiàn)的兩種,它還可以實(shí)現(xiàn)權(quán)限校驗(yàn),包括我上面提到的灰度發(fā)布等等。
當(dāng)然,Zuul作為網(wǎng)關(guān)肯定也存在單點(diǎn)問題,如果我們要保證Zuul的高可用,我們就需要進(jìn)行Zuul的集群配置,這個(gè)時(shí)候可以借助額外的一些負(fù)載均衡器比如Nginx。
Spring Cloud配置管理——Config
為什么要使用進(jìn)行配置管理?
當(dāng)我們的微服務(wù)系統(tǒng)開始慢慢地龐大起來,那么多Consumer、Provider、[Eureka] Server、Zuul系統(tǒng)都會(huì)持有自己的配置,這個(gè)時(shí)候我們?cè)陧?xiàng)目運(yùn)行的時(shí)候可能需要更改某些應(yīng)用的配置,如果我們不進(jìn)行配置的統(tǒng)一管理,我們只能去每個(gè)應(yīng)用下一個(gè)一個(gè)尋找配置文件然后修改配置文件再重啟應(yīng)用。
首先對(duì)于分布式系統(tǒng)而言我們就不應(yīng)該去每個(gè)應(yīng)用下去分別修改配置文件,再者對(duì)于重啟應(yīng)用來說,服務(wù)無法訪問所以直接拋棄了可用性,這是我們更不愿見到的。
那么有沒有一種方法既能對(duì)配置文件統(tǒng)一地進(jìn)行管理,又能在項(xiàng)目運(yùn)行時(shí)動(dòng)態(tài)修改配置文件呢?
那就是我今天所要介紹的Spring Cloud Config。
能進(jìn)行配置管理的框架不止Spring Cloud Config一種,大家可以根據(jù)需求自己選擇(disconf,阿波羅等等)。而且對(duì)于Config來說有些地方實(shí)現(xiàn)的不是那么盡人意。
Config 是什么
Spring Cloud Config為分布式系統(tǒng)中的外部化配置提供服務(wù)器和客戶端支持。使用Config服務(wù)器,可以在中心位置管理所有環(huán)境中應(yīng)用程序的外部屬性。關(guān)注微信公眾號(hào):Java技術(shù)棧,在后臺(tái)回復(fù):cloud,可以獲取 Spring Cloud 系列教程。
簡(jiǎn)單來說,Spring Cloud Config就是能將各個(gè) 應(yīng)用/系統(tǒng)/模塊 的配置文件存放到統(tǒng)一的地方然后進(jìn)行管理(Git 或者 SVN)。
你想一下,我們的應(yīng)用是不是只有啟動(dòng)的時(shí)候才會(huì)進(jìn)行配置文件的加載,那么我們的Spring Cloud Config就暴露出一個(gè)接口給啟動(dòng)應(yīng)用來獲取它所想要的配置文件,應(yīng)用獲取到配置文件然后再進(jìn)行它的初始化工作。就如下圖。
當(dāng)然這里你肯定還會(huì)有一個(gè)疑問,如果我在應(yīng)用運(yùn)行時(shí)去更改遠(yuǎn)程配置倉庫(Git)中的對(duì)應(yīng)配置文件,那么依賴于這個(gè)配置文件的已啟動(dòng)的應(yīng)用會(huì)不會(huì)進(jìn)行其相應(yīng)配置的更改呢?
答案是不會(huì)的。
什么?那怎么進(jìn)行動(dòng)態(tài)修改配置文件呢?這不是出現(xiàn)了配置漂移嗎?你個(gè)渣男,你又騙我!
別急嘛,你可以使用Webhooks,這是 ?github提供的功能,它能確保遠(yuǎn)程庫的配置文件更新后客戶端中的配置信息也得到更新。
噢噢,這還差不多。我去查查怎么用。
慢著,聽我說完,Webhooks雖然能解決,但是你了解一下會(huì)發(fā)現(xiàn)它根本不適合用于生產(chǎn)環(huán)境,所以基本不會(huì)使用它的。
而一般我們會(huì)使用Bus消息總線 +Spring Cloud Config進(jìn)行配置的動(dòng)態(tài)刷新。
引出 Spring Cloud Bus
用于將服務(wù)和服務(wù)實(shí)例與分布式消息系統(tǒng)鏈接在一起的事件總線。在集群中傳播狀態(tài)更改很有用(例如配置更改事件)。關(guān)注微信公眾號(hào):Java技術(shù)棧,在后臺(tái)回復(fù):cloud,可以獲取 Spring Cloud 系列教程。
你可以簡(jiǎn)單理解為Spring Cloud Bus的作用就是管理和廣播分布式系統(tǒng)中的消息,也就是消息引擎系統(tǒng)中的廣播模式。當(dāng)然作為消息總線的Spring Cloud Bus可以做很多事而不僅僅是客戶端的配置刷新功能。
而擁有了Spring Cloud Bus之后,我們只需要?jiǎng)?chuàng)建一個(gè)簡(jiǎn)單的請(qǐng)求,并且加上@ResfreshScope注解就能進(jìn)行配置的動(dòng)態(tài)修改了,下面我畫了張圖供你理解。
總結(jié)
這篇文章中我?guī)Т蠹页醪搅私饬薙pring Cloud的各個(gè)組件,他們有
Eureka 服務(wù)發(fā)現(xiàn)框架
Ribbon 進(jìn)程內(nèi)負(fù)載均衡器
Open Feign 服務(wù)調(diào)用映射
Hystrix 服務(wù)降級(jí)熔斷器
Zuul 微服務(wù)網(wǎng)關(guān)
Config 微服務(wù)統(tǒng)一配置中心
Bus 消息總線
如果你能這個(gè)時(shí)候能看懂下面那張圖,也就說明了你已經(jīng)對(duì)Spring Cloud微服務(wù)有了一定的架構(gòu)認(rèn)識(shí)。
作者:FrancisQ
juejin.im/post/5de2553e5188256e885f4fa3
想要加入中生代架構(gòu)群的小伙伴,請(qǐng)?zhí)砑尤汉匣锶?strong>大白的微信
申請(qǐng)備注(姓名+公司+技術(shù)方向)才能通過哦!
? ?END ? ?? #接力技術(shù),鏈接價(jià)值#精彩推薦1.?學(xué)習(xí)Nginx,看這篇就就好了!(多圖詳解)2.?掌握 Kafka,看這篇就足夠了 3.?漫畫:學(xué)習(xí)中臺(tái),看這篇就夠了 4.?如此沙雕的代碼注釋,還是程序員會(huì)玩!漫畫推薦1.?漫畫:程序員和產(chǎn)品經(jīng)理撕得真是太太太太厲害了 2.?漫畫:程序員真的是太太太太太太太太難了!3.?漫畫:普通程序員 vs 優(yōu)秀程序員 4.?漫畫:35歲的IT何去何從? 5.?漫畫:從修燈泡來看各種 IT 崗位,你是哪一種? 6. 漫畫:一批90后已經(jīng)30歲了,更扎心的是…7. 圖解:這才是程序員加班的真正原因!8.?漫畫:中國互聯(lián)網(wǎng)往事(2000-2020)技術(shù)人素質(zhì)三連,「在看、評(píng)論加轉(zhuǎn)發(fā)」
總結(jié)
以上是生活随笔為你收集整理的Spring Cloud入门,看这篇就够了!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NYOJ 659 判断三角形
- 下一篇: NYOJ 660 逃离地球