1、什么是API網關
API網關是所有請求的入口,承載了所有的流量,API Gateway是一個門戶一樣,也可以說是進入系統的唯一節點。這跟面向對象設計模式中的Facet模式很像。API Gateway封裝內部系統的架構,并且提供API給各個客戶端。它還可能有其他功能,如授權、監控、負載均衡、緩存、請求分片和管理、靜態響應處理等
API Gateway負責請求轉發、合成和協議轉換。所有來自客戶端的請求都要先經過API Gateway,然后路由這些請求到對應的微服務。API Gateway將經常通過調用多個微服務來處理一個請求以及聚合多個服務的結果。它可以在web協議與內部使用的非Web友好型協議間進行轉換,如 HTTP協議、WebSocket協議。
畫圖表示,沒有網關的情況,客戶端的請求會直接落到后端的各個服務中,無法集中統一管理。
畫圖表示,有網關的情況,所有的請求都先經過網關,然后進行分發到對應服務
2、API網關的重要性
API網關在微服務項目中是很重要的,網關提供一個統一的管理,服務間的調度變得有序
引用nginx官方的一篇優質博客,https://www.nginx.com/blog/building-microservices-using-an-api-gateway/,例子介紹了一個龐雜的電商系統,按照微服務理論進行設計,有如下各種服務:
- 購物車服務:購物車中的物品數量
- 訂單服務:訂單歷史記錄
- 目錄服務:基本產品信息,例如其名稱,圖像和價格
- 審核服務:客戶審核
- 庫存服務:庫存不足警告
- 運送服務:運送選項,期限和費用與運送提供商的API分開提取
- 推薦服務:建議項目
在不使用網關的情況,客戶端直接調用各服務:
理想情況,各服務調用是可以正常使用的,但是隨著業務拓展,服務之間的調用越來越復雜,到時候系統就會變成如圖:
如果沒有一個統一的管理,肯定是不合理的,所以可以引入網關,作為一個統一的門戶,如圖:
3、API Gateway的作用
ok,簡單介紹網關之后,要說說網關的作用,在Spring cloud官網也有過歸納:
當然,我們可以自己挑幾個重要的介紹
-
動態路由
網關可以做路由轉發,假如服務信息變了,只要改網關配置既可,所以說網關有動態路由(Dynamic Routing)的作用,如圖:
在這里插入圖片描述 -
請求監控
請求監控可以對整個系統的請求進行監控,詳細地記錄請求響應日志,如圖,可以將日志丟到消息隊列,如果沒有使用網關的話,記錄請求信息需要在各個服務中去做
在這里插入圖片描述 -
認證鑒權
認證鑒權可以對每一個訪問請求做認證,拒絕非法請求,保護后端的服務,不需要每個服務都做鑒權,在項目中經常有加上OAuth2.0、JWT,Spring Security進行權限校驗
在這里插入圖片描述 -
壓力測試
有網關的系統,如果要要對某個服務進行壓力測試,可以如圖所示,改下網關配置既可,測試請求路由到測試服務,測試服務會有單獨的測試數據庫,這樣測試的請求就不會影響到正式的服務和數據庫
在這里插入圖片描述
4、什么是Netflix Zuul?
Netflix Zuul是Netflix公司的產品,是一款API網關中間件。Zuul是一個基于 JVM 路由和服務端的負載均衡器。提供了路由、監控、彈性、安全等服務。Zuul 能夠與 Eureka、Ribbon、Hystrix 等組件配合使用,提供統一的API網關處理
5、Netflix Zuul工作原理
參考Zuul官網wiki,Zuul的核心如圖其實就是過濾器,zuul基于Servlet實現。當一個請求進來時,會先進入 pre 過濾器,在 pre 過濾器執行完后,接著就到了 routing 過濾器中,開始路由到具體的服務中,錯誤的情況會被錯誤過濾器攔截
- 過濾器類型:
- 前置過濾器(PRE FILTER):在路由過濾器之前執行。功能可以包括請求身份驗證,選擇原始服務器以及記錄調試信息。
- 路由過濾器(ROUTE FILTER):處理將請求路由到源的過程。這是使用Apache HttpClient或Netflix Ribbon構建和發送原始HTTP請求的地方。
- 后置過濾器(POST FILTER):在將請求路由過濾器之后執行。功能可以包括向響應中添加標準HTTP標頭,收集統計信息和指標以及將響應從源流傳輸到客戶端。
- 錯誤過濾器(ERR FILTER):在其他階段之一發生錯誤時就會調用到錯誤過濾器
6、Zuul實驗環境準備
環境準備:
- JDK 1.8
- SpringBoot2.2.3
- SpringCloud(Hoxton.SR6)
- Maven 3.2+
- 開發工具
- IntelliJ IDEA
- smartGit
創建一個SpringBoot Initialize項目,詳情可以參考我之前博客:SpringBoot系列之快速創建項目教程
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
進行試驗,試驗前要運行eureka服務端,eureka服務提供者,代碼請參考上一章博客
項目創建成功后,先在啟動類加上@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,這個是微服務注冊的serviceId
spring:
application:
name: zuul-api-gateway
eureka:
client:
# 服務注冊中心url
service-url:
defaultZone: http://localhost:8761/eureka/
# 網關服務注冊、發現都開放,所以 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 配置路由規則:
zuul:
routes:
provider: # 路由標識,可以自己定義
service-id: eureka-service-provider # 服務id(必須配置)
path: /provider/** # 映射的路徑,一般和routes.provider一致
url: http://localhost:8083 # 路由到的url,可以不配置
Zuul配置訪問前綴:訪問時候需要加上前綴,eg:http://localhost:8082/api-gateway/provider/api/users/mojombo
zuul:
# 配置前綴
prefix: /api-gateway
Zuul配置Header過濾:
zuul:
# 配置過濾敏感的請求頭信息,設置為空就不會過濾
sensitive-headers: Cookie,Set-Cookie,Authorization
Zuul配置重定向添加Host:
zuul:
# 重定向會添加host請求頭
add-proxy-headers: true
Zuul超時設置:
zuul:
host:
# 配置連接超時時間
connect-timeout-millis: 15000
# socker發送超時時間
socket-timeout-millis: 60000
zuul所有配置參考,詳情參考官網:
zuul:
# 配置前綴
prefix: /api-gateway
routes:
provider: # 路由標識,可以自己定義
service-id: eureka-service-provider # 服務id
path: /provider/** # 映射的路徑,一般和routes.provider一致
url: http://localhost:8083 # 路由到的url
host:
# 配置連接超時時間
connect-timeout-millis: 15000
# socker發送超時時間
socket-timeout-millis: 60000
# 請求url編碼
decode-url: true
# 查詢字符串編碼
force-original-query-string-encoding: false
# 配置過濾敏感的請求頭信息,設置為空就不會過濾
sensitive-headers: Cookie,Set-Cookie,Authorization
# 重定向會添加host請求頭
add-proxy-headers: true
訪問:http://localhost:8082/api-gateway/provider/api/users/mojombo,要加上前綴,配置的path
可能遇到的錯誤,504錯誤:
經過排查,需要加上超時設置,因為調用服務超時,導致504錯誤
zuul:
host:
connect-timeout-millis: 15000
socket-timeout-millis: 60000
加上配置,調用服務成功
8、Zuul自定義過濾器
在前面的介紹中,已經介紹了幾種過濾器,現在自定義實現這四種過濾器
ps:spring cloud官網也提供了zuul過濾器的例子,詳情可以去github查看:https://github.com/spring-cloud-samples/sample-zuul-filters
項目結構:
過濾器類型參考org.springframework.cloud.netflix.zuul.filters.supportFilterConstants.java:
實現一個前置過濾器:攔截請求,必須帶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網關預過濾器
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改后版本: 修改人: 修改日期: 2020/08/05 18:08 修改內容:
* </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過濾該請求,不進行路由
ctx.setSendZuulResponse(false);
// 設置返回的錯誤碼
ctx.setResponseStatusCode(403);
ctx.setResponseBody("AccessToken is Invalid ");
return null;
}
log.info("accessToken: {}",accessToken);
// 否則業務繼續執行
return null;
}
}
后置過濾器,經常被用于打印日志等等操作,代碼參考:https://www.baeldung.com/zuul-filter-modifying-response-body,實現效果時,路由過濾器執行之后,執行后置過濾器打印日志:
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 修改內容:
* </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();
}
}
訪問網關:http://localhost:8082/api-gateway/provider/api/users/mojombo,不帶token的情況
http://localhost:8082/api-gateway/provider/api/users/mojombo?token=?,帶上token調用成功
9、查看Zuul路由信息
加上spring-boot-starter-actuator,進行路由信息監控:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
spring-boot-starter-actuator配置:
management:
endpoints:
web:
exposure:
# 默認只支持info,health,開啟對routes的監控
include: info,health,routes
# 開啟健康檢查詳細信息
endpoint:
health:
show-details: always
查看路由詳細,訪問http://localhost:8082/actuator/routes,SpringBoot2.2.3版本要加上actuator前綴,調用成功,返回json數據:
{
"/api-gateway/provider/**":"eureka-service-provider",
"/api-gateway/eureka-service-provider/**":"eureka-service-provider"
}
查看路由詳細信息,訪問鏈接: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官網手冊:
spring Cloud官網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
優質學習資料參考:
Nginx官網對微服務網關的介紹:https://www.nginx.com/blog/building-microservices-using-an-api-gateway/
SpringCloud組件之網關Zuul(Hoxton版本):https://juejin.im/post/6847902220214763527
方志鵬大佬系列Spring Cloud博客:https://www.fangzhipeng.com/spring-cloud.html
使用Spring Cloud與Docker實戰微服務:https://eacdy.gitbooks.io/spring-cloud-book/content/
程序員DD大佬系列Spring Cloud博客:http://blog.didispace.com/spring-cloud-learning/