Fork me on GitHub

Spring Cloud - 什么是Zuul网关(1)

此处输入图片的描述
本文介绍 Spring Cloud 微服务架构中的网关实现- Zuul

一、 Zuul

Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。在Spring Cloud微服务架构中,是整个微服务架构的网关实现,作为整个后台系统的入口。

API Gateway (网关)是微服务架构体系中的一类型特殊服务,它是所有微服务的入口,它的职责是执行路由请求、协议转换、聚合数据、认证、限流、熔断等。

通俗地讲,网关相当于微服务内网与请求客户端外网(如浏览器,APP)之间的门户,所有对微服务系统的访问都会经过这个网络,扮演了一个“反向代理”和“智能网关”的角色。

一个系统可以有一个或者多个网关,来针对性地响应不同的请求:

此处输入图片的描述

二、 Zuul 作用

当使用单体应用程序架构时,客户端通过向后端应用程序发起一次REST调用来获取数据。负载均衡器将请求路由给N个相同的应用程序实例中的一个。然后应用程序会查询各种数据库表,并将响应返回给客户端。

微服务架构下,单体应用被切割成多个微服务,如果将所有的微服务直接对外暴露,势必会出现安全方面的各种问题。

客户端可以直接向每个微服务发送请求,其问题主要如下:

  • 客户端需求和每个微服务暴露的细粒度API不匹配。
  • 部分服务使用的协议不是Web友好协议。可能使用Thrift二进制RPC,也可能使用AMQP消息传递协议。
  • 微服务难以重构。如果合并两个服务,或者将一个服务拆分成两个或更多服务,这类重构就非常困难了。

为了解决上述问题,Zuul的功能主要包括:

  • 接口管理: 如果微服务中有很多个独立服务都要对外提供服务,那么对于开发人员或者运维人员来说,如何去管理这些接口?特别是当项目非常大非常庞杂的情况下要如何管理? Zuul上管理接口,可以配置不同的请求路由到不同的微服务上,既可以避免将微服务的接口直接暴露给客户端,也可以方便运维,即是后端微服务发生了变化,网关定义的REST调用路径依然可以不改变。

  • 权限管理: 在微服务中,一个独立的系统被拆分成很多个独立的模块,为了确保安全,我难道需要在每一个模块上都添加上相同的鉴权代码来确保系统不被非法访问?如果是这样的话,那么工作量就太大了,而且维护也非常不方便。而因为Zuul等网关位于所有微服务的前端,因此可以很方便地实现权限认证,比如实现 cookietoken 的认证。

其他功能可以参考博客

  • 请求路由和版本控制
  • 方便单体应用到微服务的过渡
  • 数据聚合
  • 协议转换
  • 缓存和限流

三、 Zuul 的使用

Zuul的大部分应用场景是作为Spring Cloud的微服务体系的网关,作为一个特殊的微服务发布到Eureka注册中心。

事实单独的Zuul上也能完全承担反向代理的角色。

3.1 pom 配置

首先需要引入相关依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

如果需要用到Eureka,还需要引入如下依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

3.2 配置入口类

在入口类上添加@EnableZuulProxy注解表示开启Zuul的API网关服务功能:

1
2
3
4
5
6
7
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}

1
2
3
4
5
6
7
@EnableCircuitBreaker
@EnableDiscoveryClient
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({ZuulProxyConfiguration.class})
public @interface EnableZuulProxy {
}

注意坑1:
配置入口类的时候,通常会遇到两个注解:@EnableZuulProxy@EnableZuulServer,虽然很多人说前者是后者的增强版,当ZuulEurekaRibbon等组件配合使用时,使用@EnableZuulProxy

但是在测试过程中,如果不依赖于Eureka,使用@EnableZuulServer 实现的路由转发会遇到客户端无法收到Response的问题。

研究了两者的区别,发现@EnableZuulProxy相比于@EnableZuulServer,增加了如下过滤器:

  • pre类型过滤器
    • PreDecorationFilter:该过滤器根据提供的RouteLocator确定路由到的地址,以及怎样去路由。该路由器也可为后端请求设置各种代理相关的header。
  • route类型过滤器
    • SimpleHostRoutingFilter:该过滤器通过Apache HttpClient向指定的URL发送请求。URL在RequestContext.getRouteHost()中。

SimpleHostRoutingFilter是实现请求发送的关键。

3.3 编辑配置文件

Zuul 的路由策略默认通过配置文件实现,也可以通过插件读取配置中心或者数据库内的配置。

这里只考虑配置文件:bootstrap.yml或者application.yml

Zuul 不配合 Eureka 服务发现服务的时候,Zuul 的路由就要基于 URL 去路由,示例配置如下,实现对当前端口9999请求的转发:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: zuul-gateway

server:
port: 9999

zuul:
routes:
baidu:
path: /**
url: http://www.baidu.com

注意坑2:
yml配置文件的key-value中间包含了一个空格!另外缩进是两个空格,格式不能错!

注意坑3:
如果需要指定下一级路由,比如讲localhost:9999/test/**的所有请求转发到www.testsite.com/test/**,需要如下配置:

1
2
3
4
5
6
zuul:
routes:
baidu:
path: /test/**
url: http://www.testsite.com/test/
# url: http://www.testsite.com 这是错误的

四、 过滤器Filter

过滤器是Zuul的核心,借助不同的过滤器,Zuul可以实现路由的不同功能。

4.1 过滤器类型

Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。

  • PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • ROUTE:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • ERROR:在其他阶段发生错误时执行该过滤器。

也可以自定义特殊的STATICFilter

其生命周期如下图:

此处输入图片的描述

4.2 自定义一个过滤器

自定义一个过滤器需要继承抽象类ZuulFilter,并实现四个抽象方法:

  • filterType方法的返回值为过滤器的类型,过滤器的类型决定了过滤器在哪个生命周期执行,pre表示在路由之前执行过滤器,其他可选值还有posterrorroutestatic,当然也可以自定义。
  • filterOrder方法表示过滤器的执行顺序,当过滤器很多时,这个方法会有意义。
  • shouldFilter方法用来判断过滤器是否执行,true表示执行,false表示不执行,在实际开发中,我们可以根据当前请求地址来决定要不要对该地址进行过滤。
  • run方法则表示过滤的具体逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class TestFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// 打印请求URL
System.err.println(request.getRequestURL().toString());
return null;
}
}
  • 配置过滤器Bean
    最后需要在入口类中将自定义的过滤器交给Bean容器管理。
1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
@Bean
TestFilter testFilter() {
return new TestFilter();
}
}

其他

其他关于Zuul知识点包括“熔断”,“负载均衡”,后续再总结。


参考文献:

1. 微服务网关netflix-Zuul
2. 微服务 API Gateway 介绍
3. Spring Cloud中的API网关服务Zuul

-------------本文结束感谢阅读-------------