你們好,我是小菜。java
一個但願可以成爲 吹着牛X談架構 的男人!若是你也想成爲我想成爲的人,否則點個關注作個伴,讓小菜再也不孤單!web
本文主要介紹
SpringCloud之服務網關Gateway
正則表達式若有須要,能夠參考算法
若有幫助,不忘 點贊 ❥spring
微信公衆號已開啓,小菜良記,沒關注的同窗們記得關注哦!編程
前段時間與小夥伴閒聊時說到他們公司的現狀,近來與未來,公司將全面把單體服務向微服務架構過渡。這裏面咱們聽到了關鍵詞 --- 微服務。乍聽之下,以爲也很合理。互聯網在不斷的發展,這裏不僅是行業的發展,更是系統架構的發展。如今市面上架構大體也經歷了這幾個階段:數組
這是好事嗎?是好事,毋庸置疑。由於不跟上時代的浪潮,總會被拍死在沙灘上。但徹底是好事嗎?那也不見得。安全
咱們先要明白微服務解決了什麼問題?大方面上應該就是應用層面和人層面的問題微信
獨立
、多語言生態
也是微服務的標籤。在單體服務中,投入的人力資源越多不見得越高效,反而越容易出錯。但微服務不一樣,每一個服務都是獨立出來的,團隊能夠更加容易的協做開發,甚至一套系統,多個服務,多種語言,毫無衝突。
可是咱們凡事不能被好處矇蔽。微服務的缺點也是一直存在的:websocket
不少人認爲微服務的核心就是在於 微
。服務分的越細越好,就好像平時寫代碼的時候絲絕不考慮的單一原則,反而在服務拆分上用到淋漓盡致。這裏就須要明白一個核心概念:微服務的關鍵不在微,而是在於合適的大小
這句話好像很簡單的樣子,可是多大才算合適的大小?這可能據不一樣團隊,不一樣項目而定。合適的大小可能取決於更少的代碼倉庫,更少的部署隊列,更少的語言... 這裏更沒法一槌定音!
若是無法作到合適的大小,而無腦的拆分服務,那麼微服務可能反而成爲你項目的累贅。所以,有時全面轉型微服務反而不是好事,你以爲呢?
話題有點跑遠了,我們努力扯回來。既然微服務已經成爲主流,那麼如何設計微服務即是咱們應該作的事,而不是談及微服務之時,想到的只是與人如何爭論如何拒用微服務。那麼這篇咱們要講的是SpringCloud之服務網關Gateway
什麼是服務網關?不要給本身當頭一棒。咱們換個問法,爲何須要服務網關?
服務網關是跨一個或多個服務節點提供單個統一的訪問入口
它的做用並非無關緊要的存在,而是相當重要。咱們能夠在服務網關作路由轉發
和過濾器
的實現。優勢簡述以下:
根據圖中內容,咱們能夠得出如下信息:
路由轉發
,API監控
、權限控制
、限流
而這些即是 服務網關 存在的意義!
SpringCloud Gateway 是 SpringCloud 的一個全新項目,目標是取代Netflix Zuul。它是基於 Spring5.0 + SpringBoot2.0 + WebFlux
等技術開發的,性能高於 Zuul,據官方提供的信息來看,性能是 Zuul 的1.6倍,意在爲微服務架構提供一種簡單有效的統一的 API 路由管理方式。
SpringCloud Gateway 不只提供了統一的路由方式(反向代理),而且基於 Filter 鏈(定義過濾器對請求過濾)提供了網關基本的功能,例如:鑑權、流量控制、熔斷、路徑重寫、日誌監控等。
其實說到 Netflix Zuul,在使用或準備使用微服務架構的小夥伴應該並不陌生,畢竟Netflix 是一個老牌微服務開源者。新秀與老牌之間的爭奪,若是新秀沒有點硬實力,如何讓人安心轉型!
這裏咱們能夠順帶了解一下 Weflux,Webflux 的出現填補了 Spring 在響應式編程上的空白。
可能有不少小夥伴並不知道 Webflux,小菜接下來也會出一篇關於 Webflux 的講解,實則真香!
Webflux 的響應式編程不只僅是編程風格上的改變,而是對於一系列著名的框架都提供了響應式訪問的開發包,好比 Netty、Redis(若是不知道 Netty 的實力,能夠想一想爲何 Nginx 能夠承載那麼大的併發,底層就是基於Netty)
那麼說這麼多,跟 Zuul 有什麼關係呢?咱們能夠看下 Zuul 的 IO 模型
SpringCloud 中所集成的 Zuul 版本,採用的是 Tomcat 容器,使用的是傳統的 Servlet IO 處理模型。Servlet 是由 Servlet Container 管理生命週期的。
但問題就在於 Servlet 是一個簡單的網絡IO模型,當請求進入到 ServletContainer就會爲其綁定一個線程,在併發不高的場景下這種模型是沒有問題的,可是一旦併發上來了,線程數量就會增長。那致使的問題就是頻繁進行上下文切換,內存消耗嚴重,處理請求的時間就會變長。正所謂牽一髮而動全身!
而 SpriingCloud Zuul 即是基於 servlet 之上的一個阻塞式處理模型,即Spring實現了處理全部 request 請求的一個 servlet(DispatcherServlet),並由該 Servlet 阻塞式處理。雖然 SpringCloud Zuul 2.0 開始,也是用了 Netty 做爲併發IO框架,可是 SpringCloud 官方已經沒有集成該版本的計劃!
注:這裏沒有推崇 Gateway 的意思,具體使用依具體項目而定
最關鍵的一步即是引入網關的依賴
<!--gateway網關--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
我這裏簡單的建立了一個微服務項目,項目裏有一個 store-gateway
服務網關 和一個 store-order
訂單服務。由於咱們這篇只說明服務網關的做用,不須要太多服務提供者和消費者!
在store-order
訂單服務中只有一個控制器OrderController
,裏面也只有一個簡單到髮指的API
@RestController @RequestMapping("order") public class OrderController { @GetMapping("/{id:.+}") public String detail(@PathVariable String id) { return StringUtils.join("獲取到了ID爲", id, "的商品"); } }
咱們分別啓動兩個服務,而後訪問訂單服務的API:
結果確定是符合預期的,不至於翻車。8001
是訂單服務的接口,這個時候咱們能夠了解到,原來微服務架構每一個服務獨立啓動,都是能夠獨立訪問的,也就至關於傳統的單體服務。
咱們想一想看,若是用端口來區分每一個服務,是否也能夠達到微服務的效果?理論上好像是能夠的,可是若是成百上千個服務呢?端口爆炸,維護爆炸,治理爆炸... 不說別的,心態直接爆炸了!這個時候咱們就想着若是隻用統一的一個端口訪問各個服務,那該多好!端口一致,路由前綴不一致,經過路由前綴來區分服務,這種方式將咱們帶入了服務網關的場景。是的,這裏就說到了服務網關的功能之一 --- 路由轉發
。
既然要用到網關,那咱們上面建立的服務之一 store-gateway
就派上用場了!怎麼用?咱們在配置文件作個簡單的修改:
spring: application: name: store-gateway cloud: gateway: routes: - id: store-order uri: http://localhost:8001 order: 1 predicates: - Path=/store-order/** filters: - StripPrefix=1
很少廢話,咱們直接啓動網關,經過訪問http://localhost:8000/store-order/order/123
看是否能獲取到訂單?
很順利,咱們成功拿到了ID 爲 123 的訂單商品!
咱們看下 URL 的組成:
可以訪問到咱們的服務,說明網關配置生效了,咱們再來看下這麼配置項是怎麼一回事!
spring.cloud.gateway
這個是服務網關 Gateway 的配置前綴,沒什麼好說的,本身須要記住就是了。
routes
如下就是咱們值得關注的了,routes
是個複數形式,那麼能夠確定,這個配置項是個數組的形式,所以意味着咱們能夠配多個路由轉發,當請求知足什麼條件的時候轉到哪一個微服務上。
惟一
標識或
的關係)瞭解完必要的參數,咱們也高高興興去部署使用了,可是好景不長,咱們又迎來了新的問題。我訂單服務原先使用的 8001
端口,由於某些緣由給其餘服務使用了,這個時候小腦殼又大了,這種狀況確定不會出現 上錯花轎嫁對郎
的結果!
我們想一想看這種問題要怎麼解決比較合適?既然都採用微服務了,那咱們能不能採用服務名的方式跳轉訪問,這樣子不管端口怎麼變,都不會影響到咱們的正常業務!那若是採用服務的方式,就須要一個註冊中心
,這樣子咱們啓動的服務能夠同步到註冊中心
的 註冊表 中,這樣子網關就能夠根據 服務名
去註冊中心中尋找服務進行路由跳轉了!那我們就須要一個註冊中心,這裏就採用 Nacos 做爲註冊中心.
關於 Nacos 的瞭解,能夠空降 微服務新秀之Nacos,看了就會,我說的!
咱們分別在服務網關 和 訂單服務的配置文件都作了如下配置:
啓動兩個服務後,咱們能夠在 Nacos 的控制檯服務列表中看到兩個服務:
這個時候能夠看到 訂單服務 的服務名爲:store-order
,那咱們就能夠在網關配置文件部分作如下修改:
這裏的配置與上述不一樣點之一 http
換成了 lb
(lb 指的是從nacos中按照名稱獲取微服務,並遵循負載均衡策略),之二 端口
換成了 服務名
那咱們繼續訪問上述URL看是否可以成功訪問到訂單服務:
結果依然沒有翻車!這樣子,無論訂單服務的端口如何改變,只要咱們的服務名不變,那麼始終均可以訪問到咱們的對應的服務!
日子一天一天的過去~ 服務也一點一點的增長!終於有一天小菜煩了,由於每次增長服務都得去配置文件中增長一個routes
的配置列,雖然也只是 CV 的操做,可是,哪一天小菜不當心手一抖,那麼~~~ 算了算了,找找看有沒有什麼能夠偷懶的寫法,終於不負小菜心,找到了一種簡化版!配置以下:
就這?是的,就這!管你服務再怎麼多,我配置文件都不用修改。這樣子配置的目的即是請求統一方式都是變成了 網關地址:網關端口/服務名/接口名
的方式訪問。再次訪問頁面,依然行得通!
可是方便歸方便,在方便的同時也限制了不少擴展功能,所以使用需三思!不可貪懶
!
上面已經說完了網關的簡單使用,看完的小夥伴確定已經能夠上手了!接下來咱們繼續趁熱打鐵,瞭解下 Gateway 網關的核心。不說別的,路由轉發 確定是網關的核心!咱們從上面已經瞭解到一個具體路由信息載體,主要定義瞭如下幾個信息(回憶下):
這裏來梳理一下訪問流程:
這張圖很清楚的描述服務網關的調用流程(盲目自信)
過程瞭解了,咱們抽取一下其中的關鍵!斷言
和 過濾器
Predicate 也就是斷言,主要適用於進行條件判斷,只有斷言都返回真,纔會真正執行路由
SpringCloud Gateway 中內置了許多斷言工廠,全部的這些斷言都和 HTTP 請求不一樣的屬性相匹配,具體以下;
該類型的斷言工廠是根據時間作判斷的
一、AfterRoutePredicateFactory: 接收一個
日期參數,判斷請求日期是否晚於指定日期
二、BeforeRoutePredicateFactory:接收一個
日期參數,判斷請求日期是否早於指定日期
三、BetweenRoutePredicateFactory:接收兩個
日期參數,判斷請求日期是否在指定時間段內
該類型的斷言工廠是接收一個參數
,IP 地址端,判斷請求主機地址是否在地址段中。(eq:-RemoteAddr=192.168.1.1/24)
該類型的斷言工廠接收兩個參數
,Cookie 名字和一個正則表達式,判斷請求 cookie 是否具備給定名稱且值與正則表達式匹配。(eq:-Cookie=cbuc)
該類型的斷言工廠接收兩個參數
,標題名稱和正則表達式。判斷請求 Header 是否具備給定名稱且值與正則表達式匹配。(eq:-Header=X-Request)
該類型的斷言工廠接收一個參數
,主機名模式。判斷請求的host 是否知足匹配規則。(eq:-Host=**.cbuc.cn)
該類型的斷言工廠接收一個參數
,判斷請求類型是否跟指定的類型匹配。(eq:-Method=GET)
該類型的斷言工廠接收一個參數
,判斷請求的URI部分是否知足路徑規則。(-eq:-Path=/order/)
該類型的斷言工廠接收兩個參數
,請求 Param 和 正則表達式。判斷請求參數是否具備給定名稱且值與正則表達式匹配。(eq:-Query=cbuc)
該類型的斷言工廠接收一個[組名,權重]
,而後對於同一個組內的路由按照權重轉發
這麼多斷言工廠,這裏就不一一使用演示了,咱們結合幾個斷言工廠的使用演示一下。
咱們老樣子很少廢話,直接上代碼:
CustomPredicateRouteFactory
配置文件
測試結果
success
fail
驚呼 Amazing 的同時,不要着急的往下看,咱們迴歸代碼,看看,爲何一個能夠訪問成功,一個卻訪問失敗了。兩個方面:1. 二者訪問的URL有哪些不一樣 2. 代碼哪部分對 URL 作出了處理
先養成獨立思考,再去看解決方法
當你思考完後,可能部分同窗已經有結果了,那讓咱們繼續往下看!首先是一個 CustomRoutePredicateFactory
類,這個類的做用有點像攔截器,在作請求轉發的時候進行了攔截,咱們請求的時候能夠打個斷點:
能夠看到,確實是攔截器的功能,在每一個請求發起的時候作了攔截。那問題2 的結果就出來了,原來URL處理是在 RoutePredicateFactory 中作了處理,在 apply 方法中能夠經過 exchange.getRequest()
拿到 ServerHttpRequest 對象,從而能夠獲取到請求的參數、請求方式、請求頭等信息。 shortcutFieldOrder()
方法也是重寫的關鍵之一,咱們須要這裏返回,咱們實體類中定義的屬性,而後在apply()
方法中才能接收到咱們賦值的屬性參數!
注意:若是自定義的實體中有多個屬性須要判斷,
shortcutFieldOrder()
方法中的順序要跟配置文件中的參數順序一致
那麼當咱們編寫了該斷言工廠後,若是讓之生效?@Component
這個註解確定必不可少了,目的就是讓 Spring 容器管理。那麼已經註冊的斷言工廠如何聲明使用呢?那就得回到配置文件了!
咱們這裏重點看 predicates
這個配置項下的配置,分別有三個配置,一個是咱們已經熟悉的 Path
,其餘兩個有點陌生,可是這裏再看看 Custom
是否是又有點眼熟?是的,咱們在上面好像定義了一個叫 CustomRoutePredicate
的斷言工廠,二者有點類似,又好像差點什麼。那我就再給你一個提示:
咱們看下抽象的斷言工廠有哪些自實現的類!其中是否是有 PathRoutePredicateFactory
,沒錯,就是你想的那樣!有沒有一種撥開雨霧見青天的感受!原來咱們配置文件的 key 是以類名的前綴聲明的,也就是說斷言工廠類的格式必須是: 自定義名稱
+ RoutePredicateFactory 爲後綴,而後在配置文件中聲明。這樣子觸類旁通,咱們天然而然的就清楚了 - Before
的做用,該做用即是:限制請求時間在 xxx 以前。
而 - Custom=cbuc
,這個 cbuc 即是咱們限制的規則,只有 name 爲 cbuc 的用戶才能請求成功。若是有多個參數,能夠用 ,
隔開,順序須要與斷言工廠中shortcutFieldOrder()
返回參數的順序一致!
若是在自定義斷言工廠的途中遇到了什麼阻礙,否則看看內置的斷言工廠是如何實現的。
多看源碼總沒錯!
接下來進入第二個核心,也就是過濾器。該核心的做用也挺簡單,就是在請求的傳遞過程當中,對請求和響應作一系列的手腳。爲了怕你劃回去看請求流程過於麻煩,小菜貼心的再貼一遍流程圖:
在 Gateway 的過濾器中又能夠分爲 局部過濾器 和 全局過濾器。聽名稱就知道其做用,局部 是用於某一個路由上的,全局 是用於全部路由上的。不過無論是 局部 仍是 全局,生命週期都分爲 pre
和 post
。
局部過濾器是針對於單個路由的過濾器。一樣 Gateway 已經內置了許多過濾器
咱們選幾種經常使用的過濾器進行說明:(下列過濾器省略後綴 GaewayFilterFactory
,完整名稱爲 前綴+後綴)
過濾器前綴 | 做用 | 參數 |
---|---|---|
StripPrefix | 用於截斷原始請求的路徑 | 使用數字表示要截斷的路徑數量 |
AddRequestHeader | 爲原始請求添加 Header | Header 的名稱及值 |
AddRequestParameter | 爲原始請求添加請求參數 | 參數名稱及值 |
Retry | 針對不一樣的響應進行重試 | reties、statuses、methods、series |
RequestSize | 設置容許接收最大請求包的大小 | 請求包大小,單位字節,默認5M |
SetPath | 修改原始請求的路徑 | 修改後的路徑 |
RewritePath | 重寫原始的請求路徑 | 原始路徑正則表達式以及重寫後路徑的正則表達式 |
PrefixPath | 爲原始請求路徑添加前綴 | 前綴路徑 |
RequestRateLimiter | 對請求限流,限流算法爲令牌桶 | KeyResolver、reteLimiter、statusCode、denyEmptyKey |
內置的過濾器小夥伴們能夠本身嘗試一番,有問題歡迎提問!
咱們接下來說講如何自定義過濾器工廠。don't say so much
,咱們上代碼
CustomGatewayFilterFactory
配置文件
當咱們開啓請求計數的時候,能夠看到控制檯對於請求次數做了統計:
所以咱們能夠經過這種方式輕鬆實現局部過濾器
全局過濾器做用於全部路由,無需配置。經過全局過濾器能夠實現對權限的統一校驗,安全性驗證等功能
老樣子,咱們先看看 Gateway 中存在哪些全局過濾器:
相對於局部過濾器,全局過濾器的命名就沒有太多約束了,畢竟不須要在配置文件中進行配置。
咱們熟悉一下經典的全局過濾器
過濾器名稱 | 做用 |
---|---|
ForwardPathFilter / ForwardRoutingFilter | 路徑轉發相關過濾器 |
LoadBalanceerClientFilter | 負載均衡客戶端相關過濾器 |
NettyRoutingFilter / NettyWriteResponseFilter | Http 客戶端相關過濾器 |
RouteToRequestUrlFilter | 路由 URL 相關過濾器 |
WebClientHttpRoutingFilter / WebClientWriteResponseFilter | 請求 WebClient 客戶端轉發請求真實的URL並將響應寫入到當前的請求響應中 |
WebsocketRoutingFilter | websocket 相關過濾器 |
瞭解完內置的過濾器,咱們再看看如何定義全局的過濾器!
CustomerGlobalFilter
對於全局過濾器,咱們不須要在配置文件中配置,由於是做用於全部路由
測試結果
success
fail
能夠看到,咱們使用全局過濾器進行了鑑權
處理,若是沒有攜帶 token 將沒法訪問!
到這裏咱們已經瞭解到了服務網關的路由轉發,權限校驗甚至於能夠基於斷言和過濾器
作出粗略簡單的 API監控和限流
但其實對於 API監控和 限流,SpringCloud 中已經有了更好的組件完成這兩項工做。畢竟單一原則,作的越多每每錯的也越多!
後面會繼續整理關於 SpringCloud 組件的文章,敬請關注!
對於微服務的框架,孰好孰壞由咱們本身斷定。可是無論孰好孰壞,面對一門新技術的產生,咱們最須要作的即是接收它,包容它,而後用好它,是騾子是馬,本身溜溜就知道了。
不要空談,不要貪懶,和小菜一塊兒作個吹着牛X作架構
的程序猿吧~點個關注作個伴,讓小菜再也不孤單。我們下文見!
今天的你多努力一點,明天的你就能少說一句求人的話!
我是小菜,一個和你一塊兒變強的男人。
💋
微信公衆號已開啓,小菜良記,沒關注的同窗們記得關注哦!