javascript
SpringCloud学习(一) SpringCloud 基础介绍
作者:FrancisQ
鏈接:https://juejin.cn/post/6844904007975043079
來源:掘金
?
什么是Spring cloud
構建分布式系統不需要復雜和容易出錯。Spring Cloud 為最常見的分布式系統模式提供了一種簡單且易于接受的編程模型,幫助開發人員構建有彈性的、可靠的、協調的應用程序。Spring Cloud 構建于 Spring Boot 之上,使得開發者很容易入手并快速應用于生產中。
官方果然官方,介紹都這么有板有眼的。
我所理解的 Spring Cloud 就是微服務系統架構的一站式解決方案,在平時我們構建微服務的過程中需要做如 服務發現注冊 、配置中心 、消息總線 、負載均衡 、斷路器 、數據監控 等操作,而 Spring Cloud 為我們提供了一套簡易的編程模型,使我們能在 Spring Boot 的基礎上輕松地實現微服務項目的構建。
Spring并沒有重復制造輪子,它只是將目前各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來,通過Spring Boot風格進行再封裝屏蔽掉了復雜的配置和實現原理,最終給開發者留出了一套簡單易懂、易部署和易維護的分布式系統開發工具包。 Spring Cloud正是對Netflix(是一家公司,他家網站用的微服務,比較成熟)的多個開源組件進一步的封裝而成,同時又實現了和云端平臺,和Spring Boot開發框架很好的集成。---也就是說并沒有一個框架叫SpringCloud,而是說SpringCloud是Spring對一些成熟的分布式微服務的整合起了個名字.其中開源組件是可替換的,替換后他的名字還是SpringCloud.
Spring Cloud 的版本
當然這個只是個題外話。
Spring Cloud 的版本號并不是我們通常見的數字版本號,而是一些很奇怪的單詞。這些單詞均為英國倫敦地鐵站的站名。同時根據字母表的順序來對應版本時間順序,比如:最早 的 Release 版本 Angel,第二個 Release 版本 Brixton(英國地名),然后是 Camden、 Dalston、Edgware、Finchley、Greenwich、Hoxton。
Spring Cloud 的組成
Spring Cloud的子項目,大致可分成兩類,一類是對現有成熟框架”Spring Boot化”的封裝和抽象,也是數量最多的項目;第二類是開發了一部分分布式系統的基礎設施的實現,如Spring Cloud Stream扮演的就是kafka, ActiveMQ這樣的角色。對于我們想快速實踐微服務的開發者來說,第一類子項目就已經足夠使用,如:Spring Cloud Netflix,是對Netflix開發的一套分布式服務框架的封裝,包括服務的發現和注冊,負載均衡、斷路器、REST客戶端、請求路由等。該項目是Spring Cloud的子項目之一,主要內容是對Netflix公司一系列開源產品的包裝,它為Spring Boot應用提供了自配置的Netflix OSS整合。 通過一些簡單的注解,開發者就可以快速的在應用中配置一下常用模塊并構建龐大的分布式系統。它主要提供的模塊包括:服務發現(Eureka),斷路器(Hystrix),智能路由(Zuul),客戶端負載均衡(Ribbon)等。
Spring Cloud Netflix 這可是個大boss,地位僅次于老大,老大各項服務依賴與它,與各種Netflix OSS組件集成,組成微服務的核心,它的小弟主要有Eureka, Hystrix, Zuul, Archaius... 以下介紹
Spring Cloud 的服務發現框架——Eureka
Eureka是基于REST(代表性狀態轉移)的服務,主要在AWS云中用于定位服務,以實現負載均衡和中間層服務器的故障轉移。我們稱此服務為Eureka服務器。Eureka還帶有一個基于Java的客戶端組件Eureka Client,它使與服務的交互變得更加容易。客戶端還具有一個內置的負載平衡器,可以執行基本的循環負載平衡。在Netflix,更復雜的負載均衡器將Eureka包裝起來,以基于流量,資源使用,錯誤條件等多種因素提供加權負載均衡,以提供出色的彈性。
總的來說,Eureka 就是一個服務發現框架。何為服務,何又為發現呢?
舉一個生活中的例子,就比如我們平時租房子找中介的事情。
在沒有中介的時候我們需要一個一個去尋找是否有房屋要出租的房東,這顯然會非常的費力,一你找憑一個人的能力是找不到很多房源供你選擇,再者你也懶得這么找下去(找了這么久,沒有合適的只能將就)。這里的我們就相當于微服務中的 Consumer ,而那些房東就相當于微服務中的 Provider 。消費者 Consumer 需要調用提供者 Provider 提供的一些服務,就像我們現在需要租他們的房子一樣。
但是如果只是租客和房東之間進行尋找的話,他們的效率是很低的,房東找不到租客賺不到錢,租客找不到房東住不了房。所以,后來房東肯定就想到了廣播自己的房源信息(比如在街邊貼貼小廣告),這樣對于房東來說已經完成他的任務(將房源公布出去),但是有兩個問題就出現了。第一、其他不是租客的都能收到這種租房消息,這在現實世界沒什么,但是在計算機的世界中就會出現資源消耗的問題了。第二、租客這樣還是很難找到你,試想一下我需要租房,我還需要東一個西一個地去找街邊小廣告,麻不麻煩?
?
那怎么辦呢?我們當然不會那么傻乎乎的,第一時間就是去找 中介 呀,它為我們提供了統一房源的地方,我們消費者只需要跑到它那里去找就行了。而對于房東來說,他們也只需要把房源在中介那里發布就行了。
?
那么現在,我們的模式就是這樣的了。
?
但是,這個時候還會出現一些問題。
針對上面的問題我們來重新構建一下上面的模式圖
?
好了,舉完這個🌰我們就可以來看關于 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 架構圖,你會發現和我們前面畫的中介圖別無二致。
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@Autowiredprivate?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?{//?返回過濾器類型?這里是前置過濾器@Overridepublic?String?filterType()?{return?FilterConstants.PRE_TYPE;}//?指定過濾順序?越小越先執行,這里第一個執行//?當然不是只真正第一個?在Zuul內置中有其他過濾器會先執行//?那是寫死的?比如?SERVLET_DETECTION_FILTER_ORDER?=?-3@Overridepublic?int?filterOrder()?{return?0;}//?什么時候該進行過濾//?這里我們可以進行一些判斷,這樣我們就可以過濾掉一些不符合規定的請求等等@Overridepublic?boolean?shouldFilter()?{return?true;}//?如果過濾器允許通過則怎么進行處理@Overridepublic?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?{//?指定該過濾器的過濾類型//?此時是后置過濾器@Overridepublic?String?filterType()?{return?FilterConstants.POST_TYPE;}//?SEND_RESPONSE_FILTER_ORDER?是最后一個過濾器//?我們此過濾器在它之前執行@Overridepublic?int?filterOrder()?{return?FilterConstants.SEND_RESPONSE_FILTER_ORDER?-?1;}@Overridepublic?boolean?shouldFilter()?{return?true;}//?過濾時執行的策略@Overridepublic?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);@Overridepublic?String?filterType()?{return?FilterConstants.PRE_TYPE;}@Overridepublic?int?filterOrder()?{return?-5;}@Overridepublic?Object?run()?throws?ZuulException?{log.info("放行");return?null;}@Overridepublic?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 微服務有了一定的架構認識。
?
總結
以上是生活随笔為你收集整理的SpringCloud学习(一) SpringCloud 基础介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 零学Java(7)之数据类型,小AD竟然
- 下一篇: 《公主连结》交互设计师:如何用TV画手法