SpringCloud系列之API網(wǎng)關(guān)(Gateway)服務(wù)Zuul
1、什么是API網(wǎng)關(guān)
API網(wǎng)關(guān)是所有請(qǐng)求的入口,承載了所有的流量,API Gateway是一個(gè)門戶一樣,也可以說(shuō)是進(jìn)入系統(tǒng)的唯一節(jié)點(diǎn)。這跟面向?qū)ο笤O(shè)計(jì)模式中的Facet模式很像。API Gateway封裝內(nèi)部系統(tǒng)的架構(gòu),并且提供API給各個(gè)客戶端。它還可能有其他功能,如授權(quán)、監(jiān)控、負(fù)載均衡、緩存、請(qǐng)求分片和管理、靜態(tài)響應(yīng)處理等
API Gateway負(fù)責(zé)請(qǐng)求轉(zhuǎn)發(fā)、合成和協(xié)議轉(zhuǎn)換。所有來(lái)自客戶端的請(qǐng)求都要先經(jīng)過(guò)API Gateway,然后路由這些請(qǐng)求到對(duì)應(yīng)的微服務(wù)。API Gateway將經(jīng)常通過(guò)調(diào)用多個(gè)微服務(wù)來(lái)處理一個(gè)請(qǐng)求以及聚合多個(gè)服務(wù)的結(jié)果。它可以在web協(xié)議與內(nèi)部使用的非Web友好型協(xié)議間進(jìn)行轉(zhuǎn)換,如 HTTP協(xié)議、WebSocket協(xié)議。
畫圖表示,沒(méi)有網(wǎng)關(guān)的情況,客戶端的請(qǐng)求會(huì)直接落到后端的各個(gè)服務(wù)中,無(wú)法集中統(tǒng)一管理。
畫圖表示,有網(wǎng)關(guān)的情況,所有的請(qǐng)求都先經(jīng)過(guò)網(wǎng)關(guān),然后進(jìn)行分發(fā)到對(duì)應(yīng)服務(wù)
2、API網(wǎng)關(guān)的重要性
API網(wǎng)關(guān)在微服務(wù)項(xiàng)目中是很重要的,網(wǎng)關(guān)提供一個(gè)統(tǒng)一的管理,服務(wù)間的調(diào)度變得有序
引用nginx官方的一篇優(yōu)質(zhì)博客,https://www.nginx.com/blog/building-microservices-using-an-api-gateway/,例子介紹了一個(gè)龐雜的電商系統(tǒng),按照微服務(wù)理論進(jìn)行設(shè)計(jì),有如下各種服務(wù):
購(gòu)物車服務(wù):購(gòu)物車中的物品數(shù)量
訂單服務(wù):訂單歷史記錄
目錄服務(wù):基本產(chǎn)品信息,例如其名稱,圖像和價(jià)格
審核服務(wù):客戶審核
庫(kù)存服務(wù):庫(kù)存不足警告
運(yùn)送服務(wù):運(yùn)送選項(xiàng),期限和費(fèi)用與運(yùn)送提供商的API分開提取
推薦服務(wù):建議項(xiàng)目
在不使用網(wǎng)關(guān)的情況,客戶端直接調(diào)用各服務(wù):
理想情況,各服務(wù)調(diào)用是可以正常使用的,但是隨著業(yè)務(wù)拓展,服務(wù)之間的調(diào)用越來(lái)越復(fù)雜,到時(shí)候系統(tǒng)就會(huì)變成如圖:
如果沒(méi)有一個(gè)統(tǒng)一的管理,肯定是不合理的,所以可以引入網(wǎng)關(guān),作為一個(gè)統(tǒng)一的門戶,如圖:
3、API Gateway的作用
ok,簡(jiǎn)單介紹網(wǎng)關(guān)之后,要說(shuō)說(shuō)網(wǎng)關(guān)的作用,在Spring cloud官網(wǎng)也有過(guò)歸納:
當(dāng)然,我們可以自己挑幾個(gè)重要的介紹
動(dòng)態(tài)路由
網(wǎng)關(guān)可以做路由轉(zhuǎn)發(fā),假如服務(wù)信息變了,只要改網(wǎng)關(guān)配置既可,所以說(shuō)網(wǎng)關(guān)有動(dòng)態(tài)路由(Dynamic Routing)的作用,如圖:
請(qǐng)求監(jiān)控
請(qǐng)求監(jiān)控可以對(duì)整個(gè)系統(tǒng)的請(qǐng)求進(jìn)行監(jiān)控,詳細(xì)地記錄請(qǐng)求響應(yīng)日志,如圖,可以將日志丟到消息隊(duì)列,如果沒(méi)有使用網(wǎng)關(guān)的話,記錄請(qǐng)求信息需要在各個(gè)服務(wù)中去做
認(rèn)證鑒權(quán)
認(rèn)證鑒權(quán)可以對(duì)每一個(gè)訪問(wèn)請(qǐng)求做認(rèn)證,拒絕非法請(qǐng)求,保護(hù)后端的服務(wù),不需要每個(gè)服務(wù)都做鑒權(quán),在項(xiàng)目中經(jīng)常有加上OAuth2.0、JWT,Spring Security進(jìn)行權(quán)限校驗(yàn)
壓力測(cè)試
有網(wǎng)關(guān)的系統(tǒng),如果要要對(duì)某個(gè)服務(wù)進(jìn)行壓力測(cè)試,可以如圖所示,改下網(wǎng)關(guān)配置既可,測(cè)試請(qǐng)求路由到測(cè)試服務(wù),測(cè)試服務(wù)會(huì)有單獨(dú)的測(cè)試數(shù)據(jù)庫(kù),這樣測(cè)試的請(qǐng)求就不會(huì)影響到正式的服務(wù)和數(shù)據(jù)庫(kù)
4、什么是Netflix Zuul?
Netflix Zuul是Netflix公司的產(chǎn)品,是一款A(yù)PI網(wǎng)關(guān)中間件。Zuul是一個(gè)基于 JVM 路由和服務(wù)端的負(fù)載均衡器。提供了路由、監(jiān)控、彈性、安全等服務(wù)。Zuul 能夠與 Eureka、Ribbon、Hystrix 等組件配合使用,提供統(tǒng)一的API網(wǎng)關(guān)處理
5、Netflix Zuul工作原理
參考Zuul官網(wǎng)wiki,Zuul的核心如圖其實(shí)就是過(guò)濾器,zuul基于Servlet實(shí)現(xiàn)。當(dāng)一個(gè)請(qǐng)求進(jìn)來(lái)時(shí),會(huì)先進(jìn)入 pre 過(guò)濾器,在 pre 過(guò)濾器執(zhí)行完后,接著就到了 routing 過(guò)濾器中,開始路由到具體的服務(wù)中,錯(cuò)誤的情況會(huì)被錯(cuò)誤過(guò)濾器攔截
過(guò)濾器類型:
前置過(guò)濾器(PRE FILTER):在路由過(guò)濾器之前執(zhí)行。功能可以包括請(qǐng)求身份驗(yàn)證,選擇原始服務(wù)器以及記錄調(diào)試信息。
路由過(guò)濾器(ROUTE FILTER):處理將請(qǐng)求路由到源的過(guò)程。這是使用Apache HttpClient或Netflix Ribbon構(gòu)建和發(fā)送原始HTTP請(qǐng)求的地方。
后置過(guò)濾器(POST FILTER):在將請(qǐng)求路由過(guò)濾器之后執(zhí)行。功能可以包括向響應(yīng)中添加標(biāo)準(zhǔn)HTTP標(biāo)頭,收集統(tǒng)計(jì)信息和指標(biāo)以及將響應(yīng)從源流傳輸?shù)娇蛻舳恕?/span>
錯(cuò)誤過(guò)濾器(ERR FILTER):在其他階段之一發(fā)生錯(cuò)誤時(shí)就會(huì)調(diào)用到錯(cuò)誤過(guò)濾器
6、Zuul實(shí)驗(yàn)環(huán)境準(zhǔn)備
環(huán)境準(zhǔn)備:
JDK 1.8
SpringBoot2.2.3
SpringCloud(Hoxton.SR6)
Maven 3.2+
開發(fā)工具
IntelliJ IDEA
smartGit
創(chuàng)建一個(gè)SpringBoot Initialize項(xiàng)目,詳情可以參考我之前博客:SpringBoot系列之快速創(chuàng)建項(xiàng)目教程


