天天看點

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

原文連結:https://juejin.cn/post/6844904007975043079

寫在前面的話

馬上要考試了!!!

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

給你們看看我上課在幹啥你就知道我為啥啥都不會了。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

上課筆記。。

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

是以,冒着期末要挂科的風險👊,我也得把這篇文章寫完,給大家分享知識,自己也能重新複習和認識一下

Spring Cloud

我女朋友說,要是這篇文章能有 50 個贊就給我買個 SSD 🙏🙏🙏

首先我給大家看一張圖,如果大家對這張圖有些地方不太了解的話,我希望你們看完我這篇文章會恍然大悟。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

總體架構

什麼是Spring cloud

建構分布式系統比較複雜和容易出錯。Spring Cloud 為最常見的分布式系統模式提供了一種簡單且易于接受的程式設計模型,幫助開發人員建構有彈性的、可靠的、協調的應用程式。Spring Cloud 建構于 Spring Boot 之上,使得開發者很容易入手并快速應用于生産中。

官方果然官方,介紹都這麼有闆有眼的。

我所了解的

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

提供的一些服務,就像我們現在需要租他們的房子一樣。

但是如果隻是租客和房東之間進行尋找的話,他們的效率是很低的,房東找不到租客賺不到錢,租客找不到房東住不了房。是以,後來房東肯定就想到了廣播自己的房源資訊(比如在街邊貼貼小廣告),這樣對于房東來說已經完成他的任務(将房源公布出去),但是有兩個問題就出現了。第一、其他不是租客的都能收到這種租房消息,這在現實世界沒什麼,但是在計算機的世界中就會出現資源消耗的問題了。第二、租客這樣還是很難找到你,試想一下我需要租房,我還需要東一個西一個地去找街邊小廣告,麻不麻煩?

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

那怎麼辦呢?我們當然不會那麼傻乎乎的,第一時間就是去找 中介 呀,它為我們提供了統一房源的地方,我們消費者隻需要跑到它那裡去找就行了。而對于房東來說,他們也隻需要把房源在中介那裡釋出就行了。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

那麼現在,我們的模式就是這樣的了。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

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

  1. 房東注冊之後如果不想賣房子了怎麼辦?我們是不是需要讓房東定期續約?如果房東不進行續約是不是要将他們從中介那裡的注冊清單中移除。
  2. 租客是不是也要進行注冊呢?不然合同乙方怎麼來呢?
  3. 中介可不可以做連鎖店呢?如果這一個店因為某些不可抗力因素而無法使用,那麼我們是否可以換一個連鎖店呢?

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

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

好了,舉完這個🌰我們就可以來看關于

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

架構圖,你會發現和我們前面畫的中介圖别無二緻。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

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

公司的一個開源的負載均衡 項目,是一個用戶端/程序内負載均衡器,運作在消費者端。

我們再舉個🌰,比如我們設計了一個秒殺系統,但是為了整個系統的 高可用 ,我們需要将這個系統做一個叢集,而這個時候我們消費者就可以擁有多個秒殺系統的調用途徑了,如下圖。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

如果這個時候我們沒有進行一些 均衡操作 ,如果我們對

秒殺系統1

進行大量的調用,而另外兩個基本不請求,就會導緻

秒殺系統1

崩潰,而另外兩個就變成了傀儡,那麼我們為什麼還要做叢集,我們高可用展現的意義又在哪呢?

是以

Ribbon

出現了,注意我們上面加粗的幾個字——運作在消費者端。指的是,

Ribbon

是運作在消費者端的負載均衡器,如下圖。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

其工作原理就是

Consumer

端擷取到了所有的服務清單之後,在其内部使用負載均衡算法,進行對多個系統的調用。

Nginx 和 Ribbon 的對比

提到 負載均衡 就不得不提到大名鼎鼎的

Nignx

了,而和

Ribbon

不同的是,它是一種集中式的負載均衡器。

何為集中式呢?簡單了解就是 将所有請求都集中起來,然後再進行負載均衡。如下圖。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

我們可以看到

Nginx

是接收了所有的請求進行負載均衡的,而對于

Ribbon

來說它是在消費者端進行的負載均衡。如下圖。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結
請注意

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之熔斷和降級

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結
在分布式環境中,不可避免地會有許多服務依賴項中的某些失敗。Hystrix是一個庫,可通過添加等待時間容限和容錯邏輯來幫助您控制這些分布式服務之間的互動。Hystrix通過隔離服務之間的通路點,停止服務之間的級聯故障并提供後備選項來實作此目的,所有這些都可以提高系統的整體彈性。

總體來說

Hystrix

就是一個能進行 熔斷 和 降級 的庫,通過使用它能提高整個系統的彈性。

那麼什麼是 熔斷和降級 呢?再舉個🌰,此時我們整個微服務系統是這樣的。服務A調用了服務B,服務B再調用了服務C,但是因為某些原因,服務C頂不住了,這個時候大量請求會在服務C阻塞。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

服務C阻塞了還好,畢竟隻是一個系統崩潰了。但是請注意這個時候因為服務C不能傳回響應,那麼服務B調用服務C的的請求就會阻塞,同理服務B阻塞了,那麼服務A也會阻塞崩潰。

請注意,為什麼阻塞會崩潰。因為這些請求會消耗占用系統的線程、IO 等資源,消耗完你這個系統伺服器不就崩了麼。
【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

