Zuul API网关
zuul API 网关,为微服务应用提供统一的对外访问接口。
zuul 还提供过滤器,对所有微服务提供统一的请求校验。
Zuul API 网关总结:
- 统一调用入口
- 统一的权限校验
- 集成ribbon
- 集成hystrix
统一调用入口
- zuul依赖
- @EnableZuulProxy
- yml 配置转发规则
# zuul的默认规则:serviceid 直接映射成一个子路径
# zull可以根据注册表的信息来自动配置这个默认规则
# 为了防止注册表信息不全,最好手动配置
zuul:
routes:
item-service: /item-service/**
user-service: /user-service/**
order-service: /order-service/**
统一的权限校验
使用 zuul 的过滤器实现权限验证
http://localhost:3001/item-service/y4y433 没有登录不允许访问
http://localhost:3001/item-service/y4y433?token=78oi6i544 有token认为已登录,可以访问
添加过滤器
- 只需要添加一个zuul过滤器子类,添加 @Component 注解,没有任何其他配置,zuul可以自动配置
- 继承 ZuulProxy 父类
- 实现几个抽象方法
zuul 集成 ribbon(zuul 不推荐启用重试)
默认启用了 ribbon 的负载均衡,没有启用重试
配置启用重试:
- 添加 spring-retry 依赖
- zuul.retryable=true
- 重试参数,默认值:
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
ReadTimeout: 1000
zuul 集成 hystrix(0配置已经启用了hystrix)
添加降级代码:
- 实现 FallbackProvider 接口,按接口要求添加降级代码
- 只需要加 @Component 注解,zuul会进行自动配置
暴露hystrix.stream 端点
- actuator依赖(zuul已包含)
- m.e.w.e.i=hystrix.stream
- 修改 10-turbine,聚合的服务添加 zuul 服务
新建 sp11-zuul 项目
pom.xml
- 需要添加 sp01-commons 依赖
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
cn.tedu
sp11-zuul
0.0.1-SNAPSHOT
sp11-zuul
Demo project for Spring Boot
1.8
Hoxton.RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
cn.tedu
sp01-commons
0.0.1-SNAPSHOT
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
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
和 @EnableDiscoveryClient
注解
package cn.tedu.sp11;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableDiscoveryClient
@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 + ribbon 负载均衡
zuul 已经集成了 ribbon,默认已经实现了负载均衡
zuul + ribbon 重试
pom.xml 添加 spring-retry 依赖
- 需要 spring-retry 依赖
org.springframework.retry
spring-retry
配置 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
zuul + hystrix 降级
创建降级类
- getRoute() 方法中指定应用此降级类的服务id,星号或null值可以通配所有服务
ItemFB
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.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Component
@Slf4j
public class ItemFB implements FallbackProvider {
/*
返回: service-id
针对哪个服务进行降级
返回:"*"或是null
针对所有服务进行降级
*/
@Override
public String getRoute() {
return "item-service";
}
//设置向客户端返回的降级响应
//ClientHttpResponse中封装降级响应
@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 {
//{code:200,msg:"调用后台服务失败”,data:null}
String json= JsonResult.ok().msg("调用后台商品服务失败").toString();
return new ByteArrayInputStream(json.getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json;charset=UTF-8");
return headers;
}
};
}
}
OrderFB
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.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Component
@Slf4j
public class OrderFB implements FallbackProvider {
/*
返回: service-id
针对哪个服务进行降级
返回:"*"或是null
针对所有服务进行降级
*/
@Override
public String getRoute() {
return "order-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 {
//{code:200,msg:"调用后台服务失败”,data:null}
String json= JsonResult.ok().msg("调用后台订单服务失败").toString();
return new ByteArrayInputStream(json.getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json;charset=UTF-8");
return headers;
}
};
}
}
降低 hystrix 超时时间,以便测试降级
spring:
application:
name: zuul
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
zuul:
retryable: true
ribbon:
ConnectTimeout: 1000
ReadTimeout: 2000
MaxAutoRetriesNextServer: 1
MaxAutoRetries: 1
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
- 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 服务实例
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 请求过滤
- 继承 ZuulProxy
- 添加 @Component
定义过滤器,继承 ZuulFilter
在 sp11-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 lombok.extern.slf4j.Slf4j;
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
@Slf4j
public class AccessFilter extends ZuulFilter {
// 设置zuul过滤器的类型:pre,routing,post,error
@Override
public String filterType() {
//return "pre";
return FilterConstants.PRE_TYPE; //设置成前置过滤器
}
// 设置过滤器的顺序 该过滤器顺序要 > 5,才能得到 serviceid
@Override
public int filterOrder() {
return 6;
}
/**
对客户端的当前请求是否要执行过滤代码
*/
@Override
public boolean shouldFilter() {
// 如果调用 item-service 要执行过滤代码判断权限
// 否则不执行过滤代码
//获取调用的服务id
RequestContext ctx = RequestContext.getCurrentContext();//请求行下文对象
String serviceId = (String)ctx.get(FilterConstants.SERVICE_ID_KEY);
//判断是不是 item-service
//对指定的serviceid过滤,如果要过滤所有服务,直接返回 true
return "item-service".equalsIgnoreCase(serviceId);//equalsIgnoreCase忽略大小写来判断
}
//过滤代码
@Override
public Object run() throws ZuulException {
// http://localhost:3001/item-service/35?token=1234
//获取request对象
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//接收token参数
String token = request.getParameter("token");
// 如果没有收到token参数,认为用户没有登录,阻止继续继续调用
if(StringUtils.isBlank(token)) { //commons.lang3.StringUtils
//阻止他继续调用
ctx.setSendZuulResponse(false); //不再向后台服务转发
//设置向客户端发送的响应
ctx.addZuulResponseHeader("Content-Type", "application/json");
//JsonResult -{code:401,msg:"not login".data:null}
ctx.setResponseBody(JsonResult.err().msg("not login").toString());
}
return null; //在当前zuul版本中,这个返回值没有作用
}
}
访问测试
- 没有token参数不允许访问
http://localhost:3001/item-service/35 - 有token参数可以访问
http://localhost:3001/item-service/35?token=1234
zuul Cookie过滤
zuul 会过滤敏感 http 协议头,默认过滤以下协议头:
- Cookie
- Set-Cookie
- Authorization
可以设置 zuul 不过滤这些协议头
zuul:
sensitive-headers:
Feign 和 Zuul
- 向后台服务调用
- 集成ribbon
- 集成hystrix
什么时候使用 Feign 和 Zuul
- 业务服务之间调用使用 Feign
- Zuul 部署在最前面,作为系统入口
Feign 不推荐启用 Hystrix
- 业务服务之间添加Hystrix会造成混乱,难以确定故障点
- 应该在最前面添加 Hystrix 避免混乱
Zuul 不推荐启用 Ribbon 的重试
- 在网关处重试会造成后台多个服务压力翻倍
- 重试动作应尽量往后放