1兔乞、什么是API網(wǎng)關(guān)
API網(wǎng)關(guān)是所有請求的入口,承載了所有的流量,API Gateway是一個(gè)門戶一樣米间,也可以說是進(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ù)載均衡蜘澜、緩存、請求分片和管理趟章、靜態(tài)響應(yīng)處理等
API Gateway負(fù)責(zé)請求轉(zhuǎn)發(fā)、合成和協(xié)議轉(zhuǎn)換。所有來自客戶端的請求都要先經(jīng)過API Gateway,然后路由這些請求到對應(yīng)的微服務(wù)。API Gateway將經(jīng)常通過調(diào)用多個(gè)微服務(wù)來處理一個(gè)請求以及聚合多個(gè)服務(wù)的結(jié)果。它可以在web協(xié)議與內(nèi)部使用的非Web友好型協(xié)議間進(jìn)行轉(zhuǎn)換胆数,如 HTTP協(xié)議胰伍、WebSocket協(xié)議渗饮。
畫圖表示,沒有網(wǎng)關(guān)的情況,客戶端的請求會直接落到后端的各個(gè)服務(wù)中,無法集中統(tǒng)一管理斤富。
畫圖表示,有網(wǎng)關(guān)的情況,所有的請求都先經(jīng)過網(wǎng)關(guān)校摩,然后進(jìn)行分發(fā)到對應(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ù):
- 購物車服務(wù):購物車中的物品數(shù)量
- 訂單服務(wù):訂單歷史記錄
- 目錄服務(wù):基本產(chǎn)品信息,例如其名稱,圖像和價(jià)格
- 審核服務(wù):客戶審核
- 庫存服務(wù):庫存不足警告
- 運(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)用越來越復(fù)雜,到時(shí)候系統(tǒng)就會變成如圖:
如果沒有一個(gè)統(tǒng)一的管理钓试,肯定是不合理的,所以可以引入網(wǎng)關(guān)均抽,作為一個(gè)統(tǒng)一的門戶护蝶,如圖:
3椭微、API Gateway的作用
ok锅尘,簡單介紹網(wǎng)關(guān)之后牛欢,要說說網(wǎng)關(guān)的作用,在Spring cloud官網(wǎng)也有過歸納:
當(dāng)然嚎花,我們可以自己挑幾個(gè)重要的介紹
-
動(dòng)態(tài)路由
網(wǎng)關(guān)可以做路由轉(zhuǎn)發(fā),假如服務(wù)信息變了,只要改網(wǎng)關(guān)配置既可巍扛,所以說網(wǎng)關(guān)有動(dòng)態(tài)路由(Dynamic Routing)的作用恩脂,如圖:
在這里插入圖片描述 -
請求監(jiān)控
請求監(jiān)控可以對整個(gè)系統(tǒng)的請求進(jìn)行監(jiān)控眶诈,詳細(xì)地記錄請求響應(yīng)日志,如圖娩践,可以將日志丟到消息隊(duì)列换怖,如果沒有使用網(wǎng)關(guān)的話钙蒙,記錄請求信息需要在各個(gè)服務(wù)中去做
在這里插入圖片描述 -
認(rèn)證鑒權(quán)
認(rèn)證鑒權(quán)可以對每一個(gè)訪問請求做認(rèn)證刷钢,拒絕非法請求咏删,保護(hù)后端的服務(wù),不需要每個(gè)服務(wù)都做鑒權(quán)宛篇,在項(xiàng)目中經(jīng)常有加上OAuth2.0、JWT,Spring Security進(jìn)行權(quán)限校驗(yàn)
在這里插入圖片描述 -
壓力測試
有網(wǎng)關(guān)的系統(tǒng)须妻,如果要要對某個(gè)服務(wù)進(jìn)行壓力測試司倚,可以如圖所示,改下網(wǎng)關(guān)配置既可丹皱,測試請求路由到測試服務(wù)呢簸,測試服務(wù)會有單獨(dú)的測試數(shù)據(jù)庫蛤迎,這樣測試的請求就不會影響到正式的服務(wù)和數(shù)據(jù)庫
在這里插入圖片描述
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í)就是過濾器呐萨,zuul基于Servlet實(shí)現(xiàn)。當(dāng)一個(gè)請求進(jìn)來時(shí)莽囤,會先進(jìn)入 pre 過濾器谬擦,在 pre 過濾器執(zhí)行完后,接著就到了 routing 過濾器中朽缎,開始路由到具體的服務(wù)中惨远,錯(cuò)誤的情況會被錯(cuò)誤過濾器攔截
- 過濾器類型:
- 前置過濾器(PRE FILTER):在路由過濾器之前執(zhí)行。功能可以包括請求身份驗(yàn)證话肖,選擇原始服務(wù)器以及記錄調(diào)試信息北秽。
- 路由過濾器(ROUTE FILTER):處理將請求路由到源的過程。這是使用Apache HttpClient或Netflix Ribbon構(gòu)建和發(fā)送原始HTTP請求的地方最筒。
- 后置過濾器(POST FILTER):在將請求路由過濾器之后執(zhí)行贺氓。功能可以包括向響應(yīng)中添加標(biāo)準(zhǔn)HTTP標(biāo)頭,收集統(tǒng)計(jì)信息和指標(biāo)以及將響應(yīng)從源流傳輸?shù)娇蛻舳恕?/li>
- 錯(cuò)誤過濾器(ERR FILTER):在其他階段之一發(fā)生錯(cuò)誤時(shí)就會調(diào)用到錯(cuò)誤過濾器
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ù)提供者扬蕊,代碼請參考上一章博客
項(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
@EnableZuulProxy
public class SpringcloudZuulApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudZuulApplication.class, args);
}
}
7、eureka厨相、zuul配置
eureka客戶端配置:
server:
port: 8082
# 指定application name领曼,這個(gè)是微服務(wù)注冊的serviceId
spring:
application:
name: zuul-api-gateway
eureka:
client:
# 服務(wù)注冊中心url
service-url:
defaultZone: http://localhost:8761/eureka/
# 網(wǎng)關(guān)服務(wù)注冊鸥鹉、發(fā)現(xiàn)都開放蛮穿,所以 register-with-eureka、fetch-registry都是true
register-with-eureka: true
fetch-registry: true
instance:
status-page-url-path: http://localhost:8761/actuator/info
health-check-url-path: http://localhost:8761/actuator/health
prefer-ip-address: true
instance-id: zuul-api-gateway8082
Zuul 配置路由規(guī)則:
zuul:
routes:
provider: # 路由標(biāo)識毁渗,可以自己定義
service-id: eureka-service-provider # 服務(wù)id(必須配置)
path: /provider/** # 映射的路徑践磅,一般和routes.provider一致
url: http://localhost:8083 # 路由到的url,可以不配置
Zuul配置訪問前綴:訪問時(shí)候需要加上前綴灸异,eg:http://localhost:8082/api-gateway/provider/api/users/mojombo
zuul:
# 配置前綴
prefix: /api-gateway
Zuul配置Header過濾:
zuul:
# 配置過濾敏感的請求頭信息府适,設(shè)置為空就不會過濾
sensitive-headers: Cookie,Set-Cookie,Authorization
Zuul配置重定向添加Host:
zuul:
# 重定向會添加host請求頭
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-gateway
routes:
provider: # 路由標(biāo)識肺樟,可以自己定義
service-id: eureka-service-provider # 服務(wù)id
path: /provider/** # 映射的路徑檐春,一般和routes.provider一致
url: http://localhost:8083 # 路由到的url
host:
# 配置連接超時(shí)時(shí)間
connect-timeout-millis: 15000
# socker發(fā)送超時(shí)時(shí)間
socket-timeout-millis: 60000
# 請求url編碼
decode-url: true
# 查詢字符串編碼
force-original-query-string-encoding: false
# 配置過濾敏感的請求頭信息,設(shè)置為空就不會過濾
sensitive-headers: Cookie,Set-Cookie,Authorization
# 重定向會添加host請求頭
add-proxy-headers: true
訪問:http://localhost:8082/api-gateway/provider/api/users/mojombo么伯,要加上前綴疟暖,配置的path
可能遇到的錯(cuò)誤,504錯(cuò)誤:
經(jīng)過排查田柔,需要加上超時(shí)設(shè)置俐巴,因?yàn)檎{(diào)用服務(wù)超時(shí),導(dǎo)致504錯(cuò)誤
zuul:
host:
connect-timeout-millis: 15000
socket-timeout-millis: 60000
加上配置硬爆,調(diào)用服務(wù)成功
8欣舵、Zuul自定義過濾器
在前面的介紹中,已經(jīng)介紹了幾種過濾器缀磕,現(xiàn)在自定義實(shí)現(xiàn)這四種過濾器
ps:spring cloud官網(wǎng)也提供了zuul過濾器的例子缘圈,詳情可以去github查看:https://github.com/spring-cloud-samples/sample-zuul-filters
項(xiàng)目結(jié)構(gòu):
過濾器類型參考o(jì)rg.springframework.cloud.netflix.zuul.filters.supportFilterConstants.java:
實(shí)現(xiàn)一個(gè)前置過濾器:攔截請求,必須帶token過來袜蚕,不然拋出提示信息等等
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ù)過濾器
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改后版本: 修改人: 修改日期: 2020/08/05 18:08 修改內(nèi)容:
* </pre>
*/
@Slf4j
//@Component
public class ZuulApiGatewayPreFilter extends ZuulFilter {
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String accessToken = request.getParameter("token");
if (StringUtils.isEmpty(accessToken)) {
// zuul過濾該請求准验,不進(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;
}
}
后置過濾器,經(jīng)常被用于打印日志等等操作廷没,代碼參考:https://www.baeldung.com/zuul-filter-modifying-response-body糊饱,實(shí)現(xiàn)效果時(shí),路由過濾器執(zhí)行之后颠黎,執(zhí)行后置過濾器打印日志:
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后置過濾器
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改后版本: 修改人: 修改日期: 2020/08/06 10:05 修改內(nèi)容:
* </pre>
*/
@Slf4j
//@Component
public class ZuulApiGatewayPostFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public 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;
}
}
注冊過濾器另锋,將過濾器加載到Spring容器滞项,也可以在過濾器類加上@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
@EnableZuulProxy
public class SpringcloudZuulApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudZuulApplication.class, args);
}
@Bean
public ZuulApiGatewayPreFilter zuulApiGatewayPreFilter(){
return new ZuulApiGatewayPreFilter();
}
@Bean
public ZuulApiGatewayPostFilter zuulApiGatewayPostFilter(){
return new ZuulApiGatewayPostFilter();
}
@Bean
public ZuulApiGatewayRouteFilter zuulApiGatewayRouteFilter(){
return new ZuulApiGatewayRouteFilter();
}
@Bean
public ZuulApiGatewayErrFilter zuulApiGatewayErrFilter(){
return new ZuulApiGatewayErrFilter();
}
}
訪問網(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室梅,開啟對routes的監(jiān)控
include: info,health,routes
# 開啟健康檢查詳細(xì)信息
endpoint:
health:
show-details: always
查看路由詳細(xì)戏仓,訪問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ì)信息,訪問鏈接: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)手冊:
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過濾器例子:https://github.com/spring-cloud-samples/sample-zuul-filters
優(yōu)質(zhì)學(xué)習(xí)資料參考:
Nginx官網(wǎng)對微服務(wù)網(wǎng)關(guān)的介紹:https://www.nginx.com/blog/building-microservices-using-an-api-gateway/
SpringCloud組件之網(wǎng)關(guān)Zuul(Hoxton版本):https://juejin.im/post/6847902220214763527
方志鵬大佬系列Spring Cloud博客:https://www.fangzhipeng.com/spring-cloud.html
使用Spring Cloud與Docker實(shí)戰(zhàn)微服務(wù):https://eacdy.gitbooks.io/spring-cloud-book/content/
程序員DD大佬系列Spring Cloud博客:http://blog.didispace.com/spring-cloud-learning/