Spring Cloud第十四篇 | Api網關Zuul

本文是Spring Cloud專欄的第十四篇文章,瞭解前十三篇文章內容有助於更好的理解本文:html

  1. Spring Cloud第一篇 | Spring Cloud前言及其經常使用組件介紹概覽git

  2. Spring Cloud第二篇 | 使用並認識Eureka註冊中心github

  3. Spring Cloud第三篇 | 搭建高可用Eureka註冊中心web

  4. Spring Cloud第四篇 | 客戶端負載均衡Ribbon正則表達式

  5. Spring Cloud第五篇 | 服務熔斷Hystrixspring

  6. Spring Cloud第六篇 | Hystrix儀表盤監控Hystrix Dashboardbootstrap

  7. Spring Cloud第七篇 | 聲明式服務調用Feignapi

  8. Spring Cloud第八篇 | Hystrix集羣監控Turbin緩存

  9. Spring Cloud第九篇 | 分佈式服務跟蹤Sleuth安全

  10. Spring Cloud第十篇 | 分佈式配置中心Config

  11. Spring Cloud第十一篇 | 分佈式配置中心高可用

  12. Spring Cloud第十二篇 | 消息總線Bus

  13. Spring Cloud第十三篇 | Spring Boot Admin服務監控

1、網關分類

開放Api

    開放api(openApi) 企業須要將自身數據、能力等做爲開發平臺向外開放,一般會以rest的方式向外提供,最好的例子就是淘寶開放平臺、騰訊公司的QQ開發平臺、微信開放平臺。 Open API開放平臺必然涉及到客戶應用的接入、API權限的管理、調用次數管理等,必然會有一個統一的入口進行管理,這正是API網關能夠發揮做用的時候。

微服務網關

    微服務的概念最先在2012年提出,在Martin Fowler的大力推廣下,微服務在2014年後獲得了大力發展。 在微服務架構中,有一個組件能夠說是必不可少的,那就是微服務網關,微服務網關處理了負載均衡,緩存,路由,訪問控制,服務代理,監控,日誌等。API網關在微服務架構中正是以微服務網關的身份存在。

API服務管理平臺

    上述的微服務架構對企業來講有可能實施上是困難的,企業有不少遺留系統,要所有抽取爲微服務器改動太大,對企業來講成本過高。可是因爲不一樣系統間存在大量的API服務互相調用,所以須要對系統間服務調用進行管理,清晰地看到各系統調用關係,對系統間調用進行監控等。 API網關能夠解決這些問題,咱們能夠認爲若是沒有大規模的實施微服務架構,那麼對企業來講微服務網關就是企業的API服務管理平臺。

2、網關設計

開放API接口

    一、對於OpenAPI使用的API網關來講,通常合做夥伴要以應用的形式接入到OpenAPI平臺,合做夥伴須要到 OpenAPI平臺申請應用。所以在OpenAPI網關以外,須要有一個面向合做夥伴的使用的平臺用於合做夥伴,這就要求OpenAPI網關須要提供API給這個用戶平臺進行訪問。以下架構:

    固然若是是在簡單的場景下,可能並不須要提供一個面向合做夥伴的門戶,只須要由公司的運營人員直接添加合做夥伴應用id/密鑰等,這種狀況下也就不須要合做夥伴門戶子系統。

內網API接口

    二、對於內網的API網關,在起到的做用上來講能夠認爲是微服務網關,也能夠認爲是內網的API服務治理平臺。當企業將全部的應用使用微服務的架構管理起來,那麼API網關就起到了微服務網關的做用。而當企業只是將系統與系統之間的調用使用rest api的方式進行訪問時使用API網關對調用進行管理,那麼API網關起到的就是API服務治理的做用。架構參考以下:

    三、對於公司內部公網應用(如APP、公司的網站),若是管理上比較細緻,在架構上是可能由獨立的API網關來處理這部份內部公網應用,若是想比較簡單的處理,也能夠是使用面向合做夥伴的API網關。若是使用獨立的API網關,有如下的好處:

面向合做夥伴和麪向公司主體業務的優先級不同,不一樣的API網關能夠作到業務影響的隔離。

    內部API使用的管理流程和麪向合做夥伴的管理流程可能不同。

    內部的API在功能擴展等方面的需求通常會大於OpenAPI對於功能的要求。

    基於以上的分析,若是公司有能力,那麼仍是建議分開使用合做夥伴OPEN API網關和內部公網應用網關。

3、網關框架

  • Tyk:Tyk是一個開放源碼的API網關,它是快速、可擴展和現代的。Tyk提供了一個API管理平臺,其中包括API網關、API分析、開發人員門戶和API管理面板。Try 是一個基於Go實現的網關服務。https://tyk.io

  • Kong:Kong是一個可擴展的開放源碼API Layer(也稱爲API網關或API中間件)。Kong 在任何RESTful API的前面運行,經過插件擴展,它提供了超越核心平臺的額外功能和服務,是基於Nginx+Lua進行二次開發的方案。https://konghq.com

  • Orange:Orange和Kong相似也是基於OpenResty的一個API網關程序,是由國人開發的。 http://orange.sumory.com

  • Netflix Zuul:Zuul是Netflix公司的開源項目,提供動態路由、監視、彈性、安全性等功能的邊緣服務。Zuul是Netflix出品的一個基於JVM路由和服務端的負載均衡器,Spring Cloud在Netflix項目中也已經集成了Zuul。https://github.com/Netflix/zuul

  • GateWay:GateWay是Spring Cloud的一個子項目,構建於Spring5+,基於Spring Boot 2.x 響應式的、非阻塞式的 API。https://spring.io/projects/spring-cloud-gateway

4、網關做用

    網關的做用,能夠實現負載均衡、路由轉發、日誌、權限控制、監控等。

5、網關與過濾器區別

    網關是攔截全部服務器請求進行控制

    過濾器攔截某單個服務器請求進行控制

6、Nginx與Zuul區別

    Nginx是採用服務器負載均衡進行轉發

    Zuul依賴Ribbon和Eureka實現本地負載均衡轉發
    相對來講Nginx功能比Zuul功能更增強大,可以整合其餘語言好比Lua腳本實現強大的功能,同時Nginx能夠更好的抗高併發,Zuul網關適用於請求過濾和攔截等。

7、網關

Zuul是Spring Cloud推薦的一個組件:https://github.com/Netflix/zuul

一、使用Zuul實現反向代理

1-一、在springcloud-zuul模塊中添加依賴

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

1-二、application.yml配置文件內容以下:

spring: application: name: springcloud-zuul server: port: 9999 eureka: client: service-url: defaultZone: http://localhost:8700/eureka #客戶端每隔30秒從Eureka服務上更新一次服務信息 registry-fetch-interval-seconds: 30 #須要將個人服務註冊到eureka上 register-with-eureka: true #須要檢索服務 fetch-registry: true #心跳檢測檢測與續約時間 instance: #告訴服務端,若是我10s以內沒有給你發心跳,就表明我故障了,將我剔除掉,默認90s #Eureka服務端在收到最後一次心跳以後等待的時間上限,單位爲秒,超過則剔除(客戶端告訴服務端按照此規則等待本身) lease-expiration-duration-in-seconds: 10 #每隔2s向服務端發送一次心跳,證實自已依然活着,默認30s #Eureka客戶端向服務端發送心跳的時間間隔,單位爲秒(客戶端告訴服務端本身會按照該規則) lease-renewal-interval-in-seconds: 2 #以/api-a/ 開頭的請求都轉發給springcloud-service-consumer服務 #以/api-b/開頭的請求都轉發給springcloud-service-feign服務 zuul: routes: api-a: path: /api-a/** serviceId: springcloud-service-consumer api-b: path: /api-b/** serviceId: springcloud-service-feign

1-三、在主類上添加註解

