《吃透微服務》 - 服務網關之Gateway

你們好,我是小菜。java

一個但願可以成爲 吹着牛X談架構 的男人!若是你也想成爲我想成爲的人,否則點個關注作個伴,讓小菜再也不孤單!web

本文主要介紹 SpringCloud之服務網關Gateway正則表達式

若有須要,能夠參考算法

若有幫助,不忘 點贊spring

微信公衆號已開啓,小菜良記,沒關注的同窗們記得關注哦!編程

前段時間與小夥伴閒聊時說到他們公司的現狀,近來與未來,公司將全面把單體服務向微服務架構過渡。這裏面咱們聽到了關鍵詞 --- 微服務。乍聽之下,以爲也很合理。互聯網在不斷的發展,這裏不僅是行業的發展,更是系統架構的發展。如今市面上架構大體也經歷了這幾個階段:數組

這是好事嗎?是好事,毋庸置疑。由於不跟上時代的浪潮,總會被拍死在沙灘上。但徹底是好事嗎?那也不見得。安全

咱們先要明白微服務解決了什麼問題?大方面上應該就是應用層面和人層面的問題微信

  • 應用層面: 單體服務的架構很簡單,項目開發和維護成本低是它無爭議的優勢。可是臃腫耦合又會給基礎設施帶來了太重的負擔。若是某個應用處理資源佔用了大量的CPU,就會致使其餘處理資源餓死的現象,系統延遲增高,直接影響系統的可用性。
  • 人層面: 獨立多語言生態 也是微服務的標籤。在單體服務中,投入的人力資源越多不見得越高效,反而越容易出錯。但微服務不一樣,每一個服務都是獨立出來的,團隊能夠更加容易的協做開發,甚至一套系統,多個服務,多種語言,毫無衝突。

可是咱們凡事不能被好處矇蔽。微服務的缺點也是一直存在的:websocket

  • 須要考慮各個服務之間的容錯性問題
  • 須要考慮數據一致性問題
  • 須要考慮分佈式事務問題
  • ...

不少人認爲微服務的核心就是在於 。服務分的越細越好,就好像平時寫代碼的時候絲絕不考慮的單一原則,反而在服務拆分上用到淋漓盡致。這裏就須要明白一個核心概念:微服務的關鍵不在微,而是在於合適的大小

這句話好像很簡單的樣子,可是多大才算合適的大小?這可能據不一樣團隊,不一樣項目而定。合適的大小可能取決於更少的代碼倉庫,更少的部署隊列,更少的語言... 這裏更沒法一槌定音!

若是無法作到合適的大小,而無腦的拆分服務,那麼微服務可能反而成爲你項目的累贅。所以,有時全面轉型微服務反而不是好事,你以爲呢?

話題有點跑遠了,我們努力扯回來。既然微服務已經成爲主流,那麼如何設計微服務即是咱們應該作的事,而不是談及微服務之時,想到的只是與人如何爭論如何拒用微服務。那麼這篇咱們要講的是SpringCloud之服務網關Gateway

SpringCloud之服務網關Gateway

1、認識網關

什麼是服務網關?不要給本身當頭一棒。咱們換個問法,爲何須要服務網關?

服務網關是跨一個或多個服務節點提供單個統一的訪問入口

它的做用並非無關緊要的存在,而是相當重要。咱們能夠在服務網關作路由轉發過濾器的實現。優勢簡述以下:

  • 防止內部服務關注暴露給外部客戶端
  • 爲咱們內部多個服務添加了額外的安全層
  • 減低微服務訪問的複雜性

根據圖中內容,咱們能夠得出如下信息:

  • 用戶訪問入口,統一經過網關訪問到其餘微服務節點
  • 服務網關的功能有路由轉發API監控權限控制限流

而這些即是 服務網關 存在的意義!

1)Zuul 比較

SpringCloud Gateway 是 SpringCloud 的一個全新項目,目標是取代Netflix Zuul。它是基於 Spring5.0 + SpringBoot2.0 + WebFlux 等技術開發的,性能高於 Zuul,據官方提供的信息來看,性能是 Zuul 的1.6倍,意在爲微服務架構提供一種簡單有效的統一的 API 路由管理方式。

