全網(wǎng)最詳細的一篇 SpringCloud 總結(jié)
不點藍字,我們哪來故事?


來源:juejin.im/post/5de2553e5188256e885f4fa3
什么是Spring cloud Spring Cloud 的版本 Spring Cloud 的服務發(fā)現(xiàn)框架——Eureka 負載均衡之 Ribbon 什么是 Open Feign 必不可少的 Hystrix 微服務網(wǎng)關(guān)——Zuul Spring Cloud配置管理——Config 引出 Spring Cloud Bus 總結(jié)
什么是Spring cloud
構(gòu)建分布式系統(tǒng)不需要復雜和容易出錯。Spring Cloud 為最常見的分布式系統(tǒng)模式提供了一種簡單且易于接受的編程模型,幫助開發(fā)人員構(gòu)建有彈性的、可靠的、協(xié)調(diào)的應用程序。Spring Cloud 構(gòu)建于 Spring Boot 之上,使得開發(fā)者很容易入手并快速應用于生產(chǎn)中。
官方果然官方,介紹都這么有板有眼的。
我所理解的 Spring Cloud 就是微服務系統(tǒng)架構(gòu)的一站式解決方案,在平時我們構(gòu)建微服務的過程中需要做如 服務發(fā)現(xiàn)注冊 、配置中心 、消息總線 、負載均衡 、斷路器 、數(shù)據(jù)監(jiān)控 等操作,而 Spring Cloud 為我們提供了一套簡易的編程模型,使我們能在 Spring Boot 的基礎(chǔ)上輕松地實現(xiàn)微服務項目的構(gòu)建。
Spring Cloud 的版本
當然這個只是個題外話。
Spring Cloud 的版本號并不是我們通常見的數(shù)字版本號,而是一些很奇怪的單詞。這些單詞均為英國倫敦地鐵站的站名。同時根據(jù)字母表的順序來對應版本時間順序,比如:最早 的 Release 版本 Angel,第二個 Release 版本 Brixton(英國地名),然后是 Camden、 Dalston、Edgware、Finchley、Greenwich、Hoxton。
Spring Cloud 的服務發(fā)現(xiàn)框架——Eureka
Eureka是基于REST(代表性狀態(tài)轉(zhuǎn)移)的服務,主要在AWS云中用于定位服務,以實現(xiàn)負載均衡和中間層服務器的故障轉(zhuǎn)移。我們稱此服務為Eureka服務器。Eureka還帶有一個基于Java的客戶端組件Eureka Client,它使與服務的交互變得更加容易。客戶端還具有一個內(nèi)置的負載平衡器,可以執(zhí)行基本的循環(huán)負載平衡。在Netflix,更復雜的負載均衡器將Eureka包裝起來,以基于流量,資源使用,錯誤條件等多種因素提供加權(quán)負載均衡,以提供出色的彈性。
總的來說,Eureka 就是一個服務發(fā)現(xiàn)框架。何為服務,何又為發(fā)現(xiàn)呢?
舉一個生活中的例子,就比如我們平時租房子找中介的事情。
在沒有中介的時候我們需要一個一個去尋找是否有房屋要出租的房東,這顯然會非常的費力,一你找憑一個人的能力是找不到很多房源供你選擇,再者你也懶得這么找下去(找了這么久,沒有合適的只能將就)。這里的我們就相當于微服務中的 Consumer ,而那些房東就相當于微服務中的 Provider 。消費者 Consumer 需要調(diào)用提供者 Provider 提供的一些服務,就像我們現(xiàn)在需要租他們的房子一樣。
但是如果只是租客和房東之間進行尋找的話,他們的效率是很低的,房東找不到租客賺不到錢,租客找不到房東住不了房。所以,后來房東肯定就想到了廣播自己的房源信息(比如在街邊貼貼小廣告),這樣對于房東來說已經(jīng)完成他的任務(將房源公布出去),但是有兩個問題就出現(xiàn)了。第一、其他不是租客的都能收到這種租房消息,這在現(xiàn)實世界沒什么,但是在計算機的世界中就會出現(xiàn)資源消耗 的問題了。第二、租客這樣還是很難找到你,試想一下我需要租房,我還需要東一個西一個地去找街邊小廣告,麻不麻煩?

