写点什么

架构师成长之路之限流

  • 2019-11-06
  • 本文字数:4394 字

    阅读完需:约 14 分钟

架构师成长之路之限流

我们为什么需要限流

在上一篇《架构师成长之路之服务治理漫谈》里面,我们已经谈到了高可用治理的部分。为了“反脆弱”,在微服务复杂拓扑的情况下,限流是保障服务弹性和拓扑健壮的重中之重。


想一想,如果业务推出了一个秒杀活动,而你没有任何的限流措施;当你搭建了一个账号平台,而完全没有对十几个业务方设定流量配额……这些很有可能在特定场合下给你的产品带来大量的业务损失和口碑影响。


我们通常重点关注产品业务层面正向和逆向功能的完成,而对于逆向技术保障,这一点则是企业发展过程中很容易忽视的,所以一旦业务快速增长,这将给你的产品带来很大的隐患。


当然,也不是所有的系统都需要限流,这取决于架构师对于当前业务发展的预判。

我们常见的限流手段

我们来列举业内比较常见的一些限流手段。

2.1 信号量计数

信号量竞争是用来控制并发的一个常见手段。比如 C 和 Java 中都有 Semaphore 的实现可以让你方便地上手。鼎鼎大名的弹性框架 Hystrix 也默认选择了信号量来作为隔离和控制并发的办法。它的优点即在于简单可靠,但是只能在单机环境中使用。

2.2 线程池隔离

隔离舱技术中也大量使用了线程池隔离的方式来实现,通过限制使用的线程数来对流量进行限制,一般会用阻塞队列配合线程池来实现。如果线程池和队列都被打满,可以设计对应拒绝策略。需要谨慎调整其参数和线程池隔离的个数,以避免线程过多导致上下文切换带来的高昂成本。也是基于这个考虑,Hystrix 默认采用了信号量计数的方式来控制并发。 同样,其也只能在单机环境中使用。

2.3 固定窗口计数

我们可以以第一次请求访问的时候开始进行计数,而不严格按照自然时间来计数。比如可以利用 Redis 的 INCR 和 EXPIRE 组合进行计数,如下伪代码所示:


count = redis.incrby(key)if count == 1    redis.expire(key,3600)if count >= threshold    println("exceed...")
复制代码


这种实现方式简单粗暴,可以解决绝大部分分布式限流的问题。但是其存在的问题是:


1.该计数方式并不是准确计数,由于时间窗口一旦过期,则之前积累的数据就失效,这样可能导致比如本来希望限制“一分钟内访问不能超过 100 次”,但实际上做不到精准的限制,会存在误判放过本应拒绝的流量。


2.每次请求都将访问一次 Redis,可能存在大流量并发时候将缓存打崩最终拖垮业务应用的问题。这个在高并发场景中是非常严重的问题。当然,你可以选择按照业务进行适当的缓存集群切割来缓解这种问题,但是这仍然是治标不治本。当然,如果你选择单机限流的实现方式,则无需使用 Redis,进一步,单机限流情况下该问题不存在。

2.4 自然窗口计数

有的场景会需要以自然窗口为维度进行限制,实现方式即进行分桶计数。每个 slot 一般以时间戳为 key salt,以单 slot 时间长度内的计数值为 Value,我们可以根据实际的需求对单 slot 的时间长度进行限制,比如如果你需要限制一天发送短信数不超限,则以 1 个自然天为 1 个 slot,如果希望限制 QPS,则以 1s 为 1 个 slot。然后起定时任务获取 slot,进一步取出实际的分桶计算结果,进行判断是否达到阈值,如果超过阈值则执行对应的限制操作。


该策略如果应用在分布式限流环境下,则会碰到若干个问题。这个后面章节中会提到。另外,该策略本质上其实是也是一种特殊的固定窗口计数策略,那么固定窗口所存在的弊端,自然窗口计数也会存在。那么我们不禁会问,如果希望规避固定窗口的一大问题——“无法准确计数”的话,要怎么做呢?这时,“滑动窗口计数”方式应运而生。

2.5 滑动窗口计数

