转自:https://www.cnblogs.com/brxHqs/p/9767942.html ,https://tkstorm.com/posts-list/books/cloud-native-alone/high-availability/,https://zhuanlan.zhihu.com/p/228412634
1.突发流量
高并发情况下带来的突发流量:
常见的优化方案有两种:
- 1)上游队列缓冲(push阻塞),限速发送;
- 2)下游队列缓冲(定时或者批量拉取pull,可以起到削平流量),限速执行。
如果上游发送流量过大,MQ提供拉模式确实可以起到下游自我保护的作用,会不会导致消息在MQ中堆积?
答:下游MQ-client拉取消息,消息接收方能够批量获取消息,需要下游消息接收方进行优化(提供批处理,比如批量写),否则整体吞吐量低,也会造成mq堆积。
2.高并发系统保护策略
2.1 缓存
累积一些数据,批量写入,先写redis再写数据库;内存里做缓存。
2.2 服务降级
当服务器压力剧增的情况下,降级代表系统功能部分不可用,比如降级可能出现的情况是手机号可以正常注册,但邮箱不能注册。
熔断代表的是完全不可用,注册功能完全不可用。不管降级还是熔断,在设计时都要考虑:降级熔断算法,告警,恢复机制。
产品功能梳理核心流程,找到核心服务,划分等级,确认服务关键程度。
2.3 限流
对并发访问/请求进行限速,一个时间窗口内一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。
一般来说系统的吞吐量是可以被测算的,一旦达到阈值,就需要限制流量。比如:延迟处理,拒绝处理,部分拒绝处理等等。
-
Nginx前端限流:按照一定的规则如IP、账号、调用逻辑等在Nginx层面做限流;
-
业务应用系统限流:客户端限流(验证码;获取动态请求路径pathvariable,到达接口地址隐藏的效果);服务端限流(redis限速器,延迟队列);
-
数据库限流:数据库链接池化,Mysql(如max_connections)、Redis(如tcp-backlog)都会有类似的限制连接数的配置。
3.限流算法
https://github.com/bluesea0/RateLimiting/tree/main
3.1 简单限流器(计数器固定窗口)
对一段固定时间窗口内的请求进行计数,如果请求数超过了阈值,则舍弃该请求;如果没有达到设定的阈值,则接受该请求,且计数加1。当时间窗口结束时,重置计数器为0。
优点:实现简单,容易理解。
缺点:不够平滑,有突刺,时间窗口切换时。限流直接不可用。
3.2 滑动窗口限流
将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。
优点:避免两倍阈值问题。
3.3 漏斗算法
漏桶算法:适用场景: 秒杀场景,削峰填谷
- 水流入(请求生产): 漏入桶中,桶满则溢出
- 漏桶(队列): FIFO队列
- 水流出(请求消费): 以一定速率从桶内取出请求消费
3.4 令牌桶算法
- 创建一个可放指定数量(M)令牌的桶(队列);
- 每间隔一定时间片,放入一个令牌到桶中(定时令牌生成器),桶满则溢出;
- 每当R个请求到达时,从桶内取出min(M,R)个令牌,若桶内令牌不够,则将请求缓存或者丢弃(对比网卡的环形队列作用)。
这里令牌桶允许突发流量,也是在令牌桶大小限制下最大的吧,超过令牌桶大小,还是会丢弃。
https://www.jianshu.com/p/c6b20845561a,这个讲解令牌桶浅显易懂。
4.redis分布式限流
转自: 5种限流算法,7种限流方式,挡住突发流量?-CSDN博客
4.1 固定窗口
使用 incr
命令实现,incr
命令通常用来自增计数;如果我们使用时间戳信息作为 key,自然就可以统计每秒的请求量了,以此达到限流目的。
-
对于不存在的 key,第一次新增时,value 始终为 1。
-
INCR 和 EXPIRE 命令操作应该在一个原子操作中提交,以保证每个 key 都正确设置了过期时间,不然会有 key 值无法自动删除而导致的内存溢出。通过lua脚本
local count = redis.call("incr",KEYS[1])
if count == 1 then
redis.call('expire',KEYS[1],ARGV[2])
end
if count > tonumber(ARGV[1]) then
return 0
end
return 1
同样会有窗口突变的问题。
4.2 zset滑动窗口限流
-
ZSET 集合中的 member 可以根据score自动排序,时间戳作为score。
-
ZSET 集合中的 member不能有重复值。
-
ZSET 集合可以方便的使用 ZCARD 命令获取元素个数。
-
ZSET 集合可以方便的使用 ZREMRANGEBYSCORE 命令移除指定范围的 key 值。
--KEYS[1]: 限流 key
--ARGV[1]: 时间戳 - 时间窗口
--ARGV[2]: 当前时间戳(作为score)
--ARGV[3]: 阈值
--ARGV[4]: score 对应的唯一value
-- 1. 移除时间窗口之前的数据
redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1])
-- 2. 统计当前元素数量
local res = redis.call('zcard', KEYS[1])
-- 3. 是否超过阈值
if (res == nil) or (res < tonumber(ARGV[3])) then
redis.call('zadd', KEYS[1], ARGV[2], ARGV[4])
return 1
else
return 0
end