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

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

off999 2025-04-11 04:19 20 浏览 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 数据分析——利用Pandas进行分组统计

话说天下大势,分久必合,合久必分。数据分析也是如此,我们经常要对数据进行分组与聚合,以对不同组的数据进行深入解读。本章将介绍如何利用Pandas中的GroupBy操作函数来完成数据的分组、聚合以及统计...

python数据分析:介绍pandas库的数据类型Series和DataFrame

安装pandaspipinstallpandas-ihttps://mirrors.aliyun.com/pypi/simple/使用pandas直接导入即可importpandasas...

使用DataFrame计算两列的总和和最大值_[python]

【如果对您有用,请关注并转发,谢谢~~】最近在处理气象类相关数据的空间计算,在做综合性计算的时候,DataFrame针对每列的统计求和、最大值等较为方便,对某行的两列或多列数据进行求和与最大值等的简便...

8-Python内置函数

Python提供了丰富的内置函数,这些函数可以直接使用而无需导入任何模块。以下是一些常用的内置函数及其示例:1-print()1-1-说明输出指定的信息到控制台。1-2-例子2-len()2-1-说...

Python中函数式编程函数: reduce()函数

Python中的reduce()函数是一个强大的工具,它通过连续地将指定的函数应用于序列(如列表)来对序列(如列表)执行累积操作。它是functools模块的一部分,这意味着您需要在使用它之...

万万没想到,除了香农计划,Python3.11竟还有这么多性能提升

众所周知,Python3.11版本带来了较大的性能提升,但是,它具体在哪些方面上得到了优化呢?除了著名的“香农计划”外,它还包含哪些与性能相关的优化呢?本文将带你一探究竟!作者:BeshrKay...

最全python3.11版12类75个内置函数大全

获取全部内置函数:importbuiltins#导入模块yc=[]#异常属性nc=[]#不可调用fn=[]#内置函数defll(ty=builtins):...

软件测试笔试题

测试工程师岗位,3-5年,10-14k1.我司有一款产品,类似TeamViewer,向日葵,mstsc,QQ远程控制产品,一个PC客户端产品,请设想一下测试要点。并写出2.写出常用的SQL语句8条,l...

备战各大互联网巨头公司招聘会,最全Python面试大全,共300题

前言众所周知,越是顶尖的互联网公司在面试这一part的要求就越高,需要你有很好的技术功底、项目经验、一份漂亮的简历,当然还有避免不了的笔试过关。对于Python的工程师来说,全面掌握好有关Python...

经典 SQL 数据库笔试题及答案整理

马上又是金三银四啦,有蛮多小伙伴在跳槽找工作,但对于年限稍短的软件测试工程师,难免会需要进行笔试,而在笔试中,基本都会碰到一道关于数据库的大题,今天这篇文章呢,就收录了下最近学员反馈上来的一些数据库笔...

用Python开发日常小软件,让生活与工作更高效!附实例代码

引言:Python如何让生活更轻松?在数字化时代,编程早已不是程序员的专属技能。Python凭借其简洁易学的特点,成为普通人提升效率、解决日常问题的得力工具。无论是自动化重复任务、处理数据,还是开发个...

太牛了!102个Python实战项目被我扒到了!建议收藏!

挖到宝了!整整102个Python实战项目合集,从基础语法到高阶应用全覆盖,附完整源码+数据集,手把手带你从代码小白变身实战大神!这波羊毛不薅真的亏到哭!超全项目库,学练一站式搞定这份资...

Python中的并发编程

1.Python对并发编程的支持多线程:threading,利用CPU和IO可以同时执行的原理,让CPU不会干巴巴等待IO完成。多进程:multiprocessing,利用多核CPU...

Python 也有内存泄漏?

1.背景前段时间接手了一个边缘视觉识别的项目,大功能已经开发的差不多了,主要是需要是优化一些性能问题。其中比较突出的内存泄漏的问题,而且不止一处,有些比较有代表性,可以总结一下。为了更好地可视化内存...

python爬虫之多线程threading、多进程、协程aiohttp批量下载图片

一、单线程常规下载常规单线程执行脚本爬取壁纸图片,只爬取一页的图片。importdatetimeimportreimportrequestsfrombs4importBeautifu...

取消回复欢迎 发表评论: