
做者:FrancisQ 出處:https://juejin.im/post/6844904007975043079前端
寫在前面的話
立刻要考試了!!!
做爲一個苦逼的在讀大學生,又要面臨半年一度的期末考試了,由於上課沒聽,我啥都不會,什麼通訊原理,單片機。。。饒了我吧!!!vue
給大家看看我上課在幹啥你就知道我爲啥啥都不會了。java

emmm,字比較醜😑。我還記得那是一堂英語課,老師不讓用電子設備,我只能手寫我這篇文章的思路。。。git
因此,冒着期末要掛科的風險👊,我也得把這篇文章寫完,給你們分享知識,本身也能從新複習和認識一下 Spring Cloud
。github
我女友說,要是這篇文章能有 50 個贊就給我買個 SSD 🙏🙏🙏web
首先我給你們看一張圖,若是你們對這張圖有些地方不太理解的話,我但願大家看完我這篇文章會恍然大悟。面試

什麼是Spring cloud
構建分佈式系統不須要複雜和容易出錯。Spring Cloud 爲最多見的分佈式系統模式提供了一種簡單且易於接受的編程模型,幫助開發人員構建有彈性的、可靠的、協調的應用程序。Spring Cloud 構建於 Spring Boot 之上,使得開發者很容易入手並快速應用於生產中。算法
官方果真官方,介紹都這麼有板有眼的。sql
我所理解的 Spring Cloud
就是微服務系統架構的一站式解決方案,在平時咱們構建微服務的過程當中須要作如 服務發現註冊 、配置中心 、消息總線 、負載均衡 、斷路器 、數據監控 等操做,而 Spring Cloud 爲咱們提供了一套簡易的編程模型,使咱們能在 Spring Boot 的基礎上輕鬆地實現微服務項目的構建。編程
Spring Cloud 的版本
固然這個只是個題外話。
Spring Cloud 的版本號並非咱們一般見的數字版本號,而是一些很奇怪的單詞。這些單詞均爲英國倫敦地鐵站的站名。同時根據字母表的順序來對應版本時間順序,好比:最先 的 Release 版本 Angel,第二個 Release 版本 Brixton(英國地名),而後是 Camden、 Dalston、Edgware、Finchley、Greenwich、Hoxton。
Spring Cloud 的服務發現框架——Eureka
Eureka是基於REST(表明性狀態轉移)的服務,主要在AWS雲中用於定位服務,以實現負載均衡和中間層服務器的故障轉移。咱們稱此服務爲Eureka服務器。Eureka還帶有一個基於Java的客戶端組件Eureka Client,它使與服務的交互變得更加容易。客戶端還具備一個內置的負載平衡器,能夠執行基本的循環負載平衡。在Netflix,更復雜的負載均衡器將Eureka包裝起來,以基於流量,資源使用,錯誤條件等多種因素提供加權負載均衡,以提供出色的彈性。
總的來講,Eureka
就是一個服務發現框架。何爲服務,何又爲發現呢?
舉一個生活中的例子,就好比咱們平時租房子找中介的事情。
在沒有中介的時候咱們須要一個一個去尋找是否有房屋要出租的房東,這顯然會很是的費力,一你找憑一我的的能力是找不到不少房源供你選擇,再者你也懶得這麼找下去(找了這麼久,沒有合適的只能將就)。這裏的咱們就至關於微服務中的 Consumer
,而那些房東就至關於微服務中的 Provider
。消費者 Consumer
須要調用提供者 Provider
提供的一些服務,就像咱們如今須要租他們的房子同樣。
可是若是隻是租客和房東之間進行尋找的話,他們的效率是很低的,房東找不到租客賺不到錢,租客找不到房東住不了房。因此,後來房東確定就想到了廣播本身的房源信息(好比在街邊貼貼小廣告),這樣對於房東來講已經完成他的任務(將房源公佈出去),可是有兩個問題就出現了。第1、其餘不是租客的都能收到這種租房消息,這在現實世界沒什麼,可是在計算機的世界中就會出現資源消耗的問題了。第2、租客這樣仍是很難找到你,試想一下我須要租房,我還須要東一個西一個地去找街邊小廣告,麻不麻煩?