這就叫 服務雪崩。媽耶,上面兩個 熔斷 和 降級 你都沒給我解釋清楚,你現在又給我扯什麼 服務雪崩 ?😵😵😵

别急,聽我慢慢道來。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

不聽我也得講下去!

所謂 熔斷 就是服務雪崩的一種有效解決方案。當指定時間窗内的請求失敗率達到設定門檻值時,系統将通過 斷路器 直接将此請求鍊路斷開。

也就是我們上面服務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

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結
ZUUL 是從裝置和 web 站點到 Netflix 流應用後端的所有請求的前門。作為邊界服務應用,ZUUL 是為了實作動态路由、監視、彈性和安全性而建構的。它還具有根據情況将請求路由到多個 Amazon Auto Scaling Groups(亞馬遜自動縮放組,亞馬遜的一種雲計算方式) 的能力

在上面我們學習了

Eureka

之後我們知道了 服務提供者 是 消費者 通過

Eureka Server

進行通路的,即

Eureka Server

是 服務提供者 的統一入口。那麼整個應用中存在那麼多 消費者 需要使用者進行調用,這個時候使用者該怎樣通路這些 消費者工程 呢?當然可以像之前那樣直接通路這些工程。但這種方式沒有統一的消費者工程調用入口,不便于通路與管理,而 Zuul 就是這樣的一個對于 消費者 的統一入口。

如果學過前端的肯定都知道 Router 吧,比如 Flutter 中的路由,Vue,React中的路由,用了 Zuul 你會發現在路由功能方面和前端配置路由基本是一個理。😁 我偶爾撸撸 Flutter。

大家對網關應該很熟吧,簡單來講網關是系統唯一對外的入口,介于用戶端與伺服器端之間,用于對請求進行鑒權、限流、 路由、監控等功能。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

沒錯,網關有的功能,

Zuul

基本都有。而

Zuul

中最關鍵的就是 路由和過濾器 了,在官方文檔中

Zuul

的标題就是

Router and Filter : Zuul

Zuul 的路由功能

簡單配置

本來想給你們複制一些代碼,但是想了想,因為各個代碼配置比較零散,看起來也比較零散,我決定還是給你們畫個圖來解釋吧。

請不要因為我這麼好就給我點贊 👍 。 瘋狂暗示。

比如這個時候我們已經向

Eureka Server

注冊了兩個

Consumer

、三個

Provicer

,這個時候我們再加個

Zuul

網關應該變成這樣子了。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

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 容器中就行了。

在給你們看代碼之前我先給你們解釋一下關于過濾器的一些注意點。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

過濾器類型: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

隻要是限流的活它都能幹,這裡我隻是簡單舉個🌰。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

我先來解釋一下什麼是 令牌桶限流 吧。

首先我們會有個桶,如果裡面沒有滿那麼就會以一定 固定的速率 會往裡面放令牌,一個請求過來首先要從桶中擷取令牌,如果沒有擷取到,那麼這個請求就拒絕,如果擷取到那麼就放行。很簡單吧,啊哈哈、

下面我們就通過

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

就暴露出一個接口給啟動應用來擷取它所想要的配置檔案,應用擷取到配置檔案然後再進行它的初始化工作。就如下圖。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

當然這裡你肯定還會有一個疑問,如果我在應用運作時去更改遠端配置倉庫(Git)中的對應配置檔案,那麼依賴于這個配置檔案的已啟動的應用會不會進行其相應配置的更改呢?

答案是不會的。

什麼?那怎麼進行動态修改配置檔案呢?這不是出現了 配置漂移 嗎?你個渣男🤬,你又騙我!

别急嘛,你可以使用

Webhooks

,這是

github

提供的功能,它能確定遠端庫的配置檔案更新後用戶端中的配置資訊也得到更新。

噢噢,這還差不多。我去查查怎麼用。

慢着,聽我說完,

Webhooks

雖然能解決,但是你了解一下會發現它根本不适合用于生産環境,是以基本不會使用它的。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

而一般我們會使用

Bus

消息總線 +

Spring Cloud Config

進行配置的動态重新整理。

引出 Spring Cloud Bus

用于将服務和服務執行個體與分布式消息系統連結在一起的事件總線。在叢集中傳播狀态更改很有用(例如配置更改事件)。

你可以簡單了解為

Spring Cloud Bus

的作用就是管理和廣播分布式系統中的消息,也就是消息引擎系統中的廣播模式。當然作為 消息總線 的

Spring Cloud Bus

可以做很多事而不僅僅是用戶端的配置重新整理功能。

而擁有了

Spring Cloud Bus

之後,我們隻需要建立一個簡單的請求,并且加上

@ResfreshScope

注解就能進行配置的動态修改了,下面我畫了張圖供你了解。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結

總結

這篇文章中我帶大家初步了解了

Spring Cloud

的各個元件,他們有

  • Eureka 服務發現架構
  • Ribbon 程序内負載均衡器
  • Open Feign 服務調用映射
  • Hystrix 服務降級熔斷器
  • Zuul 微服務網關
  • Config 微服務統一配置中心
  • Bus 消息總線

如果你能這個時候能看懂下面那張圖,也就說明了你已經對

Spring Cloud

微服務有了一定的架構認識。

【轉】冒着挂科的風險也要給你們看的 Spring Cloud 入門總結
如果覺得我寫的還不錯,那就留下個贊吧! 👍👍👍

作者:FrancisQ

連結:https://juejin.cn/post/6844904007975043079

來源:掘金

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

繼續閱讀