使用Zuul构建微服务网关

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Zuul构建微服务网关相关的知识,希望对你有一定的参考价值。

使用Zuul构建微服务网关

为什么要使用微服务网关

经过前文的讲解,微服务架构已经初具雏形,但还有一些问题——不同的微服务一般会有不同的网络地址,而外部客户端(例如手机APP)可能需要调用多个服务的接口才能完成一个业务需求。
如果让客户端直接与各个微服务通信,会有以下的问题:

  • 客户端多次请求不同的微服务,增加了客户端的复杂性
  • 存在跨域请求,在一定场景下处理相对复杂
  • 认证复杂,每个服务都需要独立认证
  • 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将很难实施。
  • 某些微服务可能使用了对防火墙/浏览器不友好的协议,直接访问时会有一定的困难。
    以上问题可借助微服务网关解决。微服务网关是介于客户端和服务器之间的中间层,所有的外部请求都会先经过微服务网关。
    微服务网关封装了应用程序的内部结构,客户端只用跟网关交互,而无须直接调用特定微服务的接口。这样,开发就可以得到简化。不仅如此使用微服务网关还有以下优点。
  • 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
  • 易于认证。可在微服务网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
  • 减少了客户端与各个微服务之间的交互次数。

Zuul简介

Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用。Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能。

  • 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
  • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
  • 动态路由:动态地将请求路由到不同地后端集群。
  • 压力测试:逐渐增加指向集群地流量,以了解性能。
  • 负载分配:为每一种负载类型分配对应容量。并弃用超出限定值地请求。
  • 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群。
  • 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB使用地多样化,以及让系统的边缘更贴近系统的使用者。
    Spring Cloud对Zuul进行了整合与增强。目前,Zuul使用的默认HTTP客户端是Apache HTTP Client,也可以使用RestClient或者okhttp3.OkHttpClient。如果想要使用RestClient,可以设置ribbon.restclient.enabled=true;想要使用okhttp3.OkHttpClient,可以设置ribbon.okhttp.enabled=true。

编写Zuul微服务网关

1)创建一个Maven工程,ArtifactId是microservice-gateway-zuul,并为项目添加以下依赖。

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

2)在启动类上添加注解@EnableZuulProxy,声明一个Zuul代理。该代理使用Ribbon来定位注册在Eureka Server中的微服务;同时,该代理还整合了Hystrix,从而实现了容错,所有经过Zuul的请求都会在Hystrix命令中执行。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

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

3)编写配置文件application.xml

server:
  port: 8040
spring:
  application:
    name: microservice-gateway-zuul
eureka:
  client:
    service-url: 
      defaultZone: http://localhost:8761/eureka/

测试一、路由规则
1)启动项目microservice-discovery-eureka。
2)启动项目microservice-provider-user。
3)启动项目microservice-consumer-movie-ribbon。
4)启动项目microservice-gateway-zuul。
5)访问http://localhost:8040/microservice-consumer-movie/user/1,请求会被转发到http://localhost:8010/user/1(电影微服务)。

6)访问http://localhost:8040/microservice-provider-user/1,请求会转发到http://localhost:8000/1(用户微服务)

说明在默认情况下,Zuul会代理所有注册到Eureka Server的微服务,并且Zuul的路由规则如下:
http://ZUUL_HOST:ZUUL_PORT/微服务在Eureka上的serviceId/**会被转发到serviceId对应的微服务。
测试二、负载均衡
1)启动项目microservice-discovery-eureka
2)启动多个microservice-provider-user实例
3)启动项目microservice-gateway-zuul。此时,Eureka Server首页。

4)多次访问http://localhost:8040/microservice-provider-user/1,会发现两个用户微服务节点都打印了

说明Zuul可以使用Ribbon达到负载均衡的效果。
测试三、Hystrix容错与监控
1)启动项目microservice-discovery-eureka
2)启动项目microservice-provider-user
3)启动项目microservice-consumer-movie-ribbon
4)启动项目microservice-gateway-zuul
5)启动项目microservice-hystrix-dashboard
6)访问http://localhost:8040/microservice-consumer-movie/user/1,可获得预期结果。

