服務閘道器Spring Cloud Zuul
Spring Cloud Zuul
開發環境
- idea 2019.1.2
- jdk1.8.0_201
- Spring Boot 2.1.9.RELEASE
- Spring Cloud Greenwich SR3
Zuul介紹
Zuul是Netflix開發的一款提供動態路由、監控、彈性、安全的閘道器服務,他可以和Eureka,Ribbon,Hystrix等元件配合使用。還可以通過建立過濾器對校驗過濾提供支援,使微服務應用更專注於業務邏輯的開發。
使用Zuul閘道器服務帶來的好處是統一向外系統提供REST API,並額外提供了許可權控制、負載均衡等功能,並且這些功能是從原先的服務中抽離出來並單獨存在的。
Zuul提供了不同型別的filter用於處理請求,這些filter可以讓我們實現以下功能
- 許可權控制和安全性:可以識別認證需要的資訊和拒絕不滿足條件的請求
- 監控:監控請求資訊
- 動態路由:根據需要動態地路由請求到後臺的不同服務叢集
- 壓力測試:逐漸增大到叢集的流量,以便進行效能評估
- 負載均衡:為每種型別的請求分配容量並丟棄超過限額的請求
- 限流
- 黑白名單過濾
- 靜態資源處理:直接在zuul處理靜態資源的響應而不需要轉發這些請求到內部叢集中
過濾器
ZuulFilter是一個基礎的抽象類,定義了一些抽象方法
- filterType方法: filter的型別,有”pre”, “route”, “post”, “error”, “static”
- pre:在請求被路由之前執行
- route:在請求被路由時執行
- post:在請求被路由之後執行
- error:在請求發生錯誤時執行
- static:特殊的 Filter 具體的可以看 StaticResponseFilter,它允許從 Zuul 本身生成響應,而不是將請求轉發到源
filterOrder方法:優先順序,級別越高,越快被執行(數值越小表示級別越高)
shouldFilter方法:開關,如果是true,run方法會執行,否則不會執行
run方法:filter執行的邏輯操作
程式碼實現
1.建立服務註冊中心
建立 zuul-eureka-server 專案,引入eureka-server依賴,專案完整原始碼可以檢視:Spring Cloud Zuul 示例原始碼
以下貼幾段關鍵程式碼
pom新增依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
啟動類增加 @EnableEurekaServer 註解
@EnableEurekaServer
@SpringBootApplication
public class ZuulEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulEurekaServerApplication.class, args);
}
}
yml配置
server:
port: 8761
spring:
application:
name: zuul-eureka-server
eureka:
instance:
hostname: localhost # eureka 例項名稱
client:
register-with-eureka: false # 不向註冊中心註冊自己
fetch-registry: false # 是否檢索服務
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 註冊中心訪問地址
2.建立服務提供者1
建立 zuul-server-provider 專案,引入eureka-client依賴,專案完整原始碼可以檢視:Spring Cloud Zuul 示例原始碼
以下貼幾段關鍵程式碼
pom新增依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
編寫HelloController服務
@RestController
@Slf4j
public class HelloController {
@RequestMapping("/hello")
public String index(@RequestParam String name) {
log.info("request one name is " + name);
return "hello " + name + ",this is first messge";
}
}
啟動類增加 @EnableDiscoveryClient 註解
@SpringBootApplication
@EnableDiscoveryClient
public class ZuulServerProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerProviderApplication.class, args);
}
}
yml配置
spring:
application:
name: zuul-server-provider
server:
port: 9000
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
3.建立服務提供者2
建立 zuul-server-provider2 專案,引入eureka-client依賴,其它同服務提供者1專案,專案完整原始碼可以檢視:Spring Cloud Zuul 示例原始碼
以下貼出差異部分程式碼
編寫服務,這裡為了做服務降級測試,為當前執行緒設定了一個超長休眠時間
@RestController
@Slf4j
public class HelloController {
@RequestMapping("/hello")
public String index(@RequestParam String name) {
log.info("request two name is " + name);
try{
//為做服務降級測試,設定一個超長休眠時間,故意導致該服務訪問超時
Thread.sleep(1000000);
}catch ( Exception e){
log.error(" hello two error",e);
}
return "hello " + name + ",this is two messge";
}
}
4.建立zuul服務閘道器
建立 zuul-server-gateway 專案,引入netflix-zuul及eureka-client依賴,專案完整原始碼可以檢視:Spring Cloud Zuul 示例原始碼
以下貼幾段關鍵程式碼
pom.xml配置
<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>
建立過濾器TokenFilter.java
package com.easy.zuulServerGateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
@Slf4j
public class TokenFilter extends ZuulFilter {
@Override
public String filterType() {
//可以在請求被路由之前呼叫
return "pre";
}
@Override
public int filterOrder() {
//filter執行順序,通過數字指定 ,優先順序為0,數字越大,優先順序越低
return 0;
}
@Override
public boolean shouldFilter() {
//是否執行該過濾器,此處為true,說明需要過濾
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());
//獲取請求的引數
String token = request.getParameter("token");
if (StringUtils.isNotBlank(token)) {
//對請求進行路由
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);
return null;
} else {
//不對其進行路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(400);
ctx.setResponseBody("token is empty");
ctx.set("isSuccess", false);
return null;
}
}
}
建立 zuul-server-provider 服務對應的熔斷器(這裡針對整個服務熔斷,也可以對單個服務介面做熔斷處理),ProviderFallback.java
package com.easy.zuulServerGateway.fallback;
import lombok.extern.slf4j.Slf4j;
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.InputStream;
@Slf4j
@Component
public class ProviderFallback implements FallbackProvider {
@Override
public String getRoute() {
return "zuul-server-provider";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause != null) {
String reason =cause.getMessage();
log.info("Excption {}", reason);
}
return fallbackResponse();
}
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() {
return 200;
}
@Override
public String getStatusText(){
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() {
return new ByteArrayInputStream("The service is unavailable.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
yml配置
spring:
application:
name: zuul-service-gateway
server:
port: 8888
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
#是否開啟重試功能
zuul:
retryable: true
#對當前服務的重試次數
ribbon:
MaxAutoRetries: 2
#切換相同Server的次數
MaxAutoRetriesNextServer: 0
啟動類增加 @EnableZuulProxy 註解,來啟動服務閘道器
ZuulServerGatewayApplication.java
package com.easy.zuulServerGateway;
import com.easy.zuulServerGateway.filter.TokenFilter;
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 ZuulServerGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerGatewayApplication.class, args);
}
@Bean
public TokenFilter tokenFilter() {
return new TokenFilter();
}
}
至上,示例的四個服務建立完畢,接下來執行示例檢視效果
使用
現有四個專案如下
zuul-eureka-server:服務註冊中心,服務名:zuul-eureka-server,埠:8761
zuul-server-provider:服務提供者1,服務名:zuul-server-provider,埠:9000
zuul-server-provider2:服務提供者,服務名:zuul-server-provider,埠:9001
zuul-server-gateway:服務閘道器,服務名:zuul-server-gateway,埠:8888
執行測試
分別啟動zuul-eureka-server、zuul-server-gateway、zuul-server-provider三個服務
- 訪問地址:http://localhost:8888/zuul-server-provider/hello?name=yuntian,返回:token is empty ,請求被攔截返回。
- 訪問地址:http://localhost:8888/zuul-server-provider/hello?name=yuntian&token=xx,返回:hello yuntian,this is first messge,說明請求正常響應。
啟動zuul-server-provider2
- 多次訪問http://localhost:8888/zuul-server-provider/hello?name=yuntian&token=xx,此時會交替返回
hello yuntian,this is first messge
The service is unavailable
...
從返回結果可以看出:zuul-server-provider2專案已經啟用了熔斷,返回:The service is unavailable.
資料
- Spring Cloud Zuul 示例原始碼
- Spring Boot、Spring Cloud示例學習