本文为《Spring Cloud微服务实战》一书的摘要总结

快速开始

创建网关

  • 创建一个Spring Boot应用,引入spring-cloud-starter-netflix-zuul依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

该模块下不仅包含了NetflixZuul的核心依赖zuul-core,还包括一下依赖:

  • spring-boot-starter-web
  • spring-boot-starter-actuator
  • spring-cloud-starter-netflix-hystrix
  • spring-cloud-starter-netflix-ribbon
  • spring-cloud-starter-netflix-archaius
  • 在应用主类使用@EnableZuulProxy注解开启Zuul的API网关服务功能
  • 在配置文件配置应用名称、端口等信息。

请求路由

传统路由方式

网关应用没有与成服务治理一起使用,需要手动指定服务的url:

zuul:
  routes:
    serverA:
      path: /server-a/**
      url: http://localhost:8080/

上面的配置定义了发往API网关服务的请求中,所有符合/server-a/** 规则的访问都将被路由转发到http://localhost:8080/ 地址上。其中zuul.rootes.serverA中的serverA是路由名称,可以随便定义;在.properties类型的配置文件中,一组path与url的路由名称要相同,如:

zuul.routes.serverA.path=/server-a/**
zuul.routes.serverA.url=http://localhost:8080/ #如果没有该行配置,那么符合、server-a/**的请求不会被路由转发

面向服务的路由

  • 加入对eureka的依赖:
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 修改配置

指定serviceId 而不是 url。

eureka:
  client:
     service-url:
      defaultZone: http://192.168.1.6:1111/eureka/ #将自己注册为服务
  instance:
    prefer-ip-address: true

zuul:
  routes:
    UserServer:
      path: /user-server/**
      #url: http://localhost:8080/ 不再使用url参数
      serviceId: CONSUMER # 指定服务名
    FeignServer:
      path: /feign-server/**
      serviceId: FEIGN-CONSUMER

请求过滤

我们可以通过继承com.netflix.zuul.ZuulFilter并重写它的方法来实现对请求的过滤:

public class AccessFilter extends ZuulFilter { //继承com.netflix.zuul.ZuulFilter
    private static Logger logger = Logger.getLogger("AccessFilter");

    //过滤器类型,它决定过滤器在哪个生命周期执行
    @Override
    public String filterType() {
        return "pre";
    }

    //过滤器执行顺序
    @Override
    public int filterOrder() {
        return 0;
    }
    //判断该过滤器是否需要被执行
    @Override
    public boolean shouldFilter() {
        return true;
    }
    //过滤器的具体逻辑
    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        logger.info("send " + request.getMethod() + "to " + request.getRequestURL().toString());
        Object accessToken = request.getParameter("accessToken");
        if (accessToken == null){
            logger.warning("access token is empty");
            ctx.setSendZuulResponse(false); //false:不对该请求进行路由
            ctx.setResponseStatusCode(401); //设置状态码
            return null;
        }
        logger.info("access token ok");
        return null;
    }
}

然后我们创建AccessFilterBean,启动网关服务后,该过滤器生效。

总结

  • API网关作为系统的同意入口,屏蔽了系统内部各个微服务的细节
  • API网关可以与服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发
  • API网关可以实现权限校验,使其与业务逻辑解耦
  • API网关中的过滤器,在各生命周期中区校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态行,同时降低了微服务的测试难度,让服务更集中关注雨雾逻辑的处理。

路由详解

传统路由配置

  • 单实例配置:
zuul:
  routes:
    serverA:
      path: /server-a/**
      url: http://localhost:8080/
  • 多实例配置:
zuul:
  routes:
    serverA:
      path: /server-a/**
      serviceId: SERVER-A #指定serviceId

 ribbon:
    eureka:
        enabled: false #不使用eureka服务治理服务

 SERVER-A: # 对应前面配置的serviceId的值
    ribbon:
        listOfServers: # 手工维护服务列表
            - http://localhost:8080/
            - http://localhost:8081/

服务路由配置

zuul:
  routes:
    UserServer:
      path: /user-server/**
      serviceId: CONSUMER

除了使用path和serviceId之外,还有一种更简便的配置方式:zuul.routes.=

zuul:
    routes:
        CONSUMER: /user-server/**

当zuul.routes下没有path属性时就要明白用的是这种配置方式了。

服务路由的默认规则

当我们为Spring cloud Zuul构建的API网关服务引入Spring Cloud Eureka之后,它为Eureka中的 每个服务 都自动创建一个默认路由规则:path会使用serviceId配置的服务名作为前缀。即:

zuul:
    routes:
        <serviceId>: <serviceId>/** #路径全为小写

我们也可以通过zuul.ignored-services参数来设置一个服务名表达式来定义不自动创建路由的规则:

zuul:
    ingnord-services: user-* #以"user-"开头的服务都不会自动创建默认的路由规则

自定义路由规则

我们可以创建PatternServiceRouteMapperBean来自定义服务与路由映射的生成关系(serviceId与path的关系):

@Bean
public PatternServiceRouteMapper patternServiceRouteMapper(){
    return new PatternServiceRouteMapper(
    "?(<name>^.+)-(?<version>v.+$)", //<任意多的任意字符>-<v开头后跟任意多任意字符>
    "${version}/$<name>"
    );
}

构造函数的第一个参数用来匹配服务名称是否符合自定义规则的正则表达式,第二个参数则是根据服务名中定义的内容转化出的路径表达式规则。

如果我们有一个服务的serviceId为:userServer-v1,那么Zuul会自动配置如下路由:

zuul:
    routes:
        userServer-v1: /v1/userserver/**

正则表达式

路径匹配

在Zuul中,路由匹配的路径表达式采用了Ant风格定义

通配符

说明

?

匹配任意单个字符

*

匹配任意数量的字符

**

匹配任意数量的字符,支持多级目录

当一个请求路径与多个path匹配时,Zuul将通过线性遍历的方式,使用第一个匹配到请求路径的路由规则。而所有的路由规则都存在一个LinkedHashMap中,所以我们配置路由规则的顺序会影响到请求路径的路由。而使用.properties文件的配置内容无法保证有序,我们应该使用YAML文件来配置。

忽略表达式

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

上面的配置将导致任何路径中有/hello/的请求都不会被API网关路由。

路由前缀

zuul:
  prefix: /api
  routes:
    UserServer:
      path: /user-server/**
      serviceId: CONSUMER

上面的路由规则为:/api/user-server/**

我们还可以设置stripPrefix为false:

zuul:
  prefix: /api
  stripPrefix: false
  routes:
    UserServer:
      path: /user-server/**
      serviceId: CONSUMER

此时,上面的路由规则为:/api/user-server/** ;同时也会在调用具体的CONSUMER服务是加上前缀/api,即:如果CONSUMER服务的真实地址为:localhost:8080,那么API网关会路由到localhost:8080/api/

注意在使用prefix时,要避免将prefix设置为与现有path中前缀相同,如存在路由规则:/api/user-server/** ,如果再设置前缀为/api,这条路由规则将无法路由

本地跳转

在通过使用path与url的配置方式时,可以使用forward来指定需要跳转的服务器资源路径

zuul:
    routes:
        server-a:
            path: /server-a/**
            url: forward:/local

如果网关的地址为:localhost:8080,那么请求http://localhost:8080/server-a/hello将会被转发到http://localhost:8080/local/hello。

Cookie与头信息

默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉HTTP请求头信息中的敏感信息,防止它们被传递到下游的外部服务器。

敏感的头信息包括:Cookie,Set-Cookie,Authorization三个属性,我们可以通过zuul.sensitiveHeaders参数定义。

当我们需要将这些敏感的信息传递给下游外部服务器时,我们需要设置zuul.sensitiveHeaders

zuul:
	sensitive-headers:  #将改参数设置为空,所有请求的头信息都将传递下去

或者:

zuul:
	routes:
		server-a:
			customSensitiveHeasers: true # 对指定路由开启自定义路由
			sensitiveHeaders :  #敏感头设置为空

重定向问题

当我们解决了Cookie问题后,我们可以通过网关来访问并登陆到我们的Web应用了,但是这个时候会出现另一个问题:登陆成功后跳转到的页面URL是具体Web应用实例的地址,而不是通过网关的路由地址。我们可以通过设置auul.addHostHeader=true,使得网关在进行路由转发前为请求设置Host头信息,以标识最初的服务端请求地址。

Ribbon和Hystrix

Zuul天生拥有线程隔离和断路器额自我保护能力,以及对服务调用的客户端负载均衡的能力。

但是当我们使用path与url的映射关系来配置路由规则时,由于路由转发的请求不会采用HystrixCommand来包装,所以这类路由请求没有线程隔离和断路器保护,也不会有负载均衡的能力。

我们在使用Zuul搭建API网管的时候,可以通过Hystrix和Ribbon的参数来调整路由请求的这种超时时间等配置。

过滤器详解

过滤器

过滤器是Zuul实现API网关功能的核心部件,它的路由映射、请求和转发都是通过几个过滤器实现的。

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

public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
	/*
		该函数需要返回一个字符串来代表过滤器的类型,这个类型就是HTTP请求过程中定义的各个阶段
		pre:可以在请求被路有钱被调用
		routing:在路由请求时被调用
		post:在routing和error过滤器之后被调用
		error:处理请求时发生错误时被调用
	*/
	public abstract String filterType();
	/*
		通过int值来确定过滤器的执行顺序
		数值越小优先级越高
	*/
	public abstract int filterOrder();
}
public interface IZuulFilter {
	/*
		返回一个Boolean值来判断该过滤器是否要执行。
		我们可以通过该方法来确定过滤器的有效范围
	*/
    boolean shouldFilter();
	/*
		过滤器的具体逻辑
	*/
    Object run() throws ZuulException;
}