SpringCloud Gateway 不只提供了統一的路由方式(反向代理),而且基於 Filter 鏈(定義過濾器對請求過濾)提供了網關基本的功能,例如:鑑權、流量控制、熔斷、路徑重寫、日誌監控等。

其實說到 Netflix Zuul,在使用或準備使用微服務架構的小夥伴應該並不陌生,畢竟Netflix 是一個老牌微服務開源者。新秀與老牌之間的爭奪,若是新秀沒有點硬實力,如何讓人安心轉型!

這裏咱們能夠順帶了解一下 WefluxWebflux 的出現填補了 Spring 在響應式編程上的空白。

可能有不少小夥伴並不知道 Webflux,小菜接下來也會出一篇關於 Webflux 的講解,實則真香!

Webflux 的響應式編程不只僅是編程風格上的改變,而是對於一系列著名的框架都提供了響應式訪問的開發包,好比 NettyRedis(若是不知道 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 的意思,具體使用依具體項目而定

3、掌握網關

1. Gateway 依賴

最關鍵的一步即是引入網關的依賴

<!--gateway網關-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2. 項目結構

我這裏簡單的建立了一個微服務項目,項目裏有一個 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 是訂單服務的接口,這個時候咱們能夠了解到,原來微服務架構每一個服務獨立啓動,都是能夠獨立訪問的,也就至關於傳統的單體服務。

咱們想一想看,若是用端口來區分每一個服務,是否也能夠達到微服務的效果?理論上好像是能夠的,可是若是成百上千個服務呢?端口爆炸,維護爆炸,治理爆炸... 不說別的,心態直接爆炸了!這個時候咱們就想着若是隻用統一的一個端口訪問各個服務,那該多好!端口一致,路由前綴不一致,經過路由前綴來區分服務,這種方式將咱們帶入了服務網關的場景。是的,這裏就說到了服務網關的功能之一 --- 路由轉發

3. 網關出現

既然要用到網關,那咱們上面建立的服務之一 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 是個複數形式,那麼能夠確定,這個配置項是個數組的形式,所以意味着咱們能夠配多個路由轉發,當請求知足什麼條件的時候轉到哪一個微服務上。

  • id: 當前路由的惟一標識
  • uri: 請求要轉發到的地址
  • order:路由的優先級,數字越小級別越高
  • predicates: 路由須要知足的條件,也是個數組(這裏是的關係)
  • filters: 過濾器,請求在傳遞過程當中能夠經過過濾器對其進行必定的修改

瞭解完必要的參數,咱們也高高興興去部署使用了,可是好景不長,咱們又迎來了新的問題。我訂單服務原先使用的 8001 端口,由於某些緣由給其餘服務使用了,這個時候小腦殼又大了,這種狀況確定不會出現 上錯花轎嫁對郎 的結果!

我們想一想看這種問題要怎麼解決比較合適?既然都採用微服務了,那咱們能不能採用服務名的方式跳轉訪問,這樣子不管端口怎麼變,都不會影響到咱們的正常業務!那若是採用服務的方式,就須要一個註冊中心,這樣子咱們啓動的服務能夠同步到註冊中心註冊表 中,這樣子網關就能夠根據 服務名 去註冊中心中尋找服務進行路由跳轉了!那我們就須要一個註冊中心,這裏就採用 Nacos 做爲註冊中心.

關於 Nacos 的瞭解,能夠空降 微服務新秀之Nacos,看了就會,我說的!

咱們分別在服務網關 和 訂單服務的配置文件都作了如下配置:

啓動兩個服務後,咱們能夠在 Nacos 的控制檯服務列表中看到兩個服務:

這個時候能夠看到 訂單服務 的服務名爲:store-order,那咱們就能夠在網關配置文件部分作如下修改:

這裏的配置與上述不一樣點之一 http 換成了 lblb 指的是從nacos中按照名稱獲取微服務,並遵循負載均衡策略),之二 端口 換成了 服務名

那咱們繼續訪問上述URL看是否可以成功訪問到訂單服務:

結果依然沒有翻車!這樣子,無論訂單服務的端口如何改變,只要咱們的服務名不變,那麼始終均可以訪問到咱們的對應的服務!