滑动窗口的出现,可以很好地解决精准计数的问题。随着时间窗口不断地滑动,动态地进行计数判断。可以规避自然窗口和固定窗口计数所存在的计数不准确的问题。以下有两种常见的滑动窗口计数的实现类别。

2.5.1 基于共享分布式内存

可以采用 Redis ZSet,存储结构如下图所示。Key 为功能 ID,Value 为 UUID,Score 也记为同一时间戳。整个过程简单概括为“添加记录、设置失效时间、计数、删除过期记录”四部分。使用 ZADD、EXPIRE、ZCOUNT 和 zremrangeScore 来实现,并同时注意开启 Pipeline 来尽可能提升性能。



伪代码如下:


// 开启pipepipeline = redis.pielined()// 增加一条请求pipeline.zadd(key, getUUID(), now)// 重新设置失效时间pipeline.expire(key, 3600)// 统计在滑动窗口内,有多少次的请求count = pipeline.zcount(key, expireTimeStamp, now)// 删除过期记录pipeline.zremrangeByScore(key, 0, expireTimeStamp - 1)pipeline.sync()if count >= threshold    println("exceed")
复制代码


但是该方法,有一个比较突出的问题。就是这是一个重操作,将引发高 QPS 下 Redis 的性能瓶颈,也将消耗较多的资源和时间。一般我们可以付出秒级的时延,对其做多阶段异步化的处理。比如将计数、删除过期数据和新增记录分为三部分去进行异步处理。此处就不进一步展开了。

2.5.2 基于本地内存

第一个方案中,分布式滑动窗口的难度在于,不得不进行内存共享来达到窗口计数准确的目的。如果考虑分发时进行 Key Based Routing 是不是能解决这个问题?在付出非幂等、复杂度抬升等一定代价的情况下,引入基于本地内存的分布式限流实现方式。


实现方式有如下两种:


1.如果可以接受准实时计算的话,可以采用 Storm,使用 filedsGroup,指定 Key 到对应的 Bolt 去处理;


2.如果需要实时计算的话,那么就采用 RPC 框架的 LB 策略为指定 Key 的一致性 Hash。然后路由到对应的服务实例去处理。


以上两个实现方式,当到达 Bolt 或者服务实例后,即可基于本地内存进行处理,处理方式也有三种。


1.采用 Esper,用 DSL 语句即可简单实现滑动窗口。


2.Storm 1.0 之后提供了滑动窗口的实现。


3.如果希望自实现滑动窗口(不推荐),实现思路也比较简单即:循环队列+自然窗口滑动计数。


循环队列来解决无限后延的时间里,计数空间重复利用的问题。而此处,我们看到了一个熟悉的名词——“自然窗口计数”。没错,底层仍然采用自然窗口计数,但是区别在于,我们会对自然窗口切分更细的粒度,每次批量超前获取多个分桶,来进行加和计算。这样就可以实现滑动窗口的效果,你可以认为,当分桶被细化到 10s、5s 甚至越来越细的时候,计数将趋近于更加准确。

2.6 令牌桶和漏桶算法计数

令牌桶的示意图如下:



而漏桶的示意图如下:



这个在业内也是鼎鼎大名。基本谈起限流算法,这两个算法必然会被提起,令牌桶可以有流量应对突发流量,漏桶则强调对流量的整型。二者的模型是相反的。令牌桶和漏桶算法在单机限流中较为常见,而在分布式限流中罕见踪迹。


对于令牌桶来说,你可以采用定时任务去做投递令牌的动作,也可以采用算法的方式去进行简单的计算。Guava Ratelimiter 采用的是后者。


令牌桶的优势之一,在于可以有部分余量用以应对突发流量。但是在实际生产环境中,这不一定是安全的。如果我们的服务没有做好应对更高突发流量的准备,那么很有可能会引发服务雪崩。所以考虑到这一点,Guava 采用了令牌桶 + 漏桶结合的策略来进行限流。对于默认业务,采用标准令牌桶方式进行“可超支”限速,而对于无法突然应对高峰流量的业务,会采用缓慢提升投放令牌速率(即逐步缩短业务请求等待时间)的方式来进行热启动控制,具体见 Guava Ratelimiter 源码注释描述,此处不赘述,其效果如下图所示:


微服务限流几个考虑的点

以上的限流手段,有的能应用在单机环境,有的能应用在分布式环境。而在高并发的分布式环境中,我们需要考虑清楚如下几个问题如何解决。

3.1 机器时钟不一致或者时钟回退问题

一旦出现这种问题,则可能导致收集的数据相互污染而导致判断出错。所以一方面,在运维层面需要确保机器时钟能够按期同步。另一方面,需要有准实时检测的手段,及时发现时钟偏差太大或者时钟回退的机器,基于一定策略筛选出不合格的数据来源,将其刨除出计算范围并发出警告。

3.2 在 SDK 还是 Server 端做限流逻辑

你需要考虑你的限流策略迭代的频繁程度,推动业务方改造的成本,语言/技术栈异构情况,是否有需要进行立多系统联合限流的场景,以此来进行决策。如果采用 SDK 方式,你需要做好碰到这几个棘手问题的心理准备。


而如果采用 Server 方式,你则需要更多考虑高并发下数据堆积,机器资源消耗,以及对业务方性能的影响问题。一般业内采用的是富 SDK 的方式来做,但是对于上述的 SDK 会面临的几个问题没有很好的解决方案。而 ServiceMesh 领军人物 Istio 采用了 Mixer 来实现 Server 端限流的方式,但是碰到了很严重的性能问题。所以这是一个很困难的选择。


回顾下《架构师成长之路之服务治理漫谈》一篇中所讲到的服务治理发展路径,是不是有点惊人的相似?是不是也许限流的未来,不在 SDK 也不在 Server,而在于 ServiceMesh?我不确定,但我觉得这是一个很好的探索方向。

3.3 限流是不是会让你的系统变得不可控

这是一个很有意思的问题,限流本身是为了“反脆弱”而存在的,但是如果你的分布式复杂拓扑中遍布限流功能,那么以后你每个服务的扩容,新的功能上线,拓扑结构的变更,都有可能会导致局部服务流量的骤增,进一步引发限流导致业务有损问题。这就是“反脆弱”的本身也有可能会导致“脆弱”的出现。所以,当你进行大规模限流能力扩张覆盖的时候,需要谨慎审视你的限流能力和成熟度是否能够支撑起如此大规模的应用。

3.4 拓扑的关联性能给限流带来什么

我们置身于复杂服务拓扑和各种调用链路中,这一方面确实给限流带来了很大的麻烦,但另一方面,我们是不是可以思考一下,这些复杂度,本身是不是可以带给我们什么样的利好?比如:底层服务扛不住,那么是不是可以在更上层的调用方入口进行限流?如此是不是可以给予用户更友好提示的同时,也可避免链路上服务各自限流后带来的系统级联处理压力?微服务的本质是自治没错,但是我们是不是可以更好地对各个服务的限流自治能力进行编排,以达到效率、体验、资源利用的优化?


相信大家都会有自己的答案。这件事情本身的难度是在于决策的准确性,但如果能很好地进行落地实现,则意味着我们的限流从自动化已经逐步转向了智能化。这也将是更高一层次的挑战和机遇。

3.5 准确性和实时性的权衡

在高并发限流场景下,准确性和实时性理论上不可兼得。在特定的场景中,你需要作出你的选择,比如前文介绍的基于 Redis ZSet 实现的滑动窗口实时计算方式可以满足实时性和准确性,但其会带来很明显的性能问题。所以我们需要作出我们的权衡,比如牺牲准确性将滑动窗口退化为固定窗口来保障性能;或者牺牲实时性,对滑动窗口多阶段去做异步化,分析和决策两阶段分离,来保障性能。这取决于你的判断。

总结

限流是高可用治理中核心的一环,实现方式也五花八门,每种方式也都有各自的问题,本文只是做了一个简单的回顾。希望随着 ServiceMesh、AIOps 等理论的兴起,我们对于限流是什么,能做什么,怎么实现,能够释放出更大的空间去想象。


本文转载自公众号云加社区(ID:QcloudCommunity)。


原文链接:


https://mp.weixin.qq.com/s/sON7E7w1yPj0Mxa0whjA2A


