限流器在项目中的应用
mos项目(盒子/投影仪)应用到限流器的接口
- 1.广告列表接口[
/v2/mos/adlist
] - 2.获取应用推荐列表[
/v2/mos/app_recommend
] - 3.获取屏保的列表v2.0[
/v2/mos/screens
]
都是使用redis实现的原子计数法限流(集群计数限流), 分布式流控不可用时(例如缓存挂掉), 切到单机流控
目的是这三个接口都是数据量大的接口, 过渡频繁的请求会严重占用带宽, 造成不必要的浪费。 通过流量控制来限制请求, 会节省资源, 和保护后台系统不被击垮
限流可以应对
- 1.热点业务带来的突发请求
- 2.调用方 bug 导致的突发请求
- 3.恶意攻击请求
限流算法
- 1.原子计数法
- 2.滑动窗口算法
- 3.令牌桶算法
- 4.木桶算法
1.原子计数法
出现”突刺现象”, 比例1秒限流1000, 前10ms有10个, 第990ms有990个请求 不能防止临界点的突发请求. 可能极端情况下被人利用临界点前后一刻的合法流量请求, 造成瞬间的时间段的双倍请求, 击垮后台服务 究其原因是原子计数法只是一个时间窗口, 不能很好的平滑请求. 原子计数法是特殊的滑动窗口算法, 只是滑动窗口只是单独一个(时间段)
2.滑动窗口算法
把大的时间段划分为n个小段的时间窗口, 根据时间窗口的各个小段的累加来限流, 可以平滑的限流
3.令牌桶算法
重点是放操作的限流, 通过队列+生产令牌线程组成, 生产线程根据设定的限流大小的速度来生产令牌, 并投入到队列中, 消费端仅仅是关注队列是否有令牌, 有就允许通过, 没有就限制通过. 生产令牌和消费令牌解耦. 可以通过设定限流大小来调整生产令牌的速度, 所以可以灵活的调整生产令牌的速度, 应对突然流量
4.木桶算法
重点是取操作的限流, 通过固定大小队列+消费请求线程. 请求进入队列, 若队列已满就丢弃请求或者暂存起来, 若队列未满就push进队列; 有个消费线程以恒定的速度从队列中取请求, 若没有就睡眠 可以防止出现”突刺现象”, 防止突发流量, 能够使请求以恒定的速度被消费; 但是无法应对短时间的突发流量
5.集群限流
限制某个资源被每个用户或者商户的访问次数,5s只能访问2次,或者一天只能调用1000次,这种需求,单机限流是无法实现的,这时就需要通过集群限流进行实现
思路: 每次有相关操作的时候,就向redis服务器发送一个incr命令,比如需要限制某个用户访问/index接口的次数,只需要拼接用户id和接口名生成redis的key,每次该用户访问此接口时,只需要对这个key执行incr命令,在这个key带上过期时间,就可以实现指定时间的访问频率
拓展知识
Hystrix熔断, 降级
先说一种比较low的做法,但是也挺实际的一般做法有:
服务码+配置中心
调用任何服务时都传入必要参数服务码和开关,默认关闭,当触发某种条件时可打开开关,或者通过配置中心手动推送开关新的值,从而保护系统不被单个服务压垮
其中开源项目中比较著名的是Hystrix熔断, 降级
Hystrix引入以下手段来保护系统
- 资源隔离(线程池和信号量两种手段的隔离)(Hystrix-go,线程隔离,每个执行是一个goroutine)
- 限流
- 降级
- 熔断(断路器)
Hystrix如何设计实现这些手段呢?
- 使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行
- 每一个依赖都有自己对应的线程池或者信号量,线程池耗尽时,拒绝请求
- 维护请求的各种状态(成功,失败,超时的次数)
- 当错误率到达一定阈值时,进行熔断,过一定的时间后又恢复
- 提供降级,失败,成功,熔断后的回调逻辑
- 实时的监控指标和配置信息的修改
Hystrix的步骤
- 1:每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中。
- 2:执行execute()/queue做同步或异步调用。
- 3:判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤。
- 4:判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤。
- 5:调用HystrixCommand的run方法。运行依赖逻辑
- 5a:依赖逻辑调用超时,进入步骤8。
- 6:判断逻辑是否调用成功
- 6a:返回成功调用结果
- 6b:调用出错,进入步骤8。
- 7:计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态。
- 8:getFallback()降级逻辑。
- 以下四种情况将触发getFallback调用:
- (1):run()方法抛出非HystrixBadRequestException异常
- (2):run()方法调用超时
- (3):熔断器开启拦截调用
- (4):线程池/队列/信号量是否跑满
- 以下四种情况将触发getFallback调用:
- 8a:没有实现getFallback的Command将直接抛出异常
- 8b:fallback降级逻辑调用成功直接返回
- 8c:降级逻辑调用失败抛出异常
- 9:返回执行成功结果