7)在Hystrix Dashboard中输入http://localhost:8040/hystrix.stream,随意指定某个Title并单击Monitor Stream按钮后

说明Zuul已经整合了Hystrix

管理端点

当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露两个端点:/routes和filters。借助这些端点,可方便、直观地查看以及管理Zuul。

routes端点

/routes端点的使用非常简单

  1. 使用GET方法访问该端点,即可返回Zuul当前映射的路由列表。
  2. 使用POST方法访问该端点就会强制刷新Zuul当前映射的路由列表。尽管路由会自动刷新,Spring Cloud依然提供了强制立即刷新的方式。
  3. Spring Cloud Edgware对/routes端点进行了进一步的增强,我们可使用/routes?format=details查看更多与路由相关的详情设置。
    由于spring-cloud-starter-netfilx-zuul已经包含了spring-boot-starter-actuator,因此之前编写的microservice-gateway-zuul已具备路由管理的能力。
    测试
    1)启动项目microservice-discovery-eureka
    2)启动项目microservice-provider-user
    3)启动项目microservice-consumer-movie-ribbon
    4)修改项目microservice-gateway-zuul的配置,设置management.security.enabled: false
    5)启动项目microservice-gateway-zuul
    6)使用浏览器访问http://localhost:8040/routes,可获得如下的结果

    7)访问http://localhost:8040/routes?format=details

    由结果可知,此时可看到更多与路由相关的配置,例如路由id、转发路径、是否重试等,这些对于我们调试很有用
    也可使用类似方式测试Zuul路由的自动刷新与强制刷新。

filters端点

从Spring Cloud Edgware版本开始,Zuul提供了/filters端点。访问该端点即可返回Zuul中当前所有过滤器的详情,并按照类型分类。
如下是/filters端点的展示结果,从中,我们可以了解当前Zuul中,error、post、pre、route四种类型的过滤器分别有哪些,每个过滤器order(执行顺序)是多少,以及是否启用等信息。这对Zuul问题的定位很有用。

路由配置详情

前文已经编写了一个简单的Zuul网关,并让该网关代理了所有注册到Eureka Server的微服务。但在现实中可能只想让Zuul代理部分微服务,又或者需要对URL进行更加精确的控制。
1)自定义指定微服务的访问路径
配置zuul.routes.指定微服务的serviceId = 指定路径接口