那怎麼辦呢?咱們固然不會那麼傻乎乎的,第一時間就是去找 中介 呀,它爲咱們提供了統一房源的地方,咱們消費者只須要跑到它那裏去找就好了。而對於房東來講,他們也只須要把房源在中介那裏發佈就好了。

那麼如今,咱們的模式就是這樣的了。

可是,這個時候還會出現一些問題。
房東註冊以後若是不想賣房子了怎麼辦?咱們是否是須要讓房東按期續約?若是房東不進行續約是否是要將他們從中介那裏的註冊列表中移除。
租客是否是也要進行註冊呢?否則合同乙方怎麼來呢?
中介可不能夠作連鎖店呢?若是這一個店由於某些不可抗力因素而沒法使用,那麼咱們是否能夠換一個連鎖店呢?
針對上面的問題咱們來從新構建一下上面的模式圖

好了,舉完這個🌰咱們就能夠來看關於 Eureka
的一些基礎概念了,你會發現這東西理解起來怎麼這麼簡單。👎👎👎
服務發現:其實就是一個「中介」,整個過程當中有三個角色:服務提供者(出租房子的)、服務消費者(租客)、服務中介(房屋中介)。
服務提供者:就是提供一些本身可以執行的一些服務給外界。
服務消費者:就是須要使用一些服務的「用戶」。
服務中介:其實就是服務提供者和服務消費者之間的「橋樑」,服務提供者能夠把本身註冊到服務中介那裏,而服務消費者如須要消費一些服務(使用一些功能)就能夠在服務中介中尋找註冊在服務中介的服務提供者。
服務註冊 Register:
官方解釋:當 Eureka
客戶端向 Eureka Server
註冊時,它提供自身的元數據,好比IP地址、端口,運行情況指示符URL,主頁等。
結合中介理解:房東 (提供者 Eureka Client Provider
)在中介 (服務器 Eureka Server
) 那裏登記房屋的信息,好比面積,價格,地段等等(元數據 metaData
)。
服務續約 Renew:
官方解釋:Eureka
客戶會每隔30秒(默認狀況下)發送一次心跳來續約。經過續約來告知 Eureka Server
該 Eureka
客戶仍然存在,沒有出現問題。正常狀況下,若是 Eureka Server
在90秒沒有收到 Eureka
客戶的續約,它會將實例從其註冊表中刪除。
結合中介理解:房東 (提供者 Eureka Client Provider
) 按期告訴中介 (服務器 Eureka Server
) 個人房子還租(續約) ,中介 (服務器Eureka Server
) 收到以後繼續保留房屋的信息。
獲取註冊列表信息 Fetch Registries:
官方解釋:Eureka
客戶端從服務器獲取註冊表信息,並將其緩存在本地。客戶端會使用該信息查找其餘服務,從而進行遠程調用。該註冊列表信息按期(每30秒鐘)更新一次。每次返回註冊列表信息可能與 Eureka
客戶端的緩存信息不一樣, Eureka
客戶端自動處理。若是因爲某種緣由致使註冊列表信息不能及時匹配,Eureka
客戶端則會從新獲取整個註冊表信息。Eureka
服務器緩存註冊列表信息,整個註冊表以及每一個應用程序的信息進行了壓縮,壓縮內容和沒有壓縮的內容徹底相同。Eureka
客戶端和 Eureka
服務器可使用JSON / XML格式進行通信。在默認的狀況下 Eureka
客戶端使用壓縮 JSON
格式來獲取註冊列表的信息。
結合中介理解:租客(消費者 Eureka Client Consumer
) 去中介 (服務器 Eureka Server
) 那裏獲取全部的房屋信息列表 (客戶端列表 Eureka Client List
) ,並且租客爲了獲取最新的信息會按期向中介 (服務器 Eureka Server
) 那裏獲取並更新本地列表。
服務下線 Cancel:
官方解釋:Eureka客戶端在程序關閉時向Eureka服務器發送取消請求。發送請求後,該客戶端實例信息將從服務器的實例註冊表中刪除。該下線請求不會自動完成,它須要調用如下內容:DiscoveryManager.getInstance().shutdownComponent();
結合中介理解:房東 (提供者 Eureka Client Provider
) 告訴中介 (服務器 Eureka Server
) 個人房子不租了,中介以後就將註冊的房屋信息從列表中剔除。
服務剔除 Eviction:
官方解釋:在默認的狀況下,當Eureka客戶端連續90秒(3個續約週期)沒有向Eureka服務器發送服務續約,即心跳,Eureka服務器會將該服務實例從服務註冊列表刪除,即服務剔除。
結合中介理解:房東(提供者 Eureka Client Provider
) 會按期聯繫 中介 (服務器 Eureka Server
) 告訴他個人房子還租(續約),若是中介 (服務器 Eureka Server
) 長時間沒收到提供者的信息,那麼中介會將他的房屋信息給下架(服務剔除)。
下面就是 Netflix
官方給出的 Eureka
架構圖,你會發現和咱們前面畫的中介圖別無二致。