日子一天一天的過去~ 服務也一點一點的增長!終於有一天小菜煩了,由於每次增長服務都得去配置文件中增長一個routes 的配置列,雖然也只是 CV 的操做,可是,哪一天小菜不當心手一抖,那麼~~~ 算了算了,找找看有沒有什麼能夠偷懶的寫法,終於不負小菜心,找到了一種簡化版!配置以下:

就這?是的,就這!管你服務再怎麼多,我配置文件都不用修改。這樣子配置的目的即是請求統一方式都是變成了 網關地址:網關端口/服務名/接口名 的方式訪問。再次訪問頁面,依然行得通!

可是方便歸方便,在方便的同時也限制了不少擴展功能,所以使用需三思!不可貪懶

4、掌握核心

上面已經說完了網關的簡單使用,看完的小夥伴確定已經能夠上手了!接下來咱們繼續趁熱打鐵,瞭解下 Gateway 網關的核心。不說別的,路由轉發 確定是網關的核心!咱們從上面已經瞭解到一個具體路由信息載體,主要定義瞭如下幾個信息(回憶下):

  • id: 路由的惟一標識,區別於其餘Route
  • uri: 路由指向目的地 uri,即客戶端請求最終被轉發到的微服務
  • order: 用於多個 Route 之間的排序,數值越小排序越靠前,匹配優先級越高
  • predicate: 用來進行條件判斷,只有斷言都返回真,纔會真正的執行路由
  • filter: 過濾器用於修改請求和響應信息

這裏來梳理一下訪問流程:

這張圖很清楚的描述服務網關的調用流程(盲目自信

  1. GatewayClientGatewayServer 發出請求
  2. 請求首先會被 HttpWebHandlerAdapter 進行提取組轉成網關上下文
  3. 而後網關的上下文會傳遞到 DispatcherHandler,它負責將請求分發給 RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping負責路由查找,並更具路由斷言判斷路由是否可用
  5. 若是斷言成功,由 FilteringWebHandler 建立過濾器鏈並調用
  6. 請求會一次通過 PreFilter ---> 微服務 ---> PostFilter 的方法,最終返回響應

過程瞭解了,咱們抽取一下其中的關鍵!斷言過濾器

1. 斷言

Predicate 也就是斷言,主要適用於進行條件判斷,只有斷言都返回真,纔會真正執行路由

1)斷言工廠

SpringCloud Gateway 中內置了許多斷言工廠,全部的這些斷言都和 HTTP 請求不一樣的屬性相匹配,具體以下;

  • 基於 Datetime 類型的斷言工廠

該類型的斷言工廠是根據時間作判斷的

一、AfterRoutePredicateFactory: 接收一個日期參數,判斷請求日期是否晚於指定日期

二、BeforeRoutePredicateFactory:接收一個日期參數,判斷請求日期是否早於指定日期

三、BetweenRoutePredicateFactory:接收兩個日期參數,判斷請求日期是否在指定時間段內

  • 基於遠程地址的斷言工廠 RemoteAddrRoutePredicateFactory

該類型的斷言工廠是接收一個參數IP 地址端,判斷請求主機地址是否在地址段中。(eq:-RemoteAddr=192.168.1.1/24)

  • 基於Cookie的斷言工廠 CookieRoutePredicateFactory

該類型的斷言工廠接收兩個參數,Cookie 名字和一個正則表達式,判斷請求 cookie 是否具備給定名稱且值與正則表達式匹配。(eq:-Cookie=cbuc)

  • 基於Header的斷言工廠HeaderRoutePredicateFactory

該類型的斷言工廠接收兩個參數,標題名稱和正則表達式。判斷請求 Header 是否具備給定名稱且值與正則表達式匹配。(eq:-Header=X-Request)

  • 基於Host的斷言工廠 HostRoutePredicateFactory

該類型的斷言工廠接收一個參數,主機名模式。判斷請求的host 是否知足匹配規則。(eq:-Host=**.cbuc.cn)

  • 基於Method請求方法的斷言工廠 MethodRoutePredicateFactory

該類型的斷言工廠接收一個參數,判斷請求類型是否跟指定的類型匹配。(eq:-Method=GET)

  • 基於Path請求路徑的斷言工廠 PathRoutePredicateFactory

該類型的斷言工廠接收一個參數,判斷請求的URI部分是否知足路徑規則。(-eq:-Path=/order/)

  • 基於Query請求參數的斷言工廠 QueryRoutePredicateFactory

