SpringCloud Zuul网关怎么用?从配置到实战详解
作者:wno704
一、前言
在微服务的架构中,服务网关就是一个介于客户端与服务端之间的中间层。在这种情况下,客户端只需要跟服务网关交互,无需调用具体的微服务接口。
这样的好处在于,客户端可以降低复杂性;对于需要认证的服务,只需要在服务网关配置即可;同样也方便后期微服务的变更和重构,即微服务接口变更只需在服务网关调整配置即可,无需更改客户端代码。
Zuul是一款由Netflix开发的微服务网关开源软件,可以和其自家开发的Eureka,Ribbon和Hystrix配合使用,Spring Cloud对其进行了封装。
二、介绍
2.1 API 网关使用场景
- 黑白名单:实现通过 IP 地址控制请求的访问
- 日志:实现访问日志的记录,进而实现日志分析,处理性能指标等
- 协议适配:实现通信协议的校验、适配转换的功能
- 身份认证:对请求进行身份认证
- 计流限流:可以设计限流规则,记录访问流量
- 路由:将请求进行内部(服务)转发
2.2 API 网关的实现
业界常用的 API 网关有很多方式,如:Spring Cloud Zuul、 Nginx、Tyk、Kong。本篇介绍的对象正是 Spring Cloud Zuul。
Zuul 是 Netflix 公司开源的一个 API 网关组件,提供了认证、鉴权、限流、动态路由、监控、弹性、安全、负载均衡、协助单点压测等边缘服务的框架。
Spring Cloud Zuul 是基于 Netflix Zuul 的微服务路由和过滤器的解决方案,也用于实现 API 网关。其中,路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入门的基础。而过滤功能是负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Spring Cloud Zuul 和 Eureka 进行整合时,Zuul 将自身注册到 Eureka 服务中,同时从 Eureka 中获取其他微服务信息,以便请求可以准确的通过 Zuul 转发到具体微服务上。
三、实战演练
本次测试案例基于之前发表的文章中介绍的案例进行演示,不清楚的读者请先转移至 《Spring Cloud Hystrix服务容错》 进行浏览。
当前的项目列表如下:
| 项目 | 服务实例 | 实例id | 端口 | 描述 |
|---|---|---|---|---|
| common-api | 无 | 无 | 无 | 公用的 api,如:实体类 |
| eureka-server | Eureka-Server | eureka-server1 | 9001 | 注册中心(Eureka 服务端) |
| eureka-server | Eureka-Server | eureka-server2 | 9002 | 注册中心(Eureka 服务端) |
| goods-server | Goods-Server | goods-server1 | 8801 | 商品服务(Eureka 客户端) |
| goods-server | Goods-Server | goods-server2 | 8802 | 商品服务(Eureka 客户端) |
| goods-server | Goods-Server | goods-server3 | 8803 | 商品服务(Eureka 客户端) |
| order-server | Order-Web | order-web | 8900 | 订单服务(Eureka 客户端) |
| user-server | User-Server | user-server1 | 8788 | 用户服务(Eureka 客户端) |
| user-server | User-Server | user-server2 | 8789 | 用户服务(Eureka 客户端) |
| gateway-server | GateWay-Server | gateway-server | 9100 | 服务网关(Eureka 客户端) |
创建一个为名 gateway-server 的 Spring Boot 项目。
3.1 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>3.2 配置文件
server:
port: 9100
spring:
application:
name: GateWay-Server
eureka:
instance:
instance-id: gateway-server
prefer-ip-address: true
client:
register-with-eureka: true # 是否向注册中心注册自己
fetch-registry: true # 是否检索服务
service-url:
#defaultZone: http://127.0.0.1:8761/eureka/
defaultZone: http://eureka01:9001/eureka/,http://eureka02:9002/eureka/3.3 启动 Zuul
在启动类上添加 @EnableZuulProxy 注解:
@EnableZuulProxy
@SpringBootApplication
public class GateWayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayServerApplication.class, args);
}
}
启动上边的所有项目,使用idea的http工具请求订单下单接口
图中,我们首先不经过网关直接访问 order-server 项目请求地址:http://localhost:8900/order/hystrix