固然,能夠充當服務發現的組件有不少:Zookeeper
,Consul
, Eureka
等。
更多關於 Eureka
的知識(自我保護,初始註冊策略等等)能夠本身去官網查看,或者查看個人另外一篇文章 深刻理解 Eureka。
負載均衡之 Ribbon
什麼是 RestTemplate?
不是講 Ribbon
麼?怎麼扯到了 RestTemplate
了?你先別急,聽我慢慢道來。
我不聽我不聽我不聽🙉🙉🙉。
我就說一句!RestTemplate
是Spring
提供的一個訪問Http服務的客戶端類,怎麼說呢?就是微服務之間的調用是使用的 RestTemplate
。好比這個時候咱們 消費者B 須要調用 提供者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);
}
複製代碼
若是你對源碼感興趣的話,你會發現上面咱們所講的 Eureka
框架中的 註冊、續約 等,底層都是使用的 RestTemplate
。
爲何須要 Ribbon?
Ribbon
是 Netflix
公司的一個開源的負載均衡 項目,是一個客戶端/進程內負載均衡器,運行在消費者端。
咱們再舉個🌰,好比咱們設計了一個秒殺系統,可是爲了整個系統的 高可用 ,咱們須要將這個系統作一個集羣,而這個時候咱們消費者就能夠擁有多個秒殺系統的調用途徑了,以下圖。

若是這個時候咱們沒有進行一些 均衡操做 ,若是咱們對 秒殺系統1
進行大量的調用,而另外兩個基本不請求,就會致使 秒殺系統1
崩潰,而另外兩個就變成了傀儡,那麼咱們爲何還要作集羣,咱們高可用體現的意義又在哪呢?
因此 Ribbon
出現了,注意咱們上面加粗的幾個字——運行在消費者端。指的是,Ribbon
是運行在消費者端的負載均衡器,以下圖。

其工做原理就是 Consumer
端獲取到了全部的服務列表以後,在其內部使用負載均衡算法,進行對多個系統的調用。
Nginx 和 Ribbon 的對比
提到 負載均衡 就不得不提到大名鼎鼎的 Nignx
了,而和 Ribbon
不一樣的是,它是一種集中式的負載均衡器。
何爲集中式呢?簡單理解就是 將全部請求都集中起來,而後再進行負載均衡。以下圖。

咱們能夠看到 Nginx
是接收了全部的請求進行負載均衡的,而對於 Ribbon
來講它是在消費者端進行的負載均衡。以下圖。