該類型的斷言工廠接收兩個參數,請求 Param 和 正則表達式。判斷請求參數是否具備給定名稱且值與正則表達式匹配。(eq:-Query=cbuc)

  • 基於路由權重的斷言工廠 WeightRoutePredicateFactory

該類型的斷言工廠接收一個[組名,權重],而後對於同一個組內的路由按照權重轉發

2)使用

這麼多斷言工廠,這裏就不一一使用演示了,咱們結合幾個斷言工廠的使用演示一下。

咱們老樣子很少廢話,直接上代碼:

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() 返回參數的順序一致!

若是在自定義斷言工廠的途中遇到了什麼阻礙,否則看看內置的斷言工廠是如何實現的。 多看源碼總沒錯!
2. 過濾器

接下來進入第二個核心,也就是過濾器。該核心的做用也挺簡單,就是在請求的傳遞過程當中,對請求和響應作一系列的手腳。爲了怕你劃回去看請求流程過於麻煩,小菜貼心的再貼一遍流程圖:

Gateway 的過濾器中又能夠分爲 局部過濾器全局過濾器。聽名稱就知道其做用,局部 是用於某一個路由上的,全局 是用於全部路由上的。不過無論是 局部 仍是 全局,生命週期都分爲 prepost

  • pre: 做用於路由到微服務以前調用。咱們能夠利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務,記錄調試記錄等
  • post: 做用於路由到微服務以後執行。咱們能夠利用這種過濾器用來響應添加標準的 HTTP Header,收集統計信息和指標、將響應從微服務發送到客戶端。
1)局部過濾器

局部過濾器是針對於單個路由的過濾器。一樣 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

配置文件

當咱們開啓請求計數的時候,能夠看到控制檯對於請求次數做了統計:

所以咱們能夠經過這種方式輕鬆實現局部過濾器

2)全局過濾器

全局過濾器做用於全部路由,無需配置。經過全局過濾器能夠實現對權限的統一校驗,安全性驗證等功能

老樣子,咱們先看看 Gateway 中存在哪些全局過濾器:

相對於局部過濾器,全局過濾器的命名就沒有太多約束了,畢竟不須要在配置文件中進行配置。

咱們熟悉一下經典的全局過濾器

過濾器名稱 做用
ForwardPathFilter / ForwardRoutingFilter 路徑轉發相關過濾器
LoadBalanceerClientFilter 負載均衡客戶端相關過濾器
NettyRoutingFilter / NettyWriteResponseFilter Http 客戶端相關過濾器
RouteToRequestUrlFilter 路由 URL 相關過濾器
WebClientHttpRoutingFilter / WebClientWriteResponseFilter 請求 WebClient 客戶端轉發請求真實的URL並將響應寫入到當前的請求響應中
WebsocketRoutingFilter websocket 相關過濾器

瞭解完內置的過濾器,咱們再看看如何定義全局的過濾器!

CustomerGlobalFilter

對於全局過濾器,咱們不須要在配置文件中配置,由於是做用於全部路由

測試結果

success

fail

能夠看到,咱們使用全局過濾器進行了鑑權處理,若是沒有攜帶 token 將沒法訪問!


到這裏咱們已經瞭解到了服務網關的路由轉發,權限校驗甚至於能夠基於斷言和過濾器作出粗略簡單的 API監控和限流

但其實對於 API監控限流,SpringCloud 中已經有了更好的組件完成這兩項工做。畢竟單一原則,作的越多每每錯的也越多!

後面會繼續整理關於 SpringCloud 組件的文章,敬請關注!

對於微服務的框架,孰好孰壞由咱們本身斷定。可是無論孰好孰壞,面對一門新技術的產生,咱們最須要作的即是接收它,包容它,而後用好它,是騾子是馬,本身溜溜就知道了。

不要空談,不要貪懶,和小菜一塊兒作個吹着牛X作架構的程序猿吧~點個關注作個伴,讓小菜再也不孤單。我們下文見!

看完不讚,都是壞蛋

今天的你多努力一點,明天的你就能少說一句求人的話!

我是小菜,一個和你一塊兒變強的男人。 💋

微信公衆號已開啓,小菜良記,沒關注的同窗們記得關注哦!

相關文章
相關標籤/搜索