一篇搞定SpringCloud面試(兩萬字)

做者:FrancisQ 出處:https://juejin.im/post/6844904007975043079前端

寫在前面的話

立刻要考試了!!!

做爲一個苦逼的在讀大學生,又要面臨半年一度的期末考試了,由於上課沒聽,我啥都不會,什麼通訊原理,單片機。。。饒了我吧!!!vue

給大家看看我上課在幹啥你就知道我爲啥啥都不會了。java


上課筆記。。


emmm,字比較醜😑。我還記得那是一堂英語課,老師不讓用電子設備,我只能手寫我這篇文章的思路。。。git

因此,冒着期末要掛科的風險👊,我也得把這篇文章寫完,給你們分享知識,本身也能從新複習和認識一下 Spring Cloudgithub

我女友說,要是這篇文章能有 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、租客這樣仍是很難找到你,試想一下我須要租房,我還須要東一個西一個地去找街邊小廣告,麻不麻煩?



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



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



可是,這個時候還會出現一些問題。

  1. 房東註冊以後若是不想賣房子了怎麼辦?咱們是否是須要讓房東按期續約?若是房東不進行續約是否是要將他們從中介那裏的註冊列表中移除

  2. 租客是否是也要進行註冊呢?否則合同乙方怎麼來呢?

  3. 中介可不能夠作連鎖店呢?若是這一個店由於某些不可抗力因素而沒法使用,那麼咱們是否能夠換一個連鎖店呢?

針對上面的問題咱們來從新構建一下上面的模式圖



好了,舉完這個🌰咱們就能夠來看關於 Eureka 的一些基礎概念了,你會發現這東西理解起來怎麼這麼簡單。👎👎👎

服務發現:其實就是一個「中介」,整個過程當中有三個角色:服務提供者(出租房子的)、服務消費者(租客)、服務中介(房屋中介)

服務提供者:就是提供一些本身可以執行的一些服務給外界。

服務消費者:就是須要使用一些服務的「用戶」。

服務中介:其實就是服務提供者和服務消費者之間的「橋樑」,服務提供者能夠把本身註冊到服務中介那裏,而服務消費者如須要消費一些服務(使用一些功能)就能夠在服務中介中尋找註冊在服務中介的服務提供者。

服務註冊 Register

官方解釋:當 Eureka 客戶端向 Eureka Server 註冊時,它提供自身的元數據,好比IP地址、端口,運行情況指示符URL,主頁等。

結合中介理解:房東 (提供者 Eureka Client Provider)在中介 (服務器 Eureka Server) 那裏登記房屋的信息,好比面積,價格,地段等等(元數據 metaData)。

服務續約 Renew

官方解釋:Eureka 客戶會每隔30秒(默認狀況下)發送一次心跳來續約。經過續約來告知 Eureka ServerEureka 客戶仍然存在,沒有出現問題。正常狀況下,若是 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 架構圖,你會發現和咱們前面畫的中介圖別無二致。


Eureka架構圖


固然,能夠充當服務發現的組件有不少:ZookeeperConsulEureka 等。

更多關於 Eureka 的知識(自我保護,初始註冊策略等等)能夠本身去官網查看,或者查看個人另外一篇文章 深刻理解 Eureka。

負載均衡之 Ribbon

什麼是 RestTemplate?

不是講 Ribbon 麼?怎麼扯到了 RestTemplate 了?你先別急,聽我慢慢道來。

我不聽我不聽我不聽🙉🙉🙉。

我就說一句!RestTemplateSpring提供的一個訪問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

有了 EurekaRestTemplateRibbon 咱們就能夠😃愉快地進行服務間的調用了,可是使用 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);
}
複製代碼

這樣每次都調用 RestRemplateAPI 是否太麻煩,我能不能像調用原來代碼同樣進行各個服務間的調用呢?

💡💡💡聰明的小朋友確定想到了,那就用 映射 呀,就像域名和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

爲何要使用進行配置管理?

當咱們的微服務系統開始慢慢地龐大起來,那麼多 ConsumerProviderEureka ServerZuul 系統都會持有本身的配置,這個時候咱們在項目運行的時候可能須要更改某些應用的配置,若是咱們不進行配置的統一管理,咱們只能去每一個應用下一個一個尋找配置文件而後修改配置文件再重啓應用

首先對於分佈式系統而言咱們就不該該去每一個應用下去分別修改配置文件,再者對於重啓應用來講,服務沒法訪問因此直接拋棄了可用性,這是咱們更不肯見到的。

那麼有沒有一種方法既能對配置文件統一地進行管理,又能在項目運行時動態修改配置文件呢?

那就是我今天所要介紹的 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 微服務有了必定的架構認識。



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

【熱門閱讀】

[1]  看完這篇Redis緩存三大問題,保你能和麪試官互扯。
[2] 我覺得我對Mysql事務很熟,直到我遇到了阿里面試官
[3]  面試官:你知道java類是怎麼跑起來的嗎?問的我一臉懵
[4]  面試造飛機系列:面對Redis持久化連環Call,你還頂得住嗎?
 [5] 面試造飛機系列:用心整理的HashMap面試題,之後都不用擔憂了

 [6] 大廠面試官必問的Mysql鎖機制


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

相關文章
相關標籤/搜索