百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Spring Cloud Zookeeper微服务集群实例之三-网关引入及熔断与限流

off999 2025-04-11 04:19 30 浏览 0 评论

在之前的文章中我们实现了服务之间的接口调用,那么集群外部的接口调用如何进行?这就必须通过网关了。网关类似其它节点一样,会将其自身注册到集群中,从而能够获取到某个服务的实例清单;然后根据我们提前配置好的规则(如按路径分发等,类似nginx),将外部过来的请求分发到对应的节点上去执行。

总的来说,网关包含有以下三大功能:

  • 路由转发:即将请求分发到合适的服务中去执行;
  • 负载均衡:类似集群内部调用负载均衡,根据配置的算法及服务的实例清单进行负载均衡处理;
  • 权限控制:可以解析登录用户信息,同时根据路径及预先配置好的规则判断用户是否有权限访问;
  • 限流:可以通过Filter过滤掉超出流量的请求,将其直接返回;
  • 熔断:调用出现异常时一段时间内减少调用对应的接口,或者全部拦截调用;

另外,我们还可以在网关层根据token信息解析出用户信息,后续集群内的接口调用都使用解析出来的用户信息进行权限方面的限制,从而使得集群内部服务能够省略权限解析的步骤,专注于核心业务逻辑的实现。

老版本的Spring Cloud中使用的是Zuul,现在我们可以使用官方的Spring Cloud Gateway来充当网关了。

1 创建网关应用

1.1 maven依赖



    4.0.0

    org.example
    gateway
    1.0-SNAPSHOT

    
        11
        11
        2.4.5
        2020.0.2
    

    
        org.springframework.boot
        spring-boot-starter-parent
        ${spring.boot.version}
         
    

    
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        
        
            org.springframework.cloud
            spring-cloud-starter-zookeeper-discovery
        
    
    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

1.2 Bootstrap配置

增加bootstrap.yml文件:

spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 启用后,默认会通过/{serviceName}/**这样的请求,将其分发到serviceName的实例上去处理;

server:
  port: 8000

上面enabled设置成true后,会启用注册将网关注册到注册中心去,同时有请求过来时,会取host后的第一串字符当前服务名,在注册中心查找这个服务对应的实例,然后转发到对应的节点上去(这一步也会有负载均衡的动作);注意分发过去的请求会自动将{serviceName}这一串给干掉。

举个例子,我们前面启动了一个service0的应用,上面有一个/test的接口;按网关的这个配置,如果我们访问地址:
http://localhost:8000/service0/test,那么就会调用到service0服务上的test接口去。

1.3 main方法

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

1.4 启动与测试

启动main函数,然后我们就可以通过网关来调用service0上的test接口了,访问地址:
http://localhost:8000/service0/test,不出意外可以调用成功。

2 网关处理流程

网关包含有三个关键的属性:

  • Route:即路由,包括id、目标地址、Predicate列表及Filter列表;
  • Predicate:用于判断其所在的Route能够用于哪些请求,如根据请求参数的断定或者根据请求路径进行断定等;
  • Filter:用于在调用实际服务时增加前置或后置处理;

网关的处理过程如下图所示:

网关处理流程

客户端请求网关,网关通过Gateway Handler Mapping查找命中的Route,然后通过Web Handler进行调用,同时会在调用前后执行所配置的拦截器。

2 熔断

我们可以通过Spring Cloud Gateway来进行熔断。熔断通过网关的Filter进行。

一般会结合resilience4j来处理。

我们结合上方的示例来进行配置。

2.1 添加依赖

需要先在gateway中添加
spring-cloud-starter-circuitbreaker-reactor-resilience4j的依赖:


        
            org.springframework.cloud
            spring-cloud-starter-circuitbreaker-reactor-resilience4j
        

2.2 修改gateway配置

在bootstrap中配置:

spring:
  application:
    name: gateway
  cloud:
    gateway:
#      discovery:
#        locator:
#          enabled: true # 启用后,默认会通过/{serviceName}/**这样的请求,将其分发到serviceName的实例上去处理;但启用这种方式后,在routes中配置的就不生效了 ;
      routes:
        - id: circuitbreaker_route # 配置熔断
          uri: lb://service0
          filters:
            - name: CircuitBreaker
              args:
                name: backendA
                statusCodes:
                  - 500   # 必须配置,如果不配置的话不会进行熔断
#                fallbackUri: forward:/test # 失败后执行的请求
            - StripPrefix=1
          predicates:
            - Path=/service0/**

resilience4j.circuitbreaker:
  configs:
    default:
      slidingWindowSize: 10
      minimumNumberOfCalls: 5 # 计算错误率的最小访问次数,超过这个次数后才会计算错误率并判断是否要进行限流、熔断(但实际不会到这个值,比如配置容忍错误率为50%,配置当前值为10,那么如果前5个都失败就会直接熔断
      permittedNumberOfCallsInHalfOpenState: 3  # 半开状态允许的请求数
      automaticTransitionFromOpenToHalfOpenEnabled: true # 是否在没有请求的情况下由全开自动恢复到半开,设置成true时需要额外的线程进行监控
      waitDurationInOpenState: 2s # 由全开到半开等待的时间
      failureRateThreshold: 50  # 熔断器打开的失败阈值,也即超过指定比例时熔断器将被打开
      eventConsumerBufferSize: 10
  instances:
    backendA:
      baseConfig: default

server:
  port: 8000

management:
  endpoints:
    web:
      exposure:
        include: gateway

重启gateway;

2.3 添加测试接口

然后在service0应用的TestController中添加以下方法:

    @GetMapping("exception")
    public void testException() throws Exception {
        throw new Exception("测试异常");
    }

在这里面我们直接模拟服务调用异常,因此直接抛出了一个异常;重启Service0;

2.4 测试

通过网关来调用service0中的/test/exception接口,访问地址:
http://localhost:8000/service0/test/exception,连续三次访问的时候都会访问到service0上,但第四次返回的异常将会是[904f51ba-10] There was an unexpected error (type=Service Unavailable, status=503)了,service0未收到请求,在网关侧就已经进行了拦截。

然后等待几秒再访问,又会调用到service0上;访问几次时又会被熔断。

3 限流

限流通过RequestRateLimiter类型的拦截器进行。可以使用Redis实现;使用Redis时需要本地启动的Redis,或者在bootstrap.yml中配置Redis地址;

3.1 Maven依赖


        
            org.springframework.boot
            spring-boot-starter-data-redis-reactive
        

3.2 配置限流参数

spring:
  application:
    name: gateway
  cloud:
    gateway:
#      discovery:
#        locator:
#          enabled: true # 启用后,默认会通过/{serviceName}/**这样的请求,将其分发到serviceName的实例上去处理;但启用这种方式后,在routes中配置的就不生效了 ;
      routes:
        - id: circuitbreaker_route # 配置熔断限流
          uri: lb://service0
          predicates:
            - Path=/service0/**
          filters:
            - name: CircuitBreaker  # 熔断
              args:
                name: backendA
                statusCodes:
                  - 500   # 必须配置,如果不配置的话上述的配置不生效
#                fallbackUri: forward:/test # 失败后执行的请求
            - StripPrefix=1
            - name: RequestRateLimiter # 限流
              args:
                redis-rate-limiter.replenishRate: 1 # 令牌生成速度,每s生成多少个
                redis-rate-limiter.burstCapacity: 2 # 令牌桶容量
                redis-rate-limiter.requestedTokens: 1 # 每个请求消耗的令牌数

3.3 配置KeyResolver

需要配置KeyResolver以便进行限流,如果未配置的话,访问接口将会返回403异常。

配置方式如下:

    /**
     * 配置限流KeyResolver
     */
    @Bean
    KeyResolver userKeyResolver() {
        return exchange -> Mono.just("normal");
    }

我这里是针对所有的请求使用一个限流策略;如果需要针对不同请求限制不同策略,需要修改这个KeyResolver,如根据查询参数中的值做分组限流:

@Bean
KeyResolver userKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}

注意如果未配置KeyResolver,限流将不会生效。

3.4 启动与测试

重启gateway,然后通过gateway调用service0的接口test:
http://localhost:8000/service0/test,根据我们的限流配置,一秒内第一次请求会成功,第二次有可能成功(如果桶中已经生成了2个令牌就会成功),第三次之后就会失败。后一秒又可以继续请求。