2019-11-06 17:421260

评论

发布
暂无评论
发现更多内容

Sovit3D引擎快速构建智慧变电站三维可视化系统

2D3D前端可视化开发

物联网 智慧变电站 智能变电站 数字孪生变电站 变电站可视化

直播 | 数据智能大咖在线研讨,带你找到适合自己的企业级方法论!

StarRocks

数据库 物流

cdr2023断网离线激活下载教程

茶色酒

cdr2023

面了40+岁的大叔,没有录用,并不是因为年龄

产品运营心经

工作经历 面试‘ 职场发展 大龄求职

喜报 | Bonree ONE 2.0荣获信通院“2022IT新治理年度明星产品”

博睿数据

可观测性 博睿数据 荣誉 ONE平台

上海靠谱的前端培训机构有没有推荐

小谷哥

AngularJS进阶(三十九)基于项目实战解析ng启动加载过程

No Silver Bullet

项目实战 AngularJS 12月月更 启动加载

AngularJS进阶(四十一)AngularJS中使用Chart.js制折线图与饼图实例

No Silver Bullet

AngularJS 12月月更 Chart.js 折线图与饼图

【计算讲谈社】第十五讲|云端即时渲染:下一代互联网的算力基座?

大咖说

数字人 云游戏

什么样的web前端培训靠谱?

小谷哥

系统的混乱并非业务本身之复杂,我们并不擅长处理『简单』

阿里技术

软件工程 复杂度

AlibabaP8,耗时182天肝出来1015页分布式全栈手册

程序知音

Java 分布式 后端 java架构

guitar pro2023下载官方版app

茶色酒

Guitar Pro guitar pro2023

PreSTU:一个专门为场景文本理解而设计的简单预训练模型

华为云开发者联盟

人工智能 华为云 OCR 12 月 PK 榜

AngularJS进阶(四十)创建模块、服务

No Silver Bullet

服务 模块 AngularJS 12月月更

银斯微与大敦科技签署MOU,推动UI开发工具在中国的落地应用

科技热闻

介绍四大并发集合类并结合单例模式下的队列来说明线程安全和非安全的场景及补充性能调优问题。

C++后台开发

多线程 并发 后端开发 异步 linux开发

如何合并Excel文档

Geek_249eec

C# Excel VB.NET

震惊,WSL2居然可以挂载USB

吴脑的键客

WSL2 usb

终于被我发现了这个推特视频下载的方法!超级简单!支持苹果安卓双系统!

frank

推特视频下载

骨灰级精品,京东百万架构师亲码的MySQL内部笔记太硬核了

小小怪下士

Java MySQL 程序员

聚焦技术,锐意创新,GaussDB给世界一个更优选择

华为云开发者联盟

数据库 后端 华为云 12 月 PK 榜

云原生 AI 的资源调度和 AI 工作流引擎设计分享

Baidu AICLOUD

AI工程化 异构计算 云原生AI 百度百舸

马斯克都不懂的 GraphQL,API 网关又能对其如何理解?

API7.ai 技术团队

api 网关 graphql APISIX

版本更新 | Towify V1.23.1 有哪些实用新功能?汇总来了!

Towify

微信小程序 无代码平台

openGemini v0.2.0版本正式发布:5大特性全面增强

华为云开发者联盟

数据库 后端 华为云 12 月 PK 榜

哪篇论文宣布了 HTAP 数据库的诞生?解读《A Common Database Approach for OLTP and OLAP Using an In-Memory Column DataBase》

StoneDB

MySQL HTAP 数据库· StoneDB 12 月 PK 榜

几种数据库jar包获取方式

华为云开发者联盟

数据库 华为云 12 月 PK 榜 jar包

一文带你快速上手云日志服务

华为云PaaS服务小智

云计算 运维 日志管理

什么是制造业数字化转型?制造业数字化转型的核心与意义

优秀

数字化转型 制造业

Kerberos 身份验证在 ChunJun 中的落地实践

袋鼠云数栈

数据集成 kerberos 大数据 开源

架构师成长之路之限流_文化 & 方法_吃橙子的狐狸_InfoQ精选文章