SpringCloud-Netflix-08-Zuul 服务网关

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 SpringCloud-Netflix-08-Zuul 服务网关,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


八、Netflix Zuul 服务网关

8.1 Zuul 网关简介

8.1.1 什么是网关

在我们的微服务架构中,调用链路错综复杂,我们来简单看一些服务中的调用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPieY9cR-1647869813358)(media/144.png)]

在分布式环境下,由于项目都被拆分成了若干的微服务,造成用户原本只需要请求一次就可以完成的业务,现在需要请求多个微服务来完成业务功能,如果客户端直接和微服务进行通信,会存在以下问题:

  • 客户端会多次请求不同微服务,增加客户端的复杂性

  • 存在跨域请求,在一定场景下处理相对复杂

  • 认证复杂,每一个服务都需要独立认证

  • 难以重构,随着项目的迭代,可能需要重新划分微服务,如果客户端直接和微服务通信,那么重构会难以实施

上述问题,都可以借助微服务网关解决。微服务网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过微服务网关。

8.1.2 什么是Zuul

SpringCloud-Netflix-08-Zuul 服务网关

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

  • 单点入口
  • 动态路由
  • 限流熔断
  • 统一认证
  • 日志监控

Spring Cloud 对 Zuul 进行了整合和增强。

引入了 Zuul网关 后,架构图演变为以下形式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zyoAqIfP-1647869813359)(media/143.png)]

Github官网:https://github.com/Netflix/Zuul

8.2 Zuul 快速入门

8.2.1 搭建Zuul网关工程

1)引入依赖:

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

2)编写application.yml:

server:
  port: 10102
spring:
  application:
    name: zuul-server

3)启动类:

package com.cloud.zuul;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@EnableZuulProxy                // 开启Zuul网关代理
@SpringBootApplication
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class);
    }
}

8.2.2 Zuul网关路由规则

1)URL地址路由

zuul:
  routes:
    item-service:                   # 路由Id,名称任意
      path: /item-service/**        # 这里是映射路径
      url: http://localhost:9000    # 映射路径对应的实际url地址
  • 通配符匹配规则:
通配符 说明 举例 示例
? 匹配任意单个字符 /item-service/? /item-service/a
* 匹配任意数量字符但不包含子路径 /item-service/* /item-service/abc
** 匹配任意数量的字符包括下属的所有路径 /item-service/** /item-service/abc/abc

访问:http://localhost:10102/item-service/item/findOrderById/1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RdJuGwXP-1647869813360)(media/142.png)]

2)服务名称路由

微服务一般是由几十、上百个服务组成,对于一个URL请求,最终会确认一个服务实例进行处理。如果对每个服务实例手动指定一个唯一访问地址,然后根据URL去手动实现请求匹配,这样做显然就不合理。

Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的路由配置。

1)添加Eureka的依赖:

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

2)在ZuulApplication引导类上添加注解:

@EnableEurekaClient
@EnableDiscoveryClient

3)修改application.yml:

server:
  port: 10102
spring:
  application:
    name: zuul-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10101/eureka    # 注册到Eureka
  instance:
    prefer-ip-address: true         # 使用ip地址注册服务(默认情况下是以主机注册)
    instance-id: 127.0.0.1:${server.port}   # 实例id
zuul:
  routes:
    item-service:                           # 路由Id,名称任意
      path: /item-service/**              	# 这里是映射路径
      serviceId: item-service               # 将拦截到的请求转发到当前微服务

再次访问:http://localhost:10102/item-service/item/findOrderById/1

tips:服务名称路由的前提条件是服务必须首先注册到Eureka注册中心,然后根据服务名路由,URL路由则可以跳转到任意网站;

3)简化路由配置

当路由id与微服务名称一致时,我们可以不写serviceId,默认取路由id为微服务名称

zuul:
  routes:
    item-service:                           # 路由Id,名称任意
      path: /abc/**              	# 这里是映射路径

再次访问:http://localhost:10102/abc/item/findOrderById/1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SnNaKGWb-1647869813361)(media/141.png)]

4)默认路由配置

如果路由id与path路径与微服务名称三者一致时,那么zuul网关的路由配置可以省略,三者默认都为微服务名称,把zuul路由的配置去掉:

#zuul:
#  routes:
#    item-service:                           # 路由Id,名称任意
#      path: /item-service/**              	# 这里是映射路径
#      serviceId: item-service               # 将拦截到的请求转发到当前微服务

访问:http://localhost:10102/item-service/item/findOrderById/1,http://localhost:10102/order-service/order/1

5)路由前缀

zuul:
  routes:
    item-service:                           # 路由Id,名称任意
      path: /item-service/**              	# 这里是映射路径
      serviceId: item-service               # 将拦截到的请求转发到当前微服务
  prefix: /api

http://localhost:10102/api/item-service/item/findOrderById/1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HlAi1FVI-1647869813362)(media/136.png)]

8.2.3 路由排除

1)URL地址排除

zuul:
  ignored-patterns: /**/item/findOrderById/**,/**/order/**      # 忽略这两个路径 多个路径用,隔开
  routes:
    item-service:                           # 路由Id,名称任意
      path: /item-service/**              	# 这里是映射路径
      serviceId: item-service               # 将拦截到的请求转发到当前微服务

2)服务名排除

根据服务名指定该服务不进行路由转发(只能忽略默认路由配置)

zuul:
  ignored-services: order-service           # order-service的服务请求不进行路由,多个服务用,隔开

3)请求头排除

关于请求头排除的配置有ignored-headerssensitiveHeaders

  • ignored-headers默认情况下,我们自己携带的请求头都会被传递到下游服务,通过ignored-headers参数可以过滤掉一些不需要传递的请求头;

配置方式:

zuul:
	ignored-headers: flag_a			# 忽略flag_a请求头,flag_a请求头将不会被传递到下游服务
  • sensitiveHeaders:和ignored-headers作用一致,配置拦截下来的请求头,默认情况下CookieSet-CookieAuthorization请求头将被拦截;

配置方式:

zuul:
  routes:
    item-service:                           # 路由Id,名称任意
      sensitiveHeaders: []					# 针对某个微服务配置,放行Cookie、Set-Cookie、Authorization等请求头
      path: /item-service/**              	# 这里是映射路径
      serviceId: item-service               # 将拦截到的请求转发到当前微服务
  sensitiveHeaders: []						# 全局配置

ignored-headers只能全局配置,不能局部配置,sensitiveHeaders可以全局配置和局部配置;

  • 测试请求头传递,扩展ItemController:
/**
 * 测试zuul传递请求头和Cookie
 *
 * @param request
 * @return
 */
