惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Know Your Adversary
Know Your Adversary
WordPress大学
WordPress大学
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
阮一峰的网络日志
阮一峰的网络日志
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Fortinet All Blogs
博客园 - 聂微东
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
J
Java Code Geeks
Vercel News
Vercel News
N
Netflix TechBlog - Medium
大猫的无限游戏
大猫的无限游戏
MyScale Blog
MyScale Blog
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
Hugging Face - Blog
Hugging Face - Blog
M
MIT News - Artificial intelligence
T
The Blog of Author Tim Ferriss
小众软件
小众软件
The GitHub Blog
The GitHub Blog
量子位
V
Visual Studio Blog
博客园_首页
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
C
CERT Recently Published Vulnerability Notes
The Cloudflare Blog
Spread Privacy
Spread Privacy
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
U
Unit 42
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
NISL@THU
NISL@THU
C
Cisco Blogs
T
Threatpost
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Secure Thoughts
The Hacker News
The Hacker News
Attack and Defense Labs
Attack and Defense Labs
IT之家
IT之家
Help Net Security
Help Net Security
G
GRAHAM CLULEY
Jina AI
Jina AI

Random Thoughts

LLM 训练与推理的基本理解 昨日的世界不再重来 Magic Brush - 画出你自己的产品宇宙 写在 AI Coding 奇点之后 尼泊尔布恩山小环线纪行 2025 投资组合年报 三十而笠 我的 8 年职业生涯回顾 我的颈椎病康复之旅 - 关于选择的故事 熊野古道中边路纪行 新加坡旅居三年再回首 高加索三国 - 阿塞拜疆行记 2024 投资组合年报 重新思考 Go:了解程序在线上是如何运行的 欧游散记 —— 环勃朗峰之旅 欧游散记 —— 特摩索斯古城 重新思考 Go:Channel 不是「消息队列」 重新思考 Go:Slice 只是「操作视图」 那一天,我决定踏出一步 True Story 少数价值 A Snapshot of Myself - 2021 RPC 漫谈: 连接问题 RPC 漫谈:序列化问题 科学,技术与工程 Pond: Golang 通用对象池 Golang for-range 内部实现 Golang Interface 内部实现 真理的有限性 Golang rand 库锁竞争优化 Meaningless or Meaningful 分布式文件系统的演化 从动物森友会聊主机游戏联机机制 创业公司的文化 伊朗见闻录 NodeJS 内存泄漏检测与定位 设计实现高性能本地内存缓存 Travel Map Linux I/O 栈浅析 SSD 背后的奥秘 什么是真正的编程能力 沉默与反沉默的理由 命令行里的设计艺术 自由意志下的选择 学习思维方式而非学习观点 从程序到人 —— 情头配对助手的前世今生 即刻多端实时通信实践 朝鲜 —— 小国寡民的主体思想实践 使用 Surge 提升多网络环境下的流畅开发体验 一份其实好吃的 LaTeX 入门餐 Kafka 的设计与实践思考 欧游散记 —— 民主专制下的德国 欧游散记 —— 维也纳 欧游散记 —— 西班牙 欧游散记 —— 德国国会大厦 社会矛盾讨论统一框架的一种可能性 欧游散记 —— 伪君子布拉格 Lemon : Koa 风格的 Python 异步 Web 框架 Golang : Make Programming Happy Again Podcast 闲言碎语 Kubernetes 中使用 API Gateway 替代 Ingress ElasticSearch 最佳实践 blog.joway.io Presentations
RPC 漫谈: 限流问题
Joway · 2021-04-23 · via Random Thoughts

微服务之间的 RPC 调用往往会使用到限流功能,但是很多时候我们都是用很简单的限流策略,亦或是工程师拍脑袋定一个限流值。

这篇文章主要讨论在 RPC 限流中,当前存在的问题和可能的解决思路。

为什么需要限流

避免连锁崩溃

一个服务即便进行过压测,但当真实运行到线上时,其收到的请求流量以及能够负载的流量是不固定的,如果服务自身没有一个自我保护机制,当流量超过预计的负载后,会将这部分负载传递给该服务的下游,造成连锁反应甚至雪崩。

