Spring Cloud 快速入门(五)微服务网关Zuul

1. 简介

1.1 网关简介

网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权、限流、路由、监控等功能。

1.2 Zuul 官网简介

在这里插入图片描述

【原文】Zuul is the front door for all requests from devices and web sites(设备和 web 站点)to the backend of the Netflix streaming application(Netflix 流应用后端). As an edge service application(边界服务应用), Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate(视情况而定, 酌情).

【翻译】ZUUL 是从设备和 web 站点到 Netflix 流应用后端的所有请求的前门。作为边界服务应用,ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。它还具有根据情况将请求路由到多个 Amazon Auto Scaling Groups 的能力。

1.3 Zuul 综合说明

Zuul 主要提供了对请求的路由与过滤功能。

  • 路由:将外部请求转发到具体的微服务实例上,是外部访问微服务的统一入口。
  • 过滤:对请求的处理过程进行干预,对请求进行校验、 鉴权等处理。

在这里插入图片描述

将官方的架构图再进一步抽象,就变为了下图。
在这里插入图片描述

2. 基本环境搭建

这里需要一个 EurekaServer,一个 zuul 网关,及两个消费者(不用提供者了,直接让消费者降级)。

2.1 创建工程 05-zuul-consumer-8080

(1) 创建工程
复制第四章的工程 04-consumer-fallbackmethod-8080,并重命名为 05-zuul-consumer-8080。

(2) 修改配置文件

server:
  port: 8080

spring:
  application:
    name: abcmsc-consumer-depart-8080

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

(3) 修改处理器
在服务降级的处理上添加当前工程端口号,方便测试:
在这里插入图片描述

@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
    
    
    @Autowired
    private RestTemplate restTemplate;
	...

    @HystrixCommand(fallbackMethod = "getHystrixHandler")
    @GetMapping("/get/{id}")
    public Depart getByIdHandler(@PathVariable("id") int id) {
    
    
        String url = SERVICE_PROVIDER + "/provider/depart/get/" + id;
        return restTemplate.getForObject(url, Depart.class);
    }

    public Depart getHystrixHandler(@PathVariable("id") int id) {
    
    
        Depart depart = new Depart();
        depart.setId(id);
        depart.setName("no this depart - 8080");
        return depart;
    }
}

2.2 创建工程 05-zuul-consumer–8090

(1) 创建工程
复制工程 05-zuul-consumer-8080,并重命名为 05-zuul-consumer-8090。

修改启动类,更名为ApplicationConsumer8090

(2) 修改配置文件

server:
  port: 8090

spring:
  application:
    name: abcmsc-consumer-depart-8090

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

(3) 修改处理器
同样修改服务降级的处理,添加当前工程的端口号:
在这里插入图片描述

@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
    
    
    @Autowired
    private RestTemplate restTemplate;
    // 要使用微服务名称来从eureka server查找提供者
    private static final String SERVICE_PROVIDER = "http://abcmsc-provider-depart";
	...

    @HystrixCommand(fallbackMethod = "getHystrixHandler")
    @GetMapping("/get/{id}")
    public Depart getByIdHandler(@PathVariable("id") int id) {
    
    
        String url = SERVICE_PROVIDER + "/provider/depart/get/" + id;
        return restTemplate.getForObject(url, Depart.class);
    }

    // 定义服务降级方法,即响应给客户端的备选方案
    public Depart getHystrixHandler(@PathVariable("id") int id) {
    
    
        Depart depart = new Depart();
        depart.setId(id);
        depart.setName("no this depart - 8090");
        return depart;
    }
}

3. 服务路由

当用户提交某请求后,该请求应交给哪个微服务的哪个主机来处理,可以通过配置完成。

3.1 创建 zuul 网关工程 00-zuul-9000

(1) 创建工程
新建工程,或者直接复制上一章的 00-hystrix-turbine-8888 工程,并重命名为 00-zuul-9000。

(2) 导入依赖
首先除了 eureka client 依赖后,将其它依赖全部删除,然后再导入 zuul 依赖。

<!--zuul依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

<!--eureka客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(3) 修改启动类
在这里插入图片描述