那怎么辦呢?我們當然不會那么傻乎乎的,第一時間就是去找 中介 呀,它為我們提供了統(tǒng)一房源的地方,我們消費者只需要跑到它那里去找就行了。而對于房東來說,他們也只需要把房源在中介那里發(fā)布就行了。

那么現(xiàn)在,我們的模式就是這樣的了。

但是,這個時候還會出現(xiàn)一些問題。
房東注冊之后如果不想賣房子了怎么辦?我們是不是需要讓房東定期續(xù)約 ?如果房東不進行續(xù)約是不是要將他們從中介那里的注冊列表中移除 。 租客是不是也要進行注冊 呢?不然合同乙方怎么來呢? 中介可不可以做連鎖店 呢?如果這一個店因為某些不可抗力因素而無法使用,那么我們是否可以換一個連鎖店呢?
針對上面的問題我們來重新構(gòu)建一下上面的模式圖

好了,舉完這個??我們就可以來看關(guān)于 Eureka 的一些基礎(chǔ)概念了,你會發(fā)現(xiàn)這東西理解起來怎么這么簡單。??????
服務發(fā)現(xiàn) :其實就是一個“中介”,整個過程中有三個角色:服務提供者(出租房子的)、服務消費者(租客)、服務中介(房屋中介) 。
服務提供者 :就是提供一些自己能夠執(zhí)行的一些服務給外界。
服務消費者 :就是需要使用一些服務的“用戶”。
服務中介 :其實就是服務提供者和服務消費者之間的“橋梁”,服務提供者可以把自己注冊到服務中介那里,而服務消費者如需要消費一些服務(使用一些功能)就可以在服務中介中尋找注冊在服務中介的服務提供者。
服務注冊 Register :
官方解釋:當 Eureka 客戶端向 Eureka Server 注冊時,它提供自身的元數(shù)據(jù) ,比如IP地址、端口,運行狀況指示符URL,主頁等。
結(jié)合中介理解:房東 (提供者 Eureka Client Provider)在中介 (服務器 Eureka Server) 那里登記房屋的信息,比如面積,價格,地段等等(元數(shù)據(jù) metaData)。
服務續(xù)約 Renew :
官方解釋:Eureka 客戶會每隔30秒(默認情況下)發(fā)送一次心跳來續(xù)約 。通過續(xù)約來告知 Eureka Server 該 Eureka 客戶仍然存在,沒有出現(xiàn)問題。正常情況下,如果 Eureka Server 在90秒沒有收到 Eureka 客戶的續(xù)約,它會將實例從其注冊表中刪除。
結(jié)合中介理解:房東 (提供者 Eureka Client Provider) 定期告訴中介 (服務器 Eureka Server) 我的房子還租(續(xù)約) ,中介 (服務器Eureka Server) 收到之后繼續(xù)保留房屋的信息。
獲取注冊列表信息 Fetch Registries :
官方解釋:Eureka 客戶端從服務器獲取注冊表信息,并將其緩存在本地。客戶端會使用該信息查找其他服務,從而進行遠程調(diào)用。該注冊列表信息定期(每30秒鐘)更新一次。每次返回注冊列表信息可能與 Eureka 客戶端的緩存信息不同, Eureka 客戶端自動處理。如果由于某種原因?qū)е伦粤斜硇畔⒉荒芗皶r匹配,Eureka 客戶端則會重新獲取整個注冊表信息。Eureka 服務器緩存注冊列表信息,整個注冊表以及每個應用程序的信息進行了壓縮,壓縮內(nèi)容和沒有壓縮的內(nèi)容完全相同。Eureka 客戶端和 Eureka 服務器可以使用JSON / XML格式進行通訊。在默認的情況下 Eureka 客戶端使用壓縮 JSON 格式來獲取注冊列表的信息。
結(jié)合中介理解:租客(消費者 Eureka Client Consumer) 去中介 (服務器 Eureka Server) 那里獲取所有的房屋信息列表 (客戶端列表 Eureka Client List) ,而且租客為了獲取最新的信息會定期向中介 (服務器 Eureka Server) 那里獲取并更新本地列表。
服務下線 Cancel :
官方解釋:Eureka客戶端在程序關(guān)閉時向Eureka服務器發(fā)送取消請求。發(fā)送請求后,該客戶端實例信息將從服務器的實例注冊表中刪除。該下線請求不會自動完成,它需要調(diào)用以下內(nèi)容:DiscoveryManager.getInstance().shutdownComponent();
結(jié)合中介理解:房東 (提供者 Eureka Client Provider) 告訴中介 (服務器 Eureka Server) 我的房子不租了,中介之后就將注冊的房屋信息從列表中剔除。
服務剔除 Eviction :
官方解釋:在默認的情況下,當Eureka客戶端連續(xù)90秒(3個續(xù)約周期)沒有向Eureka服務器發(fā)送服務續(xù)約,即心跳,Eureka服務器會將該服務實例從服務注冊列表刪除 ,即服務剔除。
結(jié)合中介理解:房東(提供者 Eureka Client Provider) 會定期聯(lián)系 中介 (服務器 Eureka Server) 告訴他我的房子還租(續(xù)約),如果中介 (服務器 Eureka Server) 長時間沒收到提供者的信息,那么中介會將他的房屋信息給下架(服務剔除)。
下面就是 Netflix 官方給出的 Eureka 架構(gòu)圖,你會發(fā)現(xiàn)和我們前面畫的中介圖別無二致。

當然,可以充當服務發(fā)現(xiàn)的組件有很多:ZooKeeper ,Consul , Eureka 等。
更多關(guān)于 Eureka 的知識(自我保護,初始注冊策略等等)可以自己去官網(wǎng)查看,或者查看我的另一篇文章 深入理解 Eureka。
負載均衡之 Ribbon
什么是 RestTemplate?
不是講 Ribbon 么?怎么扯到了 RestTemplate 了?你先別急,聽我慢慢道來。
我不聽我不聽我不聽??????。
我就說一句!RestTemplate是Spring提供的一個訪問Http服務的客戶端類 ,怎么說呢?就是微服務之間的調(diào)用是使用的 RestTemplate 。比如這個時候我們 消費者B 需要調(diào)用 提供者A 所提供的服務我們就需要這么寫。如我下面的偽代碼。
@Autowired
private RestTemplate restTemplate;
// 這里是提供者A的ip地址,但是如果使用了 Eureka 那么就應該是提供者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);
}
如果你對源碼感興趣的話,你會發(fā)現(xiàn)上面我們所講的 Eureka 框架中的 注冊 、續(xù)約 等,底層都是使用的 RestTemplate 。
為什么需要 Ribbon?
Ribbon 是 Netflix 公司的一個開源的負載均衡 項目,是一個客戶端/進程內(nèi)負載均衡器,運行在消費者端 。
我們再舉個??,比如我們設(shè)計了一個秒殺系統(tǒng),但是為了整個系統(tǒng)的 高可用 ,我們需要將這個系統(tǒng)做一個集群,而這個時候我們消費者就可以擁有多個秒殺系統(tǒng)的調(diào)用途徑了,如下圖。

如果這個時候我們沒有進行一些 均衡操作 ,如果我們對 秒殺系統(tǒng)1 進行大量的調(diào)用,而另外兩個基本不請求,就會導致 秒殺系統(tǒng)1 崩潰,而另外兩個就變成了傀儡,那么我們?yōu)槭裁催€要做集群,我們高可用體現(xiàn)的意義又在哪呢?
所以 Ribbon 出現(xiàn)了,注意我們上面加粗的幾個字——運行在消費者端 。指的是,Ribbon 是運行在消費者端的負載均衡器,如下圖。

其工作原理就是 Consumer 端獲取到了所有的服務列表之后,在其內(nèi)部 使用負載均衡算法 ,進行對多個系統(tǒng)的調(diào)用。
Nginx 和 Ribbon 的對比
提到 負載均衡 就不得不提到大名鼎鼎的 Nignx 了,而和 Ribbon 不同的是,它是一種集中式 的負載均衡器。
何為集中式呢?簡單理解就是 將所有請求都集中起來,然后再進行負載均衡 。如下圖。

我們可以看到 Nginx 是接收了所有的請求進行負載均衡的,而對于 Ribbon 來說它是在消費者端進行的負載均衡。如下圖。