maven配置:
<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>
本博客的是基于spring-cloud-starter-netflix-eureka-client進(jìn)行試驗(yàn),試驗(yàn)前要運(yùn)行eureka服務(wù)端,eureka服務(wù)提供者,代碼請(qǐng)參考上一章博客
項(xiàng)目創(chuàng)建成功后,先在啟動(dòng)類加上@EnableZuulProxy:
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.zuul.EnableZuulProxy;@SpringBootApplication@EnableZuulProxypublic class SpringcloudZuulApplication {public static void main(String[] args) {SpringApplication.run(SpringcloudZuulApplication.class, args);}}
7、eureka、zuul配置
eureka客戶端配置:
server:port: 8082# 指定application name,這個(gè)是微服務(wù)注冊(cè)的serviceIdspring:application:name: zuul-api-gatewayeureka:client:# 服務(wù)注冊(cè)中心urlservice-url:defaultZone: http://localhost:8761/eureka/# 網(wǎng)關(guān)服務(wù)注冊(cè)、發(fā)現(xiàn)都開放,所以 register-with-eureka、fetch-registry都是trueregister-with-eureka: truefetch-registry: trueinstance:status-page-url-path: http://localhost:8761/actuator/infohealth-check-url-path: http://localhost:8761/actuator/healthprefer-ip-address: trueinstance-id: zuul-api-gateway8082
Zuul 配置路由規(guī)則:
zuul:routes:provider: # 路由標(biāo)識(shí),可以自己定義service-id: eureka-service-provider # 服務(wù)id(必須配置)path: /provider/** # 映射的路徑,一般和routes.provider一致url: http://localhost:8083 # 路由到的url,可以不配置
Zuul配置訪問(wèn)前綴:訪問(wèn)時(shí)候需要加上前綴,eg:http://localhost:8082/api-gateway/provider/api/users/mojombo
zuul:# 配置前綴prefix: /api-gateway
Zuul配置Header過(guò)濾:
zuul:# 配置過(guò)濾敏感的請(qǐng)求頭信息,設(shè)置為空就不會(huì)過(guò)濾sensitive-headers: Cookie,Set-Cookie,Authorization
Zuul配置重定向添加Host:
zuul:# 重定向會(huì)添加host請(qǐng)求頭add-proxy-headers: true
Zuul超時(shí)設(shè)置:
zuul:host:# 配置連接超時(shí)時(shí)間connect-timeout-millis: 15000# socker發(fā)送超時(shí)時(shí)間socket-timeout-millis: 60000
zuul所有配置參考,詳情參考官網(wǎng):
zuul:# 配置前綴prefix: /api-gatewayroutes:provider: # 路由標(biāo)識(shí),可以自己定義service-id: eureka-service-provider # 服務(wù)idpath: /provider/** # 映射的路徑,一般和routes.provider一致url: http://localhost:8083 # 路由到的urlhost:# 配置連接超時(shí)時(shí)間connect-timeout-millis: 15000# socker發(fā)送超時(shí)時(shí)間socket-timeout-millis: 60000# 請(qǐng)求url編碼decode-url: true# 查詢字符串編碼force-original-query-string-encoding: false# 配置過(guò)濾敏感的請(qǐng)求頭信息,設(shè)置為空就不會(huì)過(guò)濾sensitive-headers: Cookie,Set-Cookie,Authorization# 重定向會(huì)添加host請(qǐng)求頭add-proxy-headers: true
訪問(wèn):http://localhost:8082/api-gateway/provider/api/users/mojombo,要加上前綴,配置的path
可能遇到的錯(cuò)誤,504錯(cuò)誤:

經(jīng)過(guò)排查,需要加上超時(shí)設(shè)置,因?yàn)檎{(diào)用服務(wù)超時(shí),導(dǎo)致504錯(cuò)誤
zuul:host:connect-timeout-millis: 15000socket-timeout-millis: 60000
加上配置,調(diào)用服務(wù)成功
8、Zuul自定義過(guò)濾器
在前面的介紹中,已經(jīng)介紹了幾種過(guò)濾器,現(xiàn)在自定義實(shí)現(xiàn)這四種過(guò)濾器
ps:spring cloud官網(wǎng)也提供了zuul過(guò)濾器的例子,詳情可以去github查看:https://github.com/spring-cloud-samples/sample-zuul-filters
項(xiàng)目結(jié)構(gòu):

過(guò)濾器類型參考o(jì)rg.springframework.cloud.netflix.zuul.filters.supportFilterConstants.java:
實(shí)現(xiàn)一個(gè)前置過(guò)濾器:攔截請(qǐng)求,必須帶token過(guò)來(lái),不然拋出提示信息等等
package com.example.springcloud.zuul.web.filter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import lombok.extern.slf4j.Slf4j;import org.apache.http.HttpHeaders;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORWARD_TO_KEY;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;/*** <pre>* API網(wǎng)關(guān)預(yù)過(guò)濾器* </pre>** <pre>* @author mazq* 修改記錄* 修改后版本: 修改人:修改日期: 2020/08/05 18:08 修改內(nèi)容:* </pre>*/@Slf4j//@Componentpublic class ZuulApiGatewayPreFilter extends ZuulFilter {@Overridepublic String filterType() {return PRE_TYPE;}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() throws ZuulException {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();String accessToken = request.getParameter("token");if (StringUtils.isEmpty(accessToken)) {// zuul過(guò)濾該請(qǐng)求,不進(jìn)行路由ctx.setSendZuulResponse(false);// 設(shè)置返回的錯(cuò)誤碼ctx.setResponseStatusCode(403);ctx.setResponseBody("AccessToken is Invalid ");return null;}log.info("accessToken: {}",accessToken);// 否則業(yè)務(wù)繼續(xù)執(zhí)行return null;}}
后置過(guò)濾器,經(jīng)常被用于打印日志等等操作,代碼參考:https://www.baeldung.com/zuul-filter-modifying-response-body,實(shí)現(xiàn)效果時(shí),路由過(guò)濾器執(zhí)行之后,執(zhí)行后置過(guò)濾器打印日志:
package com.example.springcloud.zuul.web.filter;import com.google.common.io.CharStreams;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import lombok.extern.slf4j.Slf4j;import org.apache.http.protocol.RequestContent;import org.springframework.stereotype.Component;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE;import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;/*** <pre>* API Gateway后置過(guò)濾器* </pre>** <pre>* @author mazq* 修改記錄* 修改后版本: 修改人:修改日期: 2020/08/06 10:05 修改內(nèi)容:* </pre>*/@Slf4j//@Componentpublic class ZuulApiGatewayPostFilter extends ZuulFilter {@Overridepublic String filterType() {return POST_TYPE;}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() throws ZuulException {RequestContext context = RequestContext.getCurrentContext();try (final InputStream responseDataStream = context.getResponseDataStream()) {if(responseDataStream == null) {log.warn("RESPONSE BODY: {}", "");return null;}String responseData = CharStreams.toString(new InputStreamReader(responseDataStream, "UTF-8"));log.info("RESPONSE BODY: {}", responseData);context.setResponseBody(responseData);}catch (Exception e) {throw new ZuulException(e, INTERNAL_SERVER_ERROR.value(), e.getMessage());}return null;}}
注冊(cè)過(guò)濾器,將過(guò)濾器加載到Spring容器,也可以在過(guò)濾器類加上@Component
package com.example.springcloud.zuul;import com.example.springcloud.zuul.web.filter.ZuulApiGatewayErrFilter;import com.example.springcloud.zuul.web.filter.ZuulApiGatewayPostFilter;import com.example.springcloud.zuul.web.filter.ZuulApiGatewayPreFilter;import com.example.springcloud.zuul.web.filter.ZuulApiGatewayRouteFilter;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.zuul.EnableZuulProxy;import org.springframework.context.annotation.Bean;@SpringBootApplication@EnableZuulProxypublic class SpringcloudZuulApplication {public static void main(String[] args) {SpringApplication.run(SpringcloudZuulApplication.class, args);}@Beanpublic ZuulApiGatewayPreFilter zuulApiGatewayPreFilter(){return new ZuulApiGatewayPreFilter();}@Beanpublic ZuulApiGatewayPostFilter zuulApiGatewayPostFilter(){return new ZuulApiGatewayPostFilter();}@Beanpublic ZuulApiGatewayRouteFilter zuulApiGatewayRouteFilter(){return new ZuulApiGatewayRouteFilter();}@Beanpublic ZuulApiGatewayErrFilter zuulApiGatewayErrFilter(){return new ZuulApiGatewayErrFilter();}}
訪問(wèn)網(wǎng)關(guān):http://localhost:8082/api-gateway/provider/api/users/mojombo,不帶token的情況

http://localhost:8082/api-gateway/provider/api/users/mojombo?token=?,帶上token調(diào)用成功
9、查看Zuul路由信息
加上spring-boot-starter-actuator,進(jìn)行路由信息監(jiān)控:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
spring-boot-starter-actuator配置:
management:endpoints:web:exposure:# 默認(rèn)只支持info,health,開啟對(duì)routes的監(jiān)控include: info,health,routes# 開啟健康檢查詳細(xì)信息endpoint:health:show-details: always
查看路由詳細(xì),訪問(wèn)http://localhost:8082/actuator/routes,SpringBoot2.2.3版本要加上actuator前綴,調(diào)用成功,返回json數(shù)據(jù):
{"/api-gateway/provider/**":"eureka-service-provider","/api-gateway/eureka-service-provider/**":"eureka-service-provider"}
查看路由詳細(xì)信息,訪問(wèn)鏈接:http://localhost:8082/actuator/routes/details
{"/api-gateway/provider/**":{"id":"provider","fullPath":"/api-gateway/provider/**","location":"eureka-service-provider","path":"/**","prefix":"/api-gateway/provider","retryable":false,"customSensitiveHeaders":false,"prefixStripped":true},"/api-gateway/eureka-service-provider/**":{"id":"eureka-service-provider","fullPath":"/api-gateway/eureka-service-provider/**","location":"eureka-service-provider","path":"/**","prefix":"/api-gateway/eureka-service-provider","retryable":false,"customSensitiveHeaders":false,"prefixStripped":true}}
本博客代碼例子下載:code download
Zuul官網(wǎng)手冊(cè):
spring Cloud官網(wǎng)zuul資料:https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#router-and-filter-zuul
zuul github wiki:https://github.com/Netflix/zuul/wiki/How-it-Works
github zuul過(guò)濾器例子:https://github.com/spring-cloud-samples/sample-zuul-filters

騰訊、阿里、滴滴后臺(tái)面試題匯總總結(jié) — (含答案)
面試:史上最全多線程面試題 !
最新阿里內(nèi)推Java后端面試題
JVM難學(xué)?那是因?yàn)槟銢](méi)認(rèn)真看完這篇文章

關(guān)注作者微信公眾號(hào) —《JAVA爛豬皮》
了解更多java后端架構(gòu)知識(shí)以及最新面試寶典


看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者源源不斷出文的動(dòng)力
作者:SmileNicky
出處:https://www.cnblogs.com/mzq123/p/13447769.html