請注意
Request
的位置,在Nginx
中請求是先進入負載均衡器,而在Ribbon
中是先在客戶端進行負載均衡才進行請求的。
Ribbon 的幾種負載均衡算法
負載均衡,無論 Nginx
仍是 Ribbon
都須要其算法的支持,若是我沒記錯的話 Nginx
使用的是 輪詢和加權輪詢算法。而在 Ribbon
中有更多的負載均衡調度算法,其默認是使用的 RoundRobinRule
輪詢策略。
RoundRobinRule:輪詢策略。
Ribbon
默認採用的策略。若通過一輪輪詢沒有找到可用的provider
,其最多輪詢 10 輪。若最終尚未找到,則返回 null。RandomRule: 隨機策略,從全部可用的 provider 中隨機選擇一個。
RetryRule: 重試策略。先按照 RoundRobinRule 策略獲取 provider,若獲取失敗,則在指定的時限內重試。默認的時限爲 500 毫秒。
🐦🐦🐦 還有不少,這裏不一一舉🌰了,你最須要知道的是默認輪詢算法,而且能夠更換默認的負載均衡算法,只須要在配置文件中作出修改就行。
providerName:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
複製代碼
固然,在 Ribbon
中你還能夠自定義負載均衡算法,你只須要實現 IRule
接口,而後修改配置文件或者自定義 Java Config
類。
什麼是 Open Feign
有了 Eureka
,RestTemplate
,Ribbon
咱們就能夠😃愉快地進行服務間的調用了,可是使用 RestTemplate
仍是不方便,咱們每次都要進行這樣的調用。
@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);
}
複製代碼
這樣每次都調用 RestRemplate
的 API
是否太麻煩,我能不能像調用原來代碼同樣進行各個服務間的調用呢?
💡💡💡聰明的小朋友確定想到了,那就用 映射 呀,就像域名和IP地址的映射。咱們能夠將被調用的服務代碼映射到消費者端,這樣咱們就能夠 「無縫開發」啦。
OpenFeign 也是運行在消費者端的,使用 Ribbon 進行負載均衡,因此 OpenFeign 直接內置了 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
就能夠像原來調用 Service
層代碼同樣調用它了。
@RestController
public class TestController {
// 這裏就至關於原來自動注入的 Service
@Autowired
private TestClient testClient;
// controller 調用 service 層代碼
@RequestMapping(value = "/test", method = RequestMethod.POST)
public CommonResponse<List<Plan>> get(@RequestBody planGetRequest request) {
return testClient.getPlans(request);
}
}
複製代碼
必不可少的 Hystrix
什麼是 Hystrix之熔斷和降級

在分佈式環境中,不可避免地會有許多服務依賴項中的某些失敗。Hystrix是一個庫,可經過添加等待時間容限和容錯邏輯來幫助您控制這些分佈式服務之間的交互。Hystrix經過隔離服務之間的訪問點,中止服務之間的級聯故障並提供後備選項來實現此目的,全部這些均可以提升系統的總體彈性。
整體來講 Hystrix
就是一個能進行 熔斷 和 降級 的庫,經過使用它能提升整個系統的彈性。
那麼什麼是 熔斷和降級 呢?再舉個🌰,此時咱們整個微服務系統是這樣的。服務A調用了服務B,服務B再調用了服務C,可是由於某些緣由,服務C頂不住了,這個時候大量請求會在服務C阻塞。

服務C阻塞了還好,畢竟只是一個系統崩潰了。可是請注意這個時候由於服務C不能返回響應,那麼服務B調用服務C的的請求就會阻塞,同理服務B阻塞了,那麼服務A也會阻塞崩潰。
請注意,爲何阻塞會崩潰。由於這些請求會消耗佔用系統的線程、IO 等資源,消耗完你這個系統服務器不就崩了麼。

這就叫 服務雪崩。媽耶,上面兩個 熔斷 和 降級 你都沒給我解釋清楚,你如今又給我扯什麼 服務雪崩 ?😵😵😵
別急,聽我慢慢道來。

不聽我也得講下去!
所謂 熔斷 就是服務雪崩的一種有效解決方案。當指定時間窗內的請求失敗率達到設定閾值時,系統將經過 斷路器 直接將此請求鏈路斷開。
也就是咱們上面服務B調用服務C在指定時間窗內,調用的失敗率到達了必定的值,那麼 Hystrix
則會自動將 服務B與C 之間的請求都斷了,以避免致使服務雪崩現象。
其實這裏所講的 熔斷 就是指的 Hystrix
中的 斷路器模式 ,你可使用簡單的 @HystrixCommand
註解來標註某個方法,這樣 Hystrix
就會使用 斷路器 來「包裝」這個方法,每當調用時間超過指定時間時(默認爲1000ms),斷路器將會中斷對這個方法的調用。
固然你能夠對這個註解的不少屬性進行設置,好比設置超時時間,像這樣。
@HystrixCommand(
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1200")}
)
public List<Xxx> getXxxx() {
// ...省略代碼邏輯
}
複製代碼
可是,我查閱了一些博客,發現他們都將 熔斷 和 降級 的概念混淆了,以個人理解,降級是爲了更好的用戶體驗,當一個方法調用異常時,經過執行另外一種代碼邏輯來給用戶友好的回覆。這也就對應着 Hystrix
的 後備處理 模式。你能夠經過設置 fallbackMethod
來給一個方法設置備用的代碼邏輯。好比這個時候有一個熱點新聞出現了,咱們會推薦給用戶查看詳情,而後用戶會經過id去查詢新聞的詳情,可是由於這條新聞太火了(好比最近什麼*易對吧),大量用戶同時訪問可能會致使系統崩潰,那麼咱們就進行 服務降級 ,一些請求會作一些降級處理好比當前人數太多請稍後查看等等。
// 指定了後備方法調用
@HystrixCommand(fallbackMethod = "getHystrixNews")
@GetMapping("/get/news")
public News getNews(@PathVariable("id") int id) {
// 調用新聞系統的獲取新聞api 代碼邏輯省略
}
//
public News getHystrixNews(@PathVariable("id") int id) {
// 作服務降級
// 返回當前人數太多,請稍後查看
}
複製代碼
什麼是Hystrix之其餘
我在閱讀 《Spring微服務實戰》這本書的時候還接觸到了一個艙壁模式的概念。在不使用艙壁模式的狀況下,服務A調用服務B,這種調用默認的是使用同一批線程來執行的,而在一個服務出現性能問題的時候,就會出現全部線程被刷爆並等待處理工做,同時阻塞新請求,最終致使程序崩潰。而艙壁模式會將遠程資源調用隔離在他們本身的線程池中,以即可以控制單個表現不佳的服務,而不會使該程序崩潰。
具體其原理我推薦你們本身去了解一下,本篇文章中對艙壁模式不作過多解釋。固然還有 Hystrix
儀表盤,它是用來實時監控 Hystrix
的各項指標信息的,這裏我將這個問題也拋出去,但願有不瞭解的能夠本身去搜索一下。
微服務網關——Zuul

