从kratos分析BBR限流源码实现
什么是自适应限流
自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
核心目标:
- 自动嗅探负载和 qps,减少人工配置
- 削顶,保证超载时系统不被拖垮,并能以高水位 qps 继续运行
限流规则

计算吞吐量:利特尔法则 L = λ * W
如上图所示,如果我们开一个小店,平均每分钟进店 2 个客人(λ),每位客人从等待到完成交易需要 4 分钟(W),那我们店里能承载的客人数量就是 2 * 4 = 8 个人
同理,我们可以将 λ 当做 QPS, W 呢是每个请求需要花费的时间,那我们的系统的吞吐就是 L = λ * W ,所以我们可以使用利特尔法则来计算系统的吞吐量。
指标介绍
| 指标名称 | 指标含义 |
|---|---|
| cpu | 最近 1s 的 CPU 使用率均值,使用滑动平均计算,采样周期是 250ms |
| inflight | 当前处理中正在处理的请求数量 |
| pass | 请求处理成功的量 |
| rt | 请求成功的响应耗时 |
滑动窗口
在自适应限流保护中,采集到的指标的时效性非常强,系统只需要采集最近一小段时间内的 qps、rt 即可,对于较老的数据,会自动丢弃。为了实现这个效果,kratos 使用了滑动窗口来保存采样数据。

如上图,展示了一个具有两个桶(bucket)的滑动窗口(rolling window)。整个滑动窗口用来保存最近 1s 的采样数据,每个小的桶用来保存 500ms 的采样数据。 当时间流动之后,过期的桶会自动被新桶的数据覆盖掉,在图中,在 1000-1500ms 时,bucket 1 的数据因为过期而被丢弃,之后 bucket 3 的数据填到了窗口的头部。
限流公式
判断是否丢弃当前请求的算法如下:
cpu > 800 AND (Now - PrevDrop) < 1s AND (MaxPass * MinRt * windows / 1000) < InFlight
MaxPass 表示最近 5s 内,单个采样窗口中最大的请求数。 MinRt 表示最近 5s 内,单个采样窗口中最小的响应时间。 windows 表示一秒内采样窗口的数量,默认配置中是 5s 50 个采样,那么 windows 的值为 10。
源码分析
代码地址:
BBR struct
type BBR struct {
cpu cpuGetter
passStat window.RollingCounter
rtStat window.RollingCounter
inFlight int64
bucketPerSecond int64
bucketSize time.Duration
// prevDropTime defines previous start drop since initTime
prevDropTime atomic.Value
maxPASSCache atomic.Value
minRtCache atomic.Value
opts *options
}
cpu- cpu的指标函数,CPU的使用率, 这里为了减小误差,把数字扩大化,乘以1000,比赛使用率60%,也就是0.6 cpu的值就为600
passStat- 请求数的采样数据,使用滑动窗口进行统计
rtStat- 响应时间的采样数据,同样使用滑动窗口进行统计
inFlight- 当前系统中的请求数,数据得来方法是:中间件原理在处理前+1,处理handle之后不管成功失败都减去1
bucketPerSecond- 一个 bucket 的时间
bucketSize- 桶的数量
prevDropTime- 上次触发限流时间
maxPASSCache- 单个采样窗口中最大的请求数的缓存数据
minRtCache- 单个采样窗口中最小的响应时间的缓存数据
Allow接口
// Allow checks all inbound traffic.
// Once overload is detected, it raises limit.ErrLimitExceed error.
func (l *BBR) Allow(ctx context.Context) (func(), error) {
if l.shouldDrop() { // shouldDrop 判断是否需要限流,如果true表示拒绝 之后重点讲
return nil, ErrLimitExceed
}
atomic.AddInt64(&l.inFlight, 1) // 之前说的,正在处理数+1
stime := time.Since(initTime) // 现在时间减去程序初始化时间 表示程序开始执行时刻
return func() { // allow返回函数 在中间件(拦截器)中handle执行完成后调用
rt := int64((time.Since(initTime) - stime) / time.Millisecond) // 执行完handle的时间减去stime 表示 程序执行的总时间 单位ms
l.rtStat.Add(rt) // 把处理时间放进采样数据window
atomic.AddInt64(&l.inFlight, -1) // 正在处理数-1 便是处理完成
l.passStat.Add(1) // 成功了,把通过数的采样数据window加1
}, nil
}
shouldDrop方法
func (l *BBR) shouldDrop() bool {
curTime := time.Since(initTime)
if l.cpu() < l.opts.CPUThreshold {
// current cpu payload below the threshold
prevDropTime, _ := l.prevDropTime.Load().(time.Duration)
if prevDropTime == 0 {
// haven't start drop,
// accept current request
return false
}
if curTime-prevDropTime <= time.Second {
// just start drop one second ago,
// check current inflight count
inFlight := atomic.LoadInt64(&l.inFlight)
return inFlight > 1 && inFlight > l.maxInFlight()
}
l.prevDropTime.Store(time.Duration(0))
return false
}
// current cpu payload exceeds the threshold
inFlight := atomic.LoadInt64(&l.inFlight)
drop := inFlight > 1 && inFlight > l.maxInFlight()
if drop {
prevDrop, _ := l.prevDropTime.Load().(time.Duration)
if prevDrop != 0 {
// already started drop, return directly
return drop
}
// store start drop time
l.prevDropTime.Store(curTime)
}
return drop
}
maxInFlight()方法代表过去的负载
int64(math.Floor(float64(l.maxPASS()*l.minRT()*l.bucketPerSecond)/1000.0) + 0.5)
参考算法:https://github.com/alibaba/Sentinel/wiki/系统自适应限流
- maxPass * bucketPerSecond / 1000 为每毫秒处理的请求数
- l.minRT() 为 单个采样窗口中最小的响应时间
- T ≈ QPS * Avg(RT)
+ 0.5为向上取整
流程图

