Zuul是Netflix开源的微服务网关,主要功能是路由转发和过滤器,并和Ribbon结合实现了负载均衡的功能。本文主要介绍Zuul的基本使用,文中使用到的软件版本:Spring Boot 2.2.5.RELEASE、Spring Cloud Hoxton.SR3、Java 1.8.0_191。
1、Zuul功能架构
zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。
2、过滤器
2.1、过滤器生命周期
2.2、过滤器类型
(1)PRE:这种过滤器在请求被路由之前调用,可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
(2)ROUTING:这种过滤器将请求路由到微服务,这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
(3)POST:这种过滤器在路由到微服务以后执行,这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
(4)ERROR:在其他阶段发生错误时执行该过滤器。
2.3、默认过滤器
2.3.1、pre过滤器
ServletDetectionFilter:用来检测当前请求是通过Spring的DispatcherServlet处理运行的,还是通过ZuulServlet来处理运行的
Servlet30WrapperFilter:将原始的HttpServletRequest包装成Servlet30RequestWrapper对象
FormBodyWrapperFilter:将符合要求的请求体包装成FormBodyRequestWrapper对象
DebugFilter:将当前请求上下文中的debugRouting和debugRequest参数设置为true
PreDecorationFilter:判断当前请求上下文中是否存在forward.do和serviceId参数,如果都不存在,那么它就会执行具体过滤器的操作
2.3.2、route过滤器
route过滤器:对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效
SimpleHostRoutingFilter:该过滤器只对请求上下文存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效
SendForwardFilter:该过滤器只对请求上下文中存在的forward.do参数进行处理请求,即用来处理路由规则中的forward本地跳转
2.3.3、post过滤器
SendErrorFilter:该过滤器利用上下文中的错误信息来组成一个forward到api网关/error错误端点的请求来产生错误响应
SendResponseFilter:该过滤器的处理逻辑就是利用上下文的响应信息来组织需要发送回客户端的响应内容
3、Zuul功能
认证和安全:识别每个需要认证的资源,拒绝不符合要求的请求
性能监测:在服务边界追踪并统计数据,提供精确的生产视图
动态路由:根据需要将请求动态路由到后端集群
压力测试:逐渐增加对集群的流量以了解其性能
Load Shedding:为每种类型的请求分配容量并删除超过限制的请求
静态资源处理:直接在边缘构建一些响应,而不是将它们转发到内部集群
4、使用
4.1、引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
4.2、启动类
package com.inspur.scdemo.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableZuulProxy public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }
4.3、配置zuul
zuul:
ignored-patterns: /**/test/** #忽略所有包含/test/的地址请求
ignored-services: '*' #'*',忽略的所有服务;如果不配置该项,则默认为所有服务生成路由:http://zull-ip:zull-port/serviceId/**
routes:
scdemo-server: /scdemo-server/** #路由到scdemo-server服务
scdemo-server-url:
path: /scdemo-server-url/**
url: http://localhost:9001/ #直接url指定地址
scdemo-server-forward:
path: /scdemo-server-forward/**
url: forward:/test #跳转到http://zull-ip:zull-port/test
假设zuul的端口为9007,上面配置了三种路由方式:
a、路由到服务:http://localhost:9007/scdemo-server/user/getUser?id=1将路由到http://scdemo-server-host:scdemo-server-port//user/getUser?id=1
b、路由到url:这些简单的url路由不会作为HystrixCommand来执行,也不会使用Ribbon对多个URL进行负载均衡。http://localhost:9007/scdemo-server-url/user/getUser?id=1将路由到http://localhost:9001/user/getUser?id=1
c、forward跳转到在zuul中定义的请求:http://localhost:9007/scdemo-server-forward/user/getUser?id=1将路由到http://localhost:9007/test/user/getUser?id=1
4.4、自定义过滤器
如果前端发起请求没有带token将允许请求
package com.inspur.scdemo.zuul.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component public class GlobalFilter extends ZuulFilter { private Logger logger = LoggerFactory.getLogger(GlobalFilter.class); /**设置过滤类型*/ @Override public String filterType() { return FilterConstants.ROUTE_TYPE; } /**设置过过滤器优先级*/ @Override public int filterOrder() { return 0; } /**设置过过滤器优先级*/ @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String token = request.getHeader("token"); //TODO:校验token是否正确 if (StringUtils.isEmpty(token)) { //返回错误信息 context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); context.setResponseBody(HttpStatus.UNAUTHORIZED.getReasonPhrase()); context.setSendZuulResponse(false); } return null; } }