微服务网关:Spring Cloud Gateway —— Zuul
系列文章:
一 摘要
关于服务网关,我们在这篇文章中做了一个基础介绍。包括网关概念,Spring Cloud体系内的zuul1.x 和 zuul2.x(gateway)之间的相似和差别之处。本篇将通过官方示例来详细分析Spring Cloud Gateway的工作原理和使用方式。
二 Spring Cloud Gateway
2.1 定位
Spring Cloud Gateway提供一个用于为Spring WebFlux顶层构建API网关的库。旨在提供一个简单且高效的方式来路由到API,并为它们提供横切关注点例如:安全、监控/指标,和弹性。
2.2 特性
1)基于Spring Framework 5,Project Reactor和Spring Boot 2.0
2)能够匹配任意请求属性上的路由
3)谓词和过滤器特定于路由
4)断路器集成
5)Spring Cloud 服务发现客户端集成
6)易于编写谓词和过滤器
7)请求限流
8)路径重写
三 Spring Cloud Gateway示例
3.1 官方demo
JDK 1.8以上
Gradle 4+ 或 maven 3.2+
使用方法:
git clone https://github.com/spring-guides/gs-routing-and-filtering.git
也可以直接进入目录:gs-routing-and-filtering/complete,导入idea,自动加载依赖后,就可以启动工程。
3.2 导入时可能遇到的小问题
导入到idea后,run时,可能会报如下错误(无效的目标发行版):

pom.xml的java-version配置问题:


这两处的版本改为本地jdk版本即可。我这里是jdk1.8,所以修改如下:
8
之后启动正常。
如果还有报错,看一下compiler,是否如下所示:

如果是,那么改为选择8即可。
3.3 测试-使用方式
book服务端口为8090,内部提供了两个接口:
@RequestMapping(value = "/available")
public String available() {
return "Spring in Action";
}
@RequestMapping(value = "/checked-out")
public String checkedOut() {
return "Spring Boot in Action";
}
可以通过http://localhost:8090/available直接访问,或者通过gateway+服务名访问:http://localhost:8080/books/available
服务名和地址配置在application.properties:
zuul.routes.books.url=http://localhost:8090
ribbon.eureka.enabled=false
server.port=8080
请求成功后,在gateway的控制台日志中能够看到请求记录:
三 Gateway原理及过程分析
3.1 相关代码
回过头来看一下代码,结构非常简单,
1)book服务只有RoutingAndFilteringBookApplication.java一个类,启动SpringBootApplication并暴露两个Rest接口;
2)gateway除了RoutingAndFilteringGatewayApplication.java外,增加了一个SimpleFilter:
public class SimpleFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(SimpleFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
return null;
}
}
其中,run()方法中,我们可以看到获取到了调用的请求,并做了日志打印。
3.2 注解生效过程解析
在gateway工程,自定义的SimpleFilter没有加任何注解,只是继承了ZuulFilter,那么请求时是通过怎样的调用链走到了这里的?应用入口文件RoutingAndFilteringGatewayApplication只是正常的SpringApplication.run()方法,唯一与之前的差异是有@EnableZuulProxy的注解,那么显然网关使用的Filter就是通过注解来实现的过滤了。我们接下来详细分析这个过程。
3.2.1 EnableZuulProxy注解
@EnableCircuitBreaker
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({ZuulProxyMarkerConfiguration.class})
public @interface EnableZuulProxy {
}
注解比较简单,前面还是标准的Target、Retention,但引入了ZuulProxyMarkerConfiguration.class,我们继续看一下这个类:
3.2.2 ZuulProxyMarkerConfiguration
@Configuration(
proxyBeanMethods = false
)
public class ZuulProxyMarkerConfiguration {
public ZuulProxyMarkerConfiguration() {
}
@Bean
public ZuulProxyMarkerConfiguration.Marker zuulProxyMarkerBean() {
return new ZuulProxyMarkerConfiguration.Marker();
}
class Marker {
Marker() {
}
}
}
这里也没有很多业务逻辑,只是1)定义了一个内部类Marker,而且仅有无参构造方法;2)提供zuulProxyMarkerBean()方法用于返回Marker实例作为Bean。
3.2.3 ZuulProxyAutoConfiguration
从注解只能看到上面一步,那么接下来就考虑自动装配了。我们找到了ZuulProxyAutoConfiguration这个类,比较直接,从名字就能看出是负责ZuulProxy自动装配的:
@Configuration(
proxyBeanMethods = false
)
@Import({RestClientRibbonConfiguration.class, OkHttpRibbonConfiguration.class, HttpClientRibbonConfiguration.class, HttpClientConfiguration.class})
@ConditionalOnBean({Marker.class})
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
这里的Marker,就是ZuulProxyMarkerConfiguration.Marker

我们回顾一下自动装配,需要满足两个条件:1)spring.factories;2)@ConditionalOnxxx。我们在启动类加上了@EnableZuulProxy 注解,之后ZuulProxyAutoConfiguration 就会被自动装配。
接下来看一下zuul的属性配置是如何加载的,这里就涉及到了ZuulProperties。
3.2.4 ZuulProperties
我们在.yml或.properties文件配置的参数都会被加载到这个类中。其中包含的大量参数和方法,这里不一一列举,重点看一下构造方法:
public ZuulProperties() {
this.ribbonIsolationStrategy = ExecutionIsolationStrategy.SEMAPHORE;
this.semaphore = new ZuulProperties.HystrixSemaphore();
this.threadPool = new ZuulProperties.HystrixThreadPool();
this.setContentLength = false;
this.includeDebugHeader = false;
this.initialStreamBufferSize = 8192;
}
从中我们可以看出zuul和ribbon和hystrix集成的一些端倪,支持了Hystrix的信号量和线程池两种模式。
3.3 Filter执行过程解析
接下来我们继续分析执行过滤器的过程。
3.3.1 ZuulServlet
ZuulServlet在init()中完成初始化流程并协调ZuulFilter的执行。

3.3.2 FilterProcessor
这是执行过滤器的核心类。
这里面定义了4个用来判断当前filter的FilterType()返回类型的方法。
当客户端在发送请求时Zuul会开辟一个线程池 线程执行时ZuulServlet.server方法拦截到对应请求此时该方法会调用FilterProcessor中的方法依次初始化zuul的pre,rout,post,error各个阶段的过滤器。当过滤器执行过滤完请求后,请求响应 执行结束。
四 总结
本文基于zuul2.2.6.RELEASE版本,通过一个官方示例工程了解了Spring Cloud Gateway(Zuul2)的结构、使用方法和主要执行过程。后续将结合Spring Cloud的配置中心、Hystrix继续做深入分析,并加入与nacos等其他注册中心/网关的对比。