之后再修改成访问 gateway-server 项目的请求地址:http://localhost:9100/order-web/order/hystrix

最终,响应结果都一样。
提示:http://localhost:9100/order-web/order/hystrix 中第一个 order-web 表示的是注册在 Eureka 上的订单服务名称。
3.4 zuul 常用配置
传统配置
传统配置就是手动指定服务的转发地址,如在yml中配置:
zuul:
routes:
userweb:
path: /userweb/**
url: http://localhost:8765
通过上面的配置,所有符合/userweb/**规则的访问都将被路由转发到http://localhost:8765地址上,即当我们向服务网关访问http://localhost:9100/userweb/userFeign/user/1请求的时候,请求将被转发到http://localhost:8765/userFeign/user/1服务上.

基于服务名称配置
zuul:
sensitive-headers:
routes:
wno704:
path: /orderGateWay/**
service-id: order-web将订单服务的路由名称改成 orderGateWay。
使用 http 请求http://localhost:9100/orderGateWay/order/hystrix下单接口,运行结果:

请求成功。
基于服务名称的路由配置还可以进行简化,格式为zuul.routes.= ,所以我们可以将orderGateWay的路由配置修改为:
zuul:
routes:
order-web:
path: /orderGateWay/**默认路由配置规则
我们尝试访问http://localhost:9100/user-web/userFeign/user/1

我们压根没配置这个路由,为什么可以正常响应??其实Zuul配合Eureka后将会成一套默认的配置规则。
当我们使用服务名称作为请求的前缀路径时,实际上就会匹配上类似下面的默认路由配置:
zuul:
routes:
user-web:
path: /user-web/**
serviceId: user-web如果不想启动这个默认配置,我们可以使用zuul.ignored-services配置来关闭,如在yml中加入如下配置之后,再次访问http://localhost:9100/user-web/userFeign/user/1将返回404:
zuul: ignored-services: user-web
优先级
假如某个请求路径可以和多个路由配置规则相匹配的话,Zuul根据匹配的先后顺序来决定最终使用哪个路由配置。比如:
zuul:
routes:
userFeign:
path: /userFeign/**
service-id: User-Web
userFeign1:
path: /userFeign/userFeign/**
service-id: User-Web1当我们访问http://localhost:9100/userFeign/userFeign/user/1的时候,userFeign和userFeign1的路由配置都可以匹配上,但由于userFeign先于userFeign1配置,所以最终生效的是userFeign的配置。
假如将userFeign和userFeign1的配置顺序调换,再次访问http://localhost:9100/userFeign/userFeign/user/1时将抛出异常,原因是不存在服务名为User-Web1的服务:
com.netflix.zuul.exception.ZuulException: Forwarding error
…
Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: User-Web1
…
禁用路由:
zuul: ignored-patterns: - /orderGateWay/order/place/** #禁用路由
http://localhost:9100/orderGateWay/order/place 无法被正常路由到订单服务,响应返回 404。

本地跳转
Zuul网关除了支持将服务转发到各个微服务上之外,还支持将服务跳转到网关本身的服务上,比如现在yml中有如下一段配置:
zuul:
routes:
test2:
path: /test2/**
url: forward:/test我们在gateway-server入口类中加上该REST服务:
@RestController
public class TestController {
@GetMapping("/test/hello")
public String hello() {
return "hello zuul";
}
}
当访问http://localhost:9100/test2/hello时,Zuul会从本地/test/hello获取服务。

前缀配置
zuul: prefix: /api
所有请求中的 path 需要添加 api 前缀。如: http://localhost:9100/orderGateWay/order/hystrix 需要改成 http://localhost:9100/api/orderGateWay/order/hystrix。
头部过滤
在使用Zuul网关的时候你可能会遇到Cookie丢失的情况,这是因为默认情况下Zuul会过滤掉HTTP请求头中的一些敏感信息,这些敏感信息通过下面的配置设定:
zuul: sensitive-headers: Cookie,Set-Cookie,Authorization # 设置全局敏感头,如果为空,表示接收所有敏感头信息
或
zuul:
routes:
wno704:
path: /orderGateWay/**
service-id: order-web
sensitive-headers: Cookie,Set-Cookie,Authorization # 针对 /orderGateWay/ 的请求设置敏感头信息使用Zuul另一个常见问题是重定向的问题,可以通过下面的设置解决:
zuul: add-host-header: true
四、核心过滤器
Spring Cloud Zuul为各个生命周期阶段实现了一批过滤器,这些过滤器的优先级和作用如下表所示:
| 生命周期 | 优先级 | 过滤器 | 功能描述 |
|---|---|---|---|
| pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
| pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
| pre | -1 | FormBodyWrapperFilter | 包装请求体 |
| route | 1 | DebugFilter | 标记调试标志 |
| route | 5 | PreDecorationFilter | 处理请求上下文供后续使用 |
| route | 10 | RibbonRoutingFilter | serviceId请求转发 |
| route | 100 | SimpleHostRoutingFilter | url请求转发 |
| route | 500 | SendForwardFilter | forward请求转发 |
| post | 0 | SendErrorFilter | 处理有错误的请求响应 |
| post | 1000 | SendResponseFilter | 处理正常的请求响应 |
其中优先级数字越小,优先级越高。
要关闭这些过滤器可以在application.yml中按照格式zuul...disable=true配置即可。比如关闭SendResponseFilter过滤器:
zuul:
SendResponseFilter:
post:
disable: true五、Zuul 自定义过滤器
Zuul 的核心技术就是过滤器,该框架提供了 ZuulFilter 接口让开发者可以自定义过滤规则。
我们以身份检验为例,自定义 ZuulFilter 过滤器实现该功能。
5.1 改造user-server
登录接口:
LoginController.java
@RestController
@RequestMapping("/user")
public class LoginController {
@PostMapping("/login")
public Result login(String username, String password, HttpServletResponse response) {
if ("admin".equals(username) && "admin".equals(password)) {
// 模拟生成 token,实际开发中 token 应存放在数据库或缓存中
String token = "123456";
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
cookie.setMaxAge(60 * 10);
response.addCookie(cookie);
return Result.success();
}
return Result.fail(401, "账号或密码错误");
}
}
5.2 创建 ZuulFilter 过滤器
在 gateway-server 项目中,新建一个过滤器,需要继承 ZuulFilter 类:
@Component
public class AuthenticationFilter extends ZuulFilter {
/**
* 过滤器类型
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器执行顺序
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 是否开启过滤
*/
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
boolean flag = request.getRequestURI().contains("/login");
// 如果是登录请求不进行过滤
if (flag) {
System.out.println("========不执行 zuul 过滤方法=======");
} else {
System.out.println("========执行 zuul 过滤方法=======");
}
return !flag;
}
/**
* 过滤器执行内容
*/
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String token = request.getParameter("token");
// 此处模拟获取数据库或缓存中的 token
String dbToken = "123456";
// 此处简单检验 token
if (token == null || "".equals(token) || !dbToken.equals(token)) {
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
其中,filterType 有 4 种类型:
1>pre: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
2>routing:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。
3>post:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
4>error:在其他阶段发生错误时执行该过滤器。
其过滤顺序如下图:

5.3 测试过滤器
运行所有项目,测试操作步骤如下:
测试效果图如下:
请求用户服务的登录接口(http://localhost:9100/api/user/user/login),请求不执行 zuul 过滤方法,并且请求响应返回的 cookie 包含 token

请求订单服务的下单接口(http://localhost:9100/api/orderGateWay/order/hystrix),但不携带 token,请求需要执行 zuul 过滤方法,请求响应 401 权限不足

请求订单服务的下单接口(http://localhost:9100/api/orderGateWay/order/hystrix),携带之前登录接口返回的 token,请求需要执行 zuul 过滤方法,校验通过后路由到订单服务执行之后的操作

总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