@EnableZuulProxy//开启服务网关
@SpringCloudApplication
public class ApplicationZuul9000 {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(ApplicationZuul9000.class, args);
    }

}

(4) 定义配置文件
在这里插入图片描述

server:
  port: 9000

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

spring:
  application:
    name: abcmsc-zuul-depart

(5) 启动测试

通过网关应用访问微服务:
在这里插入图片描述

3.2 路由策略配置

(1) 修改配置文件
前面的访问方式,需要将微服务名称暴露给用户,会存在安全性问题。所以,可以自定义路径来替代微服务名称,即自定义路由策略

在配置文件中添加如下配置。

zuul:
  routes:
    # 指定微服务的路由规则
    # *为通配符
    # /** 可以匹配0到多级路径
    # /* 只能匹配一级路径
    # /? 只能匹配一级路径,且路径只能包含一个字符
    abcmsc-consumer-depart-8080: /abc8080/**
    abcmsc-consumer-depart-8090: /abc8090/**

效果:
在这里插入图片描述

3.3 服务名屏蔽

前面的设置方式可以使用指定的路由路径访问到相应微服务,但使用微服务名称也可以访问到,为了防止服务侵入,可以将服务名称屏蔽。

在配置文件中添加如下内容:
在这里插入图片描述

zuul:
  routes:
    # 指定微服务的路由规则
    # *为通配符
    # /** 可以匹配0到多级路径
    # /* 只能匹配一级路径
    # /? 只能匹配一级路径,且路径只能包含一个字符
    abcmsc-consumer-depart-8080: /abc8080/**
    abcmsc-consumer-depart-8090: /abc8090/**
  # 屏蔽指定微服务名称
  # ignored-services: abcmsc-consumer-depart-8080
  # 屏蔽所有微服务名称
  ignored-services: "*"

效果:
在这里插入图片描述

3.4 路由前辍

在配置路由策略时,可以为路由路径配置一个统一的前辍,以便为请求归类。在前面的配置文件中增加如下配置。
在这里插入图片描述

zuul:
  routes:
    # 指定微服务的路由规则
    # *为通配符
    # /** 可以匹配0到多级路径
    # /* 只能匹配一级路径
    # /? 只能匹配一级路径,且路径只能包含一个字符
    abcmsc-consumer-depart-8080: /abc8080/**
    abcmsc-consumer-depart-8090: /abc8090/**
    abcmsc-consumer-depart: /abc123/**
  # 屏蔽指定微服务名称
  # ignored-services: abcmsc-consumer-depart-8080
  #屏蔽所有微服务名称
  ignored-services: "*"
  # 指定统一的前辍
  prefix: /abc

效果:
在这里插入图片描述

3.5 路径屏蔽

可以屏蔽掉指定的URI路径,即只要用户请求中包含指定的 URI 路径,那么该请求将无法访问到指定的服务。通过该方式可以限制用户的权限。
在这里插入图片描述

zuul:
  routes:
    # 指定微服务的路由规则
    # *为通配符
    # /** 可以匹配0到多级路径
    # /* 只能匹配一级路径
    # /? 只能匹配一级路径,且路径只能包含一个字符
    abcmsc-consumer-depart-8080: /abc8080/**
    abcmsc-consumer-depart-8090: /abc8090/**
    abcmsc-consumer-depart: /abc123/**
  # 屏蔽指定微服务名称
  # ignored-services: abcmsc-consumer-depart-8080
  #屏蔽所有微服务名称
  ignored-services: "*"
  # 指定统一的前辍
  prefix: /abc
  # 屏蔽指定的URI
  ignored-patterns: /**/list/**

效果:
在这里插入图片描述

3.6 敏感请求头屏蔽

默认情况下,像 Cookie、Set-Cookie 等敏感请求头信息会被 zuul 屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。

(1) 修改 05-zuul-consumer-8080 工程
在服务降级的处理中,获取指定的请求头并打印到控制台:
在这里插入图片描述

@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
    
    
    @Autowired
    private RestTemplate restTemplate;
    
    // 要使用微服务名称来从eureka server查找提供者
    private static final String SERVICE_PROVIDER = "http://abcmsc-provider-depart";
	...
    @HystrixCommand(fallbackMethod = "getHystrixHandler")
    @GetMapping("/get/{id}")
    public Depart getByIdHandler(@PathVariable("id") int id, HttpServletRequest request) {
    
    
        String url = SERVICE_PROVIDER + "/provider/depart/get/" + id;
        return restTemplate.getForObject(url, Depart.class);
    }

    public Depart getHystrixHandler(@PathVariable("id") int id, HttpServletRequest request) {
    
    
        System.out.println("token = " + request.getHeader("token"));
        System.out.println("Set-Cookie = " + request.getHeader("Set-Cookie"));

        Depart depart = new Depart();
        depart.setId(id);
        depart.setName("no this depart - 8080");
        return depart;
    }
	...
}

(2) 修改 00-zuul-9000 配置文件
在这里插入图片描述

在这里插入图片描述

zuul:
  routes:
    # 指定微服务的路由规则
    # *为通配符
    # /** 可以匹配0到多级路径
    # /* 只能匹配一级路径
    # /? 只能匹配一级路径,且路径只能包含一个字符
    abcmsc-consumer-depart-8080: /abc8080/**
    abcmsc-consumer-depart-8090: /abc8090/**
    abcmsc-consumer-depart: /abc123/**
  # 屏蔽指定微服务名称
  # ignored-services: abcmsc-consumer-depart-8080
  #屏蔽所有微服务名称
  ignored-services: "*"
  # 指定统一的前辍
  prefix: /abc
  # 屏蔽指定的URI
  ignored-patterns: /**/list/**
  # 屏蔽掉指定的敏感头信息,其会将原来默认的Cookie、SetCookie、Authorization敏感头信息放开
  sensitive-headers: token

(3) 演示:
在这里插入图片描述

看到token被屏蔽掉了:
在这里插入图片描述

3.7 负载均衡

用户提交的请求被路由到一个指定的微服务中,若该微服务名称的主机有多个,则默认采用轮询负载均衡策略。

zuul的负载均衡也是Ribbon实现的:

在这里插入图片描述

可以看到还包含hystrix,也能服务降级。

(1) 创建三个消费者工程
这里要创建三个工程。这三个工程均复制于 05-zuul-consumer-8080 工程。

A、创建 05-zuul-consumer-8180
启动类更名为ApplicationConsumer8180
在这里插入图片描述

server:
  port: 8180

spring:
  application:
    name: abcmsc-consumer-depart

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

修改处理器,打印当前工程的端口号,方便测试:
在这里插入图片描述
在这里插入图片描述

@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
    
    
    @Autowired
    private RestTemplate restTemplate;

    @Value("${server.port}")
    private Integer port;
    ...
    public Depart getHystrixHandler(@PathVariable("id") int id, HttpServletRequest request) {
    
    
        Depart depart = new Depart();
        depart.setId(id);
        depart.setName("no this depart — " + port);
        return depart;
    }
}

B、 创建 05-zuul-consumer-8280
启动类更名为ApplicationConsumer8280

server:
  port: 8280

spring:
  application:
    name: abcmsc-consumer-depart

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

修改处理器:同上

C、 创建 05-zuul-consumer-8380
启动类更名为ApplicationConsumer8380

server:
  port: 8380

spring:
  application:
    name: abcmsc-consumer-depart

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

修改处理器:同上

(2) 修改配置文件
在00-zuul-9000工程的配置文件中添加如下内容:
为新的微服务配置路由规则
在这里插入图片描述

server:
  port: 9000

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

spring:
  application:
    name: abcmsc-zuul-depart

zuul:
  routes:
    # 指定微服务的路由规则
    # *为通配符
    # /** 可以匹配0到多级路径
    # /* 只能匹配一级路径
    # /? 只能匹配一级路径,且路径只能包含一个字符
    abcmsc-consumer-depart-8080: /abc8080/**
    abcmsc-consumer-depart-8090: /abc8090/**
    abcmsc-consumer-depart: /abc123/**
  # 屏蔽指定微服务名称
  # ignored-services: abcmsc-consumer-depart-8080
  #屏蔽所有微服务名称
  ignored-services: "*"
  # 指定统一的前辍
  prefix: /abc
  # 屏蔽指定的URI
  ignored-patterns: /**/list/**
  # 屏蔽掉指定的敏感头信息,其会将原来默认的Cookie、SetCookie、Authorization敏感头信息放开
  sensitive-headers: token

(3) 修改负载均衡策略
修改00-zuul-9000的启动类,在其中直接添加如下代码,更换负载均衡策略为随机算法。
在这里插入图片描述

@EnableZuulProxy//开启服务网关
@SpringCloudApplication
public class ApplicationZuul9000 {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(ApplicationZuul9000.class, args);
    }

    // 设置zuul对consumer的负载均衡策略为”随机策略“
    @Bean
    public IRule loadBalanceRule() {
    
    
        return new RandomRule();
    }

}

(4) 演示

随机的就不演示了:
在这里插入图片描述

3.8 服务降级

当消费者调用提供者时由于各种原因出现无法调用的情况时,消费者可以进行服务降级。那么,若客户端通过网关调用消费者无法调用时,是否可以进行服务降级呢?当然可以,zuul也具有服务降级功能。

(1) 创建工程 00-zuul-fallback-9000
复制 00-zuul-9000 工程,并重命名为 00-zuul-fallback-9000。
启动类更名为ApplicationZuuFallbackl9000。

(2) 定义 fallback 类
需要实现接口org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider
在这里插入图片描述

@Component //别忘记交给Spring容器
public class ConsumerFallback implements FallbackProvider {
    
    
    @Override
    public String getRoute() {
    
    
        // 对指定的微服务(路由路径)进行降级
        // return "abcmsc-consumer-depart-8080";
        // 指定对所有微服务进行降级(可以用通配符)
        return "*";
    }

    /**
     * 定制降级响应
     * @param route  请求中的微服务名称
     * @param cause
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
    
    
        return new ClientHttpResponse() {
    
    
            @Override
            public HttpHeaders getHeaders() {
    
    
	            //设置响应头
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }

            @Override
            public InputStream getBody() throws IOException {
    
    
	            //设置响应体内容、降级信息
                String msg = "fallback:" + route;
                return new ByteArrayInputStream(msg.getBytes());
            }

            @Override
            public HttpStatus getStatusCode() throws IOException {
    
    
	            //返回状态常量
                return HttpStatus.SERVICE_UNAVAILABLE;
            }

            @Override
            public int getRawStatusCode() throws IOException {
    
    
	            //返回状态码,这里为503,Service Unavailable
                return HttpStatus.SERVICE_UNAVAILABLE.value();
            }

            @Override
            public String getStatusText() throws IOException {
    
    
	            //返回状态码对应的状态短语,这里为"Service Unavailable"
                return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
            }

            @Override
            public void close() {
    
    
                // 写资源释放代码
            }
        };
    }
}

(3) 修改配置文件
这里没有对配置文件进行任何设置,仅仅是将一些不必要的配置给删除了。

server:
  port: 9000

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

spring:
  application:
    name: abcmsc-zuul-depart

zuul:
  routes:
    abcmsc-consumer-depart-8080: /abc8080/**
    abcmsc-consumer-depart-8090: /abc8090/**

(4) 效果
首先访问正常请求:
在这里插入图片描述

关闭abcmsc-consumer-depart-8080和abcmsc-consumer-depart-8090的工程,再次请求:
在这里插入图片描述

4. 请求过滤

在服务路由之前、中、后,可以对请求进行过滤,使其只能访问它应该访问到的资源,增强安全性。此时需要通过 ZuulFilter 过滤器来实现对外服务的安全控制。

4.1 路由过滤架构

在这里插入图片描述

4.2 自定义过滤器

4.2.1 需求

我们需要实现的过滤的条件是,只有请求参数携带有 user 的请求才可访问/abc8080 工程,否则返回401,未授权。当然,对/abc8090 工程的访问没有限制。简单来说就是,只有当访问/abc8080 且 user 为空时是通不过过滤的,其它请求都可以。

4.2.2 路由过滤实现 00-zuul-filter-9000

(1) 创建工程
复现 00-zuul-9000 工程,并重命名为 00-zuul-filter-9000。
启动类更名为ApplicationZuuFilter9000。

(2) 定义 RouteFilter 类

需要继承自com.netflix.zuul.ZuulFilter,有下面4个方法要实现:

  • filterType
    在这里插入图片描述
  • filterOrder
    在这里插入图片描述
    在这里插入图片描述
  • shouldFilter,run
    过滤的逻辑其实写在shouldFilter还是run都可以
    在这里插入图片描述
@Component
public class RouteFilter extends ZuulFilter {
    
    
    @Override
    public String filterType() {
    
    
	    // 指定过滤时机
        // "pre",进行路由之前过滤
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
    
    
	    // 系统内置过滤器的最小值为-3
	    // 让我们定义的在内置过滤器之前执行
        return -5;
    }
	//对请求进行过滤器的核心逻辑
    @Override
    public boolean shouldFilter() {
    
    
        // 获取当前的请求上下文对象(底层从ThreadLocal获取的)
        RequestContext context = RequestContext.getCurrentContext();
        // 从请求上下文中获取当前请求信息
        HttpServletRequest request = context.getRequest();
        String user = request.getParameter("user");
        String uri = request.getRequestURI();
        if (uri.contains("/abc8080") && StringUtils.isEmpty(user)) {
    
    
            // 指定当前请求未通过zuul过滤,默认值为true
            context.setSendZuulResponse(false);
            // 401未授权
            context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }
	// 对请求通过过滤的后的逻辑
    @Override
    public Object run() throws ZuulException {
    
    
        System.out.println("通过过滤");
        return null;
    }
}

(3) 演示
abc8090通过:
在这里插入图片描述

abc8080没通过:
在这里插入图片描述

加上user参数后又可以通过了:
在这里插入图片描述

以下 令牌桶限流、多维请求限流、灰度发布 都是对请求过滤的一种应用。

4.3 令牌桶限流

通过对请求限流的方式避免系统遭受“雪崩之灾”。

我们下面的代码使用 Guava 库的 RateLimit 完成限流的,而其底层使用的是令牌桶算法实现的限流,所以我们先来学习一下令牌桶限流算法。

4.3.1 原理

(1) 令牌桶算法

令牌桶算法(Token Bucket):是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。令牌桶算法示意图如下所示:
在这里插入图片描述
大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。

(2) 扩展:漏斗限流算法

漏桶算法(Leaky Bucket):主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。漏桶算法的示意图如下:
在这里插入图片描述

请求先进入到漏桶里,漏桶以一定的速度出水,当水/请求过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

(3) 对比:

两者主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,所以它适合于具有突发特性的流量。

令牌桶算法可以应对突发流量情况

4.3.2 实现 00-zuul-tockenbucket-9000

(1) 创建工程
复制工程 00-zuul-filter-9000,并命名为 00-zuul-tockenbucket-9000。
修改启动类更名为ApplicationZuulTockenBucket9000。
不需要额外导入Guava库,已经包含进来了。

(2) 修改 RouteFilter 类
这里导入的 RateLimiter 是 Google 的类。这里为了测试的方便,使令牌桶每秒仅生成 2个令牌。
在这里插入图片描述
在这里插入图片描述

@Component
public class RouteFilter extends ZuulFilter {
    
    
    // 创建一个令牌桶,每秒生成2个令牌
    // 即每秒最多通过2个请求,QPS为2
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);

    @Override
    public String filterType() {
    
    
        // "pre",进行路由之前过滤
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
    
    
        return -5;
    }

    @Override
    public boolean shouldFilter() {
    
    
        // 获取当前的请求上下文对象
        RequestContext context = RequestContext.getCurrentContext();
        // RATE_LIMITER.tryAcquire():
        //     若可以立即获取到1个令牌,则返回true,否则返回false。不阻塞
        // RATE_LIMITER.tryAcquire(5, 3, TimeUnit.SECONDS):
        //     若在3秒内可以立即获取到5个令牌,则返回true,否则返回false。不阻塞
        // RATE_LIMITER.acquire():获取1个令牌,若获取不到,则阻塞直到获取到为止
        // RATE_LIMITER.acquire(5):获取5个令牌,若获取不到,则阻塞直到获取到为止
        if (!RATE_LIMITER.tryAcquire()) {
    
    
            // 指定当前请求未通过zuul过滤
            context.setSendZuulResponse(false);
            // 向客户端响应“请求数量太多”,429
            context.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
        }
        return true;
    }

    @Override
    public Object run() throws ZuulException {
    
    
        System.out.println("通过过滤");
        return null;
    }
}

(3) 效果
在这里插入图片描述

4.4 多维请求限流

4.4.1 原理

使用 Guava 的 RateLimit 令牌桶算法可以实现对请求的限流,但其限流粒度有些大。有个老外使用路由过滤,针对 Zuul 编写了一个限流库(spring-cloud-zuul-ratelimit),提供多种细粒度限流策略,在导入该依赖后我们就可以直接使用了。

其限流策略,即限流查验的对象类型有:

  • user:针对用户的限流,即对单位时间窗内经过网关的用户数量的限制。
  • origin:针对客户端 IP 的限流,即对单位时间窗内经过网关的 IP 数量的限制。
  • url:针对请求 URL 的限流,即对单位时间窗内经过网关的 URL 数量的限制。

4.4.2 实现 00-zuul-ratelimit-9000

(1) 创建工程
复制工程 00-zuul-tockenbucket-9000,并命名为 00-zuul-ratelimit-9000。
启动类更名ApplicationZuulRateLimit9000。

(2) 删除 RouteFilter
删除 RouteFilter 类,该工程中不使用。

(3) 添加依赖
若要使用 spring-cloud-zuul-ratelimit 限流库,首先需要导入该依赖,然后再在配置文件中对其进行相关配置。

<!-- spring-cloud-zuul-ratelimit依赖 -->
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

(4) 修改配置文件
在配置文件中添加如下配置。
在这里插入图片描述

server:
  port: 9000

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

spring:
  application:
    name: abcmsc-zuul-depart

zuul:
  routes:
    abcmsc-consumer-depart-8080: /abc8080/**
    abcmsc-consumer-depart-8090: /abc8090/**

  ratelimit:
    enabled: true  # 开启限流
    # 设置限流策略
    # 在一个单位时间窗内通过该zuul的用户数量、ip数量及url数量,都不能超过3个
    default-policy:
      quota: 1   # 指定限流的时间窗数量
      refresh-interval: 3    # 指定单位时间窗大小,单位秒
      limit: 3  # 在指定的单位时间窗内启用限流的限定值
      type: user,origin,url   # 指定限流查验的类型

(5) 添加异常处理页面
在 src/main/resources 目录下再定义新的目录 public/error,必须是这个目录名称。然后在该目录中定义一个异常处理页面,名称必须是异常状态码,扩展名必须为 html。
在这里插入图片描述

(6) 演示
在这里插入图片描述

配置了错误页面后:
在这里插入图片描述

4.5 灰度发布

4.5.1 原理

(1) 什么是灰度发布
灰度发布,又名金丝雀发布,是系统迭代更新、平滑过渡的一种上线发布方式。

比如系统某一个模块更新了,在实际生产环境下新的模块的更新不可能全部替换了,所以大部分用户用的还是老的模块,让一部分用户用新的模块,用了一段时间感觉没问题在增加一部分用户用新模块,逐步逐步最终全部由老模块迁移到新模块。

(2) Zuul 灰度发布原理
生产环境中,可以实现灰度发布的技术很多,我们这里要讲的是 zuul 对于灰度发布的实现。而其实现是基于 Eureka 元数据的。

Eureka 元数据是指,Eureka 客户端向 Eureka Server 中注册时的描述信息。有两种类型的元数据:

  • 标准元数据
  • 自定义元数据

4.5.2 修改三个消费者工程

直接复用修改之前的三个工程:05-zuul-consumer-8180、05-zuul-consumer-8280、05-zuul-consumer-8380
(1) 修改 05-zuul-consumer-8180
在这里插入图片描述

server:
  port: 8180

spring:
  application:
    name: abcmsc-consumer-depart

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

  instance:
    metadata-map: # 添加自定义元数据,key随意,我们指定为host-mark
      host-mark: running-host

(2) 修改 05-zuul-consumer-8280

server:
  port: 8280

spring:
  application:
    name: abcmsc-consumer-depart

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

  instance:
    metadata-map:
      host-mark: running-host

(3) 修改 05-zuul-consumer-8380
第三个工程对应元数据的值与其他不一样,代表灰度发布的方式:
在这里插入图片描述

server:
  port: 8380

spring:
  application:
    name: abcmsc-consumer-depart

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

  instance:
    metadata-map:
      host-mark: gray-host

4.5.3 创建 zuul 工程 00-zuul-gray-9000

(1) 创建工程
复制工程 00-zuul-ratelimit-9000,并重命名为 00-zuul-gray-9000。
修改启动类类名为ApplicationZuulGray9000。

(2) 添加依赖
在 pom 文件中增加新的依赖。该依赖通过Eureka元数据实现灰度发布。

<dependency>
    <groupId>io.jmnarloch</groupId>
    <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
    <version>2.1.0</version>
</dependency>

(3) 修改配置文件
将配置文件内容修改如下(删掉了多维请求限流的设置、路由的微服务名更改):
在这里插入图片描述

server:
  port: 9000

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

spring:
  application:
    name: abcmsc-zuul-depart

zuul:
  routes:
    abcmsc-consumer-depart: /abc123/**

(4) 定义过滤器
在这里插入图片描述

@Component
public class GrayFilter extends ZuulFilter {
    
    
    @Override
    public String filterType() {
    
    
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
    
    
        return -5;
    }

    @Override
    public boolean shouldFilter() {
    
    
        // 所有请求都通过zuul过滤
        return true;
    }

    @Override
    public Object run() throws ZuulException {
    
    
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        // 获取指定的请求头信息,该头信息在浏览器提交请求时携带,用于区分该请求要被路由到哪个主机处理
        String mark = request.getHeader("gray-mark");
        // 默认将请求路由到running-host上
        // "host-mark"和"running-host"是在消费者工程中添加的元数据key-value
        RibbonFilterContextHolder.getCurrentContext().add("host-mark", "running-host");
        // 若mark的值不为空且值为enable,则将请求路由到gray-host,其它请求会路由到默认的running-host
        if (!StringUtils.isEmpty(mark) && "enable".equals(mark)) {
    
    
            RibbonFilterContextHolder.getCurrentContext().add("host-mark", "gray-host");
        }
        return null;
    }
}

效果:

  • gray-mark != enable
    在这里插入图片描述
    访问的8180、8280是running-host的:
    在这里插入图片描述

  • gray-mark == enable
    在这里插入图片描述

    访问的8380是gray-host的:
    在这里插入图片描述

(5) 定义过滤器(另一种灰度发布规则)
在这里插入图片描述

在这里插入图片描述

@Component
public class GrayFilter2 extends ZuulFilter {
    
    
    // 定义一个原子布尔变量,是为了解决当前单例中全局变量的线程安全问题
    private AtomicBoolean flag = new AtomicBoolean(true);

    @Override
    public String filterType() {
    
    
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
    
    
        return -5;
    }

    @Override
    public boolean shouldFilter() {
    
    
        // 所有请求都通过zuul过滤
        return true;
    }

    @Override
    public Object run() throws ZuulException {
    
    
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();

        // 根据布尔变量的值的不同,路由到不同的主机,然后再将布尔值取反
        if (flag.get()) {
    
    
            RibbonFilterContextHolder.getCurrentContext().add("host-mark", "running-host");
            flag.set(false);
        } else {
    
    
            RibbonFilterContextHolder.getCurrentContext().add("host-mark", "gray-host");
            flag.set(true);
        }

        return null;
    }
}

5. Zuul 的高可用

Zuul 的高可用非常关键,因为外部请求到后端微服务的流量都会经过 Zuul。故而在生产环境中,我们一般都需要部署高可用的 Zuul 以避免单点故障。

作为整个系统入口路由的高可用,需要借助额外的负载均衡器来实现,例如 Nginx、HAProxy、F5 等。在 Zuul 集群的前端部分部署负载均衡服务器。Zuul 客户端将请求发送到负载均衡器,负载均衡器将请求转发到其代理的其中一个 Zuul 节点。这样,就可以实现 Zuul的高可用。

借助Nginx、HAProxy、F5 等

猜你喜欢

转载自blog.csdn.net/weixin_41947378/article/details/109049165