@EnableZuulProxy //開啓Zuul的支持
@EnableEurekaClient //開啓Eureka客戶端支持

完成上面的操做以後,咱們能夠啓動相應的服務,啓動相應服務以下:

    而後訪問springcloud-service-consumer服務中的接口:http://localhost:9999/api-a/consumer/hello,同理springcloud-service-feign的服務中接口也是這樣 http://localhost:9999/api-b/feign/hello路徑中的api-a,和api-b分別被路由到響應的服務上去,你也能夠配置忽略api-a,api-b等等其餘配置

二、Zuul對Ribbon和Hystrix的支持

    從依賴上能夠看出來,Zuul自身會依賴Ribbon和Hystrix的依賴,因此Zuul自己就擁有線程隔離和斷路器的自我保護功能,以及對服務調用的客戶端負載均衡功能,可是僅限於咱們path和serviceId的組合使用

zuul.routes.<route>.path zuul.routes.<route>.serviceId

不支持path和url的組合使用

zuul.routes.<route>.path zuul.routes.<route>.url

三、使用Zuul過濾器

    微服務數量多的狀況下,咱們爲每一個服務都加上安全校驗和權限控制,是很是麻煩的,這樣的作法並不可取,它會增長後期系統的維護難度,由於每個系統中的各類校驗邏輯不少狀況下大體相同或者相似,而後這些非業務的邏輯代碼分散到各個服務中,產生的冗餘代碼是咱們不想看到的,因此一般的作法是經過網關服務來完成這些非業務性質的校驗。

3-一、Filter的生命週期

    Filter的生命週期有4個,分別是 「PRE」、「ROUTING」、「POST」 和「ERROR」,整個生命週期能夠用下圖來表示

Zuul大部分功能都是經過過濾器來實現的,這些過濾器類型對應於請求的典型生命週期。

  • PRE:這種過濾器在請求被路由以前調用。咱們可利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務、記錄調試信息等。

  • ROUTING:這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用 Apache HttpClient 或 Netfilx Ribbon 請求微服務。

  • POST:這種過濾器在路由到微服務之後執行。這種過濾器可用來爲響應添加標準的 HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。

  • ERROR:在其餘階段發生錯誤時執行該過濾器。

在Zuul網關中,咱們須要自定義一個類來繼承ZuulFilter抽象類並實現4個相應的抽象方法便可。

簡單實例,驗證請求有沒有userToken參數:

@Component public class TokenFilter  extends ZuulFilter { // 過濾器類型 pre 表示在 請求以前進行攔截
 @Override public String filterType() { return "pre"; } ​ // 過濾器的執行順序。當請求在一個階段的時候存在多個多個過濾器時,須要根據該方法的返回值依次執行
 @Override public int filterOrder() { return 0; } ​ // 判斷過濾器是否生效
 @Override public boolean shouldFilter() { return true; } ​ @Override public Object run() throws ZuulException { // 獲取上下文
        RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); String userToken = request.getParameter("userToken"); if (StringUtils.isEmpty(userToken)) { //setSendZuulResponse(false)令zuul過濾該請求,不進行路由
            currentContext.setSendZuulResponse(false); //設置返回的錯誤碼
            currentContext.setResponseStatusCode(401); currentContext.setResponseBody("userToken is null"); return null; } // 不然正常執行業務邏輯.....
        return null; } }

3-二、重啓springcloud-zuul服務,訪問:

    http://localhost:9999/api-b/feign/hello返回:userToken is null

    http://localhost:9999/api-b/feign/hello?userToken=""返回:spring cloud provider-01 hello world