提供可靠的响应时间

服务调用方一般都设有超时时间,如果一个服务由于拥塞,导致响应时间都处于超时状态,那么即便服务最终正确提供了响应,对于 Client 来说也完全没有意义。

一个服务对于调用方提供的承诺既包含了响应的结果,也包含了响应的时间。限流能够让服务自身通过主动丢弃负载能力外的流量,以达到在额定负载能力下,依然能够维持有效的响应效率。

传统方案

漏斗

优点:

  • 能够强制限制出口流量速率

缺点:

  • 无法适应突发性流量

令牌桶

优点:

  • 在统计上维持一个特定的平均速度
  • 在局部允许短暂突发性流量通过

存在的问题

在两类传统方案中,都需要去指定一个固定值用以标明服务所能够接受的负载,但在现代的微服务架构中,一个服务的负载能力往往是会不断变化的,有以下几个常见的原因:

  • 随着新增代码性能变化而变化
  • 随着服务依赖的下游性能变化而变化
  • 随着服务部署的机器(CPU/磁盘)性能变化而变化
  • 随着服务部署的节点数变化而变化
  • 随着业务需求变化而变化
  • 随着一天时间段变化而变化

通过人工声明一个服务允许的负载值,即便这个值是维护在配置中心可以动态变化,但依然是不可持续维护的,况且该值具体设置多少也极度依赖于人的个人经验和判断。甚至人自身的小心思也会被带入到这个值的选择中,例如 Server 会保守估计自己的能力,Client 会过多声明自己的需求,长期以往会导致最终的人为设定值脱离了实际情况。

什么是服务负载

当我们向一个服务发起请求时,我们关心的无外乎两点:

  • 服务能够支撑的同时并发请求数
  • 服务的响应时间

并发请求数

对于 Server 而言,有几个指标常常容易搞混:

  • 当前连接数
  • 当前接受的请求数
  • 当前正在并发处理的请求数
  • QPS

连接数和请求数是 1:N 的关系。在现代 Server 的实现中,连接本身消耗的服务器资源已经非常少了(例如 Java Netty 实现,Go Net 实现等),而且一般对内网的服务而言,多路复用时,请求数变多也并不一定会导致连接数变多。

有些 Server 出于流量整形角度的考虑,并不一定会在收到请求以后,立马交给 Server 响应函数处理,而是先加入到一个队列中,等 Server 有闲置 Worker 后再去执行。所以这里就存在两类请求:接受的请求与正在处理的请求。

而 QPS 是一个统计指标,仅仅只表示每秒通过了多少请求。

在当下的时间点,对 Server 有负载起到决定性影响的,一般都是当前的并发请求数。

服务响应时间

当一个在线服务去响应一个请求时,其自身所做的工作抽象来看,无外乎以下两类:

  • 计算:时间取决于 CPU 频率,固定值(不考虑超频)
  • 等待:时间取决于当前并行请求数,不固定
    • 排队等待 Server 有闲置线程/协程来响应请求
    • 等待其他线程释放竞争资源(可能是锁,也可能占用的 CPU)
    • 等待 IO(Storage, Network)返回 (不考虑下游抖动,该时间一般也为固定值)

从上面但分析我们可以看出,一个服务最终的响应时间:RT = 工作时间 + 等待时间

负载能力估算

我们可以把一个微服务抽象为一个输入输出的水管,如下图所示:

中间的这根水管的“负载能力”其实就是这个水管的体积(管径 * 管长),这是一个非常好量化的指标。

我们的服务在线上所遇到的情况其实和这根水管是类似的,现在需要做的是找到一个类似计算水管体积的量化方法,来估算服务的负载能力。

现在我们在某一秒时间窗口内,对一个服务实例进行观测,很容易可以得到以下两个值:

  • QPS:这一秒内的请求数,单位为 req/s 。
  • AvgRT: 这一秒内的平均请求响应时间,单位 ms。

根据 Little’s law

在一个稳定的系统(L)中,长期的平均顾客人数,等于长期的有效抵达率(λ),乘以顾客在这个系统中平均的等待时间(W)。

