zuul是什么
zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
zuul的例子可以参考 netflix 在github上的 simple webapp,可以按照netflix 在github wiki 上文档说明来进行使用(https://github.com/Netflix/zuul/wiki)。
zuul的工作原理
过滤器机制
zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。
zuul把Request route到 用户处理逻辑 的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等。
Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行。
Zuul的过滤器之间没有直接的相互通信,他们之间通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据。
Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面。Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中以便过滤请求使用。
下面有几种标准的过滤器类型:
Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
- PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。
内置的特殊过滤器
zuul还提供了一类特殊的过滤器,分别为:StaticResponseFilter和SurgicalDebugFilter
StaticResponseFilter:StaticResponseFilter允许从Zuul本身生成响应,而不是将请求转发到源。
SurgicalDebugFilter:SurgicalDebugFilter允许将特定请求路由到分隔的调试集群或主机。
自定义的过滤器
除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。
例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
过滤器的生命周期
Zuul请求的生命周期如图,该图详细描述了各种类型的过滤器的执行顺序。
zuul 能做什么
Zuul可以通过加载动态过滤机制,从而实现以下各项功能:
- 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
- 审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
- 动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
- 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
- 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
- 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
- 多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
搭建zuul服务
首先搭建Eureka server和一个Eureka-Provider
在上一节从零开始搭建spring-cloud(1) ----eureka中拷贝spring-cloud-eureka-server到这个项目中。
在上一节从零开始搭建spring-cloud(1) ----eureka中拷贝spring-cloud-eureka-provider-A-1到这个项目中。
搭建zuul服务
新建项目spring-cloud-zuul,pom.xml内容如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.vincent</groupId>
<artifactId>spring-cloud-zuul</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
</project>
我们都知道,在zuul过滤器里PRE_TYPE类型是在路由前执行的,所以我要给大家演示配置三个PRE_TYPE类型的过滤器,按照顺序依次处理不同的业务。以及,三个PRE_TYPE类型过滤器中任意一个出现异常时他的下游业务应该怎么处理。
我们首先看一下项目的目录结构:
各个Filter内容如下:
ErrorFilter.java
package com.vincent.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ERROR_TYPE;
/**
* @author vincent
* @time 2019-06-23 16:15
*/
@Component
public class ErrorFilter extends ZuulFilter {
@Override
public String filterType() {
return ERROR_TYPE;
}
@Override
public int filterOrder() {
return -1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
System.out.println("这是ErrorFilter");
return null;
}
}
FirstPreFilter.java
package com.vincent.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* @author vincent
* @time 2019-06-23 16:17
*/
@Component
public class FirstPreFilter extends ZuulFilter {
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
System.out.println("这是第一个自定义Zuul Filter!");
return null;
}
}
SecondPreFilter.java
package com.vincent.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* @author vincent
* @time 2019-06-23 16:18
*/
@Component
public class SecondPreFilter extends ZuulFilter {
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
System.out.println("这是SecondPreFilter!");
//从RequestContext获取上下文
RequestContext ctx = RequestContext.getCurrentContext();
//从上下文获取HttpServletRequest
HttpServletRequest request = ctx.getRequest();
//从request尝试获取a参数值
String a = request.getParameter("a");
//如果a参数值为空则进入此逻辑
if (null == a) {
//对该请求禁止路由,也就是禁止访问下游服务
ctx.setSendZuulResponse(false);
//设定responseBody供PostFilter使用
ctx.setResponseBody("{\"status\":500,\"message\":\"a param is null\"}");
//logic-is-success保存于上下文,作为同类型下游Filter的执行开关
ctx.set("logic-is-success", false);
//到这里此Filter逻辑结束
return null;
}
//设置避免报空
ctx.set("logic-is-success", true);
return null;
}
}
ThirdPreFilter.java
package com.vincent.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* @author vincent
* @time 2019-06-23 16:19
*/
@Component
public class ThirdPreFilter extends ZuulFilter {
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 3;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
//从上下文获取logic-is-success值,用于判断此Filter是否执行
return (boolean)ctx.get("logic-is-success");
}
@Override
public Object run() throws ZuulException {
System.out.println("这是ThirdPreFilter!");
//从RequestContext获取上下文
RequestContext ctx = RequestContext.getCurrentContext();
//从上下文获取HttpServletRequest
HttpServletRequest request = ctx.getRequest();
//从request尝试获取b参数值
String b = request.getParameter("b");
//如果b参数值为空则进入此逻辑
if (null == b) {
//对该请求禁止路由,也就是禁止访问下游服务
ctx.setSendZuulResponse(false);
//设定responseBody供PostFilter使用
ctx.setResponseBody("{\"status\":500,\"message\":\"b param is null\"}");
//logic-is-success保存于上下文,作为同类型下游Filter的执行开关,假定后续还有自定义Filter当设置此值
ctx.set("logic-is-success", false);
//到这里此Filter逻辑结束
return null;
}
return null;
}
}
PostFilter.java
package com.vincent.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE;
/**
* @author vincent
* @time 2019-06-23 16:20
*/
public class PostFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
System.out.println("这是PostFilter!");
//从RequestContext获取上下文
RequestContext ctx = RequestContext.getCurrentContext();
//处理返回中文乱码
ctx.getResponse().setCharacterEncoding("GBK");
//获取上下文中保存的responseBody
String responseBody = ctx.getResponseBody();
//如果responseBody不为空,则说明流程有异常发生
if (null != responseBody) {
//设定返回状态码
ctx.setResponseStatusCode(500);
//替换响应报文
ctx.setResponseBody(responseBody);
}
return null;
}
}
新建App.java,内容如下:
@SpringBootApplication
@EnableZuulProxy
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
我们开始配置application.properties,内容如下:
eureka.client.service-url.defaultZone=http://127.0.0.1:8080/eureka/
spring.application.name=service-zuul
zuul.routes.users.url=http://localhost:8081/
zuul.routes.users.path=/**
zuul.ignored-headers=Access-Controller-Allow-Credentials, Access-Control-Allow-Origin
zuul.host.connect-timeout-millis=10000000
zuul.host.socket-timeout-millis=10000000
server.port=8082
这里zuul的匹配规则是通过url进行匹配。
zuul.host.connect-timeout-millis=10000000
zuul.host.socket-timeout-millis=10000000这两句的作用是防止服务提供方返回响应的时间过长
先添加参数访问微服务
控制台输出结果如下:
这是第一个自定义Zuul Filter!
这是SecondPreFilter!
这是PostFilter!
添加参数a访问,
控制台输出结果如下:
这是第一个自定义Zuul Filter!
这是SecondPreFilter!
这是ThirdPreFilter!
这是PostFilter!
添加参数a和参数b访问
控制台输出结果如下:
2019-06-23 17:29:44.348 INFO 3363 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
这是第一个自定义Zuul Filter!
这是SecondPreFilter!
这是ThirdPreFilter!
这是PostFilter!
zuul转发有两种配置
根据 eureka server 的serviceId 转发
zuul.routes.serviceName.path=/exampleService/**
zuul.routes.serviceName.serviceId=serviceId
注:
zuul.routes 是固定的
serviceName 是可以随便写的,但最好根据要路由的服务取
serviceId 是 eureka 服务注册时的名称
exampleService 是前端请求某个微服务的一个公共的路径名,如/users
而微服务在 Controller层的 RequestMapping 注解中可以不包含/users
例如本项目中的配置如下:
zuul.routes.myservice.path=/**
zuul.routes.myservice.service-id=service-provider-A
根据具体 URL 转发:
zuul.routes.serviceName.path=/exampleService/**
zuul.routes.serviceName.url=http://127.0.0.1:8080/
如果项目尚未使用eureka,可以采用了第二种转发规则。这种转发有很多好处,最大的好处就是可以很好地过渡到Spring Cloud,使用Zuul可以直接HTTP调用,方便很多。
例如本项目中的URL规则转发如下:
zuul.routes.users.url=http://localhost:8081/
zuul.routes.users.path=/**