目录
一、zuul的功能概览
上一篇中讲到了zuul是一个基于JVM的内存路由器,并且是一个基于服务端的负载均衡,其实我们用它主要是作为Api的网关。比如我们之前的电商项目,包含了商品、价格、权限、商家、库存等大小70多个系统服务,则想Zuul这样的Api官网还是非常有必要的。我们再大体看一下它能做什么:
安全和监控: 识别每一个需要认证的资源,拒绝不符合的请求(比如之前项目app和后端服务约定好的加密规则就可以在这一层进行处理)。
性能监控:在服务边界追踪并统计数据,提供精确的生产视图(这个就比较灵活了,自定义上报等实现)。
动态路由:根据需要将请求动态的路由到后端不同的集群中
负载卸载:预先为每一种类型的请求分配容量,当请求超过容量的时候做出相应的处理
静态资源处理:直接在边界响应数据
二、常用路由策略
1、默认的路由规则为:http://ZUULHOST:ZUULPORT/serviceId/**,在上一篇中已经演示了
2、常用bootstrap.properties可配置策略
zuul.routes.ribbon-user-consumer-eureka = /kevin-user/** : 把注册的eureka服务名称,更换为kevin-user
zuul.ignored-services=user-provider : 忽略服务
zuul.ignoredPatterns: /**/user/* : 按照规则忽略服务
zuul.prefix: /api : 路由前缀
3、使用正则匹配 PatternServiceRouteMapper
@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
/**
* A RegExp Pattern that extract needed information from a service ID. Ex :
* "(?<name>.*)-(?<version>v.*$)"
*/
//private Pattern servicePattern;
/**
* A RegExp that refer to named groups define in servicePattern. Ex :
* "${version}/${name}"
*/
//private String routePattern;
return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
三、高可用
单节点的Zuul肯能会挂掉,则可以有以下方案: 可以在Zuul前面加一层代理,如Nginx。后面进行实现
四、ZuulFilter
上一篇中简单的利用filter实现了日志的打印,知道了要实现一个自定义的Filter需要:
1)、自定义的Filter实现自ZuulFilter并实现其abstract的方法
2)、将自己的Filter注册成一个Bean
1、Filter Types(Filter的类型)
Filter按照不同的作用分为下面四中类型,并且四种类型是按照一个请求进入的顺序排列的。
1)、Pre Filter
在请求路由到目标之前执行,一般用于请求的认证和负载均衡、日志记录。
2)、Routing Filter
处理目标请求,这里使用Apache HttpClient或者Netflix Robbin构造日志对目标的Http请求。官网中解释大多情况是在这里将请求和响应数据转换为客户机所需的模型和从模型转换为模型,比如Servlet请求转换为Okhttp请求。
3)、Error Filter
整个流程出错时执行,可以在前面的路由验证的时候抛出自定义异常,在这一层进行捕获然后按照类型进行抛出不同的信息,或者转向不同的视图。
4)、Post Filter
在目标执行返回后执行,一般会在此步骤加响应头,收集统计和性能数据等。
2、实现ZuulFilter的抽象方法
1)、filterType方法
filterType方法返回定义的Filter Types,按照上面的类型,分别返回: "pre"、"route"、"error"、"post"
2)、shouldFilter方法
shouldFilter返回是否,控制是否开启此Filter
3)、run方法
run方法中是自己的业务逻辑
4)、filterOrder方法
filterOrder方法返回的是一个排序的数据,数字越小则在该类型中优先进入。看一下ZuulFilter实现了Comparable接口,并且通过compareTo方法进行比较我们返回的filterOrder值。所以在Zuul中的执行顺序应该是(具体实现可以参见我在分析Zuul Filter责任链模式的博客:责任链模式-实现和优秀源码使用分析):
3、Filter实现
1)、在之前的demo中再添加一个pre类型的Filter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import io.micrometer.core.instrument.util.MathUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Random;
public class PreRequestLog2Filter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
Random random = new Random();
if (random.nextBoolean()) {
System.out.println(String.format("pre filterOrder = 2 %s request to %s",
request.getMethod(), request.getRequestURL()));
} else {
System.out.println("验证参数错误!");
throw new RuntimeException("验证参数错误!");
}
return null;
}
}
2)、添加一个route类型的Filter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpServletRequest;
public class RouteRequestFilter extends ZuulFilter {
@Override
public String filterType() {
return "route";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
System.out.println("route filter 转换请求信息了!");
return null;
}
}
3)、添加一个error类型的filter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpServletRequest;
public class ErrorFilter extends ZuulFilter {
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
System.out.println("到了ErrorFilter,我需要进行处理,转向错误视图!");
return null;
}
}
4)、添加一个post类型的filter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
public class PostInfoFilter extends ZuulFilter {
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
String uuid = UUID.randomUUID().toString();
servletResponse.addHeader("X-Sample", uuid);
System.out.println("设置返回的header值为:" + uuid);
return null;
}
}
5)、启动类中将所有的filter注册成为Bean
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
/**
* 路由Api gateway
* 默认路由规则为: http://ZUUL-HOST:ZUUL-PORT/serviceId/**
*
* @author kevin
* @date 2019/5/30 9:15
* @since 1.0
*/
@SpringBootApplication
@EnableZuulProxy
public class ZuulDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApplication.class, args);
}
@Bean
public PreRequestLogFilter preRequestLogFilter(){
return new PreRequestLogFilter();
}
@Bean
public PreRequestLog2Filter preRequestLog2Filter(){
return new PreRequestLog2Filter();
}
@Bean
public RouteRequestFilter routeRequestFilter(){
return new RouteRequestFilter();
}
@Bean
public PostInfoFilter postInfoFilter(){
return new PostInfoFilter();
}
@Bean
public ErrorFilter errorFilter(){
return new ErrorFilter();
}
}
6)、启动服务调用接口(证明了上面图的调用流程)
1、当请求正常时调用顺序为:
PreRequestLogFilter > PreRequestLog2Filter > RouteRequestFilter > PostInfoFilter
日志如下:
send GET request to http://localhost:8800/ribbon-user-consumer-eureka/getRemoteUser/1
pre filterOrder = 2 GET request to http://localhost:8800/ribbon-user-consumer-eureka/getRemoteUser/1
route filter 转换请求信息了!设置返回的header值为:d78ed7c1-451a-40c9-901a-af0dca23b1f8
2、当请求报错时调用顺序为:
PreRequestLogFilter > PreRequestLog2Filter > ErrorFilter > PostInfoFilter
日志如下:
send GET request to http://localhost:8800/ribbon-user-consumer-eureka/getRemoteUser/1
验证参数错误!
2019-05-30 15:34:53.522 WARN 1044 --- [nio-8800-exec-1] o.s.c.n.z.filters.post.SendErrorFilter : Error during filteringcom.netflix.zuul.exception.ZuulException: Filter threw Exception
at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:227) ~[zuul-core-1.3.1.jar:1.3.1]
at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157) ~[zuul-core-1.3.1.jar:1.3.1]
。。。。。。。。。。。。。。。。。。。。。。。
at java.lang.Thread.run(Thread.java:744) [na:1.8.0]
Caused by: java.lang.RuntimeException: 验证参数错误!
at com.kevin.zuuldemo.PreRequestLog2Filter.run(PreRequestLog2Filter.java:36) ~[classes/:na]
at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:117) ~[zuul-core-1.3.1.jar:1.3.1]
at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193) ~[zuul-core-1.3.1.jar:1.3.1]
... 61 common frames omitted到了ErrorFilter,我需要进行处理,转向错误视图!
设置返回的header值为:e4154c52-6b1c-4227-8a49-21b147e2a5bb