失败时会报:429 Too Many Request异常,成功实现限流。

4 转发规则配置

我们还可以通过routes配置一些转发规则,如:

spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: https://example.org
        predicates:
        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

意思是在指定时间前的请求转发到对应的uri上;

Spring Cloud Gateway支持很多种转发规则,其中节选比较有用的列表如下:

  • 根据权重转发即根据配置的权重决定往每个节点分发的请求比例。如:
spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

即往weighthigh上分发80%的流量 ,剩下的往wieghtlow上分发。

比如我们想要做灰度测试,这种配置方式就非常有用。

另外,Spring Cloud Gateway还包含以下转发规则:

  • 指定时间之后;
  • 指定时间之前;
  • 指定时间区间;
  • 根据Cookie值转发;
  • 根据Header值转发;
  • 根据Host值转发;
  • 根据Method(GET、POST)进行转发;
  • 根据Path进行转发;
  • 根据查询条件进行转发;
  • 根据远程地址进行转发;

具体可以参考官方文档,在此不再展开。

5 拦截器配置

5.1 拦截器配置

上面我们在限流与熔断中我们已经配置过拦截器了,再来看一个示例:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue

上面通过AddRequestHeader拦截器,在转发的请求报文头中添加了名称为X-Request-red的头,其值为blue;

5.2 通用拦截器配置

我们也可以通过default-filters来做通用的配置:

spring:
  cloud:
    gateway:
      default-filters:
      - AddResponseHeader=X-Response-Default-Red, Default-Blue
      - PrefixPath=/httpbin

我们可以使用这种默认的配置来优化我们之前的配置文件,看优化前的:

spring:
  application:
    name: gateway
  cloud:
    gateway:
