服務網(wǎng)關——Spring Cloud Zuul

(git上的源碼:https://gitee.com/rain7564/spring_microservices_study/tree/master/fifth-spring-cloud-zuul)

對于服務網(wǎng)關是什么狈定、有什么用?使用API Gateway這篇文章已經講得很清楚了昌屉,這里就不再贅述惯悠。當然這只是翻譯版钧汹,原版在這里:Building Microservices: Using an API Gateway

Spring Cloud和Netflix Zuul

Using an API Gateway一文中提到Netflix API Gateway其實就是Netflix Zuul。Zuul是一個服務網(wǎng)關,Spring Cloud融入Zuul后墩朦,使用Spring Cloud提供的注解很容易就能搭建一個API Gateway。Zuul提供了幾個功能翻擒,包括:

  • 只用一個URL就能映射應用中所有服務的路由氓涣。但Zuul并不局限于單個URL,也可以定義多個路由入口陋气,做到細粒度的路由映射劳吠。
  • 自定義過濾器對經過網(wǎng)關的所有請求進行過濾。服務網(wǎng)關的過濾器巩趁,可以實現(xiàn)對所有請求進行過濾痒玩,而不用在各個服務實現(xiàn)過濾器。

下面進入正題议慰,服務網(wǎng)關的實現(xiàn)蠢古。

創(chuàng)建gateway-zuul服務

創(chuàng)建一個Zuul服務端,首先要創(chuàng)建一個Spring Boot項目别凹,然后引入Zuul相關的啟動依賴草讶。

創(chuàng)建Spring boot項目并修改pom文件

創(chuàng)建一個空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诵棵。如果使用該注解抠蚣,雖然也會創(chuàng)建一個Zuul服務端,但不加載任何反向代理過濾器履澳,不使用Eureka的服務發(fā)現(xiàn)來發(fā)現(xiàn)其他服務嘶窄。@EnableZuulServer只在搭建自己的路由服務并不使用Zuul的預構建功能時使用。比如需要使用Zuul來配合其它不是Eureka的服務發(fā)現(xiàn)引擎距贷,如Consul柄冲。

與Eureka結合使用

Zuul proxy server本來就是被設計用在Spring Cloud項目中。正因為如此忠蝗,Zuul自動使用Eureka來作為服務發(fā)現(xiàn)的依賴现横,可以通過服務發(fā)現(xiàn)其它服務,然后在Zuul內部使用Ribbon實現(xiàn)客戶端負載均衡阁最。

添加application.yml文件戒祠,然后在該文件中加入如下配置:

server:
  port: 5555

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

配置路由規(guī)則

Zuul的核心是反向代理。反向代理實際上是一個中間服務器闽撤,處于想要訪問某個資源的客戶端與資源中間得哆。反向代理會捕捉客戶端的請求并代表客戶端想遠程資源發(fā)起請求。

在微服務架構中哟旗,Zuul(反向代理)從客戶端“得到”一個調用贩据,然后轉發(fā)給下游服務。所以闸餐,從客戶端服務角度來看饱亮,與客戶端交互的實際上是Zuul。而Zuul需要與下游的服務進行交互舍沙,所以必須知道客戶端的請求想要路由到哪個服務近上。Zuul可以通過幾種途徑來達到這一目標,包括:

  • 通過服務發(fā)現(xiàn)自動映射路由
  • 使用服務發(fā)現(xiàn)手動映射路由
  • 使用靜態(tài)URLs手動映射路由

通過服務發(fā)現(xiàn)自動映射路由

Zuul的所有路由映射都可以在zuul服務的application.yml文件中定義拂铡。然而壹无,Zuul還可以在零配置的情況下,根據(jù)請求url攜帶的serviceId將請求正確路由到目標服務的某個實例感帅。如果沒有指定特定的路由(手動配置宗兼,下文會介紹)架专,Zuul默認會使用被調用服務的Eureka service ID并將其映射到其中一個目標服務實例。舉個簡單的例子,如果你想訪問organization-service服務的一個接口畔师,該接口是根據(jù)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使用的優(yōu)美之處在于阶剑,不僅可以通過單個端點來訪問應用的所有服務,而且危号,在添加或移除服務實例的時候不用修改Zuul的路由配置。另外素邪,也可以添加一個新的服務到Eureka外莲,而Zuul會對訪問新添加的服務自動路由,因為Zuul是通過與Eureka通信然后從Eureka獲取微服務實例真正物理地址兔朦,只要服務托管在Eureka中偷线。

如果想要查看Zuul服務器管理的路由,可以訪問Zuul暴露的/routes端點沽甥。該端點會返回所有服務的映射列表声邦。啟動所有服務,然后訪問http://localhost:5555/routes摆舟,若跟著本教程走亥曹,應該可以看到類似如下圖的返回:

image.png

如果出現(xiàn)下面的情況:


image.png

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

management:
  security:
    enabled: false

觀察正常訪問http://localhost:5555/routes后的返回json對象,類似"/config-server/**"的key恨诱,是Zuul基于Eureka service ID自動為服務創(chuàng)建的服務路由媳瞪,請求匹配到的服務路由就可以映射得到Eureka service ID,即json對象的value照宝,最后就可以根據(jù)這個ID定位具體的服務實例蛇受。

使用服務發(fā)現(xiàn)手動映射路由

Zuul允許配置更細粒度的路由映射規(guī)則,可以明確定義路由映射而不是單純依賴使用服務的Eureka service ID自動創(chuàng)建厕鹃。假設想要縮短organizationservice來簡化路由兢仰,而不是使用Zuul默認提供的/organizationservice/**,那么可以通過在Zuul服務的配置文件中手動定義路由映射關系剂碴,例如:

zuul:
  routes: 
    organizationservice: /organization/**

在Zuul服務的application.yml加上上面的配置后把将,就可以使用類似/organization/v1/organizations/{organization-id}的路由來訪問organization服務了。此時汗茄,若重啟Zuul然后再次訪問http://localhost:5555/routes秸弛,可以出現(xiàn)如下返回結果:

image.png

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

注意:如果使用Zuul基于Eureka service ID自動創(chuàng)建的路由映射,那么當某個服務沒有任何一個實例處于運行狀態(tài)绞铃,那么Zuul將不會為該服務創(chuàng)建路由映射镜雨。然而,如果手動將路由映射到Eureka service ID儿捧,那么荚坞,即使沒有實例注冊到Eureka,Zuul依舊會暴露出手動配置的菲盾。當然颓影,若嘗試使用不存在任何服務實例的路由,Zuul將直接返回500錯誤懒鉴。

忽略某些服務

如果想要將Zuul自動創(chuàng)建的路由映射從路由列表中移除诡挂,只留下手動配置的,那么可以在application.yml文件中在加一個額外的Zuul參數(shù)——ignored-services即可临谱。示例如下:

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

這樣璃俗,就能將Zuul根據(jù)Eureka Service ID自動創(chuàng)建的"/organizationservice/**": "organizationservice"從路由映射列表中移除。添加上面的配置然后重啟悉默,訪問/routes端點城豁,可以看到如下返回:


image.png

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

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

加上如上配置后重啟剖膳,然后訪問端點/routes魏颓,返回如下:


image.png
手動配置多個服務

云應用肯定會包含許多微服務,所以一般都有為多個服務手動配置路由映射的需求吱晒,這樣的需求實現(xiàn)起來也比較簡單甸饱,如下是對organization服務和license服務的路由映射做手動配置:

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

訪問/routes結果是:


image.png
不同API路由共用一樣的模型

在不同服務路由的開頭附加一個的前綴是很常見的。比如希望在不同服務的路由的開頭都加上一個/api的前綴仑濒,Zuul也是支持的叹话。可以使用如下配置來實現(xiàn)這一功能:

zuul:
  ignored-services: '*'
  prefix: /api
  routes:
    organizationservice: /organization/**
    licenseservice: /license/**

可見墩瞳,zuul.prefix屬性可以用來定制所有服務路由的統(tǒng)一前綴驼壶。再次訪問/routes端點,返回如下:


image.png

現(xiàn)在若需要訪問organization服務的/v1/organizations/{organization-id}端口喉酌,則需要使用:

http://localhost:5555/api/organization/v1/organizations/{organization-id}

使用靜態(tài)URLs手動映射路由

Zuul也可以用來轉發(fā)沒有注冊到Eureka的服務的請求热凹。在某些情況下泵喘,需要設置Zuul將部分請求直接路由到定義的靜態(tài)URL。比如般妙,假設organization服務是使用Python編寫的纪铺,然后也想讓Zuul來做反向代理,那么可以使用如下的配置實現(xiàn):

zuul:
  prefix: /api
  routes:
    organizationstatic:
      path: /organizationstatic/**
      url: http://localhost:11000

關閉其他服務碟渺,只啟動zuul服務和organization服務鲜锚,啟動過程中會報錯,這是因為eureka沒啟動苫拍,服務沒辦法注冊到eureka芜繁,可以不管它,只要成功啟動就行绒极。然后訪問zuul的/routes端點骏令,可以看到只有剛剛配置的靜態(tài)URL,如圖:


image.png

然后你會發(fā)現(xiàn)集峦,訪問
http://localhost:5555/api/organizationstatic/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/時伏社,可以成功訪問,結果如下:

image.png

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


image.png

但是速妖,問題又來了高蜂,因為這樣的配置會繞過eureka,這就導致請求都會指向單個路由罕容,那么當organization服務有多個怎么辦备恤?怎么利用Ribbon來實現(xiàn)服務均衡?有兩種做法:
第一種:

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

第一種實現(xiàn)方法需要Ribbon禁用eureka锦秒,感覺不太友好露泊。官方文檔是這樣說的:


image.png

我的理解是:因為如果不禁用的話,zuul會以為organizationstatic這個service ID是來自Eureka的旅择,所以就去eureka那邊查找對應的服務實例惭笑,然而,organizationstatic根本就沒注冊到eureka生真,所以就直接報500了沉噩。

而第二種實現(xiàn)方法,是告訴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/康聂,會發(fā)現(xiàn)負載均衡器起作用了贰健。至于如何啟動多個實例,請參考另一篇教程服務注冊與發(fā)現(xiàn)——Netflix Eureka早抠,這里面有提及霎烙。

非java服務的處理
之前說到靜態(tài)映射路由負載均衡的第一種實現(xiàn),需要zuul服務器的Ribbon禁用eureka蕊连,禁用后會有一個問題悬垃,就是其他注冊到eureka的服務無法通過網(wǎng)關訪問,需要手動自己配置.listOfServers屬性甘苍。

所以對于其它語言編寫的服務也想要通過zuul統(tǒng)一管理尝蠕,有兩個方案:第一,使用一個獨立的zuul服務專門轉發(fā)那些其它語言的服務载庭;第二看彼,創(chuàng)建Spring Cloud Sidecar實例(推薦使用這種),Spring Cloud sidecar允許注冊其它語言實現(xiàn)的服務到eureka囚聚,然后就可以通過zuul代理了靖榕。Spring Cloud sidecar這里就不細講了,實現(xiàn)也比較簡單顽铸,可參考Polyglot support with Sidecar茁计。

zuul的超時時間

zuul使用Hystrix和Ribbon庫來幫助避免一個耗時較長的調用影響到整個網(wǎng)關的性能。默認情況下谓松,當一個調用經過1s后還為處理完成并返回星压,zuul會終止該調用并統(tǒng)一返回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

動態(tài)重新加載路由配置

zuul動態(tài)加載路由配置,需要具備Spring Cloud Config基礎票灰,若不了解Spring Cloud Config女阀,可參考另一篇教程分布式配置——Spring Cloud Configuration

動態(tài)加載路由在實際應用中是極其有用的屑迂,因為可以在變更路由配置后浸策,不用重新編譯、重新部署zuul服務惹盼。在分布式配置——Spring Cloud Configuration中已經講過如何將配置文件遷移到config server中庸汗,我們也可以將zuul的配置交由config server管理。(注意:本教程不使用git作為config server的配置文件存儲庫手报,而是直接使用config server的classpath蚯舱,這樣比較簡單,只是每次改完配置文件需要重新啟動config server)掩蛤。

在config server的classpath:config目錄下枉昏,創(chuàng)建目錄gateway-zuul,然后創(chuàng)建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已經動態(tài)加載配置成功了立宜。

有關Spring Cloud Zuul的入門教程就介紹到這里冒萄,后續(xù)會在進階教程中繼續(xù)介紹zuul真正強大的功能——filter。

完橙数!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末尊流,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子灯帮,更是在濱河造成了極大的恐慌崖技,老刑警劉巖逻住,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異迎献,居然都是意外死亡瞎访,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門吁恍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扒秸,“玉大人,你說我怎么就攤上這事冀瓦“榘拢” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵咕幻,是天一觀的道長渔伯。 經常有香客問我,道長肄程,這世上最難降的妖魔是什么锣吼? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮蓝厌,結果婚禮上玄叠,老公的妹妹穿的比我還像新娘。我一直安慰自己拓提,他們只是感情好读恃,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著代态,像睡著了一般寺惫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹦疑,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天西雀,我揣著相機與錄音,去河邊找鬼歉摧。 笑死艇肴,一個胖子當著我的面吹牛,可吹牛的內容都是我干的叁温。 我是一名探鬼主播再悼,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼膝但!你這毒婦竟也來了冲九?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤锰镀,失蹤者是張志新(化名)和其女友劉穎娘侍,沒想到半個月后咖刃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡憾筏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年嚎杨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氧腰。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡枫浙,死狀恐怖,靈堂內的尸體忽然破棺而出古拴,到底是詐尸還是另有隱情箩帚,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布黄痪,位于F島的核電站紧帕,受9級特大地震影響,放射性物質發(fā)生泄漏桅打。R本人自食惡果不足惜是嗜,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挺尾。 院中可真熱鬧鹅搪,春花似錦、人聲如沸遭铺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魂挂。三九已至甫题,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涂召,已是汗流浹背幔睬。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芹扭,地道東北人。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓赦抖,卻偏偏與公主長得像舱卡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子队萤,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內容