流量控制
当系统资源不够,不足以应对大量请求,即系统资源与访问量出现矛盾的时候,我们为了保证有限的资源能够正常服务,因此对系统按照预设的规则进行流量限制或功能限制的一种方法
限流原因
- 流量大
- 保护服务
限流形式:
- 技术层面限流 限制并发 限制速率等
- 网关层限流
- 本地限流 分布式限流
- 业务层面限流 类似于秒杀下场景的商品总量 抢完就完
分层限流:制定每一层的限流策略时,都应该抱着不信任上层限流的思维,这样即便某一层限流机制发生问题,也不至于引发全局问题,最终形成的限流体系才是最健壮、最可靠的
对于限流 我们需要考虑如下问题:
- 限流指标
- 限流手段
- 超额流量处理
柔性限流:不快速失败 进行排队处理
前端柔性:随机延迟一段时间发起请求或在高峰期随机失效用户的部分频繁请求
水位确定
通过全链路压测确定系统承载的极限,水位确定的过高没有保护意义,定的过低又会导致资源浪费
- 安全水位
- 极限水位
流量统计指标
- 每秒事务数(Transactions per Second,TPS):“事务”可以理解为一个逻辑上具备原子性的业务操作
- 每秒请求数(Hits per Second,HPS):指每秒从客户端发向服务端的请求数
- 每秒查询数(Queries per Second,QPS):一个请求的响应往往要由后台多个服务节点共同协作来完成 所以看似客户端发送了一条请求 但背后很可能涉及到多个查询
当然具体业务相关的系统也有其他统计指标:如IO密集型的使用最高带宽 游戏服务采用在线用户数
计算扩展
- 上游业务扩容了 下游业务也要跟着扩展
流量切换
- DNS层切换
- 虚拟IP
- 应用层 Nginx 网关切换
限流设计模式
计数器算法
系统维护一个计数器,来一个请求就加1,请求处理完成就减1,当计数器大于指定的阈值,就拒绝新的请求。
基于这个简单的方法,可以再延伸出一些高级功能,比如阈值可以不是固定值,是动态调整的。另外,还可以有多组计数器分别管理不同的服务,以保证互不影响等。
线程池大小,数据库连接池大小、nginx连接数等都属于计数器算法。
全局或某段时间范围达到阈值则限流
时间窗计数
- 固定时间窗 统计固定时间周期内的请求量或者资源消耗量
这种问题是当时间处于两个时间窗的临界点时,会超过限额
- 滑动窗口 滑动窗口原理是在每次有访问进来时,先判断前 N 个单位时间内的总访问量是否超过了设置的阈值,并对当前时间片上的请求数 +1
队列
就是基于FIFO队列,所有请求都进入队列,后端程序从队列中取出待处理的请求依次处理。
基于队列的方法,也可以延伸出更多的玩法来,比如可以设置多个队列以配置不同的优先级
桶算法
漏桶算法
漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率
本质是总量控制,桶大小是设计关键
漏桶算法主要适用于瞬时高并发流量的场景,在短短几分钟内涌入大量请求时,为了更好的业务效果和用户体验,即使处理慢一些,也要做到尽量不丢弃用户请求
令牌桶算法
设置一个令牌桶,另外有一个脚本以持续恒定的速度往令牌桶里面放令牌,后端处理程序每处理一个请求就必须从桶里拿出一个令牌,如果令牌拿完了,那就不能处理请求了。我们可以控制脚本放令牌的速度来达到控制后端处理的速度,以实现动态流控
质是速率控制,令牌产生的速率是设计关键,原本是用于网络设备控制传输速度的,而且它控制的目的是保证一段时间内的平均传输速度
使用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,再超就再剪
分布式限流
- 集中式存储统计信息 将原本在每个服务节点自己内存当中的统计数据给开放出来,让全局的限流服务可以访问到 这种方式每次服务调用额外增加了一次网络开销
- 使用货币令牌 一个令牌会携带着一定额度贯穿整条链路 每次服务调用这个额度都会被减去点 当额度为0时 要不直接失败 要不就再向中心申请一定的额度 继续请求
单机限流
将集群总配额除以集群总的节点数,得到每个节点上可用的配额。在各个节点下发配额数据,然后在单机维度使用限流算法,实现单机维度的限流。可以实时监控每台节点的限流情况,动态修改每台节点的配额。通过判断,给流量较高的节点分配较多的配额,给流量较少的节点分配较少的配额,从而在流量倾斜的时候,也能够做到较为精准的限流
全局限流
选择一个集中式的限流服务器。该服务器用来记录限流配额。在生产和消费时,向该限流服务器记录配额信息,获取限流状态,判断是否进行限流。同时根据单机限流的方案,在本地缓存一份均分的配额数据,当限流服务器异常时,直接使用本地缓存的配额数据进行计算限流。同时提供开关,在某些情况下可以关闭限流