概述

Spring Cloud Zuul 是 Spring Cloud Netflix 子项目的核心组件之一,可以作为微服务架构中的 API 网关使用,有以下用途:

  • 鉴权:对于访问每个服务的请求进行鉴权,拒绝鉴权失败的请求
  • 监控:对系统的请求进行监控,记录请求响应日志,实时统计当前系统的访问量以及监控状态
  • 压力测试:帮助对集群进行可控的压力测试
  • 灰度测试:灰度发布可以保证整体系统的稳定,在初始灰度时就可以发现问题并进行调整
  • 动态路由:基于请求路径,将请求分发到指定的客户端
  • 负载控制:统一控制客户端请求压力,超过压力的请求直接拒绝
  • 静态响应处理:在边缘位置直接建立部分响应,避免其流入内部集群

构建 Zuul 网关

创建 zuul-service 项目,引入依赖,本项目基于 SpringBoot 2.3.1,SpringCloud Hoxton.SR12

    org.springframework.cloud    spring-cloud-starter-netflix-zuul

在 application.yml 配置文件中添加如下配置:

server:  port: 9080 # 指定运行端口spring:  application:    name: zuul-service # 指定服务名称    zuul:  routes:    blog:      path: /baidu/**      url: https://www.baidu.com  # url用于配置符合path的请求路径路由到的服务地址

在启动类中添加 @EnableZuulProxy 注解

@EnableZuulProxy@SpringBootApplicationpublic class ZuulServiceApplication {    public static void main(String[] args) {        SpringApplication.run(ZuulServiceApplication.class, args);    }}

Spring Cloud Netflix Zuul 提供了许多过滤器,具体取决于启用的 Zuul 的注解,@EnableZuulProxy@EnableZuulServer 的超集,包含 @EnableZuulServer 安装的所有过滤器

启动项目,在浏览器中输入访问地址 http://localhost:9080/baidu,发现请求被路由到百度界面,Zuul 服务搭建成功

Zuul 路由配置

上一节,我们使用路径的方式匹配路由规则,path 的结构如下

# 其中customName 为用户自定义名称zuul:  routes:    customName:      # 可使用的通配符有以下几种:      # ?:单个字符      # *:任意多个字符,不包含多级路径      # **:任意多个字符,包含多级路径      path: xxx

对于 url 路径匹配,还可以使用服务名称匹配

zuul:  routes:    # users为用户自定义名称    users:      path: /users/**      # serviceId用于配置符合path的请求路径路由到的服务名称      serviceId: users-service

服务名称匹配也可以使用简化的配置

zuul:  routes:    service-provider:      path: /users/**

如果只配置 path 不配置 serviceld,则 customName 相当于服务名称,即 service-provider 会被当作服务名称,使用 serviceId 要将 zuul 服务注册到注册中心使用,比如 Eureka,从而拉取注册服务列表名称完成调用

如果想排查配置,可以使用 ignored-services

zuul:  ignoredServices: "*"  routes:    users:      path: /users/**

ignored-services 可以配置不被 zuul 管理的服务列表,多个服务名称使用号分隔,配置的的务将不被 Zuul代理,在上面的实例中,除了用户服务外,所有的服务均被忽略

可以通过 zuul.prefix 配置路由前缀,例如:

zuul:  prefix: /api  routes:    users:      path: /users/**

配置请求路径前缀,所有基于此前缀的请求都由 Zuul 网关提供代理

Zuul 过滤器

Zuul 定义过滤器用来过滤代理请求,提供额外功能逻辑,如权限验证、日志记录等,filter 与 filter 之间不直接通信,在请求线程中会通过 RequestContext 来共享状态,它内部是用 ThreadLocal 实现的

Zuul 过滤器分为前置过滤、路由后过滤、后置过滤以及异常过滤:

  • 前置过滤:在请求进入 Zuul 后,立刻执行的过滤逻辑
  • 路由后过滤:在请求进入 Zuul 后,Zuul 实现请求路由,并在远程服务调用之前执行过滤逻辑
  • 后置过滤:远程服务调用结束后执行过滤逻辑
  • 异常过滤:任意一个过滤器发生异常或远程服务调用无结果反馈时(调用超时)执行过滤逻辑

ZuulFilter 类及其父类 IZuulFilter 共提供了四种抽象方法:

  • filterType:返回字符串数据,代表当前过滤器的类型,可选值有:
    • pre:前置过滤器,在请求被路由前执行,通常用于处理身份认证、日志记录等
    • route:在路由执行后,服务调用前被调用
    • error:任意一个 filter 发生异常或远程服务调用没有反馈时执行(超时),通常用于处理异
    • post:在 route 或 error 执行后被调用,一般用于收集服务信息、统计服务性能指标等,也可以对 response 结果做特殊处理
  • filterOrder:返回 int 数据,用于为同一种 filterType 的多个过滤器定制执行顺序,返回值越小,执行顺序越优先
  • shouldFilter:返回 boolean 数据,代表当前 filter 是否生效
  • run:具体的过滤执行逻辑

具体实例如下:

public class tokenFilter extends ZuulFilter {    @Override    public String filterType() {        //定义过滤器的类型,pre 表示在请求被路由前执行        return "pre";    }    @Override    public int filterOrder() {        //返回int 数据,用于为同一种 filterType 的多个过滤器定制执行顺序        //返回值越小,执行顺序越优先        return 0;    }    @Override    public boolean shouldFilter() {        //判断过滤器是否生效,true 代表生效        return true;    }    @Override    public Object run() throws ZuulException {        //获取上下文        RequestContext currentContext = RequestContext.getCurrentContext();        //获取 request 对象        HttpServletRequest request = currentContext.getRequest();        //从请求头获取 token 的参数        String userToken = request.getParameter("token");        if (StringUtils.isEmpty(userToken)) {            //返回错误提示            //false:表示不会继续往下执行,不会调用服务接口,直接响应给客户            currentContext.setSendZuulResponse(false);            currentContext.setResponseBody("token is null");            currentContext.setResponseStatusCode(401);            return null;        }        //否则正常执行,调用服务接口...        return null;    }}