請注意
Request的位置,在Nginx中請求是先進入負載均衡器,而在Ribbon中是先在客戶端進行負載均衡才進行請求的。
Ribbon 的幾種負載均衡算法
負載均衡,不管 Nginx 還是 Ribbon 都需要其算法的支持,如果我沒記錯的話 Nginx 使用的是 輪詢和加權(quán)輪詢算法。而在 Ribbon 中有更多的負載均衡調(diào)度算法,其默認是使用的 RoundRobinRule 輪詢策略。
RoundRobinRule :輪詢策略。 Ribbon默認采用的策略。若經(jīng)過一輪輪詢沒有找到可用的provider,其最多輪詢 10 輪。若最終還沒有找到,則返回 null。RandomRule : 隨機策略,從所有可用的 provider 中隨機選擇一個。 RetryRule : 重試策略。先按照 RoundRobinRule 策略獲取 provider,若獲取失敗,則在指定的時限內(nèi)重試。默認的時限為 500 毫秒。
?????? 還有很多,這里不一一舉??了,你最需要知道的是默認輪詢算法,并且可以更換默認的負載均衡算法,只需要在配置文件中做出修改就行。
providerName:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
當然,在 Ribbon 中你還可以自定義負載均衡算法 ,你只需要實現(xiàn) IRule 接口,然后修改配置文件或者自定義 Java Config 類。
什么是 Open Feign
有了 Eureka,RestTemplate,Ribbon 我們就可以??愉快地進行服務間的調(diào)用了,但是使用 RestTemplate 還是不方便,我們每次都要進行這樣的調(diào)用。
@Autowired
private RestTemplate restTemplate;
// 這里是提供者A的ip地址,但是如果使用了 Eureka 那么就應該是提供者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、請求、返回類型的
return restTemplate.postForObject(url, request, Boolean.class);
}
這樣每次都調(diào)用 RestRemplate 的 API 是否太麻煩,我能不能像調(diào)用原來代碼一樣進行各個服務間的調(diào)用呢?
??????聰明的小朋友肯定想到了,那就用 映射 呀,就像域名和IP地址的映射。我們可以將被調(diào)用的服務代碼映射到消費者端,這樣我們就可以 “無縫開發(fā)” 啦。
OpenFeign 也是運行在消費者端的,使用 Ribbon 進行負載均衡,所以 OpenFeign 直接內(nèi)置了 Ribbon。
在導入了 Open Feign 之后我們就可以進行愉快編寫 Consumer 端代碼了。
// 使用 @FeignClient 注解來指定提供者的名字
@FeignClient(value = "eureka-client-provider")
public interface TestClient {
// 這里一定要注意需要使用的是提供者那端的請求相對路徑,這里就相當于映射了
@RequestMapping(value = "/provider/xxx",
method = RequestMethod.POST)
CommonResponse<List<Plan>> getPlans(@RequestBody planGetRequest request);
}
然后我們在 Controller 就可以像原來調(diào)用 Service 層代碼一樣調(diào)用它了。
@RestController
public class TestController {
// 這里就相當于原來自動注入的 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之熔斷和降級

在分布式環(huán)境中,不可避免地會有許多服務依賴項中的某些失敗。Hystrix是一個庫,可通過添加等待時間容限和容錯邏輯來幫助您控制這些分布式服務之間的交互。Hystrix通過隔離服務之間的訪問點,停止服務之間的級聯(lián)故障并提供后備選項來實現(xiàn)此目的,所有這些都可以提高系統(tǒng)的整體彈性。
總體來說 Hystrix 就是一個能進行 熔斷 和 降級 的庫,通過使用它能提高整個系統(tǒng)的彈性。
那么什么是 熔斷和降級 呢?再舉個??,此時我們整個微服務系統(tǒng)是這樣的。服務A調(diào)用了服務B,服務B再調(diào)用了服務C,但是因為某些原因,服務C頂不住了,這個時候大量請求會在服務C阻塞。

服務C阻塞了還好,畢竟只是一個系統(tǒng)崩潰了。但是請注意這個時候因為服務C不能返回響應,那么服務B調(diào)用服務C的的請求就會阻塞,同理服務B阻塞了,那么服務A也會阻塞崩潰。
請注意,為什么阻塞會崩潰。因為這些請求會消耗占用系統(tǒng)的線程、IO 等資源,消耗完你這個系統(tǒng)服務器不就崩了么。

這就叫 服務雪崩 。媽耶,上面兩個 熔斷 和 降級 你都沒給我解釋清楚,你現(xiàn)在又給我扯什么 服務雪崩 ???????
別急,聽我慢慢道來。

不聽我也得講下去!
所謂 熔斷 就是服務雪崩的一種有效解決方案。當指定時間窗內(nèi)的請求失敗率達到設(shè)定閾值時,系統(tǒng)將通過 斷路器 直接將此請求鏈路斷開。
也就是我們上面服務B調(diào)用服務C在指定時間窗內(nèi),調(diào)用的失敗率到達了一定的值,那么 Hystrix 則會自動將 服務B與C 之間的請求都斷了,以免導致服務雪崩現(xiàn)象。
其實這里所講的 熔斷 就是指的 Hystrix 中的 斷路器模式 ,你可以使用簡單的 @HystrixCommand 注解來標注某個方法,這樣 Hystrix 就會使用 斷路器 來“包裝”這個方法,每當調(diào)用時間超過指定時間時(默認為1000ms),斷路器將會中斷對這個方法的調(diào)用。
當然你可以對這個注解的很多屬性進行設(shè)置,比如設(shè)置超時時間,像這樣。
@HystrixCommand(
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1200")}
)
public List<Xxx> getXxxx() {
// ...省略代碼邏輯
}
但是,我查閱了一些博客,發(fā)現(xiàn)他們都將 熔斷 和 降級 的概念混淆了,以我的理解,降級是為了更好的用戶體驗,當一個方法調(diào)用異常時,通過執(zhí)行另一種代碼邏輯來給用戶友好的回復 。這也就對應著 Hystrix 的 后備處理 模式。你可以通過設(shè)置 fallbackMethod 來給一個方法設(shè)置備用的代碼邏輯。比如這個時候有一個熱點新聞出現(xiàn)了,我們會推薦給用戶查看詳情,然后用戶會通過id去查詢新聞的詳情,但是因為這條新聞太火了(比如最近什么*易對吧),大量用戶同時訪問可能會導致系統(tǒng)崩潰,那么我們就進行 服務降級 ,一些請求會做一些降級處理比如當前人數(shù)太多請稍后查看等等。
// 指定了后備方法調(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) {
// 做服務降級
// 返回當前人數(shù)太多,請稍后查看
}
什么是Hystrix之其他
我在閱讀 《Spring微服務實戰(zhàn)》這本書的時候還接觸到了一個艙壁模式 的概念。在不使用艙壁模式的情況下,服務A調(diào)用服務B,這種調(diào)用默認的是使用同一批線程來執(zhí)行 的,而在一個服務出現(xiàn)性能問題的時候,就會出現(xiàn)所有線程被刷爆并等待處理工作,同時阻塞新請求,最終導致程序崩潰。而艙壁模式會將遠程資源調(diào)用隔離在他們自己的線程池中,以便可以控制單個表現(xiàn)不佳的服務,而不會使該程序崩潰。
具體其原理我推薦大家自己去了解一下,本篇文章中對艙壁模式 不做過多解釋。當然還有 Hystrix 儀表盤 ,它是用來實時監(jiān)控 Hystrix 的各項指標信息的 ,這里我將這個問題也拋出去,希望有不了解的可以自己去搜索一下。
微服務網(wǎng)關(guān)——Zuul

