其实网关是很强大,能做的事情很多,包含很多过滤器包括限流,具体的网关可以参考我的另外一篇博文Spring Cloud GateWay-过滤器

今天我们来说下网关如何限流,主要两种方案:

Spring Cloud GateWay整合hystrx

environment: test
management:
  security:
    enabled: false
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    default-property-inclusion: NON_NULL
    time-zone: GMT+8
  servlet:
    multipart:
      file-size-threshold: 100MB
      max-file-size: 4GB
      max-request-size: 4GB
  thymeleaf:
    cache: false
    mode: LEGACYHTML5
  cloud:
    # 服务发现
    nacos:
      discovery:
        server-addr: nacos-headless.ota.svc.cluster.local:8848 #服务端url
        namespace: ota-prd
    gateway:
      discovery:
        locator:
          enabled: true
      enabled: true
      routes:
          # 公共服务 基础数据 base服务
          - id: vota-api-core
            uri: lb://vota-api-core
            predicates:
              - Path=/mgapi/vota-api-core/**
            filters:
              - name: Hystrix
                args:
                  name: vota-api-core
                  fallbackUri: forward:/fallback
              - name: StripPrefix
                args:
                  parts: 2
              - name: SessionAccess
                args:
                  ignorePath:
                    - /mgapi
                    - /static
                    - /client-api
                    - /busi
                    - /zuul
         
vota-api-core:
  ribbon:
    ConnectTimeout: 500
    MaxAutoRetries: 0
    MaxAutoRetriesNextServer: 1
    OkToRetryOnAllOperations: true
    ReadTimeout: 3600000
    
hystrix:
  command:
    vota-api-core:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2401000

其实上面如何需要整合hystrix的信号量模式 ,在 application.yml 或 application.properties 文件中配置 Hystrix 的命令属性比较完整的

hystrix:
  command:
    default:
      execution:
        isolation:
          # 设置隔离策略为信号量隔离(semaphore isolation)
          strategy: SEMAPHORE
          semaphore:
            # 设置信号量隔离的最大并发请求数
            maxConcurrentRequests: 10
        # 设置超时时间
        timeout:
          isolation:
            thread:
              timeoutInMilliseconds: 1000
      circuitBreaker:
        # 是否启用断路器
        enabled: true
        # 请求阈值,当在统计窗口内请求次数达到此值后,断路器才开始生效
        requestVolumeThreshold: 20
        # 统计窗口时间长度,单位为毫秒
        sleepWindowInMilliseconds: 5000
        # 错误率阈值,当在统计窗口内的错误率超过此值,断路器将打开
        errorThresholdPercentage: 50
      # 是否启用请求缓存
      requestCache:
        enabled: true
      metrics:
        # 设置滑动窗口大小,用于统计请求和错误率
        rollingStats:
          # 滑动窗口的大小,单位为秒
          timeInMilliseconds: 10000
          # 滑动窗口的分割数量
          numberOfBuckets: 10

maxConcurrentRequests: 设置信号量隔离的最大并发请求数,超过这个数量的请求将被立即拒绝。
timeoutInMilliseconds: 设置命令执行的超时时间,单位为毫秒,超时后将触发断路器或回退逻辑。
requestVolumeThreshold: 断路器生效前的最小请求量,只有当在统计窗口内的请求次数达到此值,断路器的统计和规则才会生效。
sleepWindowInMilliseconds: 断路器打开后的休眠窗口时间,单位为毫秒,在这段时间内,断路器将保持打开状态,不接受新的请求。
errorThresholdPercentage: 错误率阈值,当在统计窗口内的错误率超过此值,断路器将打开。
enabled: 控制断路器和请求缓存的开关

注意事项:
1、上面的代码块通过熔断器这种超时实现,其实上面这样的配置是不会生效的,通过配置文件其实想要通过hystrx的线程隔离模式,但是呢,这样的配置不会生效的,这个地方很多人都会陷入误区,首先呢,hystrx在springcloud中要不被feign整合,要不被gateway整合,前者默认是线程池,后置默认是信号量,具体两者的区别,信号量是单线程不会涉及到header上下文传递,但是呢性能不好,线程池呢会涉及到上下文传递问题,这个地方我就不会扩散了
2、这些配置适用于所有使用默认配置的 Hystrix 命令。如果你想为特定的命令配置不同的属性,可以使用命令的名称替换 default,例如 hystrix.command.myCommandName,比如hystrix.command.vota-api-core
3、当使用信号量隔离策略时,maxConcurrentRequests 是一个重要的配置,它决定了在任何给定时刻可以并发执行的请求数量。断路器的配置 (circuitBreaker) 用于控制何时以及如何打开和关闭断路器,以保护服务免受故障传播的影响
4、请求缓存 (requestCache) 可以减少对后端服务的重复调用,当相同的输入再次请求时,直接从缓存中返回结果

线程池资源隔离模式

hystrix:
  command:
    vota-api-core:
      execution:
        isolation:
          strategy: THREAD
        timeoutInMilliseconds: 2401000

这样明确指定了隔离模式才行,否则还是走的默认的,而且只有线程池资源隔离才会有超时概念,否则信号量资源隔离是没有的 ,只有并发数的配置

信号量资源隔离模式

hystrix:
  command:
    vota-api-core:
      execution:
        isolation:
          strategy: SEMAPHORE
          semaphore:
            maxConcurrentRequests: 10
        timeoutInMilliseconds: 2401000

这里默认是10,可以调整的50/100,上面这两种配置都是整合hystrx的,但是目前hystrx是Netflix旗下的组建以及不维护了,现在启用的都是CircuitBreaker

spring:
  application:
    name: vota-api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          # 表明 Gateway 开启服务注册和发现的功能,并且 Spring Cloud Gateway 自动根据服务发现为每一个服务创建了一个 router,这个 router 将以服务名开头的请求路径转发到对应的服务
          enabled: true
          # 是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时服务名被转成大写了)
          lower-case-service-id: true
      routes:
        - id: vota-api-proxy
          uri: lb://vota-api-core/v50/vehicle/api/
          predicates:
            # - Path=/v50/vehicle/api/register,/v50/vehicle/api/{segment}/current/time
            - Path=/v50/vehicle/api/**
            - Method=POST
          filters:
            - name: CircuitBreaker
              args:
                name: fallbackCmdA
                fallbackUri: forward:/fallbackA
            - name: RequestRateLimiter
              args:
                # 解析限流 Key 的 Bean 对象的名字,使用 SpEL 表达式
                key-resolver: '#{@vehicleKeyResolver}'
                # 限流实现
                rate-limiter: '#{@redisRateLimiter}'
                # 令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 2000
                # 令牌桶总容量
                redis-rate-limiter.burstCapacity: 5000
public static void main(String[] args) {
        SpringApplication.run(VehicleGatewayApplication.class, args);
    }

    /**
     * <p>限流和防重放的区别,分别的定义</p>
     * <p>令牌桶限流</p>
     * <p>注册接口使用 REGISTER + vin 作为 Key</p>
     * <p>非注册接口使用 API_NAME + deviceId 作为 Key</p>
     * <p>令牌桶算法基于 Lua + Redis 实现,Lua 脚本位置为:spring-cloud-gateway-core-2.1.5.RELEASE
     * .jar/META-INF/scripts/request_rate_limiter.lua</p>
     * <p>如需自定义算法,则继承 AbstractRateLimiter 类</p>
     *
     * @return KeyResolver
     * @see org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter
     * @see org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory
     */
    @Bean
    public KeyResolver vehicleKeyResolver() {
        return exchange -> {
            String value = exchange
                .getRequest()
                .getPath()
                .value();
            return Mono.just(exchange.getRequest().getPath().value());
        };
    }