首先有必要了解一下什么是Zuul,它和Spring Cloud有什么关系。
Zuul在Spring Cloud中承担着网关的职责,可以理解为客户端和服务端交互中的唯一通道。所有的客户端请求都会首先发送到网关,而后路由转发到对应的微服务。同时,Zuul还提供了诸如服务聚合、权限校验等,另外也对客户端与服务端的交互起到了解耦的作用。
我们可以通过一个具体的场景来理解一下。假设需要开发一个校园图书管理系统,在客户端可能发起登录、查看图书列表、进行订阅等请求,如下图所示:
而其中,即使是非本校的学生也可以在客户端查询有哪些图书,但是只有登录成功的本校学生才能发起借阅。这时客户端需要做的事情就多了,既要区分不同的请求发往哪个服务,还要判断当前的权限是否可以调用这个服务。另外,在客户端进行权限的控制也存在安全隐患,前后端的职责划分也不够明确。在引入Zuul网关之后的效果是这样:
服务的路由以及权限的校验统统在网关层面完成即可。这样还有一个好处就是后续即使把图书查询服务进行拆分,变成中文图书查询与英文图书查询,或者进行其他的服务合并,对于客户端也基本是无感知了。
了解了Zuul的功能之后,我们来探究一下Zuul的原理。
Zuul的核心是一组过滤器,而Zuul的绝大部分功能也都是通过这一组过滤器实现的。Zuul提供了对这些过滤器进行动态加载、编译以及运行的框架。这一组过滤器包括pre、routing、post、error等,通过一张图片来了解一下这些过滤器分别在一笔请求的哪些环节生效:
从上图中可以看到对应的几类过滤器分别是如下的作用:
PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份认证、在集群中选择具体需要请求的微服务、记录日志信息等。
ROUTING:这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务(题外话:这里也能看出,Ribbon是在客户端实现负载均衡,这一点和Nginx不同)。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
ERROR:在其他阶段发生错误时执行该过滤器。
Spring Cloud借助Eureka进行服务注册与发现,微服务的访问通过REST请求。Zuul的访问方式也是REST,其URL地址默认格式为:
http://zuulHostIp:port/要访问的服务名称/服务中的URL
我这里为了方便在一个测试的微服务项目中为Zuul网关添加一个Module(在不在一个项目无所谓,大家通过同一个Eureka服务进行注册发现即可):
pom中需要添加Zuul的相关依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> <version>1.4.7.RELEASE</version> </dependency>
application.yml中指定网关服务名、连接的Eureka服务信息等:
server: port: 10290 spring: application: name: spring-cloud-zuul-gateway eureka: client: service-url: defaultZone: http://127.0.0.1:9090/eureka fetch-registry: true register-with-eureka: true
接下来我们添加路由配置来验证Zuul的路由转发功能:
zuul:
routes:
spring-cloud-service-provider:
path: /provider/**
url: http://127.0.0.1:10090
spring-cloud-service-provider为路由规则的条目名称。path为向Zuul网关进行请求时紧跟在端口后的服务名称,**表示具体服务下的url。而url项则需要填写目的服务的真实地址端口信息。
创建应用时需要添加自动发现以及Zuul的注解:
@SpringCloudApplication @EnableZuulProxy @EnableDiscoveryClient public class ZuulGatewayApplication { public static void main(String[] args) { SpringApplication.run(ZuulGatewayApplication.class, args); } }
启动Eureka服务、目的服务以及Zuul网关服务验证一下,没问题:
接下来我们玩一下Zuul的核心,过滤器。可以试着创建一个PRE过滤器,来打印请求的详细信息。
可以看到在继承ZuulFilter类时需要实现如下四个方法:
filterType:表示过滤器类型,值域分别对应不同的生命周期也就是pre、error、post以及route。
filterOrder:同种类(生命周期)过滤器的执行优先级,按照返回值升序进行优先级的确定。
shouldFilter:表示当前过滤器是否生效。
run:过滤器的具体执行逻辑。
补充之后的过滤器代码如下所示:
@Component public class PreLogFilter extends ZuulFilter { public String filterType() { return "pre"; } public int filterOrder() { return 0; } public boolean shouldFilter() { return true; } public Object run() throws ZuulException { RequestContext rc = RequestContext.getCurrentContext(); HttpServletRequest request = rc.getRequest(); System.out.println("PreLogFilter enters with [url=" + request.getRequestURL() + "]..."); return null; } }
重新启动Zuul网关服务之后再次进行请求,可以看到控制台打印如下输出:
参考资料:
https://www.cnblogs.com/jing99/p/11696192.html
https://zhuanlan.zhihu.com/p/138943446
https://blog.csdn.net/luckystar_99/article/details/105114198