#      discovery:
#        locator:
#          enabled: true # 启用后,默认会通过/{serviceName}/**这样的请求,将其分发到serviceName的实例上去处理;但启用这种方式后,在routes中配置的就不生效了 ;
      routes:
        - id: circuitbreaker_route # 配置熔断限流
          uri: lb://service0
          predicates:
            - Path=/service0/**
          filters:
            - name: CircuitBreaker  # 熔断
              args:
                name: backendA
                statusCodes:
                  - 500   # 必须配置,如果不配置的话上述的配置不生效
#                fallbackUri: forward:/test # 失败后执行的请求
            - StripPrefix=1
            - name: RequestRateLimiter # 限流
              args:
                redis-rate-limiter.replenishRate: 1 # 令牌生成速度,每s生成多少个
                redis-rate-limiter.burstCapacity: 2 # 令牌桶容量
                redis-rate-limiter.requestedTokens: 1 # 每个请求消耗的令牌数

resilience4j.circuitbreaker:
  configs:
    default:
      slidingWindowSize: 10
      minimumNumberOfCalls: 5 # 计算错误率的最小访问次数,超过这个次数后才会计算错误率并判断是否要进行限流、熔断(但实际不会到这个值,比如配置容忍错误率为50%,配置当前值为10,那么如果前5个都失败就会直接熔断
      permittedNumberOfCallsInHalfOpenState: 3  # 半开状态允许的请求数
      automaticTransitionFromOpenToHalfOpenEnabled: true # 是否在没有请求的情况下由全开自动恢复到半开,设置成true时需要额外的线程进行监控
      waitDurationInOpenState: 2s # 由全开到半开等待的时间
      failureRateThreshold: 50  # 熔断器打开的失败阈值,也即超过指定比例时熔断器将被打开
      eventConsumerBufferSize: 10
  instances:
    backendA:
      baseConfig: default

server:
  port: 8000

management:
  endpoints:
    web:
      exposure:
        include: gateway

如果有新的service,也需要修改这个配置文件,然后重启网关;这比较蛋痛,通过default-filters改造后,配置文件如下:

spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 启用后,默认会通过/{serviceName}/**这样的请求,将其分发到serviceName的实例上去处理;
      default-filters: # 默认的拦截器,对所有请求都生效
        - name: CircuitBreaker  # 熔断
          args:
            name: backendA
            statusCodes:
              - 500   # 必须配置,如果不配置的话上述的配置不生效
            #                fallbackUri: forward:/test # 失败后执行的请求
        - name: RequestRateLimiter # 限流
          args:
            redis-rate-limiter.replenishRate: 1 # 令牌生成速度,每s生成多少个
            redis-rate-limiter.burstCapacity: 2 # 令牌桶容量
            redis-rate-limiter.requestedTokens: 1 # 每个请求消耗的令牌数

resilience4j.circuitbreaker:
  configs:
    default:
      slidingWindowSize: 10
      minimumNumberOfCalls: 5 # 计算错误率的最小访问次数,超过这个次数后才会计算错误率并判断是否要进行限流、熔断(但实际不会到这个值,比如配置容忍错误率为50%,配置当前值为10,那么如果前5个都失败就会直接熔断
      permittedNumberOfCallsInHalfOpenState: 3  # 半开状态允许的请求数
      automaticTransitionFromOpenToHalfOpenEnabled: true # 是否在没有请求的情况下由全开自动恢复到半开,设置成true时需要额外的线程进行监控
      waitDurationInOpenState: 2s # 由全开到半开等待的时间
      failureRateThreshold: 50  # 熔断器打开的失败阈值,也即超过指定比例时熔断器将被打开
      eventConsumerBufferSize: 10
  instances:
    backendA:
      baseConfig: default

server:
  port: 8000

management:
  endpoints:
    web:
      exposure:
        include: gateway

不需要再给每个服务进行配置了,添加服务的时候也不需要对网关做改动与重启。

5.3 内置拦截器

当前Spring Cloud Gateway内置以下拦截器:

  • AddRequestHeader:添加报文头
  • AddRequestParameter:添加请求参数
  • AddResponseHeader:添加返回报文头
  • DedupeResponseHeader:删除返回报文头
  • CircuitBreaker:熔断
  • FallbackHeaders:失败后调用fallbackUri地址时,并异常信息塞到请求头中;
  • MapRequestHeader:请求报文头中的报文名称转换
  • PrefixPath:为请求统一添加前缀
  • RequestRateLimit:限流
  • RedirectTo:转发
  • RemoveRequestHeader:删除请求报文头
  • RemoveResponseHeader:删除返回报文头
  • RemoveRequestParameter:删除请求参数
  • RewritePath:重定向
  • StripePrefix:自动移除路径前面的串,如指定为1时,请求路径为/test/call,那么处理后将会访问/call
  • Retry:重试
  • RequestSize:请求报文大小,默认为5M,如上传文件时超过5M,不修改此参数将会报错。
  • TokenReply:使用Spring Security OAuth2时,配置这个Filter会将前端访问传过来的Token转发给实际调用的服务;
  • ...

具体使用请参考官方文档。

6 自定义拦截器

在实际项目中,我们可能需要根据用户请求判断是否有权限访问对应的接口地址,用户与接口权限关系可能被后台管理动态的维护并且存储在MySql数据库中;此时我们可以在网关进行统一拦截,权限不足的请求直接返回前端401异常,实现如下,本实现省略了具体的登录用户信息获取及权限查询等步骤,仅做一个模拟,具体实现以业务需求为准。

6.1 定义拦截器

/**
 * @author LiuQi 2021/4/28-18:11
 * @version V1.0
 **/
@Component
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory
 {
    public PreGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 模拟无权限,直接返回401  
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            
            // 不再调用后续的拦截器
            return Mono.empty();
        };
    }

    public static class Config {
        //Put the configuration properties for your filter here
    }
}

在这个拦截器中,我们将response的状态码设置成UNAUTHORIZED,并返回一个空的Mono;返回空的Mono会使得后续的Filter不会继续执行。

6.2 配置文件引用

然后在配置文件中引用这个Factory:

spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 启用后,默认会通过/{serviceName}/**这样的请求,将其分发到serviceName的实例上去处理;
      default-filters: # 默认的拦截器,对所有请求都生效
        - name: Pre

注意我们在配置文件中使用的名称是Pre,Spring Cloud Gateway将会自动在后面拼上GatewayFilterFactory然后去容器中找到对应的实例使用。也就是说,我们自定义的Factory其名称必须是以GatewayFilterFactory结尾,否则使用不了。

7 部署情况

引入网关后的集群部署情况:

集群部署情况

这个时候我们的集群才真正成为了一个集群,能够真正的向外提供服务了。

但还是缺少一些关键东西,如整个集群的监控等。接下来的文章中将对这一内容进行讲解

相关推荐

python入门到脱坑经典案例—清空列表

在Python中,清空列表是一个基础但重要的操作。clear()方法是最直接的方式,但还有其他方法也可以实现相同效果。以下是详细说明:1.使用clear()方法(Python3.3+推荐)...

python中元组,列表,字典,集合删除项目方式的归纳

九三,君子终日乾乾,夕惕若,厉无咎。在使用python过程中会经常遇到这四种集合数据类型,今天就对这四种集合数据类型中删除项目的操作做个总结性的归纳。列表(List)是一种有序和可更改的集合。允许重复...

Linux 下海量文件删除方法效率对比,最慢的竟然是 rm

Linux下海量文件删除方法效率对比,本次参赛选手一共6位,分别是:rm、find、findwithdelete、rsync、Python、Perl.首先建立50万个文件$testfor...

数据结构与算法——链式存储(链表)的插入及删除,

持续分享嵌入式技术,操作系统,算法,c语言/python等,欢迎小友关注支持上篇文章我们讲述了链表的基本概念及一些查找遍历的方法,本篇我们主要将一下链表的插入删除操作,以及采用堆栈方式如何创建链表。链...

Python自动化:openpyxl写入数据,插入删除行列等基础操作

importopenpyxlwb=openpyxl.load_workbook("example1.xlsx")sh=wb['Sheet1']写入数据#...

在Linux下软件的安装与卸载(linux里的程序的安装与卸载命令)

通过apt安装/协助软件apt是AdvancedPackagingTool,是Linux下的一款安装包管理工具可以在终端中方便的安装/卸载/更新软件包命令使用格式:安装软件:sudoapt...

Python 批量卸载关联包 pip-autoremove

pip工具在安装扩展包的时候会自动安装依赖的关联包,但是卸载时只删除单个包,无法卸载关联的包。pip-autoremove就是为了解决卸载关联包的问题。安装方法通过下面的命令安装:pipinsta...

用Python在Word文档中插入和删除文本框

在当今自动化办公需求日益增长的背景下,通过编程手段动态管理Word文档中的文本框元素已成为提升工作效率的关键技术路径。文本框作为文档排版中灵活的内容容器,既能承载多模态信息(如文字、图像),又可实现独...

Python 从列表中删除值的多种实用方法详解

#Python从列表中删除值的多种实用方法详解在Python编程中,列表(List)是一种常用的数据结构,具有动态可变的特性。当我们需要从列表中删除元素时,根据不同的场景(如按值删除、按索引删除、...

Python 中的前缀删除操作全指南(python删除前导0)

1.字符串前缀删除1.1使用内置方法Python提供了几种内置方法来处理字符串前缀的删除:#1.使用removeprefix()方法(Python3.9+)text="...

每天学点Python知识:如何删除空白

在Python中,删除空白可以分为几种不同的情况,常见的是针对字符串或列表中空白字符的处理。一、删除字符串中的空白1.删除字符串两端的空白(空格、\t、\n等)使用.strip()方法:s...

Linux系统自带Python2&yum的卸载及重装

写在前面事情的起因是我昨天在测试Linux安装Python3的shell脚本时,需要卸载Python3重新安装一遍。但是通过如下命令卸载python3时,少写了个3,不小心将系统自带的python2也...

如何使用Python将多个excel文件数据快速汇总?

在数据分析和处理的过程中,Excel文件是我们经常会遇到的数据格式之一。本文将通过一个具体的示例,展示如何使用Python和Pandas库来读取、合并和处理多个Excel文件的数据,并最终生成一个包含...

【第三弹】用Python实现Excel的vlookup功能

今天继续用pandas实现Excel的vlookup功能,假设我们的2个表长成这样:我们希望把Sheet2的部门匹在Sheet1的最后一列。话不多说,先上代码:importpandasaspd...

python中pandas读取excel单列及连续多列数据

案例:想获取test.xls中C列、H列以后(当H列后列数未知时)的所有数据。importpandasaspdfile_name=r'D:\test.xls'#表格绝对...

取消回复欢迎 发表评论: