为什么要使用Zuul
- Zuul作为路由网关组件,在微服务架构中有着非常重要的作用,主要体现在以下6个方面。
- Zuul、 Ribbon 以及Eureka相结合,可以实现智能路由和负载均衡的功能,Zuul 能够将请求流量按某种策略分发到集群状态的多个服务实例。
- 网关将所有服务的API接口统一聚合,并统一对外暴露。外界系统调用API接口时,都是由网关对外暴露的API接口,外界系统不需要知道微服务系统中各服务相互调用的复杂性。微服务系统也保护了其内部微服务单元的API接口,防止其被外界直接调用,导致服务的敏感信息对外暴露。
- 网关服务可以做用户身份认证和权限认证,防止非法请求操作API接口,对服务器起到保护作用。
- 网关可以实现监控功能,实时日志输出,对请求进行记录。
- 记网关可以用来实现流量监控,在高流量的情况下,对服务进行降级。
- API接口从内部服务分离出来,方便做测试。
API网关为微服务架构中的服务提供了统一的访问入口,客户端通过API网关访问相关服务。API网关的定义类似于设计模式中的门面模式,它相当于整个微服务架构中的门面,所有客户端的访问都通过它来进行路由及过滤。它实现了请求路由、负载均衡、校验过滤、服务容错、服务聚合等功能
Zuul作用
- Zuul可以通过加载动态过滤机制,从而实现以下各项功能:
- 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。 对每一个resource进行身份认证
- 审查与监控: 实时观察后端微服务的TPS、响应时间,失败数量等准确的信息
- 动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
- 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
- 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
- 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
- 多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
- 日志 - 记录所有请求的访问日志数据,可以为日志分析和查询提供统一支持
Zuul的工作原理
-
Zuul是通过Servlet来实现的,Zuul通过自定义的ZuulServlet(类似于Spring MVC的DispatcherServlet)来对请求进行控制。
-
Zuul的核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列的过滤器。
-
Zuul包括以下4种过滤器:
- PRE过滤器:它是在请求路由道具体的服务之前执行的,这种类型的过滤器可以做安全验证,例如身份验证、参数验证等。
- ROUTING过滤器:它用于将请求路由到具体的微服务实例。在默认情况下,它使用Http Client进行网络请求。
- POST过滤器:它是请求已被路由到微服务后执行的。一般情况下,用作收集统计信息、指标,以及将响应传送到客户端。
- ERROR过滤器:它是在其他过滤器发生错误时执行的。
- 自定义过滤器
-
filter特点
- filter的类型:filter的类型,决定了它在整个filter链中的执行顺序,可能在端点路由前执行,也可能在端点路由时执行,还有可能在端点路由后执行,甚至是端点路由发生异常时执行。
- filter的执行顺序:同一种类型的filter,可以通过filterOrder()方法设置执行顺序,一般都是根据业务场景自定义filter执行顺序。
- filter执行条件:filter运行所需的标准,或条件。
- filter执行效果:符合某个filter执行条件,产生执行效果。
-
-
zuul内部有一套完整的机制动态读取、编译和运行这些过滤器。过滤器之间不能直接相互通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。
-
Zuul 过滤器具有以下关键特性。
- Type(类型):Zuul过滤器的类型,这个类型决定了过滤器在请求的哪个阶段起作用,例如Pre、Post 阶段等。
- Execution Order (执行顺序):规定了过滤器的执行顺序,Order的值越小,越先执行。
- Criteria (标准): Filter 执行所需的条件。
- Action (行动):如果符合执行条件,则执行Action (即逻辑代码)。
-
过滤器生命周期图示
-
核心过滤器
过滤器名称 | 过滤类型 | 优先级 | 过滤器作用 |
---|---|---|---|
ServletDetectionFilter | pre | -3 | 检测当前请求是通过DispatcherServlet处理运行的还是ZuulServlet运行处理的。 |
Servlet30WrapperFilter | pre | -2 | 对原始的HttpServletRequest进行包装。 |
FormBodyWrapperFilter | pre | -1 | 将Content-Type为application/x-www-form-urlencoded或multipart/form-data的请求包装成FormBodyRequestWrapper对象。 |
DebugFilter | route | 1 | 根据zuul.debug.request的配置来决定是否打印debug日志。 |
PreDecorationFilter | route | 5 | 对当前请求进行预处理以便执行后续操作。 |
RibbonRoutingFilter | route | 10 | 通过Ribbon和Hystrix来向服务实例发起请求,并将请求结果进行返回。 |
SimpleHostRoutingFilter | route | 100 | 只对请求上下文中有routeHost参数的进行处理,直接使用HttpClient向routeHost对应的物理地址进行转发。 |
SendForwardFilter | route | 500 | 只对请求上下文中有forward.to参数的进行处理,进行本地跳转。 |
SendErrorFilter | post | 0 | 当其他过滤器内部发生异常时的会由它来进行处理,产生错误响应。 |
SendResponseFilter | post | 1000 | 利用请求上下文的响应信息来组织请求成功的响应内容。 |
Zuul请求的生命周期
-
当一个客户端Request请求进入Zuul网关服务时,网关先进入“pre filter”, 进行一系列的验证、操作或者判断。
-
然后交给“routing filter” 进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。
-
当具体的服务处理完后,最后由“post filter”进行处理。
-
该类型的处理器处理完之后,将Response信息返回给客户端。
-
ZuulServlet是Zuul的核心Servlet
- ZuulServlet 的作用是初始化ZuulFilter,并编排这些ZuulFilter的执行顺序。
- 该类中有一个service()方法,执行了过滤器执行的逻辑。如下
- 首先执行preRoute()方法,这个方法执行的是PRE类型的过滤器的逻辑。
- 如果执行这个方法时出错了,那么会执行error(e)和postRoute()。
- 然后执行route()方法,该方法是执行ROUTING类型过滤器的逻辑。
- 最后执行postRoute(),该方法执行了POST类型过滤器的逻辑。
服务初始化过程
-
Spring Cloud Netflix Zuul中初始化网关服务有两种方式:@EnableZuulServer和@EnableZuulProxy。
-
这两种方式都可以启动网关服务,不同的主要地方是:
- @EnableZuulProxy是@EnableZuulServer的超集,即使用@EnableZuulProxy加载的组件除了包含使用@EnableZuulServer加载的组件外,还增加了其他组件和功能;
- @EnableZuulServer是纯净版的网关服务,不具备代理功能,只实现了简单的请求转发、响应等基本功能,需要自行添加需要的组件;
- @EnableZuulProxy在@EnableZuulServer的基础上实现了代理功能,并可以通过服务发现来路由服务。
-
@EnableZuulServer和@EnableZuulProxy的初始化过程一致,最大的区别在于加载的过滤器不同。
-
蓝色是@EnableZuulServer加载的过滤器;红色是@EnableZuulProxy额外添加的过滤器。
Java实现zuul
- 创建一个zuul-proxy模块
- pom
<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>
- 在application.yml中进行配置
server:
port: 8801
spring:
application:
name: zuul-proxy
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/
- 在启动类上添加@EnableZuulProxy注解来启用Zuul的API网关功能
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulProxyApplication.class, args);
}
}
- 启动eureka-server,两个user-service,feign-service和zuul-proxy服务
配置路由规则
- 我们可以通过修改application.yml中的配置来配置路由规则,这里我们将匹配userService/的请求路由到user-service服务上去,匹配feignService的请求路由到feign-service上去。
zuul:
routes: #给服务配置路由
user-service:
path: /userService/**
feign-service:
path: /feignService/**
- 访问http://localhost:8801/userService/user/1可以发现请求路由到了user-service上了;
- 访问http://localhost:8801/feignService/user/1可以发现请求路由到了feign-service上了。
默认路由规则
- Zuul和Eureka结合使用,可以实现路由的自动配置,自动配置的路由以服务名称为匹配路径,相当于如下配置:
zuul:
routes: #给服务配置路由
user-service:
path: /user-service/**
feign-service:
path: /feign-service/**
- 访问http://localhost:8801/user-service/user/1同样可以路由到了user-service上了;
- 访问http://localhost:8801/feign-service/user/1同样可以路由到了feign-service上了。
- 如果不想使用默认的路由规则,可以添加以下配置来忽略默认路由配置:
zuul:
ignored-services: user-service,feign-service #关闭默认路由配置
负载均衡功能
- 多次调用http://localhost:8801/user-service/user/1进行测试,可以发现运行在8201和8202的user-service服务交替打印如下信息。
2022-09-24 10:31:58.738 INFO 11520 --- [nio-8202-exec-5] c.macro.cloud.controller.UserController : 根据id获取用户信息,用户名称为:macro
2022-09-24 10:32:00.356 INFO 11520 --- [nio-8202-exec-6] c.macro.cloud.controller.UserController : 根据id获取用户信息,用户名称为:macro
配置访问前缀
- 我们可以通过以下配置来给网关路径添加前缀,此处添加了/proxy前缀,这样我们需要访问http://localhost:8801/proxy/user-service/user/1才能访问到user-service中的接口。
zuul:
prefix: /proxy #给网关路由添加前缀
Header过滤及重定向添加Host
- Zuul在请求路由时,默认会过滤掉一些敏感的头信息,以下配置可以防止路由时的Cookie及Authorization的丢失:
zuul:
sensitive-headers: Cookie,Set-Cookie,Authorization #配置过滤敏感的请求头信息,设置为空就不会过滤
- Zuul在请求路由时,不会设置最初的host头信息,以下配置可以解决:
zuul:
add-host-header: true #设置为true重定向是会添加host请求头
查看路由信息
- 我们可以通过SpringBoot Actuator来查看Zuul中的路由信息。
- pom添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 修改application.yaml配置文件,开启查看路由的端点:
management:
endpoints:
web:
exposure:
include: 'routes'
- 通过访问http://localhost:8801/actuator/routes查看简单路由信息:
- 通过访问http://localhost:8801/actuator/routes/details查看详细路由信息:
过滤器
- 自定义过滤器
- 添加PreLogFilter类继承ZuulFilter
- 这是一个前置过滤器,用于在请求路由到目标服务前打印请求日志。
@Component
public class PreLogFilter extends ZuulFilter {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
/**
* 过滤器类型,有pre、routing、post、error四种。
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器执行顺序,数值越小优先级越高。
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 是否进行过滤,返回true会执行过滤。
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 自定义的过滤器逻辑,当shouldFilter()返回true时会执行。
*/
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String host = request.getRemoteHost();
String method = request.getMethod();
String uri = request.getRequestURI();
LOGGER.info("Remote host:{},method:{},uri:{}", host, method, uri);
return null;
}
}
- 过滤器功能演示
- 添加过滤器后,我们访问http://localhost:8801/user-service/user/1测试下,会打印如下日志。
2022-09-24 15:13:10.232 INFO 11040 --- [nio-8801-exec-7] com.macro.cloud.filter.PreLogFilter : Remote host:0:0:0:0:0:0:0:1,method:GET,uri:/user-service/user/1
- 禁用过滤器
- 我们可以对过滤器进行禁用的配置,配置格式如下:
zuul:
filterClassName:
filter:
disable: true
- 以下是禁用PreLogFilter的示例配置:
zuul:
PreLogFilter:
pre:
disable: true
Ribbon和Hystrix的支持
- 由于Zuul自动集成了Ribbon和Hystrix,所以Zuul天生就有负载均衡和服务容错能力,我们可以通过Ribbon和Hystrix的配置来配置Zuul中的相应功能。
- 可以使用Hystrix的配置来设置路由转发时HystrixCommand的执行超时时间:
hystrix:
command: #用于控制HystrixCommand的行为
default:
execution:
isolation:
thread:
#配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理
timeoutInMilliseconds: 1000
- 可以使用Ribbon的配置来设置路由转发时请求连接及处理的超时时间:
ribbon: #全局配置
ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
常用配置
zuul:
#给服务配置路由
routes:
user-service:
path: /userService/**
feign-service:
path: /feignService/**
#关闭默认路由配置
ignored-services: user-service,feign-service
#给网关路由添加前缀
prefix: /proxy
#配置过滤敏感的请求头信息,设置为空就不会过滤
sensitive-headers: Cookie,Set-Cookie,Authorization
#设置为true重定向是会添加host请求头
add-host-header: true
# 关闭重试机制
retryable: true
PreLogFilter:
pre:
#控制是否启用过滤器
disable: false