在上面實現的過濾器代碼中,咱們經過繼承ZuulFilter抽象類並重寫了下面的四個方法來實現自定義的過濾器。這四個方法分別定義了:

  • filterType():過濾器的類型,它決定過濾器在請求的哪一個生命週期中執行。這裏定義爲pre,表明會在請求被路由以前執行。

  • filterOrder():過濾器的執行順序。當請求在一個階段中存在多個過濾器時,須要根據該方法返回的值來依次執行。經過數字指定,數字越大,優先級越低。

  • shouldFilter():判斷該過濾器是否須要被執行。這裏咱們直接返回了true,所以該過濾器對全部請求都會生效。實際運用中咱們能夠利用該函數來指定過濾器的有效範圍。

  • run():過濾器的具體邏輯。這裏咱們經過currentContext.setSendZuulResponse(false)令 Zuul 過濾該請求,不對其進行路由,而後經過currentContext.setResponseStatusCode(401)設置了其返回的錯誤碼,固然咱們也能夠進一步優化咱們的返回,好比,經過currentContext.setResponseBody(body)對返回 body 內容進行編輯等。

四、Zuul路由規則

4-一、默認路由規則

    因爲Zuul引入Eureka網關的時候,會爲每個服務創鍵一個默認的路由規則,默認狀況下實例名做爲請求的前綴,這樣不對外開放的服務也會被外界訪問到,咱們能夠控制一下爲哪些服務創鍵路由規則。

zuul.ignored-services: *

*表示不爲全部的服務建立默認的路由規則,則須要咱們本身配置路由規則。