请求生命周期

springboot 集成邮件服务 springboot集成zuul_springboot 集成邮件服务

  • 第一阶段:请求之前做一些前置工作
  • 第二阶段:完成"pre"类型的过滤器处理之后,开始将外部请求转发到具体服务实例上的过程,当服务实例将请求结果都返回之后,routing阶段才完成
  • 第三阶段:当服务实例返回请求结果后进入第三阶段,"post"类型过滤器中,我们可以对处理结果你进行一些加工。
  • 特殊阶段:当上面的三个阶段中发生异常是才会触发,它的最后流向还是"post"类型的过滤器。

核心过滤器

Spring Cloud Zuul在HTTP请求生命周期的各个阶段默认实现了一批核心过滤器,它们定义在org.springframework.cloud.netflix.zuul.filters包下

springboot 集成邮件服务 springboot集成zuul_springboot 集成邮件服务_02

pre 过滤器

过滤器

优先级

作用

ServletDetectionFilter

-3

最先被执行的过滤器,总是会被执行,主要用来检测当前的请求是通过Spring的DispatcherServlet处理运行的,还是通过ZuulServlet来处理运行的,处理结果会以Boolean类型存在请求上下文的isDispatcherServletRequest参数中

Servlet30WrapperFilter

-2

第二个被执行的顾虑器,总是会被执行,主要是为了将原始的HttpServletRequest包装成Servlet30RequestWrapper对象

