SpringCloud Zuul 路由映射规则配置

前言

本文起笔于2018-06-26周二,接了一个这周要完成的开发任务,需要先等其他人的接口,可能更新的会慢一些,还望大家见谅。这篇博客我们主要讲Spring Cloud Zuul。项目地址:我的github

Spring Cloud Zuul大家可以理解为一个集网关(路由)、负载均衡、校验过滤、结合服务治理框架、请求转发时熔断机制、服务聚合等 一系列功能。我们可以将Zuul当成一个门面,所有外部请求都经过Zuul的转发到具体的服务实例,减少了每个服务之间互相鉴权代码冗余问题,统一交给Zuul进行鉴权,在此基础上集成上边说的高级功能。路由功能相当于反向代理。

快速入门

1. 构建网关

项目结构:

创建一个新的Model,取名为ZuulGateway,pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.cnblogs.hellxz<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>ZuulGateway<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>Dalston.SR5<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Spring Cloud Zuul的依赖 --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-zuul<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">source</span>&gt;</span>1.8<span class="hljs-tag">&lt;/<span class="hljs-name">source</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">target</span>&gt;</span>1.8<span class="hljs-tag">&lt;/<span class="hljs-name">target</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>

</project>

注意:spring-cloud-starter-parent的版本用的Dalston.SR5,原因是我发现如果用1.5.9.RELEASE并不会给给zuul的依赖引用进来,还需要额外指定版本号,Dalston.RELEASE也是不能引入依赖的,如果想使用1.5.9.RELEASE,使用spring-boot-starter-parent指定1.5.9.RELEASE版本,并在下边的依赖中指定spring-cloud.versionRELEASE即可

  <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>

创建主类ZuulApp

package com.cnblogs.hellxz;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy //开启zuul网关服务功能
@SpringCloudApplication
public class ZuulApp {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
    SpringApplication.run(ZuulApp.class, args);
}

}

2. 请求路由

a. 传统路由实现方式

resources包下创建application.yml

spring:
  application:
    name: zuul-gateway
server:
  port: 5555

