SpringCloud - Zuul路由网关使用详解

614 阅读12分钟

Zuul是什么
Zuul是从设备和网站到Netflix流应用程序后端的所有请求的前门。作为边缘服务应用程序,Zuul旨在实现动态路由,监控,弹性和安全性。它还可以根据需要将请求路由到多个合适的服务弹性收缩组。

为什么创建Zuul?

Netflix API流量的数量和多样性有时会导致生产问题迅速而且没有任何警告。我们需要一个允许我们快速改变行为的系统,以便对这些情况做出反应。

Zuul使用一系列不同类型的过滤器,使我们能够快速灵活地将功能应用于我们的边缘服务。这些过滤器可帮助我们执行以下功能:

身份验证和安全性 - 识别每个资源的身份验证要求并拒绝不满足这些要求的请求。

洞察和监控 - 在边缘跟踪有意义的数据和统计数据,以便为我们提供准确的生产视图。

动态路由 - 根据需要动态地将请求路由到不同的后端群集。

压力测试 - 逐渐增加群集的流量以衡量性能。

Load Shedding - 为每种类型的请求分配容量并删除超过限制的请求。

静态响应处理 - 直接在边缘构建一些响应,而不是将它们转发到内部集群

多区域弹性 - 跨AWS区域路由请求,以使我们的ELB使用多样化,并使我们的优势更接近我们的成员。

综上,Zuul包含了对请求的路由和过滤两个最主要的功能。其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。

Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的微服务的访问都是通过Zuul跳转后获得。

Zuul官网地址:github.com/Netflix/zu.…
Zuul的基本配置
① 创建Module/microservicecloud-zuul-gateway-9527

pom依赖如下:

[XML] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<font style="color:rgb(79, 79, 79)"><font face="&quot;"><font style="font-size:16px"><dependencies>
<!-- zuul路由网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<!-- Eureka服务注册 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- actuator监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix熔断 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- 日常标配 -->
<dependency>
<groupId>com.web.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Boot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 热部署插件 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies></font></font></font>

查看spring-cloud-starter-zuul依赖如下:

[XML] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<font style="color:rgb(79, 79, 79)"><font face="&quot;"><font style="font-size:16px"><dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-archaius</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.zuul</groupId>
<artifactId>zuul-core</artifactId>
</dependency>
</dependencies></font></font></font>

spring-cloud-starter-hystrix:该依赖用来在网关服务中实现对微服务转发时候的保护机制,通过线程隔离和断路器,防止微服务的故障引发API网关资源无法释放,从而影响其他应用的对外服务。

spring-cloud-starter-ribbon:该依赖用来实现在网关服务进行路由转发时候的客户端负载均衡以及请求重试。

spring-boot-starter-actuator:该依赖用来提供常规的微服务管理端点。另外,在Spring Cloud Zuul中还特别提供了/routes端点来返回当前的所有路由规则。

yml配置文件

[XML] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 9527

spring:
application:
name: microservicecloud-zuul-gateway

eureka:
client:
service-url:
defaultZone: [url=eureka7001.com:7001/eureka]eureka7001.com:7001/eureka[/url],[url=eureka7002.com:7002/eureka]eureka7002.com:7002/eureka[/url],[url=eureka7003.com:7003/eureka]eureka7003.com:7003/eureka[/url]
instance:
instance-id: gateway-9527.com
prefer-ip-address: true


info:
app.name: web-microcloud
company.name: [url=www.web.com]www.web.com[/url]
build.artifactId:


build.version:


hosts文件修改


本地域名解析:

[Java] 纯文本查看 复制代码
?
1
2
3
4
5
6
<font style="color:rgb(79, 79, 79)"><font face="&quot;"><font style="font-size:16px"># spring cloud eureka
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com

spring cloud zuul

127.0.0.1 myzuul.com</font></font></font>

主启动类如下

[Java] 纯文本查看 复制代码
?
1
2
3
4
5
6
7
8
9
<font style="color:rgb(79, 79, 79)"><font face="&quot;"><font style="font-size:16px">@SpringBootApplication
@EnableZuulProxy
public class Zuul_9527_StartSpringCloudApp
{
public static void main(String[] args)
{
SpringApplication.run(Zuul_9527_StartSpringCloudApp.class, args);
}
}</font></font></font>

此时整个项目架构如下图:

测试


启动三个服务集群、服务提供者8001和路由9527。

不用路由直接测试:http://localhost:8001/dept/get/1

使用路由测试:myzuul.com:9527/microservicecloud-dept/dept/get/1
(默认使用服务名进行跳转)

路由映射规则实例
如对访问myzuul.com:9527/microservicecloud-dept/dept/get/1请求进行转发和加固。

修改yml文件