ZUUL 是從設(shè)備和 web 站點到 Netflix 流應用后端的所有請求的前門。作為邊界服務應用,ZUUL 是為了實現(xiàn)動態(tài)路由、監(jiān)視、彈性和安全性而構(gòu)建的。它還具有根據(jù)情況將請求路由到多個 Amazon Auto Scaling Groups(亞馬遜自動縮放組,亞馬遜的一種云計算方式) 的能力
在上面我們學習了 Eureka 之后我們知道了 服務提供者 是 消費者 通過 Eureka Server 進行訪問的,即 Eureka Server 是 服務提供者 的統(tǒng)一入口。那么整個應用中存在那么多 消費者 需要用戶進行調(diào)用,這個時候用戶該怎樣訪問這些 消費者工程 呢?當然可以像之前那樣直接訪問這些工程。但這種方式?jīng)]有統(tǒng)一的消費者工程調(diào)用入口,不便于訪問與管理,而 Zuul 就是這樣的一個對于 消費者 的統(tǒng)一入口。
如果學過前端的肯定都知道 Router 吧,比如 Flutter 中的路由,Vue,React中的路由,用了 Zuul 你會發(fā)現(xiàn)在路由功能方面和前端配置路由基本是一個理。?? 我偶爾擼擼 Flutter。
大家對網(wǎng)關(guān)應該很熟吧,簡單來講網(wǎng)關(guān)是系統(tǒng)唯一對外的入口,介于客戶端與服務器端之間,用于對請求進行鑒權(quán) 、限流 、 路由 、監(jiān)控 等功能。

