服務網關 Spring Cloud Zuul

(git上的源碼:https://gitee.com/rain7564/spring_microservices_study/tree/master/fifth-spring-cloud-zuul)
對於服務網關是什麼、有什麼用?使用API Gateway這篇文章已經講得很清楚了,這裏就不再贅述。當然這只是翻譯版,原版在這裏:Building Microservices: Using an API Gateway
Spring Cloud和Netflix Zuul
Using an API Gateway一文中提到Netflix API Gateway其實就是Netflix Zuul。Zuul是一個服務網關,Spring Cloud融入Zuul後,使用Spring Cloud提供的註解很容易就能搭建一個API Gateway。Zuul提供了幾個功能,包括:

只用一個URL就能映射應用中所有服務的路由。但Zuul並不侷限於單個URL,也可以定義多個路由入口,做到細粒度的路由映射。
自定義過濾器對經過網關的所有請求進行過濾。服務網關的過濾器,可以實現對所有請求進行過濾,而不用在各個服務實現過濾器。

下面進入正題,服務網關的實現。
創建gateway-zuul服務
創建一個Zuul服務端,首先要創建一個Spring Boot項目,然後引入Zuul相關的啓動依賴。
創建Spring boot項目並修改pom文件
創建一個空Spring Boot項目後,pom文件修改如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.study.microservice</groupId>
    <artifactId>gateway-zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>gateway-zuul</name>
    <description>API Gateway</description>

    <parent>
        <groupId>cn.study.microservice</groupId>
        <artifactId>fifth-spring-cloud-zuul</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>

</project>

上面的xml文件,只引入了兩個啓動依賴,第一個是Zuul的啓動依賴,該啓動依賴除了包含Zuul的核心jar包外,還包括Hystrix、Ribbon、actuator等。第二個是後文介紹將Zuul服務託管在Eureka時會用到。
修改啓動類
pom文件修改好之後,需要修改gateway-zuul服務的啓動類,如下:
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayZuulApplication.class, args);
    }
}

上面的代碼實際上只加了一個註解—@EnableZuulProxy。

若在添加註解@EnableZuulProxy時是手動輸入,且IDE開啓自動補全功能,那麼應該會看到另一個註解——@EnableZuulServer。如果使用該註解,雖然也會創建一個Zuul服務端,但不加載任何反向代理過濾器,不使用Eureka的服務發現來發現其他服務。@EnableZuulServer只在搭建自己的路由服務並不使用Zuul的預構建功能時使用。比如需要使用Zuul來配合其它不是Eureka的服務發現引擎,如Consul。

與Eureka結合使用
Zuul proxy server本來就是被設計用在Spring Cloud項目中。正因爲如此,Zuul自動使用Eureka來作爲服務發現的依賴,可以通過服務發現其它服務,然後在Zuul內部使用Ribbon實現客戶端負載均衡。
添加application.yml文件,然後在該文件中加入如下配置:
server:
  port: 5555

eureka:
  instance:
    preferIpAddress: true
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

配置路由規則
Zuul的核心是反向代理。反向代理實際上是一箇中間服務器,處於想要訪問某個資源的客戶端與資源中間。反向代理會捕捉客戶端的請求並代表客戶端想遠程資源發起請求。
在微服務架構中,Zuul(反向代理)從客戶端“得到”一個調用,然後轉發給下游服務。所以,從客戶端服務角度來看,與客戶端交互的實際上是Zuul。而Zuul需要與下游的服務進行交互,所以必須知道客戶端的請求想要路由到哪個服務。Zuul可以通過幾種途徑來達到這一目標,包括:

通過服務發現自動映射路由
使用服務發現手動映射路由
使用靜態URLs手動映射路由

通過服務發現自動映射路由
Zuul的所有路由映射都可以在zuul服務的application.yml文件中定義。然而,Zuul還可以在零配置的情況下,根據請求url攜帶的serviceId將請求正確路由到目標服務的某個實例。如果沒有指定特定的路由(手動配置,下文會介紹),Zuul默認會使用被調用服務的Eureka service ID並將其映射到其中一個目標服務實例。舉個簡單的例子,如果你想訪問organization-service服務的一個接口,該接口是根據orgId獲取對應的organization詳細信息,而且希望使用Zuul的自動路由,那麼你可以讓客戶端直接訪問Zuul服務的實例,然後使用如下URL:
http://localhost:5555/organizationservice/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a