ZUUL 是從設備和 web 站點到 Netflix 流應用後端的全部請求的前門。做爲邊界服務應用,ZUUL 是爲了實現動態路由、監視、彈性和安全性而構建的。它還具備根據狀況將請求路由到多個 Amazon Auto Scaling Groups(亞馬遜自動縮放組,亞馬遜的一種雲計算方式) 的能力
在上面咱們學習了 Eureka
以後咱們知道了 服務提供者 是 消費者 經過 Eureka Server
進行訪問的,即 Eureka Server
是 服務提供者 的統一入口。那麼整個應用中存在那麼多 消費者 須要用戶進行調用,這個時候用戶該怎樣訪問這些 消費者工程 呢?固然能夠像以前那樣直接訪問這些工程。但這種方式沒有統一的消費者工程調用入口,不便於訪問與管理,而 Zuul 就是這樣的一個對於 消費者 的統一入口。
若是學過前端的確定都知道 Router 吧,好比 Flutter 中的路由,Vue,React中的路由,用了 Zuul 你會發如今路由功能方面和前端配置路由基本是一個理。😁 我偶爾擼擼 Flutter。
你們對網關應該很熟吧,簡單來說網關是系統惟一對外的入口,介於客戶端與服務器端之間,用於對請求進行鑑權、限流、 路由、監控等功能。

沒錯,網關有的功能,Zuul
基本都有。而 Zuul
中最關鍵的就是 路由和過濾器 了,在官方文檔中 Zuul
的標題就是
Router and Filter : Zuul
Zuul 的路由功能
簡單配置
原本想給大家複製一些代碼,可是想了想,由於各個代碼配置比較零散,看起來也比較零散,我決定仍是給大家畫個圖來解釋吧。
請不要由於我這麼好就給我點贊 👍 。瘋狂暗示。
好比這個時候咱們已經向 Eureka Server
註冊了兩個 Consumer
、三個 Provicer
,這個時候咱們再加個 Zuul
網關應該變成這樣子了。