沒錯,網(wǎng)關(guān)有的功能,Zuul 基本都有。而 Zuul 中最關(guān)鍵的就是 路由和過濾器 了,在官方文檔中 Zuul 的標題就是
Router and Filter : Zuul
Zuul 的路由功能
簡單配置
本來想給你們復制一些代碼,但是想了想,因為各個代碼配置比較零散,看起來也比較零散,我決定還是給你們畫個圖來解釋吧。
請不要因為我這么好就給我點贊 ?? 。瘋狂暗示。
比如這個時候我們已經(jīng)向 Eureka Server 注冊了兩個 Consumer 、三個 Provicer ,這個時候我們再加個 Zuul 網(wǎng)關(guān)應該變成這樣子了。

emmm,信息量有點大,我來解釋一下。關(guān)于前面的知識我就不解釋了?? 。
首先,Zuul 需要向 Eureka 進行注冊,注冊有啥好處呢?
你傻呀,Consumer 都向 Eureka Server 進行注冊了,我網(wǎng)關(guān)是不是只要注冊就能拿到所有 Consumer 的信息了?
拿到信息有什么好處呢?
我拿到信息我是不是可以獲取所有的 Consumer 的元數(shù)據(jù)(名稱,ip,端口)?
拿到這些元數(shù)據(jù)有什么好處呢?拿到了我們是不是直接可以做路由映射 ?比如原來用戶調(diào)用 Consumer1 的接口 localhost:8001/studentInfo/update 這個請求,我們是不是可以這樣進行調(diào)用了呢?localhost:9000/consumer1/studentInfo/update 呢?你這樣是不是恍然大悟了?
這里的url為了讓更多人看懂所以沒有使用 restful 風格。
上面的你理解了,那么就能理解關(guān)于 Zuul 最基本的配置了,看下面。
server:
port: 9000
eureka:
client:
service-url:
# 這里只要注冊 Eureka 就行了
defaultZone: http://localhost:9997/eureka
然后在啟動類上加入 @EnableZuulProxy 注解就行了。沒錯,就是那么簡單??。
統(tǒng)一前綴
這個很簡單,就是我們可以在前面加一個統(tǒng)一的前綴,比如我們剛剛調(diào)用的是 localhost:9000/consumer1/studentInfo/update,這個時候我們在 yaml 配置文件中添加如下。
zuul:
prefix: /zuul
這樣我們就需要通過 localhost:9000/zuul/consumer1/studentInfo/update 來進行訪問了。
路由策略配置
你會發(fā)現(xiàn)前面的訪問方式(直接使用服務名),需要將微服務名稱暴露給用戶,會存在安全性問題。所以,可以自定義路徑來替代微服務名稱,即自定義路由策略。
zuul:
routes:
consumer1: /FrancisQ1/**
consumer2: /FrancisQ2/**
這個時候你就可以使用 localhost:9000/zuul/FrancisQ1/studentInfo/update 進行訪問了。
服務名屏蔽
這個時候你別以為你好了,你可以試試,在你配置完路由策略之后使用微服務名稱還是可以訪問的,這個時候你需要將服務名屏蔽。
zuul:
ignore-services: "*"
路徑屏蔽
Zuul 還可以指定屏蔽掉的路徑 URI,即只要用戶請求中包含指定的 URI 路徑,那么該請求將無法訪問到指定的服務。通過該方式可以限制用戶的權(quán)限。
zuul:
ignore-patterns: **/auto/**
這樣關(guān)于 auto 的請求我們就可以過濾掉了。
** 代表匹配多級任意路徑
*代表匹配一級任意路徑
敏感請求頭屏蔽
默認情況下,像 Cookie、Set-Cookie 等敏感請求頭信息會被 zuul 屏蔽掉,我們可以將這些默認屏蔽去掉,當然,也可以添加要屏蔽的請求頭。
Zuul 的過濾功能
如果說,路由功能是 Zuul 的基操的話,那么過濾器 就是 Zuul的利器了。畢竟所有請求都經(jīng)過網(wǎng)關(guān)(Zuul),那么我們可以進行各種過濾,這樣我們就能實現(xiàn) 限流 ,灰度發(fā)布 ,權(quán)限控制 等等。
簡單實現(xiàn)一個請求時間日志打印
要實現(xiàn)自己定義的 Filter 我們只需要繼承 ZuulFilter 然后將這個過濾器類以 @Component 注解加入 Spring 容器中就行了。
在給你們看代碼之前我先給你們解釋一下關(guān)于過濾器的一些注意點。

過濾器類型:Pre、Routing、Post。前置Pre就是在請求之前進行過濾,Routing路由過濾器就是我們上面所講的路由策略,而Post后置過濾器就是在 Response 之前進行過濾的過濾器。你可以觀察上圖結(jié)合著理解,并且下面我會給出相應的注釋。
// 加入Spring容器
@Component
public class PreRequestFilter extends ZuulFilter {
// 返回過濾器類型 這里是前置過濾器
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
// 指定過濾順序 越小越先執(zhí)行,這里第一個執(zhí)行
// 當然不是只真正第一個 在Zuul內(nèi)置中有其他過濾器會先執(zhí)行
// 那是寫死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3
@Override
public int filterOrder() {
return 0;
}
// 什么時候該進行過濾
// 這里我們可以進行一些判斷,這樣我們就可以過濾掉一些不符合規(guī)定的請求等等
@Override
public boolean shouldFilter() {
return true;
}
// 如果過濾器允許通過則怎么進行處理
@Override
public Object run() throws ZuulException {
// 這里我設(shè)置了全局的RequestContext并記錄了請求開始時間
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set("startTime", System.currentTimeMillis());
return null;
}
}
// lombok的日志
@Slf4j
// 加入 Spring 容器
@Component
public class AccessLogFilter extends ZuulFilter {
// 指定該過濾器的過濾類型
// 此時是后置過濾器
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
// SEND_RESPONSE_FILTER_ORDER 是最后一個過濾器
// 我們此過濾器在它之前執(zhí)行
@Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
// 過濾時執(zhí)行的策略
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
// 從RequestContext獲取原先的開始時間 并通過它計算整個時間間隔
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;
}
}
上面就簡單實現(xiàn)了請求時間日志打印功能,你有沒有感受到 Zuul 過濾功能的強大了呢?
沒有?好的、那我們再來。
令牌桶限流
當然不僅僅是令牌桶限流方式,Zuul 只要是限流的活它都能干,這里我只是簡單舉個??。

我先來解釋一下什么是 令牌桶限流 吧。
首先我們會有個桶,如果里面沒有滿那么就會以一定 固定的速率 會往里面放令牌,一個請求過來首先要從桶中獲取令牌,如果沒有獲取到,那么這個請求就拒絕,如果獲取到那么就放行。很簡單吧,啊哈哈、
下面我們就通過 Zuul 的前置過濾器來實現(xiàn)一下令牌桶限流。
@Component
@Slf4j
public class RouteFilter extends ZuulFilter {
// 定義一個令牌桶,每秒產(chǎn)生2個令牌,即每秒最多處理2個請求
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("訪問量超載");
// 指定當前請求未通過過濾
context.setSendZuulResponse(false);
// 向客戶端返回響應碼429,請求數(shù)量過多
context.setResponseStatusCode(429);
return false;
}
return true;
}
}
這樣我們就能將請求數(shù)量控制在一秒兩個,有沒有覺得很酷?
關(guān)于 Zuul 的其他
Zuul 的過濾器的功能肯定不止上面我所實現(xiàn)的兩種,它還可以實現(xiàn) 權(quán)限校驗 ,包括我上面提到的 灰度發(fā)布 等等。
當然,Zuul 作為網(wǎng)關(guān)肯定也存在 單點問題 ,如果我們要保證 Zuul 的高可用,我們就需要進行 Zuul 的集群配置,這個時候可以借助額外的一些負載均衡器比如 Nginx 。
Spring Cloud配置管理——Config
為什么要使用進行配置管理?
當我們的微服務系統(tǒng)開始慢慢地龐大起來,那么多 Consumer 、Provider 、Eureka Server 、Zuul 系統(tǒng)都會持有自己的配置,這個時候我們在項目運行的時候可能需要更改某些應用的配置,如果我們不進行配置的統(tǒng)一管理,我們只能去每個應用下一個一個尋找配置文件然后修改配置文件再重啟應用 。
首先對于分布式系統(tǒng)而言我們就不應該去每個應用下去分別修改配置文件,再者對于重啟應用來說,服務無法訪問所以直接拋棄了可用性,這是我們更不愿見到的。
那么有沒有一種方法既能對配置文件統(tǒng)一地進行管理,又能在項目運行時動態(tài)修改配置文件呢?
那就是我今天所要介紹的 Spring Cloud Config 。
能進行配置管理的框架不止
Spring Cloud Config一種,大家可以根據(jù)需求自己選擇(disconf,阿波羅等等)。而且對于Config來說有些地方實現(xiàn)的不是那么盡人意。
Config 是什么
Spring Cloud Config為分布式系統(tǒng)中的外部化配置提供服務器和客戶端支持。使用Config服務器,可以在中心位置管理所有環(huán)境中應用程序的外部屬性。
簡單來說,Spring Cloud Config 就是能將各個 應用/系統(tǒng)/模塊 的配置文件存放到 統(tǒng)一的地方然后進行管理 (Git 或者 SVN)。
你想一下,我們的應用是不是只有啟動的時候才會進行配置文件的加載,那么我們的 Spring Cloud Config 就暴露出一個接口給啟動應用來獲取它所想要的配置文件,應用獲取到配置文件然后再進行它的初始化工作。就如下圖。

當然這里你肯定還會有一個疑問,如果我在應用運行時去更改遠程配置倉庫(Git)中的對應配置文件,那么依賴于這個配置文件的已啟動的應用會不會進行其相應配置的更改呢?
答案是不會的。
什么?那怎么進行動態(tài)修改配置文件呢?這不是出現(xiàn)了 配置漂移 嗎?你個渣男??,你又騙我!
別急嘛,你可以使用 Webhooks ,這是 github 提供的功能,它能確保遠程庫的配置文件更新后客戶端中的配置信息也得到更新。
噢噢,這還差不多。我去查查怎么用。
慢著,聽我說完,Webhooks 雖然能解決,但是你了解一下會發(fā)現(xiàn)它根本不適合用于生產(chǎn)環(huán)境,所以基本不會使用它的。

而一般我們會使用 Bus 消息總線 + Spring Cloud Config 進行配置的動態(tài)刷新。
引出 Spring Cloud Bus
用于將服務和服務實例與分布式消息系統(tǒng)鏈接在一起的事件總線。在集群中傳播狀態(tài)更改很有用(例如配置更改事件)。
你可以簡單理解為 Spring Cloud Bus 的作用就是管理和廣播分布式系統(tǒng)中的消息 ,也就是消息引擎系統(tǒng)中的廣播模式。當然作為 消息總線 的 Spring Cloud Bus 可以做很多事而不僅僅是客戶端的配置刷新功能。
而擁有了 Spring Cloud Bus 之后,我們只需要創(chuàng)建一個簡單的請求,并且加上 @ResfreshScope 注解就能進行配置的動態(tài)修改了,下面我畫了張圖供你理解。

總結(jié)
這篇文章中我?guī)Т蠹页醪搅私饬?nbsp;Spring Cloud 的各個組件,他們有
Eureka 服務發(fā)現(xiàn)框架 Ribbon 進程內(nèi)負載均衡器 Open Feign 服務調(diào)用映射 Hystrix 服務降級熔斷器 Zuul 微服務網(wǎng)關(guān) Config 微服務統(tǒng)一配置中心 Bus 消息總線
如果你能這個時候能看懂下面那張圖,也就說明了你已經(jīng)對 Spring Cloud 微服務有了一定的架構(gòu)認識。

往期推薦
下方二維碼關(guān)注我

技術(shù)草根,堅持分享 編程,算法,架構(gòu)