该法则用于计算一个稳定且资源有限系统的吞吐量。

在我们的系统里,利用该法则,可以得到 Throughput = QPS * (AvgRT / 1000)。其中 AvgRt / 1000 将毫秒单位换算成秒。

从另一个角度去理解这个公式也可以认为,如果我们能够保证 Server 当前正在处理的请求量(inflight) 不超过该值,理论上每个请求的平均响应时间也应该维持在 AvgRt 左右(进水速度 ~= 出水速度)。这个最终算出来的量,就是我们想要得到的对于服务当前负载情况的估算值。而只有当服务开始出现负载压力以后,这个当前负载情况才可以被认为是负载能力

Inflight,RT 与 Throughput 的关系

对于大部分在线服务来说,Inflight 和 RT 以及 Throughput 的关系有以下两个阶段:

  • 在没有发生资源限制(CPU/磁盘/网络/内存)的情况下,随着 inflight 变大,RT 一般不会发生明显变化,Throughput 开始变大。
  • 当 inflight 继续变大,出现了资源竞争时,RT 会随着 inflight 增大而增大,但 Throughput 并不会显著变化,因为有限的资源只能干有限的事情。

根据上面的这个现象,我们可以画出三个坐标图:

前两个图反应的是 Server 的实际现象,而第三个图,是将前两个图的相同的横坐标 inflight 消掉,单独看 RT 与 Throughput 的关系。第三个图表现了我们限流工作的本质:用 RT损失去换取吞吐量的提升

更加符合实际情况的图是:

这里的斜率越低,代表这部分RT损失越划算。我们的限流策略,就是去找到这个最佳限流点。

工程实践

上述分析只是在一个理论模型上进行的一个推导,在实际工程应用中,需要考虑到以下现实情况。

何时启动限流

在一开始不存在资源竞争的阶段,我们没有任何必要去进行流控。所以需要一个启发指标来标志服务开始进入到来竞争的忙碌状态以触发流控逻辑,一般我们可以选择以下一些常见指标:

  • OS Load1
  • CPU Usage
  • Avg RT 值
  • Thread Number
  • QPS

RT 的选择

前面已经推演得到了一个公式:RT = 工作时间 + 等待时间。而这里的 等待时间 还等于 排队时间 + 竞争时间

但是 RT 只是一个单请求指标,要计算 Throughput 需要的是一个统计意义上的 RT 值,这时候选择是 AvgRt,还是 MinRT,还是 P95RT 就是一个细节但极为重要的事情了。

如果我们是一个性能极为稳定的系统,类似交换机,路由器这类,那么包与包之间不会彼此互相影响,所以这类系统的 RT = 工作时间 + 排队时间。而我们希望尽可能减少排队时间(因为没必要,大家都去排队会造成更加拥挤),所以这类情况 RT 可以选择使用 MinRt,因为 MinRT 最接近固定时间。这也是 Google BBR 算法所使用的值。

但是业务服务的不同之处在于,请求与请求之间不仅会争抢 CPU,存储,也可能出现类似锁竞争的问题。这部分问题导致竞争等待时间非常不确定,抖动频繁且不可预计。如果依然使用 MinRT,会导致低估了服务的并发能力。

一个例子可以更加直观感受这个过程:某大学每年招收1000研究生,2年学制,但考试太难,导致平均3年才能毕业。这个时候去计算该大学同时能够容纳多少研究生,如果取2年,显然不合适。

但是不是意味着我们就可以直接用 AvgRT 呢?其实也不是,限流很难做到刚好卡在一个精确的点上,就算真的卡精确了,也会导致容错性不高。所以限流会倾向于略微低估系统的负载能力。Bilibili 在他们公开的微服务框架 Kratos 中,使用的是对每一个采样窗口中的 AvgRT 中取最小的那个 AvgRT。而阿里的 Sentinel 使用的是真正意义的最小 RT

当根据实际业务特性选定好了 RT 指标后,再乘以前面统计到的 QPS 则可得到 Throughput 值,此时只要判断当 Inflight > Throughput 直接丢弃便可实现一个自适应且有科学度量标准的限流策略。