zuul:
routes:
traditional-url: #传统的路由配置,此名称可以自定义
path: /tr-url/** #映射的url
url: http://localhost:9001/ #被映射的url

这里使用了传统路由的方式先做一个展示,之后 会整合Eureka注册中心实现

测试一下,分别启动注册中心、服务提供者、FeignCustomer、刚建好的zuul项目,我们直接访问zuul项目,使用映射的url进行测试

如上图,请求被转发到了http://localhost:9001/feign/hello接口

b. 面向服务的路由

使用传统路由对运维人员很不友好,需要大量的时间去维护路由的映射关系,需要转发的url少倒还好,多起来就会更容易配置出错。

为了解决这个问题,Spring Cloud Zuul实现了与Spring Cloud Eureka的无缝结合,让path不是映射到具体url上,而是映射到某个具体的服务上,具体的url交给Eureka的服务发现机制去自动维护,这类路由称为面向服务的路由。

将Zuul注册到中心的服务的同时,Zuul同时能够获取当前注册中心中的服务清单,从清单中挑出实例进行请求转发,从而达到请求转发的完整路由机制。

改造我们之前的项目,为pom.xml添加Eureka的依赖,使zuul项目注册到注册中心中

        <!-- Eureka的依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

修改application.yml,在zuul.routes节点下加入配置,为了防止出错,这里给出了包含上文中的传统路由配置,这里要注意的是面向服务的路由需要指定注册中心的地址

zuul:
  routes:
    traditional-url:                             #传统的路由配置,此名称可以自定义
      path: /tr-url/**                           #映射的url
      url: http://localhost:9001/                #被映射的url
    orient-service-url:                          #面向服务的路由配置,此名称可以自定义
      path: /os-url/**
      service-id: feign-customer                 #服务名

eureka:
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka/

重启zuul项目,postman进行测试,结果相同,路由正常

这里为了方便都映射到了http://localhost:9001/也就是feign-customer的项目,大家可以自行体验

3. 请求过滤

微服务应用中的每个客户端在提供服务的接口时,都会将访问权限加以限制,并不会放开所有的接口,为了安全,我们应该为每个微服务加入校验签名和鉴权等的过滤器或者拦截器,这样一来,会增加日后系统的维护难度,同时大部分的校验和鉴权的逻辑代码是相同的,那么我们就应该将这些重复逻辑提取出来,上文中曾说到“Zuul相当于整个微服务系统的门面”,那么接下来我们来看下在zuul网关中实现客户端的请求校验,即Spring Cloud Zuul 的核心功能之一的请求过滤

我们先定义一个简单的Zuul过滤器,实现检查HttpServletRequest中是否有accessToken参数,如果有就进行路由,没有则拒绝访问,还回 401 Unauthorized 错误

创建一个filter包下创建AccessFilter,继承ZuulFilter并重写以下方法

package com.cnblogs.hellxz.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**

  • 访问过滤器
    */
    @Component
    public class AccessFilter extends ZuulFilter {

    private final Logger logger = LoggerFactory.getLogger(AccessFilter.class);

    /**

    • 过滤器类型选择:
    • pre 为路由前
    • route 为路由过程中
    • post 为路由过程后
    • error 为出现错误的时候
    • 同时也支持static ,返回静态的响应,详情见StaticResponseFilter的实现
    • 以上类型在会创建或添加或运行在FilterProcessor.runFilters(type)
      */
      @Override
      public String filterType() {
      return "pre"; //ZuulFilter源码中注释"pre"为在路由前过滤
      }

    /**

    • 用来过滤器排序执行的
    • @return 排序的序号
      */
      @Override
      public int filterOrder() {
      return 0;
      }

    /**

    • 是否通过这个过滤器,默认为true,改成false则不启用
      */
      @Override
      public boolean shouldFilter() {
      return false; //返回true表示执行这个过滤器
      }

    /**

    • 过滤器的逻辑
      */
      @Override
      public Object run() {
      //获取当前请求上下文
      RequestContext ctx = RequestContext.getCurrentContext();
      //取出当前请求
      HttpServletRequest request = ctx.getRequest();
      logger.info("进入访问过滤器,访问的url:{},访问的方法:{}",request.getRequestURL(),request.getMethod());
      //从headers中取出key为accessToken值
      String accessToken = request.getHeader("accessToken");//这里我把token写进headers中了
      //这里简单校验下如果headers中没有这个accessToken或者该值为空的情况
      //那么就拦截不入放行,返回401状态码
      if(StringUtils.isEmpty(accessToken)) {
      logger.info("当前请求没有accessToken");
      //使用Zuul来过滤这次请求
      ctx.setSendZuulResponse(false);
      ctx.setResponseStatusCode(401);
      return null;
      }
      logger.info("请求通过过滤器");
      return null;
      }
      }

在上个实验的基础上重启这个Zuul项目,使用postman访问http://localhost:5555/os-url/feign/hello

没有在headers中加accessToken的测试

在headers中加accessToken的测试

注意:这里除了在类头上打上@Component注解外,还可以使用@Bean注入的办法

    @Bean
    public AccessFilter accessFilter(){
        return new AccessFilter();
    }

快速入门这一块就到这里,总结一下:

  • 它作为系统的统一入口,屏蔽了系统内部各个微服务的细节
  • 可以和服务治理框架结合,实现自动化服务实例维护和负载均衡
  • 实现接口权限校验与微服务业务逻辑的解耦
  • 通过网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外的服务层做的校验前移,保证微服务的无状态性,降低测试难度,解放程序员专注业务的处理

路由详解

1.传统路由配置

传统路由就是不依赖于服务发现机制通过配置文件映射服务实例关系来实现的API网关对外请求路由。

没有服务治理框架的帮助,不同服务实例的数量采用不同的方式配置来实现路由规则

1.1 单实例配置

通过zuul.routes.<路由名>.pathzuul.routes.<路由名>.url

举例:

zuul: 
  routes: 
    service-provider: 
      path: /eureka-service/**
      url: http://localhost:8080

请求进来的时候先去匹配path,然后用这个path的路由名去找对应的url,进行请求转发

1.2 多实例配置

通过zuul.routes.<路由名>.pathzuul.routes.<路由名>.serviceId

举例:

zuul: 
  routes: 
    service-provider: 
      path: /eureka-service/**
      serviceId: eureka-service
ribbon: 
  eureka: 
    enabled: false
eureka-service: 
  ribbon: 
    listOfServers: http://localhost:8080/,http://localhost:8081/

请求进来的时候先去匹配path,然后用这个path的路由名去找对应的服务名,这里需要手动去命名服务名称,配合ribbon.listOfServers参数实现服务与实例的维护。由于存在多个实例,API网关在进行路由转发时需实现负载均衡,于是使用Ribbon的配合,Zuul中自带了对Ribbon的依赖。

  • 参数说明
  • ribbon.eureka.enabled:由于zuul.routes.<路由名>.serviceId用来指定服务名称,默认Ribbon会根据发现机制来获取配置服务名对应的实例清单,但是,这里没有使用Eureka之类的服务发现治理框架,所以需要将该参数设为false,否则配置的serviceId获取不到对应实例的清单
  • eureka-service.ribbon.listOfServers:该参数内容与zuul.routes.<路由名>的配置相对应,此配置打头的eureka-service对应了serviceId的值,相当于手动维护了在该应用内部手工维护了服务与实例的对应关系

无论单实例还是多实例的配置方式,传统路由方式都需要手动为每一对映射指定一个名称,每个<路由名>对应了一条路由规则;每第路由规则都必须有一个paht用来匹配请求路径表达式,并通过与之相对应的url或serviceId属性来请求表达式映射的url或服务名

2. 服务路由配置

快速入门中已经讲了面向服务路由的配置,通过与Eureka的整合,实现了对服务实例的自动维护,所以在使用服务路由的时候,无须指定serviceId所指定具体服务实例地址,只需要通过zuul.routes.<路由名>.pathzuul.routes.<路由名>.serviceId成对配置即可。

举例:

zuul: 
  routes: 
    eureka-service: 
      path: /eureka-service/**
      serviceId: eureka-service

面向服务的路由配置除了上边的方法还有一种更简单的方式:zuul.routes.<服务名>=<映射地址>

举例:

zuul: 
  routes: 
    eureka-service: /eureka-service/**

需要注意的是,这里边本人举的例子用的路由名一直是和serviceId保持相同,这里是可以不同的,请不要在这里迷惑

与传统路由相比,有外部请求到API网关的时候,面向服务路由发生了什么?

当有外部请求到达API网关的时候,根据请求的URL路径去匹配path的规则,通过path找到路由名,去找对应的serviceId的服务名,

  • 传统路由就会去根据这个服务名去找listOfServers参数,从而进行负载均衡和请求转发
  • 面向服务路由会从注册到服务治理框架中取出服务实例清单,通过清单直接找到对应的实例地址清单,从而通过Ribbon进行负载均衡选取实例进行路由(请求转发)

3. 服务路由的默认配置

Eureka与Zuul整合为我们省去了大量的维护服务实例清单的配置工作,但是实际操作中我们会将path与serviceId都用服务名开头,如上边我所举的几个例子都是,这样的配置其实Zuul已经默认为我们实现了,当我们直接使用http://localhost:5555/feign-customer/hello的时候,会发现我们没有配置这个path确实访问成功了!

其实,Zuul在注册到Eureka服务中心之后,它会为Eureka中的每个服务都创建一个默认的路由规则,默认规则的path会使用serviceId配置的服务名作为请求前缀,如上图的发生的情况。

这会使一些我们不希望的开放的服务有可能被外部访问到,此时,我们可以使用zuul.ignored-services参数来设置一个不自动创建该服务的默认路由。Zuul在自动创建服务路由的时候会根据这个表达式进行判断,如果服务名匹配表达式,那么Zuul将跳过此服务,不为其创建默认路由。如下

zuul: 
  ignored-services: feign-customer,eureka-service

postman测试通过,结果和eureka-serivce一样,此处不再赘述

如果不想使用自动创建默认路由功能,可以使用如下方法跳过默认路由

zuul: 
  ignored-services: '*'

4. 自定义路由映射规则

可以使用regexmapper在serviceId和路由之间提供约定。它使用正则表达式命名组从serviceId提取变量并将它们注入路由模式。

引用自官方文档

ApplicationConfiguration.java.

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>v.+$)",
        "${version}/${name}");
}

This means that a serviceId "myusers-v1" will be mapped to route "/v1/myusers/". Any regular expression is accepted but all named groups must be present in both servicePattern and routePattern. If servicePattern does not match a serviceId, the default behavior is used. In the example above, a serviceId "myusers" will be mapped to route "/myusers/" (no version detected) This feature is disabled by default and only applies to discovered services.

PatternServiceRouteMapper对象可以让开发者通过正则表达式来自定义服务与路由映射的生成关系。其中构造函数的第一个参数用来匹配服务名称是否符合该自定义规则的正则表达式(serviceId),而第二个参数则是定义服务名中定义的内容转换出的路径表达式(path),只要有符合第一个参数定义的serviceId,那么就会优先使用该实现构建出path,如果没有找到则使用默认路由映射需——采用完整服务名做为前缀的路径表达式。

5. 路径匹配

无论传统还是服务路由都是要使用path的,即用来匹配请求url中的路径。

5.1 正则路径匹配

path通常需要使用通配符,这里简单讲讲

通配符 说明 举例
? 匹配单个字符 /feign/?
* 匹配任意数量字符,但不支持多级目录 /feign/*
** 匹配任意数量字符,支持多级目录 /feign/**

如果有一个可以同时满足多个path的匹配的情况,此时匹配结果取决于路由规则的定义顺序,

这里需要注意的是:properties无法保证路由规则的顺序,推荐使用yml格式配置文件

5.2 忽略表达式

Zuul提供了用于忽略路径表达式的参数zuul.ignored-patterns。使用该参数可以用来设置不希望被API网关进行路由的URL表达式。

举例:

上文中使用过/feign/hello的接口,这次我们使用如下代码去忽略这个/hello的接口

zuul: 
  ignored-patterns: /**/hello/**

使用postman测试并加入accessToken

服务终端打印输出

测试发现明明存在的接口已经被忽略了

6. 路由前缀

为了方便全局为路由path增加前缀信息,Zuul提供了zuul.prefix参数来进行设置,但是代理前缀会从默认路径中移除掉,为避免这种情况,可以使用zuul.stripPrefix=false 来关闭移除代理前缀的动作,也可以通过zuul.routes.<路由名>.strip-prefix=false来指定服务关闭移除代理前缀的动作

举例:

 zuul:
  prefix: /api
  routes:
    feign-customer:
      path: /feign/**
      stripPrefix: false

书中提到使用路由前需谨慎,不同的版本可能会存在一些bug.

Cookie与头信息

默认情况下,Zuul在请求路由时会过滤掉HTTP请求头信息中的一些敏感信息,防止这些敏感的头信息传递到下游外部服务器。但是如果我们使用安全框架如Spring Security、Apache Shiro等,需要使用Cookie做登录和鉴权,这时可以通过zuul.sensitiveHeaders参数定义,包括Cookie、Set-Cookie、Authorization三个属性来使Cookie可以被传递。

可以分为全局指定放行Cookie和Headers信息和指定路由放行

1.全局放行

zuul: 
  sensitiveHeaders: Cookie,Set-Cookie,Authorization

2.指定路由名放行

 zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders: Cookie,Set-Cookie,Authorization
      url: https://downstream

如果全局配置和路由配置均有不同程度的放行,那么采取就近原则,路由配置的放行规则将生效

本地跳转

迁移现有应用程序或API时的一种常见模式是“关闭”旧的端点,并慢慢地用不同的实现替换它们。Zuul代理是一个有用的工具,因为可以使用它来处理来自旧端点客户端的所有流量,但会将某些请求重定向到新端点。

zuul.routes.<路由名>.url=forward:/<要跳转到的端点>

举例:

 zuul:
  routes:
    first:
      path: /first/**
      url: http://first.example.com
    second:
      path: /second/**
      url: forward:/first

这样就会将请求到/second端点的请求转发到/first端点

Hystrix和Ribbon支持

上文有讲过,Zuul中包含了Hystrix和Ribbon的依赖,所以Zuul拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡,需要注意的是传统路由也就是使用path与url映射关系来配置路由规则的时候,对于路由转发的请求不会使用HystrixCommand来包装,所以没有线程隔离和断路器的保护,并且也不会有负载均衡的能力。所以我们在使用Zuul的时候推荐使用path与serviceId的组合来进行配置。

1. 设置Hystrix超时时间

使用hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置API网关中路由转发请求的命令执行时间超过配置值后,Hystrix会将该执行命令标记为TIMEOUT并抛出异常,Zuul会对该异常进行处理并返回如下JSON信息给外部调用方

{
    "timestamp":20180705141032,
    "status":500,
    "error":"Internal Server Error",
    "exception":"com.netflix.zuul.exception.ZuulException",
    "message":"TIMEOUT"
}

2. 设置Ribbon连接超时时间

使用ribbon.ConnectTimeout参数创建请求连接的超时时间,当ribbon.ConnectTimeout的配置值小于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值时,若出现请求超时的时候,会自动进行重试路由请求,如果依然失败,Zuul会返回如下JSON信息给外部调用方

{
    "timestamp":20180705141032,
    "status":500,
    "error":"Internal Server Error",
    "exception":"com.netflix.zuul.exception.ZuulException",
    "message":"NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED"
}

如果ribbon.ConnectTimeout的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值时,当出现请求超时的时候不会进行重试,直接超时处理返回TIMEOUT的错误信息

3. 设置Ribbon的请求转发超时时间

使用ribbon.ReadTimeout来设置请求转发超时时间,处理与ribbon.ConnectTimeout类似,不同点在于这是连接建立之后的处理时间。该值小于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值时报TIMEOUT错误,反之报TIMEOUT的错误。小于的时候会先重试,不成才报错;大于的时候直接报错。

4. 关闭重试配置

  • 全局配置zuul.retryable=false
  • 针对路由配置zuul.routes.<路由名>.retryable=false

过滤器解释

1. 过滤器

在快速入门的请求过滤这一节的学习中,我们使用的pre类型的过滤器,那时我们就比较好奇:为什么不用route或其它类型?

实际上,路由运行过程中,它的路由映射和请求转发是由几个不同的过滤器完成的。其中,

  • pre类型的过滤器用于路由的映射,将请求路径和路由规则进行匹配,用以找到需要转发目标地址。
  • route类型的过滤器用来请求转发,从pre类型过滤器获取的目标地址进行转发。

在Spring Cloud Zuul 中实现过滤器必须包含4 个基本特征:过滤类型、执行顺序、执行条件、具体操作。实际上就是ZuulFilter抽象类中定义的抽象方法:

String filterType();
int filterOrder();
boolean shouldFilter();
Object run();

这里分别进行解释:

  • filterType:该方法需要返回一个字符串来代表过滤器的类型,而这个类型就是Zuul中的4种不同生命周期的过滤器类型,如下
    • pre:在请求到达路由前被调用
    • route:在路由请求时被调用
    • error: 处理请求时发生的错误时被调用。
    • post:在route和error过滤器之后被调用,最后调用。
  • filterOrder:通过int值定义过滤器执行顺序,数值越小优先级越高。
  • shouldFilter:返回布尔值来判断该过滤器是否执行。
  • run:过滤器的具体逻辑。可以在此确定是否拦截当前请求等。

2. 请求的生命周期

和书的作者一样,我也去爬了下Zuul的官方wiki,这里我们简化梳理一下流程

首先HTTP请求到达Zuul,最先来到pre过滤器,在这里会去映射url patern到目标地址上然后将请求与找到的地址交给route类型的过滤器进行求转发,请求服务实例获取响应,通过post类型过滤器对处理结果进行加工与转换等操作返回。error类型的过滤器比较特殊,在这整个请求过程中只要有异常才会触发,将异常结果交给post类型过滤器加工返回

routing在filterType方法返回的类型是"route",不是routing,查官方代码发现的

3. 禁用过滤器

说到禁用过滤器,第一想到的是自定义的过滤器中shouldFilter返回false,实际应用中,这样还需要重新编译代码。

Zuul贴心地提供了一个参数用来禁用指定过滤器,zuul.<过滤器名>.<过滤器类型>.disable=true

之前我们的实验必须headers中有accessToken才能通过AccessFilter,现在我们禁用一下试试

zuul: 
  AccessFilter:
    pre:
      disable: true          #禁用名为AccessFilter的过滤器

测试结果:

终端也没有输出进入过滤器的字样,禁用成功。

书中这部分还有很多解读源码的部分,这里就不上了,有兴趣去买书看吧

动态加载

在微服务中,API网关担负着外部统一入口的重任,与其他服务不同,它必须保证不关闭不停机,从而确保整个系统的对外服务。所以我们就不能关机改东西再上线,这样会影响到其它服务。Zuul早已考虑了这点,它实现了不关机状态下的动态路由和动态添加/删除过滤器等功能。接下来我们分别来看看这些功能如何使用。

1. 动态路由

之前我们对于路由的规则控制几乎都是在application.yml或application.properties中完成的,网关服务不同于其它服务,需要不关闭不停机,所以这需要和Spring Cloud Config 组合起来,起到动态刷新配置路由的功能,这里简单说一下Config的工作原理,下一文会详细讲

Spring Cloud Config 的Server端无需注册到注册中心,只需要配置好github的路径以及在github上配好配置文件,用这个Server去拉取下来。然后我们的动态路由项目通过添加Config 的Client的依赖,这样就可以起到动态路由的功能

这里因为书中也是把这块放在了Spring Cloud Config前讲的,看了下官方文档,发现他最先讲的就是Spring Cloud Config……这里留到下文讲了

项目demo的结构如下:

创建一个新项目取名为DynamicRouteZuul,pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
<span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.cnblogs.hellxz<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>DynamicRouteZuul<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>Dalston.SR5<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Zuul的依赖 --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-zuul<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Eureka的依赖 --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-eureka<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-comment">&lt;!--Config客户端--&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-config<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">source</span>&gt;</span>1.8<span class="hljs-tag">&lt;/<span class="hljs-name">source</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">target</span>&gt;</span>1.8<span class="hljs-tag">&lt;/<span class="hljs-name">target</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>

</project>

这个文件我就不多讲了,就是maven的那一套,然后我们加

我们在java目录下创建一个主类DynamicZuulApp,

import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

@EnableZuulProxy
@SpringCloudApplication
@ComponentScan("com.cnblogs.hellxz")
public class DynamicZuulApp {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
    SpringApplication.run(DynamicZuulApp.class, args);
}

<span class="hljs-meta">@Bean</span>
<span class="hljs-meta">@RefreshScope</span>
<span class="hljs-meta">@ConfigurationProperties</span>(<span class="hljs-string">"zuul"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> ZuulProperties <span class="hljs-title">zuulProperties</span><span class="hljs-params">()</span></span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ZuulProperties();
}

}

同时创建一个com.cnblogs.hellxz的包,这里有这个包是因为如果你没有包用来包扫描,会报一个错误,如下图。

这里为了提github代码的时候不会将这个空目录忽略掉,加了一个只有包路径的一个类

接下来,我们在resources下我们创建一个bootstrap.yml

spring:
  application:
    name: dynamic-route-zuul                            #这个名字用做拉取配置文件的名称
  cloud:
    config:
      uri: http://localhost:7001/                         #指定Spring Cloud Config开放的项目端口
server:
  port: 5556
eureka:
  client:
    serviceUrl:
      defaultZone: http://peer1:1111/eureka/     #注册到注册中心中

其中,spring.application.name不仅作为注册中心的名称,Config Client会使用这个名称去Config Server项目中拉取dynamic-route-zuul.properties或yml

测试

分别启动注册中心、服务提供者、ConfigServer还有刚刚创建的动态路由项目

启动动态路由项目的过程中会看到控制台打印了如下图

博客园的图片无法看大图,想看大图可以复制这个图片的地址放到地址栏访问查看

我们在github中配置了dynamic-route-zuul.yml为

zuul:
  routes:
    service-provider:
      path: /service/**
      serviceId: eureka-service

也就是说,我们为service路径匹配到了服务提供者项目中,我们在这里使用postman访问一下

至此测试成功

这里省去了动态过滤器的实现,因为用到groovy,本人能力有限,有兴趣了解这一块的请看书查验

后记

这篇文章处在一个很尴尬的时间里,正如开头说的是开始于6.26日,一个是最近工作忙,一个是有些代码没有测试成功就没有提github上,然后回去只能干看着,重新写还浪费时间……然后今天抽空毅然决然的把这篇文章发了出来,看到之前几篇文章最近的阅读数不少,我心甚慰,如果你有更好的想法和建议,欢迎大家评论拍砖。

最后感谢一下《Spring Cloud微服务实战》的作者 程序员DD 翟永超 老师,书写的不错,Spring Cloud坑本来就不少,这种情况下还能将这些概念说的比较清晰,实属不易。

下文我们开始Spring Cloud Config的学习

本文参考:

《Spring Cloud微服务实战》的作者 程序员DD 翟永超

Spring Cloud 官方文档 Finchley.RELEASE

原文地址:https://blog.csdn.net/lifupingcn/article/details/88062637
posted @ 2019-07-12 09:39  星朝  阅读(10896)  评论(0编辑  收藏  举报