@PostMapping("/testHeader")
public Map testHeader(HttpServletRequest request) {

    return new HashMap() {{
        put("flag_a", request.getHeader("flag_a"));
        put("flag_b", request.getHeader("flag_b"));
        put("cookie", Arrays.asList(request.getCookies() == null ? "null" : request.getCookies()));
    }};
}

使用Postman发送请求:http://localhost:10102/item-service/item/testHeader

SpringCloud-Netflix-08-Zuul 服务网关

tips:此时zuul并没有配置任何的请求头过滤;

  • 配置zuul请求头过滤:
zuul:
  routes:
    item-service:                           # 路由Id,名称任意
      ignored-headers: flag_a				# ignored-headers局部过滤是不生效的
      sensitiveHeaders: []					# 将这里设为空,放行Cookie、Set-Cookie、Authorization等请求头
      path: /item-service/**              	# 这里是映射路径
      serviceId: item-service               # 将拦截到的请求转发到当前微服务

再次访问:http://localhost:10102/item-service/item/testHeader

SpringCloud-Netflix-08-Zuul 服务网关

配置全局忽略:

zuul:
  routes:
    item-service:                           # 路由Id,名称任意
      path: /item-service/**              	# 这里是映射路径
      serviceId: item-service               # 将拦截到的请求转发到当前微服务
  ignored-headers: flag_a
  sensitiveHeaders: []

SpringCloud-Netflix-08-Zuul 服务网关

8.3 Zuul 的过滤器

在Zuul包含有两大核心功能,第一个是路由,另一个就是过滤器了,路由可以对客户端访问的URL进行跳转,过滤器则是对整个请求的一些拦截操作;在Zuul中提供有一系列内置的过滤器,也可以让用户自定义编写过滤器来实现自身业务的功能;

8.3.1 过滤器的执行流程

在Zuul中提供有4种过滤器,分别为prerouteposterror

SpringCloud-Netflix-08-Zuul 服务网关

  • pre:在请求被路由之前调用;
  • route:将要进行路由时调用(路由之前调用,只不过比pre过滤器后执行)
  • post:路由调用完毕或error过滤器之后执行
  • error:在其他过滤器出现异常时执行;

8.3.2 自定义过滤器

如果我们想要自定义一个过滤器可以继承ZuulFilter抽象类:

package com.cloud.zuul.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Component
public class PreFilter extends ZuulFilter {
    /**
     * 执行时机
     * pre:     在路由到下游微服务之前执行
     * route:   在路由到下游微服务之前执行(在pre过滤器之后)执行
     * post:    执行完了下游微服务的逻辑之后来到post过滤器
     * error:   在其他过滤器执行出现异常的时候执行error过滤器
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 执行顺序
     * 数字越大,优先级越低
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 是否执行该过滤器
     * true:执行
     * false:不执行
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器执行逻辑
     *
     * @return 返回null代表放行, 访问对应的微服务
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        System.out.println("PreFilter...." + System.currentTimeMillis());
        return null;
    }
}

定义四个过滤器,类型分别为pre、route、post、error,发送请求查看执行顺序

8.3.3 Zuul的异常处理

在Zuul网关的众多过滤器中,如果有某个过滤器出现了异常,虽然会执行我们自定义的Error过滤器,但是错误响应信息却不能被我自定义,我们需要禁用Zuul网关提供的错误过滤器,如果出现了异常,则使用我们自己的过滤器,定制错误响应信息:

编写一个Error类型的过滤器:

package com.cloud.zuul.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Component
public class ErrorFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

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

    @Override
    public Object run() throws ZuulException {

        System.out.println("error...");
        // Zuul网关提供的上下文对象
        RequestContext context = RequestContext.getCurrentContext();

        // 终止请求往下执行
        context.setSendZuulResponse(false);
        // 状态码
        context.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());

        HttpServletResponse response = context.getResponse();

        // 自定义响应内容
        HashMap jsonMap = new HashMap() {{
            put("flag", true);
            put("statusCode", "500");
            put("message", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
        }};
        response.setContentType("application/json;charset=utf8");
        try {
            response.getWriter().write(new ObjectMapper().writeValueAsString(jsonMap));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }
}

在Pre过滤器中触发异常:

package com.cloud.zuul.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.bouncycastle.asn1.ocsp.ResponseData;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Component
public class PreFilter extends ZuulFilter {
    /**
     * 执行时机
     * pre:     在进入微服网关之间执行
     * route:   在执行微服务网关时执行
     * post:    在执行微服务网关之后执行
     * error:   在执行微服务出错执行
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 执行顺序
     * 数字越大,优先级越低
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 是否执行该过滤器
     * true:执行
     * false:不执行
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器执行逻辑
     *
     * @return 访问对应的微服务
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        
        System.out.println("PreFilter...." + System.currentTimeMillis());
        
        // 触发异常
        int i=1/0;
        return null;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2FuUvRRg-1647869813363)(media/121.png)]

发现错误信息不是我们自定义的错误响应;

  • 禁用Zuul默认的Error过滤器:
zuul:
  SendErrorFilter:
    error:
        disable: true

重启Zuul网关,重新访问:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tOMtNs52-1647869813365)(media/120.png)]

8.3.4 Zuul统一鉴权

设计一个微服务网关,前端传递token=1,否则进行拦截:

package com.cloud.zuul.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Component
public class PreFilter extends ZuulFilter {
    /**
     * 执行时机
     * pre:     在进入微服网关之间执行
     * route:   在执行微服务网关时执行
     * post:    在执行微服务网关之后执行
     * error:   在执行微服务出错执行
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 执行顺序
     * 数字越大,优先级越低
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 是否执行该过滤器
     * true:执行
     * false:不执行
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器执行逻辑
     *
     * @return 访问对应的微服务
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        System.out.println("PreFilter...." + System.currentTimeMillis());

        // Zuul网关提供的上下文对象
        RequestContext context = RequestContext.getCurrentContext();

        HttpServletRequest request = context.getRequest();

        HttpServletResponse response = context.getResponse();

        // 获取提交的token
        String token = request.getParameter("token");

        response.setContentType("application/json;charset=utf8");
        if(!"1".equals(token)){

            // 终止请求
            context.setSendZuulResponse(false);

            HashMap jsonMap = new HashMap() {{
                put("flag", true);
                put("statusCode", "500");
                put("message","权限不足");
            }};

            // 设置响应体
            try {
                context.setResponseBody(new ObjectMapper().writeValueAsString(jsonMap));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            
            // return方法,为了防止代码往下执行,返回值随意
            return null;
        }

        return null;
    }
}

访问:http://localhost:10102/item-service/item/findOrderById/1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-no98lZYG-1647869813366)(media/132.png)]

8.4 Zuul 实现服务降级

在Zuul中提供了FallbackProvider接口,来帮助我们实现Zuul在路由时进行服务降级,需要注意的是,Zuul提供的服务降级只针对于timeout超时情况的降级,如果路由到的具体下游服务出现异常,那么不会触发Zuul的降级处理,应该由具体微服务来提供降级处理;

Tips:在Edgware版本之前(不含),Zuul降级接口为ZuulFallbackProvider,从Edgware后(含),Zuul提供FallbackProvider来提供降级处理;

修改ItemController:

@GetMapping("/findOrderAll")
public Map findOrderAll() {
    // 模拟线程阻塞
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Map resultMap = restTemplate.getForObject("http://order-service/order/", Map.class);

    return resultMap;
}

1)降级处理类

  • 在Zuul网关服务编写降级类:
package com.cloud.zuul.fallback;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Component
public class ItemFallBack implements FallbackProvider {

    /**
     * 需要降级的服务名称 '*'表示为所有服务提供降级
     */
    @Override
    public String getRoute() {
        return "item-service";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {

        return new ClientHttpResponse() {
            /**
             * ClientHttpResponse 的 fallback 的状态码 返回HttpStatus
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 int
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return getStatusCode().value();
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 String
             */
            @Override
            public String getStatusText() throws IOException {
                return getStatusCode().getReasonPhrase();
            }

            /**
             * 设置响应体
             */
            @Override
            public InputStream getBody() throws IOException {
                HashMap jsonMap = new HashMap() {{
                    put("flag", true);
                    put("statusCode", "500");
                    put("message", "服务器忙,请稍后重试!");
                }};
                String jsonResult = new ObjectMapper().writeValueAsString(jsonMap);
                return new ByteArrayInputStream(jsonResult.getBytes());
            }

            /**
             * 设置响应的头信息
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                MediaType mediaType = new MediaType("application", "json", Charset.forName("utf-8"));
                httpHeaders.setContentType(mediaType);
                return httpHeaders;
            }

            @Override
            public void close() {
            }
        };
    }
}

访问:http://localhost:10102/item-service/item/findOrderAll

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ksN4o4nW-1647869813366)(media/131.png)]

2)修改超时设置

Zuul的路由转发是通过Ribbon来转发实现的,因此超时设置是通过Ribbon的超时设置来控制的:

  • 在ZuulServer中的application.yml配置:
ribbon:
  ReadTimeout: 4000       # zuul路由到具体微服务,微服务响应的时间
  ConnectTimeout: 4000    # zuul路由到具体微服务的连接时间

Tips:默认情况下,如果没有配置降级,超时则执行Error类型的Filter来处理;

8.5 Zuul 整合Hystrix DashBoard

我们搭建微服务网关后,由于网关提供了下游服务的统一路口,请求都是由网关统一路由到下游服务,我们可以通过之前学习过的Hystrix仪表盘来监控网关达到监控各个微服务的调用情况;

8.5.1 搭建DashBoard工程

1)引入Dash Board依赖:

<!--dashboard监控依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

2)编写配置:

server:
  port: 8888
hystrix:
  dashboard:
    proxy-stream-allow-list: "localhost"        # 允许本机访问

3)启动类:

package com.cloud.dashboard;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@EnableHystrixDashboard         // 开启DashBoard仪表盘
@SpringBootApplication
public class DashBoardApplication {

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

8.5.2 修改Zuul网关工程

1)引入actuator依赖:

<!--SpringBoot监控依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2)开放端点:

management:
  endpoints:
    web:
      exposure:
        include: '*'      # 开放所有监控端点

8.5.3 测试

访问DashBoard工程:http://localhost:8888/hystrix

SpringCloud-Netflix-08-Zuul 服务网关

访问:http://localhost:10102/actuator/hystrix.stream

SpringCloud-Netflix-08-Zuul 服务网关

访问任意接口:http://localhost:10102/item-service/item/findOrderById/1?token=1

SpringCloud-Netflix-08-Zuul 服务网关

查看Hystrix DashBoard:

SpringCloud-Netflix-08-Zuul 服务网关

8.6 Zuul 服务限流

我们都知道Zuul是所有服务的入口,Zuul提供了一套完善的服务限流机制,在访问量过大时,我们可以通过Zuul提供的限流机制来对大流量进行限流处理,保护下游服务;

任何的服务都应该流量限制功能,在防止流量异常激增导致下游服务出现雪崩等情况,如:

  • 双十一、618等
  • 微博热搜
  • 秒杀活动
  • 恶意请求等

上述情况都属于未知场景,超出的流量远远大于微服务所能承受的最大极限,我们希望在到达服务流量极限时,我们可以对客户端进行限流处理,防止服务瘫痪、溢出等严重情况的发生;

通常的限流算法有:计数器算法(Counter)、漏桶算法(Leaky Bucket)、令牌通算法(Token Bucket)等;

8.6.1 计数器算法

1)算法原理

计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们限制一个接口每分钟调用不能超过100次,我们可以这样设计,定义2个变量,一个是访问次数,一个是记录访问次数计算的开始时间,如果记录时间和当前时间比较大于1分钟,则重新计时,如果在一分钟以内,我们把访问次数加一。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8FeVHlbB-1647869813368)(media/126.png)]

2)临界值问题:

计数器算法(Connter)实现起来非常简单,但却有个非常大的弊端,那就是临界值问题;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpXPtg9F-1647869813368)(media/125.png)]

我们知道计数器算法等到指定的时间(我们上面是1分钟)结束后会清空Counter,在下一分钟后会重新累计阈值100,那么如果在第1分钟的59s发送了100个请求,紧接着第2分钟的1s发送了100个请求呢?此时后端的访问便在1秒钟接受到了200个请求;我们设定的阈值是100/min,平均约为1.7/s个请求,而临界值在1s钟发送了200个请求!

3)性能浪费

我们规定1min之内可以接受100个请求,我们预期的想法是希望100个请求能够平均分散在这1min,如果在30s时请求满了100个时,那么接下来的30s内访问便处于空闲状态;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-59GJpf7W-1647869813370)(media/124.png)]

8.6.2 漏桶算法

1)算法原理

漏桶(Leaky Bucket)算法思路也很简单,水(请求)先进入到储蓄桶中,储蓄桶有一定的水流频率(接口的响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率;

示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RBcm8wof-1647869813371)(media/123.png)]

我们之前所学的RabbitMQ限流其实就是一种漏桶算法,可以接受大量的Message(水),存储到Queue中(储蓄桶),但是消费频率低(水流频率)

2)弊端

  • 1、漏桶算法的实现思路是一种”牺牲自己,保护他人”的理念,假设次数请求很多,而响应速率比较慢,则大量的请求挤压在网关中
  • 2、漏桶算法无法应对突发情况的大流量调用,不管网关接受多少请求,响应速率始终一成不变,容易导致网关请求挤压过多;

8.6.3 令牌桶算法

令牌桶算法是基于漏桶算法的一种改进,令牌桶算法能应对一些突发情况的大流量调用。在令牌桶算法中,以一定速率往桶中放令牌,桶中存储了大量的令牌(Token),每次处理请求前需要先获取令牌才可以执行,否则处于等待令牌状态或直接拒绝。如果桶中的令牌达到上限,就丢弃令牌;

算法示意图:

SpringCloud-Netflix-08-Zuul 服务网关

8.6.4 Zuul网关实现流量限流

基于Zuul的filter开发的限流功能,可以结合本地缓存、redis、consul、mysql等做数据存储。

引入限流相关依赖:

<!--Zuul限流的相关依赖-->
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

<!--Redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1)全局网关限流

Zuul的全局限流针对于所有的服务

zuul:
  ratelimit:
    key-prefix: zuul_limit            # 缓存key的名称定义前缀。
    enabled: true                     # 开启Zuul网关限流
    repository: REDIS                 # 采用Redis方案
    default-policy-list:              # 默认的全局配置,如果根据规则查询不到配置则应用这个定义的配置
      - limit: 3                      # 令牌的个数
        refresh-interval: 10          # 刷新令牌的时间(单位:秒)
        type:                         # 限流类型
          - origin	                  # 根据客户端ip地址进行区分
          - url                       # 根据请求URL进行区分

访问Item-servier、order-service,10s内生成3个令牌;10s内访问了第4次则触发限流;

查看Redis:

SpringCloud-Netflix-08-Zuul 服务网关

2)根据服务限流

zuul:
  ratelimit:
    key-prefix: zuul_limit            # 缓存key的名称定义前缀。
    enabled: true                     # 开启Zuul网关限流
    repository: REDIS                 # 采用Redis方案
    policy-list:              # 默认的全局配置,如果根据规则查询不到配置则应用这个定义的配置
      item-service:
        - limit: 3                      # 时间窗口内最多访问次数
          refresh-interval: 20          # 默认的刷新时间窗口的间隔。
          type:                         # 限流类型
            - origin	                  # 根据客户端ip地址进行区分
            - url                       # 根据请求URL进行区分

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131745.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!