FormBodyWrapperFilter

-1

过滤器对两种请求生效:一种是Content-Type为application/x-www-form-urlencoded的请求;另一种是Content-Type为multipart/form-data并且是有Spring的DispatcherServlet处理的请求。该过滤器的主要作用是将符合要求的请求体包装成FormBodyRequestWrapper对象

DebugFIlter

1

该过滤器会根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行。它的主要作用是将当前请求上下文的debugRoutingdebugRequest参数设置为true

PreDecorationFilter

5

判断当前请求上线文中是否存在forward.to和serviceId参数,如果不存在就会执行具体过滤器操作————为当前请求做一些预处理,如进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及路由匹配结果等一些设置信息。

routing 过滤器

过滤器

优先级

作用

RibbonRoutingFilter

10

routing阶段第一个执行的过滤器,该过滤器只对当前请求上下文中存在serviceId参数的请求进行处理,它的处理逻辑就是面向服务路由的核心,通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的请求结果返回。

SimpleHostRoutingFilter

100

该顾虑器只处理请求上下文中存在routeHost参数的请求,即只对通过url配置路由规则的请求生效。操作逻辑就是直接向routeHost参数的物理地址发起请求,请求是使用httpclient包实现的,所以这类请求没有线程隔离和断路器保护。

SendForwardFilter

500

只对上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中的forward本地跳转配置

post 过滤器

过滤器

优先级

作用

SendErrorFilter

0

post阶段第一个执行的过滤器,该过滤器仅在请求上下文中包含error.status_code参数并且还没有被该过滤器处理过的时候执行。过滤器的处理逻辑就是利用请求上下文中的错误信息来组成一个forward到API网关的/error错误端点的请求产生错误响应。

SendResponseFilter

1000

是post最后阶段执行的过滤器。该过滤器会检查当前请求上下文是否包含请求响应相关的头信息、响应数据流或是响应体,只有包含其中之一的时候才执行。执行的逻辑就是利用请求上下文中的响应信息来组织需要发送回客户端的相应内容。

异常处理

Spring Cloud Zuul 默认实现了SendErrorFilter这个"error"类型的过滤器,所有其他类型过滤器在执行过程中抛出错误,都会在SendErrorFilter过滤器中进行处理,而处理的逻辑只是简单的在控制台打印出异常的栈信息,设置状态码为500,跳转到Spring boot默认的错误页面。

我们可以继承SendErrorFilter,然后覆盖它shouldFilter方法来限制处理

后面再补充