zuul:
  routes: 
    microservice-provider-user: /user/**

完成设置后,microservice-provider-user微服务就会被映射到/user/** 路径。
2)忽略指定微服务
忽略服务非常简单,可以使用zuul.ignored-services配置需要忽略的服务,多个服务间用逗号分隔。

zuul:
  ignored-services: microservice-provider-user,microservice-consumer-movie

这样就可让Zuul忽略microservice-provider-user和microservice-consumer-movie微服务,只代理其他微服务。
3)忽略所有微服务,只路由指定微服务
很多场景下,可能只想要让Zuul代理指定的微服务,此时可以将zuul.ignored-services设为’*’

zuul:
  routes:
    microservice-provider-user: /user/**
  ignored-services: '*'

这样就可以让Zuul只路由microservice-provider-user微服务。
4)同时指定微服务的serviceId和对应路径。

zuul:
  routes:
    user_route:  #该配置方式中,user-routes只是给路由一个名称,可以任意起名。
      service-id: microservice-provider-user
      path: /user/**  #service-id对应的路径

5)同时指定path和URL

zuul:
  routes:
    user_route:
      url: http://localhost:8000/
      path: /user/**

这样可以将/user/**映射到http://localhost:8000/**
需要注意的是,使用这种方式配置的路由不会作为HystrixCommand执行,同时也不能使用RIbbon来负载均衡多个URL。
6)同时指定path和URL,并且不破坏Zuul的Hystrix、Ribbon特性

zuul:
  routes:
    user_route:
      service-id: microservice-provider-user
      path: /user/**
ribbon:
  eureka:
    enable: false
microservice-provider-user:
  ribbon:
    listOfServers: localhost:8000,localhost:8001

7)使用正则表达式指定Zuul的路由匹配规则
借助PatternServiceRouteMapper,实现从微服务到映射路由的正则配置

package com.example.config;

import org.springframework.cloud.netflix.zuul.filters.discovery.PatternServiceRouteMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RouteMapperConfig {
    @Bean
    public PatternServiceRouteMapper serviceRouteMapper(){
        //调用构造函数PatternServiceRouteMapper(String servicePattern, String routePattern)
        //servicePattern指定微服务的正则
        //routePattern指定路由正则
        return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)","${version}/${name}");
    }
}

通过这段代码即可实现将诸如microservice-provider-user-v1这个微服务,映射到/v1/microservice-provider-user/**这个路径。
8)路由前缀

zuul:
  prefix: /api
  strip-prefix: false
  routes:
    microservice-provider-user: /user/**

这样,访问Zuul的/api/user/1路径,请求将被发送到microservice-provider-user的/api/1。

zuul:
  routes:
    microservice-provider-user:
      path: /user/**
      strip-prefix: false

这样,访问Zuul的/api/user/1路径,请求将被发送到microservice-provider-user的/user/1。
9)忽略某些路径
上文讲解了如何忽略微服务,但有时还需要更细粒度的路由控制。例如,想让Zuul代理某个微服务,同时又想保护该微服务的某些敏感路径。此时,可使用ignored-Patterns,指定忽略的正则。

zuul:
  ignored-patterns: /**/admin/**
  routes:
    microservice-provider-user: /user/**

这样就可将microservice-provider-user微服务映射到/user/**路径,但会忽略该微服务中所有包含/admin/的路径。
10)本地转发

zuul:
  routes: 
    route-name:
      path: /path-a/**
      url: 
        forward: /path-b

这样,当访问Zuul的/path-a/**路径,将转发到Zuul的/path-b/**
注:若无法掌握Zuul路由的规律,可将com.example包的日志级别设为DEBUG。这样,Zuul就会打印转发的具体细节,从而有助于更好地理解Zuul地路由配置。

logging:
  level:
    com.example: DEBUG

Zuul的安全与Header

敏感Header的设置

一般来说,可在同一个系统中的服务之间共享Header。不过应尽量防止让一些敏感的Header外泄。因此,在很多场景下,需要通过为路由指定一些了敏感Header列表。

zuul:
  routes:
    microservice-consumer-movie:
      path: /users/**
      sensitive-headers: Cookie,Set-Cookie,Authorization
      url: https://downstream

这样就可为microservice-consumer-movie微服务指定敏感Header了。
也可用zuul.sensitive-headers全局指定敏感Header

zuul:
  sensitive-headers: Cookie,Set-Cookie,Authorization

需要注意的是,如果使用zuul.routes.*.sensitive-headers的配置方式,会覆盖全局的配置。

忽略Header

可使用zuul.ignoredHeaders属性丢弃一些Header

zuul:
  ignored-headers: Header1,Header2

这样设置后,Header1和Header2将不会传播到其他的微服务中。
默认情况下,zuul.ignored-headers是空值,但如果Spring Security在项目的classpath中,那么zuul.ignored-headers的默认值就是Pragma,Cache-Contronl,X-Frame-Options,X-Content-Type-Option,X-XSS-Protection,Expires。所以,当Spring Security在项目的classpath中,同时又需要使用下游微服务的Spring Security的Header时,可以将zuul.ignoreSecurity-Headers设置false。

使用Zuul上传文件

对于小文件(1M以内)上传,无须任何处理,即可正常上传。对于大文件(10M以上)上传,需要为上传路径添加/zuul前缀。也可使用zuul.servlet-path自定义前缀。
也就是说,假设zuul.routes.microservice-flie-upload = /microservice-file-upload/**,如果http://{HOST}:{PORT}/upload是微服务microservice-file-upload的上传路径,则可使用Zuul的/zuul/microservice-file-upload/upload路径上传大文件。
如果Zuul使用了Ribbon做负载均衡,那么对于超大的文件,需要提升超时设置。

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMillseconds: 60000
ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000

1)创建一个Maven工程,ArtifactId是microservice-file-upload,并为项目添加以下依赖。

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2)在启动类上添加@SpringBootApplication、@EnableEurekaClient注解。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class FileUploadApplication {
    public static void main(String[] args){
        SpringApplication.run(FileUploadApplication.class,args);
    }
}

3)编写Controller

package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Controller
public class FileUploadController {
    @RequestMapping(value = "/upload",method = RequestMethod.POST)
    public @ResponseBody
    String handleFileUpload(@RequestParam(value = "file",required = true)MultipartFile file) throws IOException {
        byte[] bytes=file.getBytes();
        File fileTOSave=new File(file.getOriginalFilename());
        FileCopyUtils.copy(bytes,fileTOSave);
        return fileTOSave.getAbsolutePath();
    }
}

配置文件application.yml,在其中添加如下内容

server:
  port: 8050
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
spring:
  application:
    name: microservice-file-upload
  http:
    multipart:
      max-file-size: 2000Mb			#Max file size,默认1M
      max-request-size: 2500Mb		#Max request size,默认10M

由配置可知,已经将该服务注册到Eureka Server上,并配置了文件上传大小的限制。这样一个文件上传的微服务就编写完成了。

curl -F "file=@D:/user.csv" localhost:8050/upload

测试
1)准备1个小文件(1M以下),记为small.file;1个超大文件(1G以上,2G以上),记为large.file。
2)启动microservice-discovery-eureka
3)启动microservice-file-upload
4)启动microservice-gateway-zuul
5)测试直接上传到microservice-file-upload上:使用命令curl -F "file=@D:/user.csv" localhost:8050/upload,上传大文件,发现可以正常上传。同理,小文件也可以上传。
6)测试通过Zuul上传小文件

curl -v -H "Transfer-Encoding: chunked" -F "file=@D:/user.csv" localhost:8040/microservice-file-upload/upload


7)测试通过Zuul上传大文件,不添加/zuul前缀。

curl -v -H "Transfer-Encoding: chunked" -F "file=@D:/user.csv" localhost:8040/microservice-file-upload/upload


8)测试通过Zuul上传大文件,添加/zuul前缀

curl -v -H "Transfer-Encoding: chunked" -F "file=@D:/test.exe" localhost:8040/zuul/microservice-file-upload/upload


9)因此,不妨在application.yml中添加如下内容。

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000

10)关闭microservice-gateway-zuul,启动microservice-gateway-zuul-file-upload再次使用如下命令进行测试,此时已可正常上传文件。

Zuul的过滤器

过滤器类型与请求生命周期

Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了4种标准过滤器类型,这些过滤器类型对应请求的典型生命周期。

  • PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • POUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • ERROR:在其他阶段发送错误时执行该过滤器。
    除了默认的过滤器类型,Zuul还允许创建自定义的过滤器类型。例如,可以定制一种STATIC类型的 过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。

内置过滤器详解

Spring Cloud默认为Zuul编写并启用了一些过滤器,这些过滤器有什么作用呢?我们不妨结合@EnableZuulServer、@EnableZuulProxy两个注解进行讨论。
可将@EnableZuulProxy简单理解为@EnableZuulServer的增强版。事实上,当Zuul与Eureka、Ribbon等组件配合使用时,@EnableZuulProxy是我们最常用的注解,这里使用的也是@EnableZuulServer。
@RequestContext,其用于在过滤器之间传递消息。它的数据保存在每个请求的ThreadLocal中。它用于存储请求路由到哪里、错误、HttpServletRequest、HttpServletResponse等信息。RequestContext扩展了ConcurrentHashMap,所以,理论上任何数据都可以存储在RequestContext中。

@EnableZuulServer所启用的过滤器

pre类型过滤器
1)ServletDetectionFilter:该过滤器用于查看请求是否通过Spring Dispatcher。检查后,通过FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY设置布尔值。
2)FormBodyWrapperFilter:解析表单数据,并为请求重新编码。
3)DebugFilter:顾名思义,调试用的过滤器。当设置zuul.includer-debug-header=true抑或设置zuul.debug.request=true,并在请求时加上了debug=true的参数,例如$ZUUL_HOST:ZUUL_PORT/some-path?debug=true,就会开启该过滤器。该过滤器会把RequestContext.setDebugRouting()以及RequestContext.setDebugRequest()设为true。
route类型过滤器
SendForwardFilter:该过滤器使用Servlet RequestDispatcher转发请求,转发位置存储在RequestContext的属性FilterConstants.FORWARD_TO_KEY中。这对转发到Zuul自身的端点很有用。

zuul:
	routes:
		abc:
			path: /path-a/**
			url: forward:/path-b

post类型过滤器
SendResponseFilter:将代理请求的响应写入当前响应。
error类型过滤器
SendErrorFilter:若RequestContext.getThrowable()不为null,则默认转发到/error,也可以设置error.path属性来修改默认的转发路径。

@EnableZuulProxy所启用的过滤器

如果使用注解@EnableZuulProxy,那么除上述过滤器外,Spring Cloud还会安装以下过滤器。
pre类型过滤器
PreDecorationFilter:该过滤器根据提供的RouteLocator 确定路由到的地址,以及怎样去路由。同时,该过滤器还为下游请求设置各种代理相关的header。
route类型过滤器
1)RibbonRoutingFilter:该过滤器使用Ribbon、Hystrix和可插拔的HTTP客户端发送请求。serviceId在RequestContext的属性FilterConstants.SERVICE_ID_KEY中。该过滤器可使用如下这些不同的HTTP客户端。

  • Apache HttpClient:默认的HTTP客户端
  • Squareup OkHttpClient v3:若需使用该客户端,需保证com.squareup.okhttp3的依赖在classpath中,并设置ribbon.okhttp.enable=true。
  • Netflix RibbonHTTP Client:设置ribbon.restclient.enable=true即可启用该HTTP客户端。该客户端有一定限制,例如不支持PATCH方法,另外,它有内置的重试机制。
    2)SimpleHostRoutingFilter:该过滤器通过Apache HttpClient向指定的URL发送请求。URL在RequestContext.getRouteHost()中。

编写Zuul过滤器

1)复制项目microservice-gateway-zuul,将ArtifactId修改为microservice-gateway-zuul-filter。
2)编写自定义Zuul过滤器

package com.example.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;

import javax.servlet.http.HttpServletRequest;

public class PreRequestLogFilter extends ZuulFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(PreRequestLogFilter.class);
    @Override
    public String filterType() {
        //pre类型的过滤器
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        //在org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter之前执行
        return FilterConstants.PRE_DECORATION_FILTER_ORDER -1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request=ctx.getRequest();
        PreRequestLogFilter.LOGGER.info(String.format("send %s request to %s",request.getMethod(),request.getRequestURI().toString()));
        return null;
    }
}

由代码可知,自定义的Zuul Filter需实现以下几个方法。

  • filterType:返回过滤器的类型。有pre、route、post、error等几种取值,分别对应上文的几种过滤器。
  • filterOrder:返回一个int值来指定过滤器的执行顺序,不同的过滤器允许返回相同的数字。
  • shouldFilter:返回一个boolean值来判断该过滤器是否要执行,true表示执行,false表示不执行。
  • run:过滤器的具体逻辑。本例中让它打印了请求的HTTP方法以及请求的地址。
    3)创建配置类
package com.example.config;

import com.example.filter.PreRequestLogFilter;
import org.springframework.context.annotation以上是关于使用Zuul构建微服务网关的主要内容,如果未能解决你的问题,请参考以下文章

springCloud(14):使用Zuul构建微服务网关-路由端点与路由配置详解

springCloud(16):使用Zuul构建微服务网关-容错回退与高可用

11 微服务集群网关Zuul介绍

使用 Keycloak 作为授权服务器,Zuul 作为 API 网关

SpringCloud服务网关Zuul分析①分发

Spring Boot + Spring Cloud 构建微服务系统:API服务网关(Zuul)