Zuul 入门教程 - 基础篇
大纲
Zuul 介绍
Zuul 是什么
Zuul 是由 Netflix 孵化的一个致力于 “网关” 解决方案的开源组件,在动态路由、监控、弹性、服务治理以及安全方面起着举足轻重的作用。从 2012 年 3 月以来,陆续发布了 Zuul 1.0 与 Zuul 2.0 版本,后经 Pivotal 公司将 Zuul 1.0 整合到 Spring Cloud 的生态系统中,即现在的 Spring Cloud Zuul。在 Netflix 官方的解释中,Zuul 是从设备和网站到后端应用程序所有请求的前门,为内部服务提供可配置的对外 URL 到服务的映射关系,基于 JVM 的后端路由器。其底层基于 Servlet
实现,本质组件是一系列 Filter
所构成的责任链,并且 Zuul 的逻辑引擎与 Filter
可用其他基于 JVM 的编程语言编写(比如 Groovy)。Zuul 默认集成了 Ribbon、Hystrix,其中 Zuul 2.x 版本改动相较 1.x 比较大,底层使用了 Netty。虽然 Netflix 已经在 2018 年 5 月开源了 Zuul 2.x,但由于 Zuul 2.x 在 Spring Cloud Gateway 孵化之前一直跳票发布,而且 Spring Cloud Gateway 目前已经孵化成功,相较于 Zuul 1.x 在功能以及性能上都有明显的提升。因此在 Spring Boot 2.0 以上版本中,并没有对 Zuul 2.0 以上最新高性能版本进行集成,仍然使用 Zuul 1.x 非 Reactor 模式(基于 Servlet 2.5 阻塞架构)的旧版本。更多介绍可参考:Zuul 项目、Zuul 官方英文教程、Spring Cloud Zuul 官方中文文档
Zuul 的特性
主要特性包括:认证和鉴权、压力控制、动态路由、负载削减、静态响应处理、主动流量管理、金丝雀测试
Zuul 1.x 与 Zuul 2.x 对比
Zuul 1.x 是一个基于 Servlet 2.5 的同步阻塞 I/O 网关,不支持任何长连接(如 WebSocket)。Zuul 1.x 的设计和 Nginx 比较像,每次 I/O 操作都是从工作线程池中选择一个来执行,请求线程被阻塞到工作线程完成为止;但是差别是 Nginx 是基于 C/C++ 实现,而 Zuul 1.x 是使用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得 Zuul 1.x 的性能相对较差。根据官方提供的基准测试,Spring Cloud Gateway 的 RPS(每秒请求数)是 Zuul 1.x 的 1.6 倍,平均延迟是 Zuul 1.x 的一半。
Zuul 2.x 的理念更先进,基于 Netty 的异步非阻塞 I/O 模型,支持长连接。Zuul 2.x 最大的改进就是基于 Netty Server 实现了异步非阻塞 I/O 来接入请求,同时基于 Netty Client 实现了到后端业务服务 API 的请求,这样就可以实现更高的性能、更低的延迟。此外也调整了 Filter 类型,将原来的三个核心 Filter 显式命名为:Inbound Filter、Endpoint Filter 和 Outbound Filter。值得一提的是,Zuul 2.x 与 Spring Cloud Gateway 的性能差不多。Zuul 2.x 的核心功能如下:
- GZip
- HTTP/2
- Retries
- Mutual TLS
- WebSocket/SSE
- Proxy Protocol
- Load Balancing
- Request Passport
- Request Attempts
- Status Categories
- Service Discovery
- Connection Pooling
- Origin Concurrency Protection
Zuul 入门案例
这里的案例将使用到的 Spring Cloud 组件是 Eureka 与 Zuul,另外再使用一个普通服务作为 Zuul 路由的下级服务,来模拟真实开发中的一次路由过程。
1. 版本说明
在本文中,默认使用的 Spring Cloud 版本是 Finchley.RELEASE,对应的 Spring Boot 版本是 2.0.3,Zuul 版本是 1.x,点击下载完整的案例代码
2. 创建 Maven 父级 Pom 工程
在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 Maven 配置如下:
1 | <parent> |
3. 创建 Eureka Server 工程
创建 Eureka Server 的 Maven 工程,配置工程里的 pom.xml
文件,需要引入 spring-cloud-starter-netflix-eureka-server
1 | <dependencies> |
创建 Eureka Server 的启动主类,这里添加相应注解,作为程序的入口:
1 |
|
添加 Eureka Server 需要的 application.yml
配置文件到工程中
1 | server: |
4. 创建 Provider 下游服务工程
创建 Provider 的 Maven 工程,配置工程里的 pom.xml
文件,需要引入 spring-cloud-starter-netflix-eureka-client
1 | <dependencies> |
创建 Provider 的启动主类,添加注解 @EnableDiscoveryClient
,将服务注册到 Eureka Server:
1 |
|
在 application.yml
文件中指定服务名称(provider-service
)、注册中心地址与端口号:
1 | server: |
创建用于测试的 Controller 类:
1 |
|
5. 创建 Zuul Server 工程
创建 Zuul Server 的 Maven 工程,配置工程里的 pom.xml
文件,需要引入 spring-cloud-starter-netflix-eureka-client
、spring-cloud-starter-netflix-zuul
1 | <dependencies> |
创建 Zuul Server 的启动主类,添加注解 @EnableZuulProxy
、@EnableDiscoveryClient
1 |
|
在 application.yml
文件中指定服务名称(zuul-server
)、注册中心地址与端口号:
1 | server: |
6. 测试效果
- 分别启动 eureka-server、provider-service、zuul-server 应用
- 访问 provider-service 应用:
http://127.0.0.1:9090/provider/add?a=3&b=5
- 方式一:通过 zuul-server 访问 provider-service 应用:
http://127.0.0.1:8092/provider-service/provider/add?a=3&b=5
,provider-service
为服务实例的名称 - 方式二:通过 zuul-server 访问 provider-service 应用:
http://127.0.0.1:8092/provider/service/provider/add?a=3&b=6
,这里使用了路由映射规则/provider/service/**
- 若上面通过 zuul-server 访问 provider-service 应用后,都可以正常返回结果,则说明 Zuul 成功发挥了网关的作用
提示:若在 Zuul 的配置文件中指定了路由映射规则,当向 Zuul Server 发起请求的时候,Zuul 会去 Eureka 注册中心拉取服务列表,如果发现有指定的路由映射规则,就会按照映射规则路由到相应的服务接口
Zuul 路由配置
路由配置简化
1 | zuul: |
上述的配置中,是一个从 /client/**
路由到 client-a
服务的一个映射规则,它可以简化成如下的简单配置,在这种情况下,Zull 会为 client-a
服务添加一个默认的映射规则 /client/**
1 | zuul: |
单实例 URL 映射
除了路由到服务外,还支持路由到物理笛子,将 serviceId 替换为 url 即可:
1 | zuul: |
多实例路由映射
在默认情况下,Zuul 会使用 Eureka 中集成的负载均衡功能,如果想要使用 Ribbon 的客户端负载均衡功能,就需要指定一个 serviceId
,此操作需要禁止 Ribbon 使用 Eureka。提示:Spring Cloud 在 E 版本之后,新增了负载均衡策略的配置:
1 | zuul: |
Forward 本地跳转
在 Zuul 中有时候会做一些逻辑处理,先在网关(Zuul Server)中写好一个接口,如下:
1 |
|
如果希望在访问 /provider/service
接口的时候,跳转到上面的 add
方法上来处理,就需要用到 Zuul 的本地跳转,配置如下:
1 | zuul: |
当访问 http://127.0.0.1:8092/provider/service?a=2&b=3
,会跳转到 TestController
类的 add
本地方法
相同路径的加载规则
有一种特殊的情况,为一个映射路径指定多个 serviceId
时,那么 Zuul 总是会路由到 YML 配置文件中最后面的那个服务。即在 YML 解释器工作的时候,如果同一个映射路径对应多个服务,按照加载顺序,最后加载的映射规则会把之前的映射规则覆盖掉。
1 | zuul: |
路由通配符
此外,映射路径 /client/**
之后的 /**
也大有讲究,其还可以配置为 /*
或者 /?
,具体规则如下:
Zuul 功能配置
路由前缀
在配置路由规则的时候,可以配置一个统一的代理前缀,下次通过 Zuul 访问后端接口的时候就需要加上这个后缀了。提示,请求路径会变成 /pre/client/add
,但实际起作用的是 /client/add
,可以使用 stripPrefix=false
来关闭此功能;关闭之后,请求路径是 /pre/client/add
,实际起作用的还是 /pre/client/add
,一般不推荐使用这个配置。
1 | zuul: |
敏感头信息
在构建系统的时候,使用 HTTP 的 header 传值是十分方便的,协议的一些认证信息默认也在 header 里,比如 Cookie,或者习惯把基本认证信息通过 BASE64 加密后放在 Authorization 里面,但是如果系统要和外部系统通信,就可能会出现这些信息的泄漏。Zuul 支持在配置文件里面指定敏感头,切断它和下层服务之间的交互,配置如下:
1 | zuul: |
重定向问题
假设客户端通过 Zuul 请求认证服务,认证成功之后重定向到一个欢迎页面,但是发现重定向的这个欢迎页面的 host 变成了这个认证服务的 host,而不是 Zuul 的 host,直接导致了认证服务地址的暴露(如下图),此时可以使用下述配置来解决:
1 | zuul: |
服务屏蔽与路径屏蔽
有时候为了避免某些服务或者路径的侵入,加入 ignored-services
与 ignored-patterns
之后,可以将它们屏蔽掉:
1 | zuul: |