配置对应的path和serviceId,符合/mydept/**规则的请求都会被转发到microservicecloud-dept服务的实例上

[XML] 纯文本查看 复制代码
?
1
2
3
4
zuul:
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**

此时,请求转变为myzuul.com:9527/mydept/dept/get/1。

此时将/microservicecloud-consumer-dept-feign启动,测试如下:

此时两种方式都可以访问:

myzuul.com:9527/microservicecloud-dept/dept/get/1

myzuul.com:9527/mydept/dept/get/1

上面配置方式就是面向服务的路由,根据配置的映射关系,网关将会转发到某个服务的实例上面。如果某个服务有多个实例,Zuul默认使用轮询的负载均衡策略。

当然也可以直接配置url:

[XML] 纯文本查看 复制代码
?
1
2
3
4
zuul:
routes:
mydept.url: http://loclhost:8001/
mydept.path: /mydept/**

该配置定义了发往API网关服务的请求中,所有符合/mydept/**规则的访问都将被路由转发到http://loclhost:8001/地址上。mydept是路由的名字可以任意定义,但是一组path和url映射关系的路由名要相同。不过通常建议使用面向服务的路由。

更简洁的面向服务路由配置

对于面向服务的路由配置,除了使用path与serviceId映射的配置方式之外,还有一种更简洁的配置方式:

[XML] 纯文本查看 复制代码
?
1
zuul.routes.<serviceId>=<path>

其中serviceId指定路由的具体服务名,path用来配置匹配的请求表达式。

[XML] 纯文本查看 复制代码
?
1
2
3
4
zuul:
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**

等价于如下:

[XML] 纯文本查看 复制代码
?
1
zuul.routes.microservicecloud-dept=/mydept/**

服务路由的默认规则

当我们为Spring Cloud Zuul构建的API网关服务引入Spring Cloud Eureka之后,它为Eureka中的每个服务都自动创建一个默认路由规则。这些默认路由规则的path会使用serviceId配置的服务名作为请求前缀。

[XML] 纯文本查看 复制代码
?
1
2
zuul.routes.microservicecloud-dept.path=/microservicecloud-dept/**
zuul.routes.microservicecloud-dept.serviceId=microservicecloud-dept

由于默认情况下所有Eureka上的服务都会被Zuul自动地创建映射关系来进行路由,这会使得一些我们不希望对外开发的服务也可能被外部访问。这时就需要zuul.ignored-services参数来设置一个服务名匹配表达式来定义不自动创建路由的规则。

如何忽略真实服务名?


单服务忽略设置如下:
[XML] 纯文本查看 复制代码
?
1
2
3
4
5
zuul:
ignored-services: microservicecloud-dept
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**

此时访问myzuul.com:9527/microservicecloud-dept/dept/get/1将会出现Error page。myzuul.com:9527/mydept/dept/get/1 正常。

忽略所有服务名
[XML] 纯文本查看 复制代码
?
1
2
3
4
5
zuul:
ignored-services: "*"
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**

这时Zuul将对所有的服务都不自动创建路由规则,在这种情况下,就要在配置文件中逐个为需要路由的服务添加映射规则,只有在配置文件中出现的映射规则会被创建路由,而从Eureka中获取的其他服务,Zuul将不会再为它们创建路由规则。
设置请求统一前缀
[XML] 纯文本查看 复制代码
?
1
2
3
4
5
6
zuul:
prefix: /web
ignored-services: "*"
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**

此时myzuul.com:9527/mydept/dept/get/1将不可访问,请求转变为myzuul.com:9527/web/mydept/dept/get/1。

Zuul如何根据服务名找到URL?
其实API网关也是Eureka服务治理下的一个普通的微服务应用,它除了将自己注册到Eureka服务注册中心上之外,也会从注册中心获取所有服务以及它们的实例清单。所以在Eureka的帮助下,API网关服务本身就已经维护了系统中所有的serviceId与实例地址的映射关系。当有外部请求到达api网关的时候,根据请求的URL路径找到最佳匹配的path规则,api网关就可以知道要将该请求路由到哪个具体的serviceId上去。

由于在API网关中已经知道serviceId对应服务实例的地址清单,那么只需要通过Ribbon的负载均衡策略,直接在这些清单中选择一个具体额实例进行转发就能完成路由工作。
请求过滤每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口都对它们开发。为了实现对客户端请求的安全校验和权限控制,最简单的方法就是为每个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。而这种方法通常是不可取的,这样的实现方式会使得相似的校验逻辑代码被分散到了各个微服务中去,冗余代码的出现是我们不希望看到的。所以,比较好的做法是将这些校验逻辑剥离出去,构建出一个独立的鉴权服务。

最好的做法是通过前置的网关服务来完成这些非业务性质的校验。在请求到达的时候就完成校验和过滤。Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截和过滤,实现的方法非常简单,我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的拦截和过滤了。
[XML] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.web.springcloud;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;

public class MyZuulFilter extends ZuulFilter {

private static final Logger log = LoggerFactory.getLogger(MyZuulFilter.class);
/**
过滤器具体逻辑
*/
@Override
public Object run() {
// TODO Auto-generated method stub
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
log.info("send {} request to {}",request.getMethod(),request.getRequestURL().toString());
Object accessToken = request.getParameter("accessToken");
if (accessToken==null) {
log.warn("access token is empty");
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(401);
return null;

}
log.info("access token ok");
return null;
}

/**
判断该过滤器是否需要被执行,这里返回true,将会对所有请求生效
*/
@Override
public boolean shouldFilter() {
// TODO Auto-generated method stub
return true;
}

/**
过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行
*/
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}

@Override
public String filterType() {
// TODO Auto-generated method stub
return "pre";
}

}

主启动类添加bean:
[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
@SpringBootApplication
@EnableZuulProxy
public class Zuul_9527_StartSpringCloudApp
{
public static void main(String[] args)
{
SpringApplication.run(Zuul_9527_StartSpringCloudApp.class, args);
}

@Bean
public MyZuulFilter myZuulFilter() {
return new MyZuulFilter();
}
}

filterType
过滤器的类型,它决定了过滤器在请求的哪个生命周期中执行。
pre:请求被路由之前执行;
route : 在路由请求时被调用;
error :处理请求时发生错误时被调用
post :在routing和error过滤器之后被调用;
static:for static responses see StaticResponseFilter。