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

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

off999 2025-04-11 04:19 40 浏览 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 部署情况

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

集群部署情况

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

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

相关推荐

电脑热点怎么禁止别人连接(怎么让电脑的热点不被关闭)

方法/步骤1/7首先,在我们的屏幕上找到“设置”。2/7点击进入设置之后,正数第三位就是个人热点。3/7在个人热点中,我们可以设置热点密码,开启关闭热点。4/7排名第三的是连接管理,可以设置允许一个至...

常用浏览器有哪些

电脑常用浏览器好用的方法步骤1从用户体验和功能扩展性来看,Chrome是最好用的浏览器。2Chrome拥有快速的页面加载速度、优秀的标签页管理和内置的开发者工具等功能,还有不断更新的扩展程序,能够...

u盘怎么取消读写保护(优盘怎么去掉读写保护)

如果您的U盘启动了读写保护,那么就无法进行数据的读取和写入操作。以下是一些可能的解决方法:1.检查开关或按钮:一些U盘有物理开关或按钮,用于启用或禁用读写保护。您可以检查一下U盘上是否有这样的开关或...

打印机脱机无法打印怎么办(打印机脱机无法打印故障处理)
打印机脱机无法打印怎么办(打印机脱机无法打印故障处理)

打印机脱机无法打印怎么办?在使用打印机的过程中,经常会遇到打印机无法打印的问题,如果你的打印机已经正常使用了一段时间,而是现在打印机无法打印了,那么很可能是你的打印机脱机了。我们该怎么办呢?首先我们拿到打印机,要把它的电源线,USB打印线与...

2025-11-12 03:51 off999

台式电脑可以连接wifi吗(台式电脑没有连接wifi选项怎么办)
  • 台式电脑可以连接wifi吗(台式电脑没有连接wifi选项怎么办)
  • 台式电脑可以连接wifi吗(台式电脑没有连接wifi选项怎么办)
  • 台式电脑可以连接wifi吗(台式电脑没有连接wifi选项怎么办)
  • 台式电脑可以连接wifi吗(台式电脑没有连接wifi选项怎么办)
激活码怎么激活(激活码怎么激活steam)

首先,启动电脑,在键盘按下“Win+R”,然后“运行”程序。然后,在“运行”的对话框输入“regedit”,回车确定输入命令然后,在窗口的左侧菜单选择“HKEY_LOCAL_MACHINE\SOFTW...

pscs6安装教程序列号(ps安装序列号cs6破解)
  • pscs6安装教程序列号(ps安装序列号cs6破解)
  • pscs6安装教程序列号(ps安装序列号cs6破解)
  • pscs6安装教程序列号(ps安装序列号cs6破解)
  • pscs6安装教程序列号(ps安装序列号cs6破解)
电脑动不动就卡住不动怎么回事

可能出现卡死原因:1、病毒引起,使你的电脑检测通过的程序太多,CPU主频性能不能充分发挥出来。2、温度过高,散热不好,使CPU性能下降。3、内存条太小,内存缺陷。5、可能设置了开机后自动登陆太多,自动...

笔记本风扇声音大怎么办(笔记本风扇声音非常大)

1.清理笔记本风扇灰尘一般而言,新买来的风扇总是噪声较小,而使用一段时间后会明显变大。其实,灰尘是造成风扇噪音上升的重要原因之一,因为无孔不入的灰尘总能钻进不完全密闭的机箱。当CPU风扇高速旋转时,漩...

如何添加无线网络打印机(如何添加无线网络打印机连接)

  要添加网络打印机,您可以按照以下步骤进行操作:1.确保网络设置:首先,请确保您的计算机和打印机都已连接到同一个局域网或无线网络中,并且网络连接正常。确保您已经知道网络打印...

戴尔电脑一键重装系统(戴尔怎么一键重装系统)

若您需要重装戴尔系统,可以按照以下步骤进行操作:首先备份重要数据,然后获取系统安装介质,可以是光盘或USB驱动器。接下来,进入BIOS设置,将启动顺序调整为从安装介质启动。重启电脑后,按照屏幕提示进行...

电脑ip地址配置异常怎么修复

如果您发现IP地址配置异常,可以按照以下步骤尝试解决:1.检查网络连接:首先检查计算机、路由器或交换机等设备的网线、电源和连接状态是否正常,并确保网络设备正确连接。2.确认IP地址:检查您的计算机...

怎么把win7电脑恢复出厂设置

1.首先我们打开电脑找到“计算机”点击打开。2.进入页面然后我们点击“Windows7(C:)”打开C盘。3.我们在C盘界面找到Windows7并点击打开。4.进入到Win7文件夹中找到并双击“Sys...

ctrl c 和 ctrl v 怎么按(一键复制粘贴)

左手小指按Ctrl键,食指按C键或者V键具体在按Ctrl+C的时候,无名指放在Z键上,中指放在X键上,食指按C键如果你也用这种方式的话,可能和我一样,第一次按的时候不习惯手指这样去分工的感觉,但是你...

u盘格式转换为fat32(U盘格式转换为FAT32)
  • u盘格式转换为fat32(U盘格式转换为FAT32)
  • u盘格式转换为fat32(U盘格式转换为FAT32)
  • u盘格式转换为fat32(U盘格式转换为FAT32)
  • u盘格式转换为fat32(U盘格式转换为FAT32)

取消回复欢迎 发表评论: