1、為什麼需要Zuul
1.1、Zuul、Ribbon和Eureka相結合,可以實作智能路由和負載均衡的功能,Zuul可以将請求流量按照某種政策分發到叢集的多個服務執行個體上;
1.2、gateway将所有API接口統一聚合,并統一對外暴露,外界系統統一調用gateway提供的接口,外界無需知道微服務直接接口互相的調用關系,為微服務的API接口提供了更高的安全性;
1.3、Gateway服務可以用作身份認證和權限控制,防止非法通路API接口;
1.4、Gateway可以實作監控、日志輸出和對請求進行記錄;
1.5、Gateway可以實作流量的監控,在高流量的情況下,可以對服務進行降級;
1.6、Gateway将所有接口都統一對外,友善測試。
2、Zuul的工作原理
Zuul是通過Servlet實作的,通過自定義的ZuulServlet對請求進行控制。Zuul的核心是一系列的過濾器,可以在Http請求的發起到相應傳回期間執行一系列的過濾器,主要有一下四種過濾器:
a、Pre過濾器,請求路由之前執行的過濾器,主要用做安全驗證;
b、Routing過濾器,用于具體路由到微服務的執行個體,預設使用Http Client進行網絡請求;
c、Post過濾器,在請求被路由到微服務後執行的,用作收集統計資訊,名額,已經将相應傳輸到用戶端;
d、Error過濾器,在其他過濾器發生錯誤執行。
由于Zuul的過濾器之間是無法直接通信,是以隻能通過RequestContext對象進行共享資料,每個請求都會建立一個RequestContext對象。每個過濾器都有如下特性:
a、Type(類型),過濾器的類型,定義以上4中過濾器的一種;
b、Execution Order(執行順序),過濾的執行順序,Order值越小,越優先執行;
c、Criteria(标準),過濾器執行所需的條件;
d、Action(動作),符合Criteria的,則執行具體的Action,具體的邏輯代碼實作。
3、源碼實戰
3.1、建立Zuul服務
建立一個eureka-zuul-client的工程,添加相關依賴,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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hzlitai</groupId>
<artifactId>eureka-memo</artifactId>
<version>1.1-SNAPSHOT</version>
</parent>
<groupId>com.hzlitai</groupId>
<artifactId>eureka-zuul-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-zuul-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
程式的啟動類添加相關注解,代碼如下:
package com.hzlitai.eurekazuulclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
public class EurekaZuulClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaZuulClientApplication.class, args);
}
}
在application.yml配置中增加如下代碼:
server:
port: 5000
spring:
application:
name: service-zuul
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka
zuul:
prefix: /v1
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
ribbonapi:
path: /rebbonapi/**
serviceId: eureka-ribbon-client
feignapi:
path: /feignapi/**
serviceId: eureka-feign-client
服務端口為:5000,服務執行個體名:service-zuul,注冊到服務中心:serviceUrl,統一版本:prefix,路由配置:routes。
依次啟動各個服務後,通路:http://localhost:5000/ribbonapi/sayHi?name=zhuheliang,浏覽器會交替出現通路不同端口的結果。可見Zuul在路由轉發實作了負載均衡。
3.2、在Zuul上配置熔斷器Hystrix
在Zuul實作熔斷功能需要實作FallbackProvider接口,實作兩個方法:getRoute()和fallbackResponse(),源碼如下:
package com.hzlitai.eurekazuulclient.provider;
import com.netflix.hystrix.exception.HystrixTimeoutException;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Component
public class ZuulFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//api服務id(eureka-client),如果需要所有調用都支援回退,則return "*"或 return null
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
//return status;
return HttpStatus.BAD_REQUEST;
}
@Override
public int getRawStatusCode() throws IOException {
//return status.value();
return HttpStatus.BAD_REQUEST.value();
}
@Override
public String getStatusText() throws IOException {
//return status.getReasonPhrase();
//return HttpStatus.BAD_REQUEST.name();
return HttpStatus.BAD_REQUEST.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(("fallback:"+ZuulFallbackProvider.this.getRoute()).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
3.3、在Zuul中自定義過濾器
Zuul自定義過濾器隻需繼承ZuulFilter類,實作ZuulFilter裡的抽象方法,有filterType()和filterOrder(),還有IZuulFilter的shouldFilter()和Object run(),示例代碼如下:
package com.hzlitai.eurekazuulclient.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
@Component
public class MyFilter 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();
Object accessToken = request.getParameter("accessToken");
if(accessToken == null){
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try{
ctx.getResponse().getWriter().write("token is empty");
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
}