emmm,信息量有點大,我來解釋一下。關於前面的知識我就不解釋了😐 。
首先,Zuul
須要向 Eureka
進行註冊,註冊有啥好處呢?
你傻呀,Consumer
都向 Eureka Server
進行註冊了,我網關是否是隻要註冊就能拿到全部 Consumer
的信息了?
拿到信息有什麼好處呢?
我拿到信息我是否是能夠獲取全部的 Consumer
的元數據(名稱,ip,端口)?
拿到這些元數據有什麼好處呢?拿到了咱們是否是直接能夠作路由映射?好比原來用戶調用 Consumer1
的接口 localhost:8001/studentInfo/update
這個請求,咱們是否是能夠這樣進行調用了呢?localhost:9000/consumer1/studentInfo/update
呢?你這樣是否是恍然大悟了?
這裏的url爲了讓更多人看懂因此沒有使用 restful 風格。
上面的你理解了,那麼就能理解關於 Zuul
最基本的配置了,看下面。
server:
port: 9000
eureka:
client:
service-url:
# 這裏只要註冊 Eureka 就好了
defaultZone: http://localhost:9997/eureka
複製代碼
而後在啓動類上加入 @EnableZuulProxy
註解就好了。沒錯,就是那麼簡單😃。
統一前綴
這個很簡單,就是咱們能夠在前面加一個統一的前綴,好比咱們剛剛調用的是 localhost:9000/consumer1/studentInfo/update
,這個時候咱們在 yaml
配置文件中添加以下。
zuul:
prefix: /zuul
複製代碼
這樣咱們就須要經過 localhost:9000/zuul/consumer1/studentInfo/update
來進行訪問了。
路由策略配置
你會發現前面的訪問方式(直接使用服務名),須要將微服務名稱暴露給用戶,會存在安全性問題。因此,能夠自定義路徑來替代微服務名稱,即自定義路由策略。
zuul:
routes:
consumer1: /FrancisQ1/**
consumer2: /FrancisQ2/**
複製代碼
這個時候你就可使用 localhost:9000/zuul/FrancisQ1/studentInfo/update
進行訪問了。
服務名屏蔽
這個時候你別覺得你好了,你能夠試試,在你配置完路由策略以後使用微服務名稱仍是能夠訪問的,這個時候你須要將服務名屏蔽。
zuul:
ignore-services: "*"
複製代碼
路徑屏蔽
Zuul
還能夠指定屏蔽掉的路徑 URI,即只要用戶請求中包含指定的 URI 路徑,那麼該請求將沒法訪問到指定的服務。經過該方式能夠限制用戶的權限。
zuul:
ignore-patterns: **/auto/**
複製代碼
這樣關於 auto 的請求咱們就能夠過濾掉了。
** 表明匹配多級任意路徑
*表明匹配一級任意路徑
敏感請求頭屏蔽
默認狀況下,像 Cookie、Set-Cookie 等敏感請求頭信息會被 zuul 屏蔽掉,咱們能夠將這些默認屏蔽去掉,固然,也能夠添加要屏蔽的請求頭。
Zuul 的過濾功能
若是說,路由功能是 Zuul
的基操的話,那麼過濾器就是 Zuul
的利器了。畢竟全部請求都通過網關(Zuul),那麼咱們能夠進行各類過濾,這樣咱們就能實現 限流,灰度發佈,權限控制 等等。
簡單實現一個請求時間日誌打印
要實現本身定義的 Filter
咱們只須要繼承 ZuulFilter
而後將這個過濾器類以 @Component
註解加入 Spring 容器中就好了。
在給大家看代碼以前我先給大家解釋一下關於過濾器的一些注意點。

過濾器類型:Pre、Routing、Post。前置Pre就是在請求以前進行過濾,Routing路由過濾器就是咱們上面所講的路由策略,而Post後置過濾器就是在 Response
以前進行過濾的過濾器。你能夠觀察上圖結合着理解,而且下面我會給出相應的註釋。
// 加入Spring容器
@Component
public class PreRequestFilter extends ZuulFilter {
// 返回過濾器類型 這裏是前置過濾器
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
// 指定過濾順序 越小越先執行,這裏第一個執行
// 固然不是隻真正第一個 在Zuul內置中有其餘過濾器會先執行
// 那是寫死的 好比 SERVLET_DETECTION_FILTER_ORDER = -3
@Override
public int filterOrder() {
return 0;
}
// 何時該進行過濾
// 這裏咱們能夠進行一些判斷,這樣咱們就能夠過濾掉一些不符合規定的請求等等
@Override
public boolean shouldFilter() {
return true;
}
// 若是過濾器容許經過則怎麼進行處理
@Override
public Object run() throws ZuulException {
// 這裏我設置了全局的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 是最後一個過濾器
// 咱們此過濾器在它以前執行
@Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
// 過濾時執行的策略
@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;
}
}
複製代碼
上面就簡單實現了請求時間日誌打印功能,你有沒有感覺到 Zuul
過濾功能的強大了呢?
沒有?好的、那咱們再來。
令牌桶限流
固然不只僅是令牌桶限流方式,Zuul
只要是限流的活它都能幹,這裏我只是簡單舉個🌰。

我先來解釋一下什麼是 令牌桶限流 吧。
首先咱們會有個桶,若是裏面沒有滿那麼就會以必定 固定的速率 會往裏面放令牌,一個請求過來首先要從桶中獲取令牌,若是沒有獲取到,那麼這個請求就拒絕,若是獲取到那麼就放行。很簡單吧,啊哈哈、
下面咱們就經過 Zuul
的前置過濾器來實現一下令牌桶限流。
@Component
@Slf4j
public class RouteFilter extends ZuulFilter {
// 定義一個令牌桶,每秒產生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,請求數量過多
context.setResponseStatusCode(429);
return false;
}
return true;
}
}
複製代碼
這樣咱們就能將請求數量控制在一秒兩個,有沒有以爲很酷?
關於 Zuul 的其餘
Zuul
的過濾器的功能確定不止上面我所實現的兩種,它還能夠實現 權限校驗,包括我上面提到的 灰度發佈 等等。
固然,Zuul
做爲網關確定也存在 單點問題 ,若是咱們要保證 Zuul
的高可用,咱們就須要進行 Zuul
的集羣配置,這個時候能夠藉助額外的一些負載均衡器好比 Nginx
。
Spring Cloud配置管理——Config
爲何要使用進行配置管理?
當咱們的微服務系統開始慢慢地龐大起來,那麼多 Consumer
、Provider
、Eureka Server
、Zuul
系統都會持有本身的配置,這個時候咱們在項目運行的時候可能須要更改某些應用的配置,若是咱們不進行配置的統一管理,咱們只能去每一個應用下一個一個尋找配置文件而後修改配置文件再重啓應用。
首先對於分佈式系統而言咱們就不該該去每一個應用下去分別修改配置文件,再者對於重啓應用來講,服務沒法訪問因此直接拋棄了可用性,這是咱們更不肯見到的。
那麼有沒有一種方法既能對配置文件統一地進行管理,又能在項目運行時動態修改配置文件呢?
那就是我今天所要介紹的 Spring Cloud Config
。
能進行配置管理的框架不止
Spring Cloud Config
一種,你們能夠根據需求本身選擇(disconf,阿波羅等等)。並且對於Config
來講有些地方實現的不是那麼盡人意。
Config 是什麼
Spring Cloud Config
爲分佈式系統中的外部化配置提供服務器和客戶端支持。使用Config
服務器,能夠在中心位置管理全部環境中應用程序的外部屬性。
簡單來講,Spring Cloud Config
就是能將各個 應用/系統/模塊 的配置文件存放到 統一的地方而後進行管理(Git 或者 SVN)。
你想一下,咱們的應用是否是隻有啓動的時候纔會進行配置文件的加載,那麼咱們的 Spring Cloud Config
就暴露出一個接口給啓動應用來獲取它所想要的配置文件,應用獲取到配置文件而後再進行它的初始化工做。就以下圖。

固然這裏你確定還會有一個疑問,若是我在應用運行時去更改遠程配置倉庫(Git)中的對應配置文件,那麼依賴於這個配置文件的已啓動的應用會不會進行其相應配置的更改呢?
答案是不會的。
什麼?那怎麼進行動態修改配置文件呢?這不是出現了 配置漂移 嗎?你個渣男🤬,你又騙我!
別急嘛,你可使用 Webhooks
,這是 github
提供的功能,它能確保遠程庫的配置文件更新後客戶端中的配置信息也獲得更新。
噢噢,這還差很少。我去查查怎麼用。
慢着,聽我說完,Webhooks
雖然能解決,可是你瞭解一下會發現它根本不適合用於生產環境,因此基本不會使用它的。

而通常咱們會使用 Bus
消息總線 + Spring Cloud Config
進行配置的動態刷新。
引出 Spring Cloud Bus
用於將服務和服務實例與分佈式消息系統連接在一塊兒的事件總線。在集羣中傳播狀態更改頗有用(例如配置更改事件)。
你能夠簡單理解爲 Spring Cloud Bus
的做用就是管理和廣播分佈式系統中的消息,也就是消息引擎系統中的廣播模式。固然做爲 消息總線 的 Spring Cloud Bus
能夠作不少事而不只僅是客戶端的配置刷新功能。
而擁有了 Spring Cloud Bus
以後,咱們只須要建立一個簡單的請求,而且加上 @ResfreshScope
註解就能進行配置的動態修改了,下面我畫了張圖供你理解。

總結
這篇文章中我帶你們初步瞭解了 Spring Cloud
的各個組件,他們有
Eureka 服務發現框架
Ribbon 進程內負載均衡器
Open Feign 服務調用映射
Hystrix 服務降級熔斷器
Zuul 微服務網關
Config 微服務統一配置中心
Bus 消息總線
若是你能這個時候能看懂下面那張圖,也就說明了你已經對 Spring Cloud
微服務有了必定的架構認識。

若是以爲我寫的還不錯,那就留下個贊吧!👍👍👍

【熱門閱讀】
本文分享自微信公衆號 - 非科班的科班(LDCldc123095)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。