(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聪轿;剩下的部分則是希望調用的接口爷肝。看下面的圖可能更容易理解屹电,如下:
Eureka配合Zuul使用的優(yōu)美之處在于阶剑,不僅可以通過單個端點來訪問應用的所有服務,而且危号,在添加或移除服務實例的時候不用修改Zuul的路由配置。另外素邪,也可以添加一個新的服務到Eureka外莲,而Zuul會對訪問新添加的服務自動路由,因為Zuul是通過與Eureka通信然后從Eureka獲取微服務實例真正物理地址兔朦,只要服務托管在Eureka中偷线。
如果想要查看Zuul服務器管理的路由,可以訪問Zuul暴露的/routes端點沽甥。該端點會返回所有服務的映射列表声邦。啟動所有服務,然后訪問http://localhost:5555/routes摆舟,若跟著本教程走亥曹,應該可以看到類似如下圖的返回:
如果出現(xiàn)下面的情況:
則需要在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)如下返回結果:
觀察上圖,可以看到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端點城豁,可以看到如下返回:
上圖中,Zuul默認給organization服務創(chuàng)建的路由映射已經被忽略了抄课,只留下手動配置的唱星。如果希望Zuul忽略所有自動配置的路由映射,可以使用:
zuul:
ignored-services: '*'
routes:
organizationservice: /organization/**
加上如上配置后重啟剖膳,然后訪問端點/routes魏颓,返回如下:
手動配置多個服務
云應用肯定會包含許多微服務,所以一般都有為多個服務手動配置路由映射的需求吱晒,這樣的需求實現(xiàn)起來也比較簡單甸饱,如下是對organization服務和license服務的路由映射做手動配置:
zuul:
ignored-services: '*'
routes:
organizationservice: /organization/**
licenseservice: /license/**
訪問/routes結果是:
不同API路由共用一樣的模型
在不同服務路由的開頭附加一個的前綴是很常見的。比如希望在不同服務的路由的開頭都加上一個/api的前綴仑濒,Zuul也是支持的叹话。可以使用如下配置來實現(xiàn)這一功能:
zuul:
ignored-services: '*'
prefix: /api
routes:
organizationservice: /organization/**
licenseservice: /license/**
可見墩瞳,zuul.prefix屬性可以用來定制所有服務路由的統(tǒng)一前綴驼壶。再次訪問/routes端點,返回如下:
現(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,如圖:
然后你會發(fā)現(xiàn)集峦,訪問
http://localhost:5555/api/organizationstatic/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/時伏社,可以成功訪問,結果如下:
下面來分析一下塔淤,之前的配置是什么含義,如下圖:
但是速妖,問題又來了高蜂,因為這樣的配置會繞過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锦秒,感覺不太友好露泊。官方文檔是這樣說的:
我的理解是:因為如果不禁用的話,zuul會以為organizationstatic這個service ID是來自Eureka的旅择,所以就去eureka那邊查找對應的服務實例惭笑,然而,organizationstatic根本就沒注冊到eureka生真,所以就直接報500了沉噩。
而第二種實現(xiàn)方法,是告訴zuul在使用Ribbon做負載均衡時柱蟀,直接在提供的server列表中獲取服務實例川蒙。觀察第二種方法的配置,可以看到添加了.ribbon.NIWSServerListClassName屬性长已。官方文檔是這樣說的:
使用上面的配置后畜眨,只啟動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加載配置文件(不然就要找找是哪里出錯了):
訪問zuul服務的/routes端點稚配,返回結果如下:
修改config-server服務管理的zuul服務的配置文件,添加如下配置:
zuul:
ignored-services: '*'
prefix: /api
routes:
organizationservice: /organization/**
licenseservice: /license/**
然后使用POST方式訪問http://localhost:555/refresh(記得要先重啟config-server服務)港华,可以看到返回如下:
證明zuul服務的配置信息中的上面4個配置的值已變更道川。再次訪問zuul服務的/routes,返回結果如下:
證明zuul已經動態(tài)加載配置成功了立宜。
有關Spring Cloud Zuul的入門教程就介紹到這里冒萄,后續(xù)會在進階教程中繼續(xù)介紹zuul真正強大的功能——filter。
完橙数!