网关限流算法与实践

限流算法

在开源软件中也有这种限流的设计,例如Nginx下用于限制瞬间并发连接数的limit_conn模块,限制每秒平均速度的limit_req模块。
常见的限流算法有计数器算法、漏桶算法和令牌桶算法。
· 计数器法
计数器算法 简单粗暴。该算法会维护一个counter,规定在单位时间内counter的大小不能超过最大值,每隔固定时间就将counter的值置零。如果这个counter大于设定的阈值了,那么系统就开始拒绝请求以保护系统的负载。
· 漏桶算法
在漏桶算法中,我们会维护一个固定容量的桶,这个桶会按照指定的速度漏水。如果这个桶空了,那么就停止漏水;请求到达系统就类似于将水加入桶中,这个速度可以是匀速的也可以是瞬间的,如果这个桶满了,就会忽略后面来的请求,直到这个桶可以存放多余的水。漏桶算法的好处是可
以将系统的处理能力维持在一个比较平稳的水平,缺点是在瞬间流量过来时,会拒绝后续的请求流量。一般来说,代码中会使用一个队列实现“漏斗”的效果,当请求过多时,队列中的请求就开始积压,当队列满了之后,系统就会开始拒绝请求。
如图下图所示,把请求比作水,水来了都先放进桶里,并以限定的速度出水,当水来得过猛而出水不够快时就导致水直接溢出,即拒绝服务。
网关限流算法与实践_第1张图片

· 令牌桶算法
令牌桶算法和漏桶算法效果一样,但思路相反:随着时间的流逝,系统会按照指定速度往桶里添加token,每来一个新请求,就从桶里拿走一个token,如果没有token可拿就拒绝服务。这种算法的好处是便于控制系统的处理速度,甚至可以通过统计信息实时优化令牌桶的大小。

 令牌桶算法是比较常见的限流算法之一,大概描述如下:
1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)根据限流大小,设置按照一定的速率往桶里添加令牌;
3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
网关限流算法与实践_第2张图片

从理论上来说,令牌桶算法和漏桶算法的不同之处在于处理瞬间到达的大流量的不同:令牌桶算法由于在令牌桶里攒了很多令牌,因此在大流量到达的瞬间可以一次性将队列中所有的请求都处理完,然后按照恒定的速度处理请求;漏桶算法则一直有一个恒等的阈值,在大流量到达的时候,也
会将多余的请求拒绝。在Nginx这种基本没什么业务逻辑的网关中,自身的处理不会是瓶颈,在这种场景下,就比较适合使用令牌桶算法了。

 限流实践

RateLimiter是guava中concurrent包下的一个限流工具类,使用了令牌桶算法,它支持两种令牌获取接口:
获取不到一直阻塞;
在指定时间内获取不到就阻塞,超过这个时间就返回获取失败。
(1)RateLimiter的使用。

在下面的这个例子中,我们希望每秒最多2000次业务操作,因此先初始化令牌桶的大小为2000,在执行业务操作之前,先调用acquire()获取令牌,如果获取不到就阻塞。
网关限流算法与实践_第3张图片

 如果请求可以丢弃,并且在某种程度上需要这种快速失败,那么就不能使用acquire()方法,为了避免源源不断的请求将整体的系统资源耗尽,就需要使用 tryAcquire()方法。下面是针对tryacquire()的示例,还可以使用带超时参数的 tryAcquire()方法,在指定时间内获取不到令牌再返回false。
网关限流算法与实践_第4张图片

 (2)RateLimiter的设计思路。
RateLimiter的主要功能是通过限制请求流入的速度来提供稳定的服务速度。实现QPS速度最简单的方式就是记住上一次请求的最后授权时间,然后保证1/QPS秒内不允许请求进入。例如QPS=5,如果我们保证最后一个被授权请求之后的200ms内没有请求被授权,那么就达到了预期的速度。如果一个请求现在过来但最后一个被授权请求是在100ms之前,那么我们就要求当前这个请求等待100ms,按照这个思路请求15个新令牌(许可证)就需要3秒。

网关限流实现

需求:每个ip地址1秒内只能发送1次请求,多出来的请求返回429错误。

定义KeyResolver
指定限流的方式,可以基于IP限流、通过用户限流、通过接口限流。
在GatewayApplicatioin引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。

//定义一个KeyResolver
@Bean
public KeyResolver ipKeyResolver() {
    return new KeyResolver() {
        @Override
        public Mono resolve(ServerWebExchange exchange) {
            return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
        }
    };
}

修改application.yml中配置项,指定限制流量的配置以及REDIS的配置,修改后最终配置如下:

spring:
    application:
        name: sysgateway
cloud:
    gateway:
        globalcors:
            cors-configurations:
                '[/**]': # 匹配所有请求
                allowedOrigins: "*" #跨域处理 允许所有的域
                allowedMethods: # 支持的方法
                    - GET
                    - POST
                    - PUT
                    - DELETE
            routes:
                - id: goods
                uri: lb://goods
            predicates:
                - Path=/goods/**
            filters:
                - StripPrefix= 1
                - name: RequestRateLimiter #请求数限流 名字不能随便写
                args:
                    key-resolver: "#{@ipKeyResolver}"
                    redis-rate-limiter.replenishRate: 1
                    redis-rate-limiter.burstCapacity: 1
        - id: system
            uri: lb://system
            predicates:
                - Path=/system/**
            filters:
                - StripPrefix= 1

解释:
burstCapacity:令牌桶总容量。
replenishRate:令牌桶每秒填充平均速率。
key-resolver:用于限流的键的解析器的 Bean 对象的名字。它使用SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

通过在replenishRate 和中设置相同的值来实现稳定的速率burstCapacity 。设置burstCapacity 高于时,可以允许临时突发replenishRate 。在这种情况下,需要在突发之间允许速率限制器一段时
间(根据replenishRate ),因为2次连续突发将导致请求被丢弃( HTTP 429 - Too Many Requests )key-resolver: "#{@userKeyResolver}" 用于通过SPEL表达式来指定使用哪一个KeyResolver.
如上配置:
表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。
最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。

快速刷新,当1秒内发送多次请求,就会返回429错误。

你可能感兴趣的:(面试,java,职场和发展)