zuul-API網關
zuul API 網關,為微服務應用提供統一的對外訪問接口。
zuul 還提供過濾器,對所有微服務提供統一的請求校驗。
統一調用入口
創建項目-zuul
導入依賴
Eureka Client
,zuul
導入基本commons依賴
<?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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>sp11-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sp11-zuul</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<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>cn.tedu</groupId>
<artifactId>sp01-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml配置
spring:
application:
name: zuul
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
zuul:
routes:
item-service: /item-service/**
user-service: /user-service/**
order-service: /order-service/**
主程序
主程序添加@EnableZuulProxy
注解,啟動zuul
package cn.tedu.sp11;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class Sp11ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(Sp11ZuulApplication.class, args);
}
}
啟動測試
- http://eureka1:2001
- http://localhost:3001/item-service/35
http://localhost:3001/item-service/decreaseNumber
使用postman,POST發送以下格式數據:
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
- http://localhost:3001/user-service/7
- http://localhost:3001/user-service/7/score?score=100
- http://localhost:3001/order-service/123abc
- http://localhost:3001/order-service/
統一權限校驗-zuul 請求過濾
添加過濾器,判斷用戶權限
- 自定義過濾器,繼承過濾器父類
ZuulFilter
- 只需要添加 @Component 注解,zuul會完成自動配置
package cn.tedu.sp11.filter;
//過濾器
import cn.tedu.web.util.JsonResult;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class AccessFilter extends ZuulFilter {
/*
filterType 指過濾器類型 pre post routing error
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/*
filterOrder 順序號 指定過濾器插入的位置
在默認過濾器中,第5個過濾器在上下文對象中添加了service-id,
在第5個過濾器之后,層能從上下文對象訪問到service-id
*/
@Override
public int filterOrder() {
return 6;
}
/*
對當前請求來說,是否要進行過濾,
如果返回true,則要進行過濾,會執行過濾的run()方法
如果返回false,跳過過濾代碼,繼續執行后面的流程
*/
@Override
public boolean shouldFilter() {
//判斷用戶調用是否是商品服務
//如果是商品服務,則進行過濾
//如果不是,則不過濾
//當前請求的上下文對象
RequestContext ctx = RequestContext.getCurrentContext();
//從上下文對象中獲取客戶端調用的service id
String serviceId =(String) ctx.get(FilterConstants.SERVICE_ID_KEY);
return "item-service".equals(serviceId);
}
/*
過濾代碼
他的返回值,在當前版本中,沒有啟用,返回任何數據都無效
*/
@Override
public Object run() throws ZuulException {
//
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
if (StringUtils.isBlank(token)){
//沒有token 則阻止訪問
ctx.setSendZuulResponse(false);
//直接向客戶端發送響應
//返回JsonResult : { code:400 ,msg :not log in ,data : null}
ctx.setResponseStatusCode(JsonResult.NOT_LOGIN);
ctx.setResponseBody(JsonResult.err()
.code(JsonResult.NOT_LOGIN)
.msg("not login")
.toString());
}
return null;
}
}
集成Ribbon
zuul + ribbon 負載均衡
zuul 已經集成了 ribbon,默認已經實現了負載均衡
zuul + ribbon 重試
pom.xml 添加 spring-retry 依賴
- 需要 spring-retry 依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
1234
配置 zuul 開啟重試,並配置 ribbon 重試參數
- 需要開啟重試,默認不開啟
spring:
application:
name: zuul
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
zuul:
retryable: true
# routes:
# item-service: /item-service/**
# user-service: /user-service/**
# order-service: /order-service/**
ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
MaxAutoRetriesNextServer: 1
MaxAutoRetries: 1
集成Hystrix
默認已經啟用了hystrix,不用做任何基礎配置
降級代碼:
實現 FallbackProvider 接口,按接口規則來實現降級代碼,在實現類上添加 @Component 注解
zuul + hystrix 降級
創建降級類
getRoute() 方法中指定應用此降級類的服務id,**號或null值可以通配所有服務
ItemFallBack
package cn.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
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 ItemFallBack implements FallbackProvider {
/*
返回一個servicid 表示針對哪個服務使用當前的降級類
返回* 或者 null 值 ,表示對所有服務執行當前降級類
*/
@Override
public String getRoute() {
return "item-service";
}
/*
封裝降級響應的對象
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK; //封裝狀態碼和狀態文本
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
String josn = JsonResult.err().msg("調用遠程商品服務失敗").toString();
ByteArrayInputStream inputStream = new ByteArrayInputStream(josn.getBytes("UTF-8"));
return inputStream;
}
@Override
public HttpHeaders getHeaders() {
//Content-Type:application/json
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
OrderFallBack
package cn.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
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.IOException;
import java.io.InputStream;
@Component
@Slf4j
public class OrderFallBack implements FallbackProvider {
@Override
public String getRoute() {
return "order-service"; //"*"; //null;
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return response();
}
private ClientHttpResponse response() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
log.info("fallback body");
String s = JsonResult.err().msg("后台服務錯誤").toString();
return new ByteArrayInputStream(s.getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
降低 hystrix 超時時間
在application.yml文件中添加超時配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 500
啟動服務,測試降級
http://localhost:3001/item-service/35
zuul + hystrix 數據監控
暴露 hystrix.stream 監控端點
- zuul 已經包含 actuator 依賴
management:
endpoints:
web:
exposure:
include: hystrix.stream
測試
開啟監控
啟動 sp08-hystrix-dashboard,填入 zuul 的監控端點路徑,開啟監控
填入監控端點:
http://localhost:3001/actuator/hystrix.stream
必須通過zuul網關訪問后台服務才會生成監控數據
- http://localhost:3001/item-service/35
http://localhost:3001/item-service/decreaseNumber
使用postman,POST發送以下格式數據:
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
- http://localhost:3001/user-service/7
- http://localhost:3001/user-service/7/score?score=100
- http://localhost:3001/order-service/123abc
- http://localhost:3001/order-service/
zuul + turbine 聚合監控
修改 turbine 項目,聚合 zuul 服務實例
添加zuul服務
在turbine的application.yml配置文件中配置
spring:
application:
name: turbin
server:
port: 5001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
turbine:
app-config: order-service, zuul
cluster-name-expression: new String("default")
使用hystrix儀表盤, 對 turbine 監控端點進行監控, 此端點聚合了訂單服務和zull網關服務的監控數據
http://localhost:5001/turbine.stream
熔斷測試
ab -n 20000 -c 50 http://localhost:3001/order-service/123abc
zuul和feign的比較
- 都能做遠程調用,都可以集成ribbon 和 hystrix
- zuul 在最前面使用,做統一調用入口,而feign用在服務之間
- zuul不推薦開啟重試,feign不推薦開啟hystrix(會造成程序的混亂)