Spring Cloud Zuul
?通過前幾章的介紹洲拇,我們對于Spring Cloud Netflix下的核心組件已經(jīng)了解了一大半奈揍。這些組件基本涵蓋了微服務(wù)架構(gòu)中最基礎(chǔ)的幾個核心設(shè)施,利用這些組件我們已經(jīng)可以構(gòu)建起一個簡單的微服務(wù)架構(gòu)系統(tǒng)赋续,比如男翰,通過使用Spring Cloud Eureka實(shí)現(xiàn)高可用的服務(wù)注冊中心以及實(shí)現(xiàn)微服務(wù)的注冊與發(fā)現(xiàn);通過Spring Cloud Eureka或Feign實(shí)現(xiàn)服務(wù)間負(fù)載均衡的接口調(diào)用纽乱;同時蛾绎,為了使分布式系統(tǒng)更為健壯,對于依賴的服務(wù)調(diào)用使用Spring Cloud Hystrix來進(jìn)行包裝鸦列,實(shí)現(xiàn)線程隔離并加入熔斷機(jī)制租冠,以避免在威武架構(gòu)中因個別服務(wù)出現(xiàn)異常而引起級聯(lián)故障蔓延。通過上述思路薯嗤,我們可以設(shè)計(jì)出類似下圖點(diǎn)基礎(chǔ)系統(tǒng)架構(gòu)顽爹。
?在該架構(gòu)中,我們的服務(wù)集群包含內(nèi)部服務(wù)Service A和Service B骆姐,它們都會向Eureka Server集群進(jìn)行注冊與訂閱服務(wù)镜粤,而Open Service是一個對外的RESTful API服務(wù),它通過F5诲锹、Nginx等網(wǎng)絡(luò)設(shè)備或工具軟件實(shí)現(xiàn)對各個微服務(wù)等路由與負(fù)載均衡,并公開給外部的客戶端調(diào)用涉馅。
?在本章中归园,我們將把視線聚焦在對外服務(wù)這塊內(nèi)容,通常也稱為邊緣服務(wù)稚矿。首先需要肯定的是庸诱,上面等架構(gòu)實(shí)現(xiàn)系統(tǒng)功能是完全沒有問題等捻浦,但是我們還是可以進(jìn)一步思考一下,這樣等架構(gòu)是否還有不足的地方會使運(yùn)維人員或開發(fā)人員感到痛苦桥爽。
?首先朱灿,我們從運(yùn)維人員的角度來看看,他們平時都需要做一些什么工作來支持這樣等架構(gòu)钠四。當(dāng)客戶端應(yīng)用單擊某個功能的路由和負(fù)載均衡分配后盗扒,被轉(zhuǎn)發(fā)到各個不同的服務(wù)實(shí)例上。而為了讓這些設(shè)施能夠正確路由和分發(fā)請求缀去,運(yùn)維人員需要手工維護(hù)這些路由規(guī)則與服務(wù)實(shí)例列表侣灶,當(dāng)有實(shí)例增減或是IP地址變動等情況發(fā)生的時候,也需要手工地去同步修改這些信息以保持實(shí)例信息與中間件配置內(nèi)容的一致性缕碎。再系統(tǒng)規(guī)模不大的時候褥影,維護(hù)這些信息的工作還不會太過復(fù)雜,但是如果當(dāng)系統(tǒng)規(guī)模不斷增大咏雌,那么這些看似簡單的維護(hù)任務(wù)會變得越來越難凡怎,并且出現(xiàn)配置錯誤的概率也會逐漸增加,那么這些看似簡單的維護(hù)任務(wù)會變得越來越難赊抖,并且出現(xiàn)配置錯誤的概率也會逐漸增加统倒。很顯然,這樣的做法并不可取熏迹,所以我們需要一套機(jī)制來有效降低維護(hù)路由規(guī)則與服務(wù)實(shí)例列表的難度檐薯。
?其次,我們再從開發(fā)人員的角度來看看注暗,再這樣的架構(gòu)下坛缕,會產(chǎn)生一些怎么樣的問題呢?大多數(shù)情況下捆昏,為了保證對外服務(wù)的安全性赚楚,我們再服務(wù)端實(shí)現(xiàn)的微服務(wù)接口,往往都會有一定的權(quán)限校驗(yàn)機(jī)制骗卜,比如對用戶登錄狀態(tài)的校驗(yàn)等宠页;同時為了防止客戶端在發(fā)起請求時被篡改等安全方面的考慮,還會有一些簽名校驗(yàn)等機(jī)制存在寇仓。這時候举户,由于使用了微服務(wù)架構(gòu)的理念,我們將原本處于一個應(yīng)用中等多個模塊拆分了多個應(yīng)用遍烦,但是這些應(yīng)用提供等接口都需要這些校驗(yàn)邏輯俭嘁,我們不得不在這些應(yīng)用中都實(shí)現(xiàn)這樣一套校驗(yàn)邏輯。隨著微服務(wù)規(guī)模的擴(kuò)大服猪,這些校驗(yàn)邏輯的冗余變得越來越多供填,突然有一天我們發(fā)現(xiàn)這套校驗(yàn)邏輯有個BUG需要修復(fù)拐云,或者需要對其做一些擴(kuò)展和優(yōu)化,此時我們就不得不去每個應(yīng)用里修改這些邏輯近她,而這樣等修改不僅會引起開發(fā)人員的抱怨叉瘩,更會加重測試人員的負(fù)擔(dān)。所以粘捎,我們也需要一套機(jī)制能夠很好地解決微服務(wù)架構(gòu)中薇缅,對于微服務(wù)接口訪問時各前置校驗(yàn)的冗余問題。
?為了解決上面這些常見的架構(gòu)問題晌端,API網(wǎng)關(guān)的概念應(yīng)運(yùn)而生捅暴。API網(wǎng)關(guān)是一個更為智能的應(yīng)用服務(wù)器,它等定義類似于面向?qū)ο笤O(shè)計(jì)模式中的Facade模式咧纠,它的存在就像是整個微服務(wù)架構(gòu)系統(tǒng)的門面一樣蓬痒,所有的外部客戶端訪問都需要經(jīng)過它來進(jìn)行調(diào)度和過濾。它除了要實(shí)現(xiàn)請求路由漆羔、負(fù)載均衡梧奢、校驗(yàn)過濾等功能之外,還需要更多能力,比如與服務(wù)治理框架的結(jié)合、請求轉(zhuǎn)發(fā)時的熔斷機(jī)制捌袜、服務(wù)的聚合等一系列高級功能呐矾。
?既然API網(wǎng)關(guān)對于微服務(wù)架構(gòu)這么重要饭耳,那么在Spring Cloud中是否有相應(yīng)的解決方案呢?答案是很肯定的,Spring Cloud提供了基于Netflix Zuul實(shí)現(xiàn)的API網(wǎng)關(guān)組件---Spring Cloud Zuul。那么蹦锋,它是如何解決上面這兩個普通問題的呢?
?首先欧芽,對于路由規(guī)則與服務(wù)實(shí)例的維護(hù)問題莉掂。Spring Cloud Zuul通過與Spring Cloud Eureka進(jìn)行整合,將自身注冊為Eureka服務(wù)治理下的應(yīng)用千扔,同時從Eureka中獲得了所有其他微服務(wù)的實(shí)例信息憎妙。這樣的設(shè)計(jì)非常巧妙地將服務(wù)治理體系中維護(hù)的實(shí)例信息利用起來,使得將維護(hù)服務(wù)實(shí)例的工作交給了服務(wù)治理框架自動完成曲楚,不再需要人工介入厘唾。而對大部分情況下,這樣的默認(rèn)設(shè)置已經(jīng)可以實(shí)現(xiàn)我們大部分的路由需求龙誊,除了一些特殊情況(比如兼容一些老的URL)還需要做一些特別的配置抚垃。但是相比于之前架構(gòu)下的運(yùn)維工作量,通過引入Spring Cloud Zuul實(shí)現(xiàn)API網(wǎng)關(guān)后,已經(jīng)能夠大大減少了讯柔。
?其次,對于類似簽名校驗(yàn)护昧、登錄校驗(yàn)在微服務(wù)架構(gòu)中的冗余問題魂迄。理論上來說,這些校驗(yàn)邏輯在本質(zhì)上與微服務(wù)應(yīng)用自身的業(yè)務(wù)并沒有多大的關(guān)系惋耙,所以它們完全可以獨(dú)立成一個單獨(dú)的服務(wù)存在捣炬,只是它們被剝離和獨(dú)立出來之后,并不是給各個微服務(wù)調(diào)用绽榛,而是在API網(wǎng)關(guān)服務(wù)上進(jìn)行統(tǒng)一調(diào)用來對微服務(wù)接口前置過濾湿酸,以實(shí)現(xiàn)對微服務(wù)接口的攔截和校驗(yàn)。Spring Cloud Zuul提供了一套過濾機(jī)制灭美,它可以很好地支持這樣的任務(wù)推溃。開發(fā)者可以通過使用Zuul來創(chuàng)建各種校驗(yàn)過濾器,然后指定哪些規(guī)則的請求需要執(zhí)行校驗(yàn)邏輯届腐,只有通過校驗(yàn)的才會被路由到具體的微服務(wù)接口铁坎,不然就返回錯誤提示。通過這樣的改造犁苏,各個業(yè)務(wù)層的微服務(wù)應(yīng)用就不用需要業(yè)務(wù)性質(zhì)的校驗(yàn)邏輯了硬萍,這使得我們的微服務(wù)應(yīng)用可以更專注于業(yè)務(wù)邏輯的開發(fā),同時微服務(wù)的自動化測試也變得更容易實(shí)現(xiàn)围详。
?微服務(wù)架構(gòu)雖然可以將我們的開發(fā)單元拆分得更為細(xì)致朴乖,有效降低了開發(fā)難度,但是它所引出的各種問題如果處理不當(dāng)會成為實(shí)施過程中的不穩(wěn)定因素助赞,甚至掩蓋原本實(shí)施微服務(wù)帶來的優(yōu)勢买羞。所以,在微服務(wù)架構(gòu)的實(shí)施方案中嫉拐,API網(wǎng)關(guān)服務(wù)的使用幾乎成為了必然的選擇哩都。
?下面我們將詳細(xì)介紹Spring Cloud Zuul的使用方法、配置屬性以及一些不足之處和需要進(jìn)行的思考婉徘。
快速入門
?介紹了這么多關(guān)于API網(wǎng)關(guān)服務(wù)的概念和作用漠嵌,在這一節(jié)中,我們不妨用實(shí)際多示例來直觀地體驗(yàn)一下Spring Cloud Zuul中封裝的API網(wǎng)關(guān)是如何使用和運(yùn)作盖呼,并應(yīng)用到微服務(wù)架構(gòu)中去的儒鹿。
構(gòu)建網(wǎng)關(guān)
?首先,在實(shí)現(xiàn)各種API網(wǎng)關(guān)服務(wù)的高級功能之前几晤,我們需要做一些準(zhǔn)備工作约炎,比如,構(gòu)建起最基本的API網(wǎng)關(guān)服務(wù),并且搭建幾個用于路由和過濾使用的微服務(wù)應(yīng)用等圾浅。對于微服務(wù)應(yīng)用掠手,我們可以直接使用之前章節(jié)實(shí)現(xiàn)的hello-service-provider和kyle-feign-service。雖然之前我們一直將kyle-feign-service視為消費(fèi)者狸捕,但是在Eureka的服務(wù)注冊與發(fā)現(xiàn)體系中喷鸽,每個服務(wù)既是提供者也是消費(fèi)者,所以kyle-feign-service實(shí)質(zhì)上也是一個服務(wù)提供者灸拍。之前我們訪問http://localhost:8868/client/getHost?name=kyle等一系列接口就是它提供的服務(wù)。讀者也可以使用自己實(shí)現(xiàn)的微服務(wù)應(yīng)用鸡岗,因?yàn)檫@部分不是本章的重點(diǎn)声登,任何微服務(wù)應(yīng)用都可以被用來進(jìn)行后續(xù)的試驗(yàn)舒岸。這里蛾派,我們詳細(xì)介紹一下API網(wǎng)關(guān)服務(wù)的構(gòu)建過程俄认。
???創(chuàng)建一個基礎(chǔ)的Spring Boot工程,命名為api-gateway-zuul洪乍,并在pom.xml中引入spring-cloud-starter-zuul依賴眯杏,具體如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.5.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
?對于spring-cloud-starter-zuul依賴,可以通過查看它的依賴內(nèi)容了解到:該模塊中不僅包含了Netflix Zuul的核心依賴zuul-core壳澳,它還包含了下面這些網(wǎng)關(guān)服務(wù)需要到重要依賴岂贩。
- pring-cloud-starter-hystrix:該依賴用來在網(wǎng)關(guān)服務(wù)中實(shí)現(xiàn)對微服務(wù)轉(zhuǎn)發(fā)時候到保護(hù)機(jī)制,通過線程隔離和斷路器巷波,防止微服務(wù)到故障引發(fā)API網(wǎng)關(guān)資源無法釋放萎津,從而影響其他應(yīng)用到對外服務(wù)。
- spring-cloud-starter-ribbon:該依賴用來實(shí)現(xiàn)在網(wǎng)關(guān)進(jìn)行路由轉(zhuǎn)發(fā)到時候的客戶端負(fù)載均衡以及請求重試抹镊。
- spring-cloud-starter-actuator:該依賴用來提供常規(guī)的微服務(wù)管理端點(diǎn)锉屈。另外,在Spring Cloud Zuul中還特別提供了/routes端點(diǎn)來返回當(dāng)前到所有路由規(guī)則垮耳。
???創(chuàng)建應(yīng)用主類颈渊,使用@EnableZuulProxy注解開啟Zuul的API網(wǎng)關(guān)服務(wù)功能遂黍。
/**
* 反向代理,俊嗽,eureka客戶端 api網(wǎng)關(guān)zuul
*
* @version
* @author kyle 2018年8月6日上午11:06:42
* @since 1.8
*/
@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
@RibbonClient(value = "hello-service-provider")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
???在application.properties中配置Zuul應(yīng)用的基礎(chǔ)信息雾家,如應(yīng)用名、服務(wù)端口號绍豁,具體內(nèi)容如下:
#spring.application.name=api-gateway-zuul
eureka.instance.appname=api-gateway-zuul
eureka.instance.virtualHostName=api-gateway-zuul
eureka.instance.secureVirtualHostName=api-gateway-zuul
server.port=8869
#將自己注冊到eureka注冊中心榜贴,單節(jié)點(diǎn)關(guān)閉
eureka.client.registerWithEureka=true
#從注冊中心獲取注冊信息,單節(jié)點(diǎn)關(guān)閉
eureka.client.fetchRegistry=true
#從注冊中心獲取注冊信息的時間間隔
eureka.client.registryFetchIntervalSeconds=10
#非安全通信端口
#eureka.instance.nonSecurePort=80
#是否啟用非安全端口接受請求
#eureka.instance.nonSecurePortEnabled=true
#安全通信端口
#eureka.instance.securePort=443
#是否啟用安全端口接受請求
#eureka.instance.securePortEnabled=true
#是否優(yōu)先使用IP地址作為主機(jī)名的標(biāo)識妹田,默認(rèn)false
#eureka.instance.preferIpAddress=false
#eureka節(jié)點(diǎn)定時續(xù)約時間,默認(rèn)30
eureka.instance.leaseRenewalIntervalInSeconds=15
#eureka節(jié)點(diǎn)剔除時間鹃共,默認(rèn)90
eureka.instance.leaseExpirationDurationInSeconds=45
#從注冊中心獲取注冊信息的時間間隔
eureka.client.registryFetchIntervalSeconds=5
eureka.client.eureka-server-connect-timeout-seconds=15
eureka.client.eureka-server-read-timeout-seconds=10
eureka.instance.instance-id=${spring.cloud.client.ipAddress}:ribbon-service-provider-peer:${server.port}
#注冊到另外兩個節(jié)點(diǎn)鬼佣,實(shí)現(xiàn)集群
eureka.client.serviceUrl.defaultZone=http://localhost:8887/eureka/,http://localhost:8888/eureka/,http://localhost:8889/eureka/
logging.level.com.kyle.client.feign.inter.HelloServiceFeign=DEBUG
# 設(shè)置IO線程數(shù), 它主要執(zhí)行非阻塞的任務(wù),它們會負(fù)責(zé)多個連接, 默認(rèn)設(shè)置每個CPU核心一個線程
server.undertow.io-threads=19
# 阻塞任務(wù)線程池, 當(dāng)執(zhí)行類似servlet請求阻塞操作, undertow會從這個線程池中取得線程,它的值設(shè)置取決于系統(tǒng)的負(fù)載
server.undertow.worker-threads=20
# 以下的配置會影響buffer,這些buffer會用于服務(wù)器連接的IO操作,有點(diǎn)類似netty的池化內(nèi)存管理
server.undertow.buffer-size=1024
# 每個區(qū)分配的buffer數(shù)量 , 所以pool的大小是buffer-size * buffers-per-region
server.undertow.buffers-per-region=2048
# 是否分配的直接內(nèi)存
server.undertow.direct-buffers=true
#最大分區(qū)數(shù)量
server.undertow.max-regions=10
#socket-binding="http",保持長連接
server.undertow.always-set-keep-alive=true
#開啟hystrix容錯霜浴,默認(rèn)是不開啟的晶衷,目前應(yīng)用還未加入容錯機(jī)制
#feign.hystrix.enabled=true
hystrix.command.default.execution.timeout.enabled=true
#設(shè)置hystrix超時時間
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
#熔斷配置
#熔斷窗口時間的標(biāo)本數(shù)量
#hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
#熔斷時間
#hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
#容器窗口的錯誤占比,百分制
#hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
#熔斷窗口時間
#hystrix.command.default.metrics.rollingStats.timeInMilliseconds=10000
#hystrix窗口期內(nèi)監(jiān)控上報(bào)的并發(fā)上限
#hystrix.command.default.metrics.rollingPercentile.bucketSize=100
#重試機(jī)制開啟為true阴孟,關(guān)閉為false
spring.cloud.loadbalancer.retry.enabled=true
ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.ZoneAvoidanceRule
#以下配置全局有效
ribbon.eureka.enabled=true
#建立連接超時時間晌纫,原1000
ribbon.ConnectTimeout=60000
#請求處理的超時時間,5分鐘
ribbon.ReadTimeout=60000
#所有操作都重試
ribbon.OkToRetryOnAllOperations=true
#重試發(fā)生永丝,更換節(jié)點(diǎn)數(shù)最大值
ribbon.MaxAutoRetriesNextServer=10
#單個節(jié)點(diǎn)重試最大值
ribbon.MaxAutoRetries=1
#zuul.okhttp.enabled=true
zuul.semaphore.max-semaphores=500
#zuul路由最大連接數(shù)
zuul.host.maxTotalConnections=200
#每個路由最大線程數(shù)
zuul.host.maxPerRouteConnections=1000
zuul.host.max-per-route-connections=5
zuul.host.socket-timeout-millis=60000
zuul.host.connect-timeout-millis=60000
?完成上面的工作后锹漱,通過Zuul實(shí)現(xiàn)的API網(wǎng)關(guān)服務(wù)就構(gòu)建完畢了。
面向服務(wù)的路由
?很顯然慕嚷,傳統(tǒng)路由的配置方式對于我們來說并不友好哥牍,它同樣需要運(yùn)維人員花費(fèi)時間來維護(hù)各個路由path與url的關(guān)系。配置如下:
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.url=http://localhost:8080/
?為了解決這個問題喝检,Spring Cloud Zuul實(shí)現(xiàn)了與Spring Cloud Eureka的無縫整合嗅辣,我們可以讓路由的path不是映射具體的url,而是讓它映射到某個具體的服務(wù)挠说,具體的url則交給Eureka的服務(wù)發(fā)現(xiàn)機(jī)制去自動維護(hù)澡谭,我們稱這類路由為面向服務(wù)的路由。在Zuul中使用服務(wù)路由也同樣簡單损俭,只需做下面這些配置蛙奖。
- 為了與Eureka整合,我們需要在pom.xml中引入spring-cloud-starter-eureka依賴杆兵,具體如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
- 在api-gateway-zuul的application.properties配置文件中指定Eureka注冊中心的位置外永,并且配置服務(wù)路由。具體如下:
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-service-provider
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=feign-service-provider
eureka.client.serviceUrl.defaultZone=http://localhost:8887/eureka/,http://localhost:8888/eureka/,http://localhost:8889/eureka/
?針對我們之前準(zhǔn)備的兩個微服務(wù)應(yīng)用hello-service-provider和feign-service-provider拧咳,在上面的配置中分別定義了兩個名為api-a和api-b的路由來映射它們伯顶。另外,通過指定Eureka Server服務(wù)注冊中心的位置,除了將自己注冊成服務(wù)之外祭衩,同時也讓Zuul能夠獲取ello-service-provider和feign-service-provider服務(wù)的實(shí)例清單灶体,以實(shí)現(xiàn)path的映射服務(wù),再從服務(wù)中挑選實(shí)例來進(jìn)行請求轉(zhuǎn)發(fā)單完整路由機(jī)制掐暮。
?在完成了上面單服務(wù)路由配置之后蝎抽,我們可以將eureka-server、hello-service-provider和feign-service-provider以及這里用Spring Cloud Zuul構(gòu)建的api-gateway-zuul都啟動起來路克。啟動完畢樟结,在eureka-server的信息面板中,我們也可以看到精算,多了一個網(wǎng)關(guān)服務(wù)API-GATEWAY-ZUUL瓢宦。
?通過上面的搭建工作,我們已經(jīng)可以通過服務(wù)網(wǎng)關(guān)來訪問hello-service-provider和feign-service-provider這兩個服務(wù)了灰羽。根據(jù)配置單映射關(guān)系驮履,分別向網(wǎng)關(guān)發(fā)起下面這些請求。
- http://localhost:8869/api-a/demo/getHost?name=chandler:該url符合/api-a/**規(guī)則廉嚼,由api-a路由請求轉(zhuǎn)發(fā)玫镐,該路由映射單serviceId為hello-service-provider,所以最終hello請求會被轉(zhuǎn)發(fā)到hello-service-provider服務(wù)單某個實(shí)例上去怠噪。
- http://localhost:8869/api-b/feign/getHost?name=kyle:該url符合/api-b/**規(guī)則恐似,由api-b路由負(fù)責(zé)轉(zhuǎn)發(fā),該路由映射單serviceId為feign-service-provider傍念,所以最終/feign/getHost?name=kyle請求會被發(fā)送到feign-service-provider服務(wù)單某個實(shí)例上去蹂喻。
?通過面向服務(wù)單路由配置方式,我們不需要再為各個路由維護(hù)微服務(wù)應(yīng)用的具體實(shí)例的位置捂寿,而是通過簡單的path與serviceId的映射組合口四,使得維護(hù)工作變得非常簡單。這完全歸功于Spring Cloud Eureka的服務(wù)發(fā)現(xiàn)機(jī)制秦陋,它使得API網(wǎng)關(guān)可以自動化完成服務(wù)實(shí)例清單的維護(hù)蔓彩,完美地解決了路由映射實(shí)例單維護(hù)問題。
測試結(jié)果補(bǔ)充
#api-gateway-zuul控臺輸出
2018-08-06 14:24:05.250 INFO 2587 --- [ XNIO-2 task-1] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/api-b/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2018-08-06 14:24:05.250 INFO 2587 --- [ XNIO-2 task-1] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/api-a/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2018-08-06 14:24:05.250 INFO 2587 --- [ XNIO-2 task-1] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/hello-service-provider/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2018-08-06 14:24:05.250 INFO 2587 --- [ XNIO-2 task-1] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/eureka-server/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2018-08-06 14:24:05.250 INFO 2587 --- [ XNIO-2 task-1] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/feign-service-provider/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2018-08-06 14:24:05.300 INFO 2587 --- [ XNIO-2 task-1] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4d7493a3: startup date [Mon Aug 06 14:24:05 CST 2018]; parent: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5629510
2018-08-06 14:24:05.346 INFO 2587 --- [ XNIO-2 task-1] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-08-06 14:24:05.493 INFO 2587 --- [ XNIO-2 task-1] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-08-06 14:24:05.510 INFO 2587 --- [ XNIO-2 task-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-hello-service-provider
2018-08-06 14:24:05.536 INFO 2587 --- [ XNIO-2 task-1] c.netflix.loadbalancer.BaseLoadBalancer : Client:hello-service-provider instantiated a LoadBalancer:DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service-provider,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2018-08-06 14:24:05.540 INFO 2587 --- [ XNIO-2 task-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2018-08-06 14:24:05.564 INFO 2587 --- [ XNIO-2 task-1] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-08-06 14:24:05.566 INFO 2587 --- [ XNIO-2 task-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client hello-service-provider initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service-provider,current list of Servers=[10.166.37.142:8877],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:10.166.37.142:8877; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@3f516e95
2018-08-06 14:24:06.544 INFO 2587 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-08-06 14:24:33.652 INFO 2587 --- [ XNIO-2 task-2] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5e817cb7: startup date [Mon Aug 06 14:24:33 CST 2018]; parent: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5629510
2018-08-06 14:24:33.686 INFO 2587 --- [ XNIO-2 task-2] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-08-06 14:24:33.741 INFO 2587 --- [ XNIO-2 task-2] c.netflix.config.ChainedDynamicProperty : Flipping property: feign-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-08-06 14:24:33.746 INFO 2587 --- [ XNIO-2 task-2] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-feign-service-provider
2018-08-06 14:24:33.747 INFO 2587 --- [ XNIO-2 task-2] c.netflix.loadbalancer.BaseLoadBalancer : Client:feign-service-provider instantiated a LoadBalancer:DynamicServerListLoadBalancer:{NFLoadBalancer:name=feign-service-provider,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2018-08-06 14:24:33.748 INFO 2587 --- [ XNIO-2 task-2] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2018-08-06 14:24:33.749 INFO 2587 --- [ XNIO-2 task-2] c.netflix.config.ChainedDynamicProperty : Flipping property: feign-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-08-06 14:24:33.750 INFO 2587 --- [ XNIO-2 task-2] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client feign-service-provider initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=feign-service-provider,current list of Servers=[10.166.37.142:8868],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:10.166.37.142:8868; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@65451b8c
2018-08-06 14:24:34.751 INFO 2587 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: feign-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
請求過濾
?在實(shí)現(xiàn)了請求路由功能之后驳概,我們的微服務(wù)應(yīng)用提供的接口就可以通過統(tǒng)一的API網(wǎng)關(guān)入口被客戶端訪問到了赤嚼。但是,每個客戶端用戶請求微服務(wù)的應(yīng)用提供的接口時顺又,它們的訪問權(quán)限往往都有一定的限制更卒,系統(tǒng)并不會將所有的微服務(wù)接口都對它們開發(fā)。然而稚照,目前的服務(wù)路由并沒有限制權(quán)限這樣的功能蹂空,所有請求都會毫無保留地轉(zhuǎn)發(fā)到具體的應(yīng)用并返回結(jié)果俯萌,為了實(shí)現(xiàn)對客戶端請求的安全校驗(yàn)和權(quán)限控制,最簡單和粗暴的方法就是為每個微服務(wù)應(yīng)用都實(shí)現(xiàn)一套用于校驗(yàn)簽名和鑒別權(quán)限的過濾器或攔截器上枕。不過咐熙,這樣的做法并不可取,它會增加日后系統(tǒng)的維護(hù)難度辨萍,因?yàn)橥粋€系統(tǒng)的各種校驗(yàn)邏輯很多情況下都是大致相同或類似的棋恼,這樣的實(shí)現(xiàn)方式使得相似的校驗(yàn)邏輯代碼被分散到了各個微服務(wù)中去,冗余代碼的出現(xiàn)是我們不希望看到的锈玉。所以爪飘,比較好的做法是將這些校驗(yàn)邏輯剝離出去,構(gòu)建出一個獨(dú)立的鑒權(quán)服務(wù)拉背。在完成剝離之后师崎,有不少開發(fā)者會直接再微服務(wù)應(yīng)用中通過調(diào)用鑒權(quán)服務(wù)來實(shí)現(xiàn)校驗(yàn),但是這樣的做法僅僅只是解決了權(quán)限邏輯的分離去团,并沒有在本質(zhì)上將這部分不屬于冗余的邏輯從原有的微服務(wù)應(yīng)用中拆分出,冗余的攔截器或過濾器依然會存在穷蛹。
?對于這樣的問題土陪,更好的做法是通過前置的網(wǎng)關(guān)服務(wù)來完成這些非業(yè)務(wù)性質(zhì)的校驗(yàn)。由于網(wǎng)關(guān)服務(wù)的假如肴熏,外部客戶端訪問我們的系統(tǒng)已經(jīng)有了統(tǒng)一入口鬼雀,既然這些校驗(yàn)與具體業(yè)務(wù)無關(guān),那何不在請求到達(dá)的時候就完成校驗(yàn)和過濾蛙吏,而不是轉(zhuǎn)發(fā)后再過濾而導(dǎo)致更長的請求延遲源哩。同時,通過在網(wǎng)關(guān)中完成校驗(yàn)和過濾鸦做,微服務(wù)應(yīng)用端就可以去除各種復(fù)雜的過濾器和攔截器了励烦,這使得微服務(wù)應(yīng)用接口的開發(fā)和測試復(fù)雜度得到了相應(yīng)降低。
?為了再API網(wǎng)關(guān)中實(shí)現(xiàn)對客戶端請求的校驗(yàn)泼诱,我們將繼續(xù)介紹Spring Cloud Zuul的另一個核心功能:請求過濾坛掠。Zuul允許開發(fā)者在API網(wǎng)關(guān)上通過定義過濾器來實(shí)現(xiàn)對請求的攔截與過濾,實(shí)現(xiàn)的方法非常簡單治筒,我們之需要繼承ZuulFilter抽象類并實(shí)現(xiàn)它定義的4個抽象函數(shù)就可以完成對請求的攔截和過濾了屉栓。
?下面的代碼定義了一個簡單的Zuul過濾器,它實(shí)現(xiàn)了在請求被路由之前檢查HttpServletRequest中是否有accessToken參數(shù)耸袜,若有就進(jìn)行路由友多,若沒有就拒絕訪問,返回401 Unauthorized錯誤堤框。
/**
* 攔截器
*
* @version
* @author kyle 2018年8月6日下午3:23:53
* @since 1.8
*/
public class AccessFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(AccessFilter.class);
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
logger.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());
Object accessToken = request.getParameter("accessToken");
if (accessToken == null) {
logger.warn("access token is empty");
context.setSendZuulResponse(false);
context.setResponseStatusCode(401);
return null;
}
logger.info("access token ok");
return null;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
}
?在上面實(shí)現(xiàn)都過濾器代碼中域滥,我們通過繼承ZuulFilter抽象類并重寫下面4個方法來實(shí)現(xiàn)自定義都過濾器纵柿。這4個方法分別定義了如下內(nèi)容。
- filterType:過濾器都類型骗绕,它決定過濾器在請求都哪一個生命周期中執(zhí)行藐窄。這里定義為pre,代表會在請求被路由之前執(zhí)行酬土。
- filterOrder:過濾器的執(zhí)行順序荆忍。當(dāng)請求在一個階段中存在多個過濾器時,需要根據(jù)該方法返回都值來依次執(zhí)行撤缴。
- shouldFilter:判斷該過濾器是否需要被執(zhí)行刹枉。這里我們直接返回了true,因此過濾器對所有請求都會生效屈呕。實(shí)際運(yùn)用中我們可以利用該函數(shù)來指定過濾器的有效范圍微宝。
- run:過濾器的具體邏輯。這里我們通過context.setSendZuulResponse(false)令zuul過濾該請求虎眨,不對其進(jìn)行路由蟋软,然后也可以進(jìn)一步優(yōu)化我們的返回,比如嗽桩,通過context.setResponseBody(body)對返回的body內(nèi)容進(jìn)行編輯等岳守。
?在實(shí)現(xiàn)了自定義過濾器之后,它并不會直接生效碌冶,我們還需要為其創(chuàng)建具體的Bean才能啟動該過濾器湿痢,比如,在應(yīng)用啟動類中增加如下內(nèi)容:
/**
* 反向代理扑庞,譬重,eureka客戶端 api網(wǎng)關(guān)zuul
*
* @version
* @author kyle 2018年8月6日上午11:06:42
* @since 1.8
*/
@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
@RibbonClient(value = "hello-service-provider")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}
?當(dāng)然你也可以在AccessFilter使用@Component掃描加載成組件。在對api-gateway-zuul服務(wù)完成了上面的改造之后罐氨,我們可以重新啟動它臀规,并發(fā)起下面的請求,對上面定義的過濾器做一個驗(yàn)證栅隐。
- http://localhost:8869/api-a/demo/getHost?name=chandler:返回401錯誤以现。
- http://localhost:8869/api-a/demo/getHost?name=chandler&accessToken-token:正確路由到hello-service-provider的/demo/getHost接口,并返回結(jié)果约啊。
補(bǔ)充測試結(jié)果
#api-gateway-zuul控臺輸出
2018-08-06 16:03:43.618 INFO 3786 --- [ XNIO-2 task-3] com.kyle.api.zuul.filter.AccessFilter : send GET request to http://localhost:8869/api-a/demo/getHost
2018-08-06 16:03:43.619 WARN 3786 --- [ XNIO-2 task-3] com.kyle.api.zuul.filter.AccessFilter : access token is empty
2018-08-06 16:04:05.270 INFO 3786 --- [ XNIO-2 task-4] com.kyle.api.zuul.filter.AccessFilter : send GET request to http://localhost:8869/api-a/demo/getHost
2018-08-06 16:04:05.270 INFO 3786 --- [ XNIO-2 task-4] com.kyle.api.zuul.filter.AccessFilter : access token ok
2018-08-06 16:04:05.271 INFO 3786 --- [ XNIO-2 task-4] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f8fd98f: startup date [Mon Aug 06 16:04:05 CST 2018]; parent: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@737a135b
2018-08-06 16:04:05.294 INFO 3786 --- [ XNIO-2 task-4] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-08-06 16:04:05.352 INFO 3786 --- [ XNIO-2 task-4] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-08-06 16:04:05.356 INFO 3786 --- [ XNIO-2 task-4] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-hello-service-provider
2018-08-06 16:04:05.357 INFO 3786 --- [ XNIO-2 task-4] c.netflix.loadbalancer.BaseLoadBalancer : Client:hello-service-provider instantiated a LoadBalancer:DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service-provider,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2018-08-06 16:04:05.358 INFO 3786 --- [ XNIO-2 task-4] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2018-08-06 16:04:05.372 INFO 3786 --- [ XNIO-2 task-4] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-08-06 16:04:05.373 INFO 3786 --- [ XNIO-2 task-4] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client hello-service-provider initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service-provider,current list of Servers=[10.166.37.142:8877],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:10.166.37.142:8877; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@18210fda
2018-08-06 16:04:06.364 INFO 3786 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: hello-service-provider.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
?到這里對于API網(wǎng)關(guān)服務(wù)的快速入門示例就完成了邑遏。通過對Spring Cloud Zuul兩個核心功能的介紹,相信讀者已經(jīng)能夠體會到API網(wǎng)關(guān)服務(wù)對微服務(wù)架構(gòu)到重要性了恰矩,就目前掌握到API網(wǎng)關(guān)知識记盒,我們可以將具體原因總結(jié)如下:
- 它作為系統(tǒng)到統(tǒng)一入口,屏蔽了系統(tǒng)內(nèi)部各個微服務(wù)的細(xì)節(jié)外傅。
- 它可以與服務(wù)治理框架結(jié)合纪吮,實(shí)現(xiàn)自動化的服務(wù)實(shí)例維護(hù)以及負(fù)載均衡陸游轉(zhuǎn)發(fā)俩檬。
- 它可以實(shí)現(xiàn)接口權(quán)限校驗(yàn)與微服務(wù)業(yè)務(wù)邏輯到解耦。
- 通過服務(wù)網(wǎng)關(guān)中到過濾器碾盟,在各生命周期中去校驗(yàn)請求到內(nèi)容棚辽,將原本在對外服務(wù)層做到校驗(yàn)前移,保證了微服務(wù)的無狀態(tài)性冰肴,同時降低了微服務(wù)到測試難度屈藐,讓服務(wù)本身集中關(guān)注業(yè)務(wù)邏輯的處理。
?實(shí)際上熙尉,基于Spring Cloud Zuul實(shí)現(xiàn)到API網(wǎng)關(guān)服務(wù)除了上面所示的優(yōu)點(diǎn)之外联逻,它還有一些更加強(qiáng)大到功能,我們將在后面對其進(jìn)行更深入的介紹检痰。通過本節(jié)的內(nèi)容包归,我們只是希望以一個簡單到例子帶領(lǐng)大家先來簡單認(rèn)識一下API網(wǎng)關(guān)服務(wù)提供的基礎(chǔ)功能以及它在微服務(wù)架構(gòu)中的重要地位。下一篇博客铅歼,我將詳解網(wǎng)關(guān)zuul的技術(shù)細(xì)節(jié)公壤。
如果需要給我修改意見的發(fā)送郵箱:erghjmncq6643981@163.com
本博客的代碼示例已上傳GitHub:Spring Cloud Netflix組件入門
資料參考:《Spring Cloud 微服務(wù)實(shí)戰(zhàn)》
轉(zhuǎn)發(fā)博客,請注明椎椰,謝謝厦幅。