压测报告
场景1,请求以每秒增加1个的速度不停上升,压测效果如下:

左测是没有限流的压测效果,右侧是带限流的压测效果。 可以看到,没有限流的场景里,系统在 700qps 时开始抖动,在 1k qps 时被拖垮,几乎没有新的请求能被放行,然而在使用限流之后,系统请求能够稳定在 600 qps 左右,rt 没有暴增,服务也没有被打垮,可见,限流有效的保护了服务。
参考文章:


从kratos分析BBR限流源码实现的更多相关文章
- BAT资深工程师 由浅入深分析 Tp5&Tp6底层源码 - 分享
BAT资深工程师由浅入深分析Tp5&Tp6底层源码 第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的 ...
- BAT资深工程师由浅入深分析Tp5&Tp6底层源码☆
第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的帮助. 第2章 [TP5灵魂]自动加载Loader 深度分析 ...
- nodejs的tream(流)解析与模拟文件读写流源码实现
什么是流? 可读流于可写流 双工流于转换流 背压机制与文件流模拟实现 一.什么是流? 关于流的概念早在1964年就有记录被提出了,简单的说"流"就是控制数据传输过程的程序,比如在那 ...
- [转] jQuery源码分析-如何做jQuery源码分析
jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...
- nova创建虚拟机源码分析系列之五 nova源码分发实现
前面讲了很多nova restful的功能,无非是为本篇博文分析做铺垫.本节说明nova创建虚拟机的请求发送到openstack之后,nova是如何处理该条URL的请求,分析到处理的类. nova对于 ...
- Spring的IOC分析(二)源码
承接上节继续,分析Ioc的工作原理,在典型的 IOC 场景中,容器创建了所有对象,并设置必要的属性将它们连接在一起(同时一个叫DI"依赖注入"或DL"依赖查找" ...
- 源码分析 ucosii/source 任务源码详细分析
分析源码: 得先学会读文档, 函数前边的 note :是了解该程序员的思想的途径.不得不重视 代码前边的 Notes,了解思想后,然后在分析代码时看他是如何具体实现的. 1. ucosii/sour ...
- 面试官:你分析过SpringMVC的源码吗?
1. MVC使用 在研究源码之前,先来回顾以下springmvc 是如何配置的,这将能使我们更容易理解源码. 1.1 web.xml <servlet> <servlet-name& ...
- Redis源码分析:serverCron - redis源码笔记
[redis源码分析]http://blog.csdn.net/column/details/redis-source.html Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...
随机推荐
- 有语言基础的人应该如何学习python?
正好最近在学python,感觉有语言基础的话更多在乎一些语法糖,毕竟其他东西在之前应该接触过了. 笔者C++是起始语言,也接触过java.js,介绍一点python的特点吧.帮助自己巩固所学,也希望能 ...
- [NOI 2021] 轻重边 题解
提供一种和不太一样的树剖解法(一下考场就会做了qwq),尽量详细讲解. 思路 设重边为黑色,轻边为白色. 首先,先将边的染色转化为点的染色(即将 \(u\) 节点连向父节点的边的颜色转化为 \(u\) ...
- docker exec 参数详解
Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] Run a command in a running container Options ...
- S7-200通过以太网模块,使用kepware与ifix建立通讯连接要点
在前阵子项目改造中,需要利用先前的S7-200 PLC与ifix进行通讯,故而,在做好上位机后,在现场实际测试了下.通过CP243-1以太网模块,顺利与KEPWARE建立连接,其中当然也有些要点要注意 ...
- cytoscape-d3-force api
{ animate:true,//是否在布局运行时显示布局:特殊的"结束"值使布局具有离散布局的动画效果 maxIterations:0,//布局退出前的最大迭代次数 maxSim ...
- java标识符,关键字,注释及生成Doc文档
# java语法基础 ## 标识符,关键字与注释 ### 标识符 1.类名,变量名,方法名都称为标识符. 2.命名规则:(1):所有的标识符都应该以字母(AZ,或者az)美元符($)或者下划线(_)开 ...
- 大数据学习(10)—— Hive进阶
前面提到了Hive的知识点非常零散,我不知道该怎么把这些知识点分类,跟SQL关系没那么大的就放在这一篇吧. Hive Serde 参考Hive Serde Serde是啥 Serde是序列化和反序列化 ...
- 记一次针对excel导出的优化
最近发现我们系统导出excel文件时由于是导出百万级数据导出,速度过慢并且内存占用多,故进行了下面的一次优化. 我们使用apache的poi进行excel文件操作 主要耗时: 1.从数据库得到需要导出 ...
- 什么是软件的CLI安装
Websoft9 在进行开源软件的集成与自动化安装研究过程中发现有些软件有CLI安装模式,例如Gitlab CLI版本.Ghost CLI.PHP CLI等,CLI安装是什么意思? CLI(Comma ...
- Android 开发学习进程0.33 横竖屏切换
安卓横竖屏大多数已经在manifest文件中将activity使用android:screenOrientation="portrait"属性写死,如简单的界面切换可直接更改为&q ...