4-二、自定義路由規則

    爲了兼容客戶端不一樣版本,有時候須要咱們爲一組互相配合的微服務定義一個版本標識來方便管理,它們的版本關係,根據這個標識咱們很容易的知道這些服務須要一塊兒啓動並配合使用,好比咱們的服務都採用以版本這樣的命名方式,例如:consumer-v1,consumer-v2分版本訪問服務的話,咱們可使用自定義路由規則,注入PatternServiceRouteMapper對象便可自動的構建相似 /v1/consumer/** 的路由規則

@Bean public PatternServiceRouteMapper patternServiceRouteMapper(){ return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }

    PatternServiceRouteMapper對象能夠經過正則表達式來自定義服務與路由映射的生成關係。其中構造函數的

第一個參數: 用來匹配服務名稱是否符合該自定義規則的正則表達式,

第二個參數用來定義根據服務名中定義的內容轉換出的路徑表達式規則。

    當開發者在API網關中定義了PatternServiceRouteMapper實現以後,只要符合第一個參數定義規則的服務名,都會優先使用該實現構建出的路徑表達式,若是沒有匹配上的服務則仍是會使用默認的路由映射規則,即採用完整服務名做爲前綴的路徑表達式。

五、路徑匹配

在上面案例上咱們看到了使用通配符做爲匹配路徑,一共有三種通配符,以下:

通配符 通配符含義
? 匹配單個字符,如:/consumer/a,/consumer/b
* 匹配任意數量的字符,如:/consumer/abc,/consumer/def
** 匹配任意數量的字符,支持多級路徑,如:/consumer/abc/def,/consumer/ghi/gkl

    咱們在使用的時候,好比咱們的consumer服務路由路徑爲:/consumer/**,因爲發展還須要再次拆分出另一個consumer-ext服務,路由規則爲/consumer/ext/**,因爲**匹配多級目錄這時候咱們須要區別這些服務路徑,properties配置沒法保證配置的加載順序,但在YML配置文件中咱們可使用/consumer/ext/**配置在/consumer/**前面則能夠保證consumer-ext服務的正常路由

六、忽略表達式

zuul.ignored-patterns: /**/hello/**

該配置表示忽略路由路徑包含hello的路徑

七、路由前綴

zuul.prefix: /api zuul.strip-prefix: true

    prefix:前綴,當請求匹配前綴時會進行代理

    strip-prefix:代理前綴默認(true)會從請求路徑中移除,能夠設置爲false關閉移除代理前綴動做,也能夠經過zuul.routes.<route>.strip-prefix=false來對指定路由關閉移除代理前綴動做。

    可是在《Spring Cloud微服務實戰》中指出Brixton.SR7和Camden.SR3中有Bug,該案例版本爲Finchley.SR4未發現Bug

八、Zuul安全與Header

    敏感的Header設置,通常來講同一個系統中的服務之間共享Header,不過Zuul防止一些敏感的Header外泄,防止它們被傳遞到下游服務器,若是咱們須要傳遞Cookie,Set-Cookie,Authorization 這些信息,咱們能夠這樣作

作法一全局設置:將 zuul.sensitive-headers 的值設置爲空
作法二指定路由設置: zuul.routes.<route>.sensitiveHeaders: '' zuul.routes.<route>.custom-sensitive-headers: true

九、忽略 Header

    可用 zuul.ignoredHeaders 屬性丟棄一些 Header,這樣設置後 Cookie 將不會傳播到其它微服務中

zuul.ignored-headers: Cookie

十、禁用指定的 Filter

zuul.<SimpleClassName>.<filterType>.disable=true

具體詳細配置參考Spring官網:https://cloud.spring.io/spring-cloud-static/Finchley.SR4/single/spring-cloud.html#_router_and_filter_zuul

8、Zuul的動態路由

    Zuul做爲服務的統一入口,傳統方式將路由規則配置在配置文件中,若是路由規則發生了改變,須要重啓服務器,這就會對外界中止服務。這時候咱們結合SpringCloud Config分佈式配置中心《Spring Cloud第十篇 | 分佈式配置中心Config》實現動態路由規則,此處再也不演示消息總線Bus《Spring Cloud第十二篇 | 消息總線Bus》的使用。

一、配置中心服務端

1-一、爲了保證之前配置中心服務端模塊(springcloud-config-server)的整潔性,此處新建一個配置中心服務端模塊命名爲(springcloud-zuul-config-server)
1-二、添加依賴

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

1-三、配置application.yml文件

spring: application: name: springcloud-zuul-config-server cloud: config: server: git: #配置git倉庫地址 uri: https://gitee.com/coding-farmer/config-center #配置倉庫路徑 search-paths: "{profile}" #訪問git倉庫的用戶名 username: #訪問git倉庫的密碼 password: #配置中心經過git從遠程git庫,有時本地的拷貝被污染, #這時配置中心沒法從遠程庫更新本地配置,設置force-pull=true,則強制從遠程庫中更新本地庫 force-pull: true #默認從git倉庫克隆下載的在C:/Users/<當前用戶>/AppData/Local/Temp #basedir: server: port: 8888 eureka: client: service-url: defaultZone: http://localhost:8700/eureka #客戶端每隔30秒從Eureka服務上更新一次服務信息 registry-fetch-interval-seconds: 30 #須要將個人服務註冊到eureka上 register-with-eureka: true #須要檢索服務 fetch-registry: true #心跳檢測檢測與續約時間 instance: #告訴服務端,若是我10s以內沒有給你發心跳,就表明我故障了,將我剔除掉,默認90s #Eureka服務端在收到最後一次心跳以後等待的時間上限,單位爲秒,超過則剔除(客戶端告訴服務端按照此規則等待本身) lease-expiration-duration-in-seconds: 10 #每隔2s向服務端發送一次心跳,證實自已依然活着,默認30s #Eureka客戶端向服務端發送心跳的時間間隔,單位爲秒(客戶端告訴服務端本身會按照該規則) lease-renewal-interval-in-seconds: 2 # 啓用ip配置 這樣在註冊中心列表中看見的是以ip+端口呈現的 prefer-ip-address: true # 實例名稱 最後呈現地址:ip:2002 instance-id: ${spring.cloud.client.ip-address}:${server.port}

1-四、在啓動類上添加註解

@EnableConfigServer @EnableEurekaClient

到此配置中心服務端搭建完成

二、修改zuul服務模塊

2-一、添加配置中心客戶端依賴

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-client</artifactId>
</dependency>

2-二、將application.yml文件更名爲application-bak.yml(爲了application.yml文件),配置bootstrap.yml文件

server: port: 9999 spring: application: name: springcloud-zuul cloud: config: #uri則表示配置中心的地址 #uri: http://localhost:8888 #注:config 客戶端在沒有 spring.cloud.config.name屬性的時候,服務端{application} 獲取的是客戶端 #spring.application.name的值,不然,獲取的是 spring.cloud.config.name的值。 #1)、當沒有spring.cloud.config.name時,客戶端獲取的是spring.application.name 所對應的git庫中的文件,而且只能 #獲取一個文件, #2)、當一個項目中有需求要獲取多個文件時,就須要用到spring.cloud.config.name這個屬性,以逗號分割 name: configzuul profile: dev #label對應了label部分 label: master # username: # password: discovery: #表示開啓經過服務名來訪問config-server enabled: true #則表示config-server的服務名 service-id: springcloud-zuul-config-server #失敗快速響應 fail-fast: true retry: #配置重試次數,默認爲6 max-attempts: 6 #初始重試間隔時間,默認1000ms initial-interval: 1000 #間隔乘數,默認1.1 multiplier: 1.1 #最大間隔時間,默認2000ms max-interval: 2000 eureka: client: service-url: defaultZone: http://localhost:8700/eureka #客戶端每隔30秒從Eureka服務上更新一次服務信息 registry-fetch-interval-seconds: 30 #須要將個人服務註冊到eureka上 register-with-eureka: true #須要檢索服務 fetch-registry: true #心跳檢測檢測與續約時間 instance: #告訴服務端,若是我10s以內沒有給你發心跳,就表明我故障了,將我剔除掉,默認90s #Eureka服務端在收到最後一次心跳以後等待的時間上限,單位爲秒,超過則剔除(客戶端告訴服務端按照此規則等待本身) lease-expiration-duration-in-seconds: 10 #每隔2s向服務端發送一次心跳,證實自已依然活着,默認30s #Eureka客戶端向服務端發送心跳的時間間隔,單位爲秒(客戶端告訴服務端本身會按照該規則) lease-renewal-interval-in-seconds: 2 # 啓用ip配置 這樣在註冊中心列表中看見的是以ip+端口呈現的 prefer-ip-address: true # 實例名稱 最後呈現地址:ip:2002 instance-id: ${spring.cloud.client.ip-address}:${server.port} management: endpoints: web: exposure: include: ["info","health","refresh"]

2-三、配置刷新類

@Configuration public class Config { ​ @RefreshScope @ConfigurationProperties("zuul") public ZuulProperties zuulProperties() { return new ZuulProperties(); } }

三、在代碼倉庫添加配configzuul-dev.yml置文件

四、啓動相關服務

    Eureka服務(springcloud-eureka-server),zuul的配置中心服務(springcloud-zuul-config-server)、提供者服務(springcloud-service-provider)、消費者服務(springcloud-service-consumer)、zuul服務(springcloud-zuul)

    訪問消費者服務接口:http://localhost:9999/api-a/consumer/hello?userToken=""

    訪問配置中心服務端:http://localhost:8888/configzuul-dev.yml,查看配置結果如圖:

    修改倉庫config-center的configzuul-dev.yml配置爲api-c接着在訪問配置中心倉庫配置結果,http://localhost:8888/configzuul-dev.yml,注意api-a的key不要修改

    而後發送post請求zuul的refresh端點進行配置刷新http://localhost:9999/actuator/refresh

    而後你會發現http://localhost:9999/api-a/consumer/hello?userToken=’‘路徑訪問不通了,訪問http://localhost:9999/api-c/consumer/hello?userToken=’'結果如圖,顯示頁面爲

    到此zuul的動態刷新完成,此處動態刷新就是使用了配置中心的功能,不瞭解的能夠參考《Spring Cloud第十篇 | 分佈式配置中心Config

 

詳細參考案例源碼:https://gitee.com/coding-farmer/springcloud-learn

 

原文出處:https://www.cnblogs.com/coding-farmer/p/12329115.html

相關文章
相關標籤/搜索