其中http://localhost:5555,是Zuul服務實例的訪問地址;而organizationservice是organization服務的service ID;剩下的部分則是希望調用的接口。看下面的圖可能更容易理解,如下:











image.png

Eureka配合Zuul使用的優美之處在於,不僅可以通過單個端點來訪問應用的所有服務,而且,在添加或移除服務實例的時候不用修改Zuul的路由配置。另外,也可以添加一個新的服務到Eureka,而Zuul會對訪問新添加的服務自動路由,因爲Zuul是通過與Eureka通信然後從Eureka獲取微服務實例真正物理地址,只要服務託管在Eureka中。
如果想要查看Zuul服務器管理的路由,可以訪問Zuul暴露的/routes端點。該端點會返回所有服務的映射列表。啓動所有服務,然後訪問http://localhost:5555/routes,若跟着本教程走,應該可以看到類似如下圖的返回:










image.png

如果出現下面的情況:












image.png


則需要在application.yml或bootstrap.yml文件中加入如下配置:

management:
  security:
    enabled: false

觀察正常訪問http://localhost:5555/routes後的返回json對象,類似"/config-server/**"的key,是Zuul基於Eureka service ID自動爲服務創建的服務路由,請求匹配到的服務路由就可以映射得到Eureka service ID,即json對象的value,最後就可以根據這個ID定位具體的服務實例。
使用服務發現手動映射路由
Zuul允許配置更細粒度的路由映射規則,可以明確定義路由映射而不是單純依賴使用服務的Eureka service ID自動創建。假設想要縮短organizationservice來簡化路由,而不是使用Zuul默認提供的/organizationservice/**,那麼可以通過在Zuul服務的配置文件中手動定義路由映射關係,例如:
zuul:
  routes: 
    organizationservice: /organization/**

在Zuul服務的application.yml加上上面的配置後,就可以使用類似/organization/v1/organizations/{organization-id}的路由來訪問organization服務了。此時,若重啓Zuul然後再次訪問http://localhost:5555/routes,可以出現如下返回結果:











image.png

觀察上圖,可以看到Zuul爲organization服務提供了“入口”,第一個是剛剛手動配置上去的,而第二個則是Zuul默認提供的。

注意:如果使用Zuul基於Eureka service ID自動創建的路由映射,那麼當某個服務沒有任何一個實例處於運行狀態,那麼Zuul將不會爲該服務創建路由映射。然而,如果手動將路由映射到Eureka service ID,那麼,即使沒有實例註冊到Eureka,Zuul依舊會暴露出手動配置的。當然,若嘗試使用不存在任何服務實例的路由,Zuul將直接返回500錯誤。

忽略某些服務
如果想要將Zuul自動創建的路由映射從路由列表中移除,只留下手動配置的,那麼可以在application.yml文件中在加一個額外的Zuul參數——ignored-services即可。示例如下:
zuul:
  ignored-services: 'organizationservice'
  routes:
    organizationservice: /organization/**

這樣,就能將Zuul根據Eureka Service ID自動創建的"/organizationservice/**": "organizationservice"從路由映射列表中移除。添加上面的配置然後重啓,訪問/routes端點,可以看到如下返回:












image.png


上圖中,Zuul默認給organization服務創建的路由映射已經被忽略了,只留下手動配置的。如果希望Zuul忽略所有自動配置的路由映射,可以使用:

zuul:
  ignored-services: '*'
  routes:
    organizationservice: /organization/**

加上如上配置後重啓,然後訪問端點/routes,返回如下:












image.png

手動配置多個服務
雲應用肯定會包含許多微服務,所以一般都有爲多個服務手動配置路由映射的需求,這樣的需求實現起來也比較簡單,如下是對organization服務和license服務的路由映射做手動配置:
zuul:
  ignored-services: '*'
  routes:
    organizationservice: /organization/**
    licenseservice: /license/**

訪問/routes結果是:












image.png

不同API路由共用一樣的模型
在不同服務路由的開頭附加一個的前綴是很常見的。比如希望在不同服務的路由的開頭都加上一個/api的前綴,Zuul也是支持的。可以使用如下配置來實現這一功能:
zuul:
  ignored-services: '*'
  prefix: /api
  routes:
    organizationservice: /organization/**
    licenseservice: /license/**

可見,zuul.prefix屬性可以用來定製所有服務路由的統一前綴。再次訪問/routes端點,返回如下:












image.png

現在若需要訪問organization服務的/v1/organizations/{organization-id}端口,則需要使用:
http://localhost:5555/api/organization/v1/organizations/{organization-id}

使用靜態URLs手動映射路由
Zuul也可以用來轉發沒有註冊到Eureka的服務的請求。在某些情況下,需要設置Zuul將部分請求直接路由到定義的靜態URL。比如,假設organization服務是使用Python編寫的,然後也想讓Zuul來做反向代理,那麼可以使用如下的配置實現:
zuul:
  prefix: /api
  routes:
    organizationstatic:
      path: /organizationstatic/**
      url: http://localhost:11000

關閉其他服務,只啓動zuul服務和organization服務,啓動過程中會報錯,這是因爲eureka沒啓動,服務沒辦法註冊到eureka,可以不管它,只要成功啓動就行。然後訪問zuul的/routes端點,可以看到只有剛剛配置的靜態URL,如圖:












image.png

然後你會發現,訪問

http://localhost:5555/api/organizationstatic/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/時,可以成功訪問,結果如下:











image.png

下面來分析一下,之前的配置是什麼含義,如下圖:












image.png

但是,問題又來了,因爲這樣的配置會繞過eureka,這就導致請求都會指向單個路由,那麼當organization服務有多個怎麼辦?怎麼利用Ribbon來實現服務均衡?有兩種做法:

第一種:
zuul:
  prefix: /api
  routes:
    organizationstatic:
      path: /organizationstatic/**
      serviceId: organizationstatic
ribbon:
  eureka:
    enabled: false
organizationstatic:
  ribbon:
    listOfServers: http://localhost:11000,http://localhost:11001

第二種:
zuul:
  prefix: /api
  routes:
    organizationstatic:
      path: /organizationstatic/**
      serviceId: organizationstatic
organizationstatic:
  ribbon:
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
    listOfServers: http://localhost:11000,http://localhost:11001

第一種實現方法需要Ribbon禁用eureka,感覺不太友好。官方文檔是這樣說的:












image.png

我的理解是:因爲如果不禁用的話,zuul會以爲organizationstatic這個service ID是來自Eureka的,所以就去eureka那邊查找對應的服務實例,然而,organizationstatic根本就沒註冊到eureka,所以就直接報500了。
而第二種實現方法,是告訴zuul在使用Ribbon做負載均衡時,直接在提供的server列表中獲取服務實例。觀察第二種方法的配置,可以看到添加了.ribbon.NIWSServerListClassName屬性。官方文檔是這樣說的:












image.png

官方文檔地址:https://github.com/spring-cloud/spring-cloud-netflix/blob/master/docs/src/main/asciidoc/spring-cloud-netflix.adoc
使用上面的配置後,只啓動gateway-zuul服務和兩個organization服務實例,兩個organization服務的端口分別是11000和11001。然後訪問http://localhost:5555/api/organizationstatic/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/,會發現負載均衡器起作用了。至於如何啓動多個實例,請參考另一篇教程服務註冊與發現——Netflix Eureka,這裏面有提及。

非java服務的處理

之前說到靜態映射路由負載均衡的第一種實現,需要zuul服務器的Ribbon禁用eureka,禁用後會有一個問題,就是其他註冊到eureka的服務無法通過網關訪問,需要手動自己配置.listOfServers屬性。


所以對於其它語言編寫的服務也想要通過zuul統一管理,有兩個方案:第一,使用一個獨立的zuul服務專門轉發那些其它語言的服務;第二,創建Spring Cloud Sidecar實例(推薦使用這種),Spring Cloud sidecar允許註冊其它語言實現的服務到eureka,然後就可以通過zuul代理了。Spring Cloud sidecar這裏就不細講了,實現也比較簡單,可參考Polyglot support with Sidecar。

zuul的超時時間
zuul使用Hystrix和Ribbon庫來幫助避免一個耗時較長的調用影響到整個網關的性能。默認情況下,當一個調用經過1s後還爲處理完成並返回,zuul會終止該調用並統一返回500錯誤(這個1s的超時時間,其實是Hystrix的默認超時時間)。但是,我們可以在zuul的配置文件中對這個超時時間進行修改。比如,將其修改成7s,可以這樣設置:
...
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 7000

假如需要修改特定的服務的超時時間,比如organization服務,則需要將上面的hystrix.command.defualt.換成hystrix.command.organizationservice,即將default換成對應的服務名。
最後,需要修改另一個超時時間屬性。當我們覆蓋hystrix默認超時時間且新超時時間大於5s,那麼當一個調用超過5s時,Ribbon也會認爲該調用超時。也就是說,當配置hystrix的超時時間大於5s,那麼還需要配置Ribbon的超時時間,配置如下:
ribbon:
  ReadTimeout: 7000

如果需要指定某個服務的Ribbon超時時間,則要用(clientname爲服務名):
clientname:
  ribbon:
    ReadTimeout: 7000

動態重新加載路由配置
zuul動態加載路由配置,需要具備Spring Cloud Config基礎,若不瞭解Spring Cloud Config,可參考另一篇教程分佈式配置——Spring Cloud Configuration。
動態加載路由在實際應用中是極其有用的,因爲可以在變更路由配置後,不用重新編譯、重新部署zuul服務。在分佈式配置——Spring Cloud Configuration中已經講過如何將配置文件遷移到config server中,我們也可以將zuul的配置交由config server管理。(注意:本教程不使用git作爲config server的配置文件存儲庫,而是直接使用config server的classpath,這樣比較簡單,只是每次改完配置文件需要重新啓動config server)。
在config server的classpath:config目錄下,創建目錄gateway-zuul,然後創建gateway-zuul.yml文件,內容如下:
server:
  port: 5555

eureka:
  instance:
    preferIpAddress: true
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

接着修改config server服務的application.yml,添加config server掃描路徑,如下:
...
spring:
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          search-locations: classpath:config/,classpath:config/licenseservice, classpath:config/gateway-zuul

然後修改zuul服務的pom.xml文件和bootstrap.yml文件,分別爲:
...
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
</dependency>
...

spring:
  application:
    name: gateway-zuul
  profiles:
    active: default
  cloud:
    config:
      uri: http://localhost:8888
management:
  security:
    enabled: false

然後重啓config-server服務和gateway-zuul服務,當看到zuul服務的控制檯有類似如下輸出,才證明成功從config server加載配置文件(不然就要找找是哪裏出錯了):












image.png

訪問zuul服務的/routes端點,返回結果如下:












image.png

修改config-server服務管理的zuul服務的配置文件,添加如下配置:
zuul:
  ignored-services: '*'
  prefix: /api
  routes:
    organizationservice: /organization/**
    licenseservice: /license/**

然後使用POST方式訪問http://localhost:555/refresh(記得要先重啓config-server服務),可以看到返回如下:











image.png

證明zuul服務的配置信息中的上面4個配置的值已變更。再次訪問zuul服務的/routes,返回結果如下:












image.png

證明zuul已經動態加載配置成功了。
有關Spring Cloud Zuul的入門教程就介紹到這裏,後續會在進階教程中繼續介紹zuul真正強大的功能——filter。
完!

我的官網
我的博客

我的官網http://guan2ye.com
我的CSDN地址http://blog.csdn.net/chenjianandiyi
我的簡書地址http://www.jianshu.com/u/9b5d1921ce34
我的githubhttps://github.com/javanan
我的碼雲地址https://gitee.com/jamen/
阿里雲優惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld

阿里雲教程系列網站http://aliyun.guan2ye.com

1.png

我的開源項目spring boot 搭建的一個企業級快速開發腳手架

1.jpg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章