限流策略

任逍遥
任逍遥 这家伙很懒,还没有设置简介...

0 人点赞了该文章 · 20 浏览

限流的核心目的是对下游做一层保护,防止突然的超过服务阈值的流量让整个服务不可用。

限流方案也有很多,限流越牛的方案,接入的成本也会相对较高。这个也是需要结合业务做一层取舍的。其实我认为最理想的方案就是最低成本接入能满足业务需求的。然后方案的复杂度会随着业务复杂而不断衍化。

没必要一开始就造一门大炮去打鸟,开始用小石子就好使。

拿限流方案举例,限流分单实例和集群。针对非精确限流情况,例如最大是1000。这种就需要集群为单位限流。如果对限流要求不高,那么单实例限流即可(每个实例不可能严格的均匀分配流量)。

上面这种情况,为了提高精确度,就需要牺牲性能和增加开发维护复杂度了。集群限流,就需要引入有原子性并且并发安全的计数器,为了保证并发安全,需要考虑锁,为了保证这个计数器稳定可靠,这个计数器也需要分布式集群化。这个计数器服务ok了,还涉及一个网络请求调用1ms左右的耗时损失。

所以选择适合的就好,单实例的能满足,就没必要集群方案。

方式一 Nginx限流

Nginx限流,非常适合接入层使用,对整个外网访问做一个限制,避免遇到攻击流量或者恶意流量,导致服务程序被打垮。

提供2中限流方式:

限制访问频率限制并发连接数

这个限流方式非常实用,而且接入成本低。

但是仅仅这个限流其实不够用。目前在分布式服务场景下,Nginx限流对于外网,或者入口级别限流很有用,但是对于内部服务,就用不上了,除非内部服务的接入都配置在Nginx上。

为什么配置在Nginx上不合适,简单了解下一种分布式部署方式:

目前分布式服务比较多是以一种注册中心(zookeeper)的方式存在。例如新启动一个服务,就会有一个线程往配置中心发消息,表示我活了,这个过程可以理解为服务发现,注册中心收到这个消息后会记录下这个新实例的ip和port。服务下线,注册中心也会删掉对应ipport。然后这个线程会定时的给注册中心发消息(心跳),表示我还在,能提供服务。然后其他服务需要访问时,就直接读取配置中心的这个服务的活跃ip和port。然后按照自己的策略选中一个ipport访问即可。

这种方式对比Nginx配置负载均衡 优势很大,他能灵活的增加,减少实例。而Nginx需要修改配置。

所以在分布式服务下,限流就无法直接复用Nginx限流。需要服务自己做。

有人会想,尽然在入口处控制了流量,下游为何还需要限流。因为目前架构多是微服务形态,比如一个请求经历5个服务,最后到达数据库。如果数据库卡主。每个服务又设置了3次重试,那么如果不限流,1个请求层层重试后到达数据库的流量就是3的5次方=243次。数据库直接雪崩。

所以在这种背景下,我们需要考虑在业务中自己实现限流。首先限流有很多成熟的框架,例如golang的 ratelimit 框架。比较建议的是引入一个合适的框架。但是引入框架时,也需要理解下背后的原理,便于找到最合适的。

方案二 线程池

连接/线程池是一个非常常用的限流方式。例如访问数据库,访问redis。都会使用连接池,池子最大就20个连接。这样最大20个请求同时访问下游。第21个就需要阻塞的等待前面释放。线程池也是同样原理,设置一个20个线程的池子。这样也是最大20个请求同时访问下游。

池化的这种方式限流比较粗糙,但是对于大多是场景,其实都是够用的。

这种实现方式,主要是令牌桶的一种算法。桶里面20个令牌,拿到令牌的可以访问下游,拿不到的排队等待,使用令牌结束后就归还到桶中。

具体代码实现,可以借助阻塞队列的原理:对于golang的实现,可以用channel。

上述算法其实有一个特点:只能限制最大连接,但是没办法限制每秒请求数。例如下游每次响应都是500ms,20个线程全开,qps是40。如果下游响应是50ms,qps就是400。如果是5ms,qps就达到了4000。

如果是对qps有严格限制的,其实简单的线程池方案就不合适了。

我看到网上有一种优化策略是,令牌用过后丢弃,令牌是以一种速率去生成,达到上限就不在生成。这样就能以时间为单位去控制速率了。

方案三 计数器

这个也是比较常用的方案。原理很简单,就是单位时间内,实现一个计数(+1)。单位时间达到计数最大值,就停止下游请求(排队或者丢弃)。单位周期结束,重新开始计数。

这个也是相对粗糙的限流。例如:1秒限制100个请求(100qps),如果再一个单位周期后500ms,发出100个请求,在下一个单位周期前500ms,发出100个请求。这样其实qps就达到了200。

方案四 滑动窗口

这个算法其实是tcp控制拥塞的核心算法。看似很遥远,其实我们打开网络,就会用上。有兴趣可以了解下tcp控制拥塞的整套算法,很经典,很多流量控制都借用了他的思想实现的。

算法里面流量控制这部分,使用的就是滑动窗口。

针对上面1秒的计数器,会将1秒划成了10格,所以每格代表的是100ms。每过100ms,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器counter,比如当一个请求 在350ms秒的时候到达,那么300~390ms对应的counter就会加1。当划分的格子越多,限流就会越精确。

所以上面的计数器方案,可以看成是滑动窗口只有1格。

方案五 漏桶算法

可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

集群限流

集群限流也都是使用上面的方案。计数器和滑动窗口方案: 需要单独维护一个计数服务,集群通过锁方式保证计数原子性令牌桶和线程池方案:需要一个单独的管理令牌服务,负责令牌的生成和使用

github和giteestar (#^.^#),提issue。更多总结mysql、Redis、网络编程

发布于 2023-01-11 14:07

免责声明:

本文由 任逍遥 原创或收集发布于 火鲤鱼 ,著作权归作者所有,如有侵权可联系本站删除。

火鲤鱼 © 2024 专注小微企业服务 冀ICP备09002609号-8