微服务架构spring cloud - gateway网关限流 spring cloud网关gateway

1.算法

在高并发的应用中,限流是一个绕不开的话题。限流可以保障我们的 API 服务对所有用户的可用性,也可以防止网络攻击。

一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如 nginx 的 limit_conn 模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制 MQ 的消费速率。另外还可以根据网络连接数、网络流量、CPU 或内存负载等来限流。

限流算法

做限流 (Rate Limiting/Throttling) 的时候,除了简单的控制并发,如果要准确的控制 TPS,简单的做法是维护一个单位时间内的 Counter,如判断单位时间已经过去,则将 Counter 重置零。此做法被认为没有很好的处理单位时间的边界,比如在前一秒的最后一毫秒里和下一秒的第一毫秒都触发了最大的请求数,也就是在两毫秒内发生了两倍的 TPS。

常用的更平滑的限流算法有两种:漏桶算法和令牌桶算法。很多传统的服务提供商如华为中兴都有类似的专利,参考采用令牌漏桶进行报文限流的方法

漏桶算法

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

可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。

令牌桶算法

令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解。随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。

令牌桶的另外一个好处是可以方便的改变速度。一旦需要提高速率,则按需提高放入桶中的令牌的速率。一般会定时(比如 100 毫秒)往桶中增加一定数量的令牌,有些变种算法则实时的计算应该增加的令牌的数量。Guava 中的 RateLimiter 采用了令牌桶的算法,设计思路参见 How is the RateLimiter designed, and why?,详细的算法实现参见源码

本文讨论在gateway集成的实现

2.创建gateway工程

详情见:spring cloud网关gateway

在此基础上pom中加入 

<!--RequestRateLimiter限流-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

3.配置类

package com.common.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;

/**
 * @Title:
 * @Auther: 
 * @Date: 2019/8/28 17:13
 * @Version: 1.0
 * @Description:
 */
@Configuration
public class RequestRateLimiterConfig {
    @Bean
    @Primary
    KeyResolver apiKeyResolver() {
            //按URL限流
            return exchange -> Mono.just(exchange.getRequest().getPath().toString());
            }

    @Bean
    KeyResolver userKeyResolver() {
        //按用户限流
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
    }

    @Bean
    KeyResolver ipKeyResolver() {
        //按IP来限流
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }

}

4.yml配置

application.yml

spring:
  application:
    name: gateway8710
  cloud:
    gateway:
      default-filter:
      routes:
      - id: user-server
        predicates:
          - Path=/java/**
        filters:
          - StripPrefix=1
          # 限流过滤器,使用gateway内置令牌算法
          - name: RequestRateLimiter
            args:
              # 令牌桶每秒填充平均速率,即行等价于允许用户每秒处理多少个请求平均数
              redis-rate-limiter.replenishRate: 10
              # 令牌桶的容量,允许在一秒钟内完成的最大请求数
              redis-rate-limiter.burstCapacity: 20
              # 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
              key-resolver: "#{@apiKeyResolver}"
        uri: lb://service-helloword
        #  uri: "http://192.168.111.133:8708/project/hello"
  redis:
    #Redis数据库索引(默认为0)
    database: 0
    #连接超时时间(毫秒) springboot2.0 中该参数的类型为Duration,这里在配置的时候需要指明单位
    timeout: 20s
    #密码
    password: test
    cluster:
      # 获取失败 最大重定向次数
      max-redirects: 3
      #测试环境redis
      nodes:
        - 10.0.0.1:6380
        - 10.0.0.2:6380
        - 10.0.0.3:6380
        - 10.0.0.1:6381
        - 10.0.0.2:6381
        - 10.0.0.3:6381
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 300
        #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1s
        #连接池中的最大空闲连接
        max-idle: 100
        #连接池中的最小空闲连接
        min-idle: 20
server:
  port: 8710
eureka:
  client:
    serviceUrl:
      #指向注册中心
      defaultZone: http://192.168.111.133:8888/eureka/
  instance:
    # 每间隔1s,向服务端发送一次心跳,证明自己依然”存活“
    lease-renewal-interval-in-seconds: 1
    # 告诉服务端,如果我2s之内没有给你发心跳,就代表我“死”了,将我踢出掉。
    lease-expiration-duration-in-seconds: 2

目录结构如下

5.启动测试

需要用jmeter来做并发测试,一秒内启30个进程,重复发请求10000次。

测试结果,没有抢到令牌的请求就返回429,这边的限流相当于平均request:10/s

猜你喜欢

转载自www.cnblogs.com/pu20065226/p/11426279.html