限流详解
Why & What 限流算法 应用级限流 分布式限流 降级预案
Why & What
1. 开发高并发系统时、有很多手段来保护系统、如:缓存、降级和限流等
缓存的目的是提高系统访问速度和增大系统处理能力、可谓是高并发系统的银弹
而降级是当服务出问题或者影响到核心流程的性能、需要暂时屏蔽掉、待高峰过去后再打开的场景
而有些场景并不能使用缓存或降级来解决、eg. 秒杀、抢购、写服务(评论、下单等)、频繁的复杂查询(评论的最后几页)等、因此需要一些手段来限制这些场景下的并发/请求量、这种手段就是限流
2. 限流的目的是对并发访问/请求进行限速或者一个时间窗口内的请求进行限速来保护系统、一旦达到限制速率则可以拒绝服务(定向到指定错误页、或者告知资源没有了)、排队or等待(eg.秒杀、评论、下单)、降级(返回兜底或默认数据、eg.商品详情页默认有库存)、压测时能找出每个系统的处理峰值、然后通过设定系统峰值阈值来防止系统过载时、通过拒绝过载时的请求来保证系统可用
3. 常见方法:
1) 限制总并发数(eg. 数据库连接池、线程池)、
2) 限制瞬时并发数(eg. nginx的limit_conn模块)
3) 限制时间窗口内的评价速率(eg. nginx的limit_req模块)
4) 限制远程接口的调用速率
5) 限制MQ的消费速率等
限流算法
1. 令牌桶
1) 假设限制是2r/s, 则按照500ms的固定速率往桶中添加令牌
2) 桶中最多可以放b个令牌、当桶满时、新添加的令牌会被拒绝或者丢弃
3) 当一个n字节的数据包到达、将从桶中删除n个令牌、接着数据包被发送到网络上
4) 如果桶中的令牌不足n个、则不会删除令牌、该数据包会被限流(丢弃 or 缓冲区等待)
允许一定程度的突发
2. 漏桶算法
1) 一个固定容量的漏桶、按照常理固定速率流出水滴
2) 如果桶是空的、则不需要流出水滴
3) 可以任意速率流入水滴到漏桶
4) 如果流入的水滴超出了桶的容量、水溢出了、则被丢弃、而漏桶容量是不变的
限制的是平滑流入速率
3. 计数器
有时候会使用计数器进行限流、主要用来限制并发总数、eg. 数据库连接池大小、线程池大小、秒杀并发数等都是使用计数器的用法
应用级限流
1. 限流总并发/连接/请求数、即总有一个TPS/QPS阈值
tomcat的一些参数:
1) acceptCount: 若tomcat的线程都忙于响应、新来的连接会进入排队队列、若超出排队大小、则拒绝连接
2) maxConnections:瞬时连接最大数、超出的会排队等待
3)maxThreads:tomcat能启动用来处理请求的最大线程数、若请求处理的量一直远远大于最大线程数、会引起响应变慢或者僵死
1. 限流总资源数
有的资源是稀缺资源(eg. 数据库连接、线程)、而且有可能多个系统都会去使用它、那么需要加以限制、可以使用池化技术来限制总资源数、如连接池、线程池
假设分配给每个应用的连接是100、那么本应用最多可以使用100个资源、超出则等待或者抛异常
2. 限制某个接口的总并发、总请求
若接口有可能突发访问量很高、可能造成崩溃、如抢购业务、则可以限制接口的总并发/请求量
3. 限流某个接口的时间窗请求数
eg. 限制某个接口每min、seconds、day等的调用量
比如、当某个基础服务、会被很多其它系统调用、当他们同时大量更新时、可能将基础服务打挂、这时需要对min/s/day的调用量进行限速、一般是min或者s
4. 平滑限流某个接口的请求数
之前的限流方式都不能很好地应对突发请求、即瞬时请求可能都被允许、从而导致一些问题、因此需要在一些场景中对突发请求进行整型、整形为平均速率请求(eg. 5r/s)则每隔200ms处理一个请求、平滑了速率
上边说到的 令牌桶法 和 漏桶 算法都可以拿来使用
分布式限流
分布式限流的关键是限流服务原子化、可以使用redis+lua 或 nginx+lua来实现
-- redis+lua实现:
local key = KEYS[1] --限流key(1/s)
local limit = tonumber(ARGV[1]) -- 限流大小
local current = tonumber(redis.call("INCRBY", key, "1")) --请求数+1
if current > limit then -- 若超出限流大小
return 0
elseif current == 1 then -- 只有第一次访问时需要设置2s的过期时间
redis.call("expire", key, "2")
end
return 1
-- nginx+lua 实现
降级预案
1. 在进行降级之前、要对系统进行梳理、看看系统是不是可以丢卒保帅、从而梳理出哪些必须誓死保护、哪些可以降级、eg. 可以参考日志级别设置预案
一般:eg. 有些服务偶尔因为网络抖动或者服务正在上线而超时、可以自动降级
警告:有些服务在一段时间内成功率有波动(eg. 在95% ~ 99%) 之间、可以自动降级或者人工降级、并发送警告
错误:eg. 可用率低于90%、或者数据库的连接池用完了、或者访问量突然猛增到系统能承受的最大值、此时可以根据情况自动降级或人工降级
严重错误:eg. 因为特殊原因数据出现错误、此时需要紧急人工降级
降级按照是否自动化可分为:自动化开关降级和人工开关降级
降级按照功能维度可分为读服务降级和写服务降级
降级按照处于的系统层次可分为:多级降级
降级的功能特点主要从服务器链路考虑、即根据用户访问的服务调用链路来梳理哪里需要降级
1. 页面降级:在大促或者某些特殊情况下、某些页面占用了一些稀缺资源、在紧急情况下、可对其降级
2. 页面片段降级:eg. 页面中某一部分数据缺失、需要对其降级
3. 服务功能降级:eg. 渲染页面时、需要调用一些不太重要的服务(相关分类、热销榜等)、这些数据在异常的情况下不能直接获取、降级即可
4. 读降级:eg. 多级缓存模式、若后端server有问题、可以降级为只读缓存、适用于读读一致性要求不高的情况
5. 写降级:
eg. 秒杀系统、只进行cache的更新、然后异步扣减库存、保证最终一致性即可、即将db降级为cache
6. 爬虫降级:在大促时、可以将爬虫来了导向到静态页或者返回空数据、从而保护后端稀缺资源
7. 风控降级:eg. 秒杀、抢购,完全可以识别机器人、用户画像或者根据用户级别进行降级处理、直接拒绝高风险用户