流量控制

当系统资源不够,不足以应对大量请求,即系统资源与访问量出现矛盾的时候,我们为了保证有限的资源能够正常服务,因此对系统按照预设的规则进行流量限制或功能限制的一种方法

限流原因

限流形式:

分层限流:制定每一层的限流策略时,都应该抱着不信任上层限流的思维,这样即便某一层限流机制发生问题,也不至于引发全局问题,最终形成的限流体系才是最健壮、最可靠的

对于限流 我们需要考虑如下问题:

柔性限流:不快速失败 进行排队处理

前端柔性:随机延迟一段时间发起请求或在高峰期随机失效用户的部分频繁请求

水位确定

通过全链路压测确定系统承载的极限,水位确定的过高没有保护意义,定的过低又会导致资源浪费

流量统计指标

当然具体业务相关的系统也有其他统计指标:如IO密集型的使用最高带宽 游戏服务采用在线用户数

计算扩展

流量切换

限流设计模式

计数器算法

系统维护一个计数器,来一个请求就加1,请求处理完成就减1,当计数器大于指定的阈值,就拒绝新的请求。

基于这个简单的方法,可以再延伸出一些高级功能,比如阈值可以不是固定值,是动态调整的。另外,还可以有多组计数器分别管理不同的服务,以保证互不影响等。

线程池大小,数据库连接池大小、nginx连接数等都属于计数器算法。

全局或某段时间范围达到阈值则限流

时间窗计数

固定时间窗

这种问题是当时间处于两个时间窗的临界点时,会超过限额

滑动窗口

队列

就是基于FIFO队列,所有请求都进入队列,后端程序从队列中取出待处理的请求依次处理。

基于队列的方法,也可以延伸出更多的玩法来,比如可以设置多个队列以配置不同的优先级

排队系统

桶算法

漏桶算法

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

202001271537

本质是总量控制,桶大小是设计关键

漏桶算法主要适用于瞬时高并发流量的场景,在短短几分钟内涌入大量请求时,为了更好的业务效果和用户体验,即使处理慢一些,也要做到尽量不丢弃用户请求

令牌桶算法

设置一个令牌桶,另外有一个脚本以持续恒定的速度往令牌桶里面放令牌,后端处理程序每处理一个请求就必须从桶里拿出一个令牌,如果令牌拿完了,那就不能处理请求了。我们可以控制脚本放令牌的速度来达到控制后端处理的速度,以实现动态流控

2020789425

质是速率控制,令牌产生的速率是设计关键,原本是用于网络设备控制传输速度的,而且它控制的目的是保证一段时间内的平均传输速度

使用guava实现

@RestController
public class Controller {

    /**
     * 一个每秒创建一个token的桶
     */
    RateLimiter limiter = RateLimiter.create(1);

    @RequestMapping("index")
    public String index(){
        // 500ms内无法获取令牌,返回错误,否则成功
        if (limiter.tryAcquire(500, TimeUnit.MILLISECONDS)){
            return "success";
        }else {
            return "error";
        }

    }
}

令牌桶与漏桶

主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。

动态限流

设计的典范是 TCP 协议的拥塞控制的算法。TCP 使用 RTT - Round Trip Time 来探测网络的延时和性能,从而设定相应的“滑动窗口”的大小,以让发送的速率和网络的性能相匹配

记录下每次调用后端请求的响应时间,然后在一个时间区间内(比如,过去 10 秒)的请求计算一个响应时间的 P90 或 P99 值,如果这个 P90 或 P99 超过设定的阈值(响应太慢了),那么就可以以动态的方式限流,比如每次减去当前一半的QPS,再超就再剪

分布式限流