流控算法

漏桶算法

令牌桶算法

一年一度的「双 11」又要到了,阿里的码农们进入了一年中最辛苦的时光。各种容量评估、压测、扩容让我们忙得不可开交。洛阳亲友如相问,就说我搞双十一。

如何让系统在汹涌澎湃的流量面前谈笑风生?我们的策略是不要让系统超负荷工作。如果现有的系统扛不住业务目标怎么办?加机器!机器不够怎么办?业务降级,系统限流!

正所谓「他强任他强,清风拂山岗;他横任他横,明月照大江」,降级和限流是大促保障中必不可少的神兵利器,丢卒保车,以暂停边缘业务为代价保障核心业务的资源,以系统不被突发流量压挂为第一要务。

集团的中间件有一个不错的单机限流框架,支持两种限流模式:控制速率和控制并发。限流这种东西,应该是来源于网络里面的「流量整型」,通过控制数据包的传输速率和时机,来实现一些性能、服务质量方面的东西。令牌桶是一种常见的流控算法,属于控制速率类型的。控制并发则相对要常见的多,比如操作系统里的「信号量」就是一种控制并发的方式。

在 Wikipedia 上,令牌桶算法是这么描述的:

  1. 每秒会有 r 个令牌放入桶中,或者说,每过 1/r 秒桶中增加一个令牌
  2. 桶中最多存放 b 个令牌,如果桶满了,新放入的令牌会被丢弃
  3. 当一个 n 字节的数据包到达时,消耗 n 个令牌,然后发送该数据包
  4. 如果桶中可用令牌小于 n,则该数据包将被缓存或丢弃

令牌桶控制的是一个时间窗口内的通过的数据量,在 API 层面我们常说的 QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在 1s 罢了。

现实世界的网络工程中使用的令牌桶,比概念图中的自然是复杂了许多,「令牌桶」的数量也不是一个而是两个,简单的算法描述可用参考中兴的期刊[1]或者 RFC。

假如项目使用 Java 语言,我们可以轻松地借助 Guava 的 RateLimiter 来实现基于令牌桶的流控。RateLimiter 令牌桶算法的单桶实现,也许是因为在 Web 应用层面单桶实现就够用了,双筒实现就属于过度设计。

RateLimiter 对简单的令牌桶算法做了一些工程上的优化,具体的实现是 SmoothBursty。需要注意的是,RateLimiter 的另一个实现 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也许是出于简单起见,RateLimiter 中的时间窗口能且仅能为 1s,如果想搞其他时间单位的限流,只能另外造轮子。

SmoothBursty 积极响应李克强总理的号召,上个月的流量没用完,可以挪到下个月用。其实就是 SmoothBursty 有一个可以放 N 个时间窗口产生的令牌的桶,系统空闲的时候令牌就一直攒着,最好情况下可以扛 N 倍于限流值的高峰而不影响后续请求。如果不想像三峡大坝一样能扛千年一遇的洪水,可以把 N 设置为 1,这样就只屯一个时间窗口的令牌。

RateLimiter 有一个有趣的特性是「前人挖坑后人跳」,也就是说 RateLimiter 允许某次请求拿走超出剩余令牌数的令牌,但是下一次请求将为此付出代价,一直等到令牌亏空补上,并且桶中有足够本次请求使用的令牌为止[2]。这里面就涉及到一个权衡,是让前一次请求干等到令牌够用才走掉呢,还是让它先走掉后面的请求等一等呢?Guava 的设计者选择的是后者,先把眼前的活干了,后面的事后面再说。

当我们要实现一个基于速率的单机流控框架的时候,RateLimiter 是一个完善的核心组件,就仿佛 Linux 内核对 GNU 操作系统那样重要。但是我们还需要其他的一些东西才能把一个流控框架跑起来,比如一个通用的 API,一个拦截器,一个在线配置流控阈值的后台等等。

下面随便写了一个简单的流控框架 API,至于拦截器和后台就懒得写了,有时间再自己造一套中间件的轮子吧~

public class TrafficShaper {    public static class RateLimitException extends Exception {        private static final long serialVersionUID = 1L;        private String resource;        public String getResource() {            return resource;
        }        public RateLimitException(String resource) {            super(resource + " should not be visited so frequently");            this.resource = resource;
        }        @Override        public synchronized Throwable fillInStackTrace() {            return this;
        }
    }    private static final ConcurrentMap<String, RateLimiter>
            resourceLimiterMap = Maps.newConcurrentMap();    public static void updateResourceQps(String resource, double qps) {
        RateLimiter limiter = resourceLimiterMap.get(resource);        if (limiter == null) {
            limiter = RateLimiter.create(qps);
            RateLimiter putByOtherThread
                    = resourceLimiterMap.putIfAbsent(resource, limiter);            if (putByOtherThread != null) {
                limiter = putByOtherThread;
            }
        }
        limiter.setRate(qps);
    }    public static void removeResource(String resource) {
        resourceLimiterMap.remove(resource);
    }    public static void enter(String resource) throws RateLimitException {
        RateLimiter limiter = resourceLimiterMap.get(resource);        if (limiter == null) {            return;
        }        if (!limiter.tryAcquire()) {            throw new RateLimitException(resource);
        }
    }    public static void exit(String resource) {        //do nothing when use RateLimiter
    }
}
时间: 2024-12-09 02:17:32

流控算法的相关文章

救火队员的那些事(4)-关于流控

这次救火讨论的是流控,流控可以很简单,也可以非常复杂,特别是动态流控.我们有一个产品在T国某个运营商遇到了麻烦,这个运营商的母公司是欧洲的运营商,而欧洲的运营商对于产品验收的苛刻是出了名的,而这次给我们带来麻烦的就是流控. 这个产品的大致功能就是把订阅的短消息或者彩信内容发送到户手机上,就类似于在几年前火了一段时间的手机报.这个产品的流控有些复杂: 1.首先这套系统部署是集群,假设集群有6台主机,这6台主机的每秒下发的消息量不能超过一个值,假设为10000,为什么有这个要求,是因为下游执行发送消

高并发场景下的流控管理

任何应用都有一个设计指标,当应用的压力超过了他设计所能承载的能力时,就好比一座只允许行人通过的独木桥,是无法承载一辆坦克的重量的,这个时候,为了让机器能够继续运行,在不宕机的情况下尽其所能的对一部分用户提供服务,保证整个流程能够继续走下去,这个时候,就必须对应用进行流控,丢弃一部分用户的请无法避免. 流控可以从多个维度来进行,比如针对QPS,并发线程数,黑白名单,加权分级等等,最典型最直接的便是针对QPS和并发线程数的流控.当然,要进行流控,首先等有一个流控的阀值,这个阀值不是说拍拍脑袋就能够想

iOS开发UI篇—自定义瀑布流控件(基本实现)

iOS开发UI篇—自定义瀑布流控件(基本实现) 一.基本实现 说明:在View加载的时候,刷新数据. 1.实现代码 YYViewController.m文件 1 // 2 // YYViewController.m 3 // 06-瀑布流 4 // 5 // Created by apple on 14-7-28. 6 // Copyright (c) 2014年 wendingding. All rights reserved. 7 // 8 9 #import "YYViewControll

TCP系列33—窗口管理&流控—7、Silly Window Syndrome(SWS)

一.SWS介绍 前面我们已经通过示例看到如果接收端的应用层一直没有读取数据,那么window size就会慢慢变小最终可能变为0,此时我们假设一种场景,如果应用层读取少量数据(比如十几bytes),接收端TCP有了少量的新的接收缓存后如果立即进行window update把新的window size通告发送端的话,发送端如果立即发送数据,那么接收端缓存可能又会立即耗尽,window size又变为0,接着应用层重复读取少量数据,这个过程重复的话,那么发送端就会频繁的发送大量的小包,这种场景我们就

TCP系列34—窗口管理&流控—8、缓存自动调整

一.概述 我们之前介绍过一种具有大的带宽时延乘积(band-delay product.BDP)的网络,这种网络称为长肥网络(LongFatNetwork,即LFN).我们想象一种简单的场景,假设发送端的发送窗口为5000bytes,网络的RTT为200ms,那么每秒的最大速率则为5000*(1000/200)=25000bytes/s,这大约为24kb/s,可以看到这个速率是非常低的,这就是TCP发送窗口对于发送速率的限制,实际的window size应该至少为带宽时延积才能高效的利用网络传输

TG-NET智能流控路由系统监控

监控网络系统是网络管理员最基本的工作,管理员通过观察系统运行的相关信息来监督.管理所有的网络.每天盯着网络中所有的系统页面,这对网络管理员来说是非常繁琐同时枯燥的,TG-NET智能流控路由器可视化管理页面可以让网络管理员轻松监督网络. 登陆路由器后,查看此功能,可以了解路由器目前工作状况,作为判断网络故障和使用率的依据之一. 路由器负荷,有图表实时显示目前系统CPU占用资源率,内存使用率,当前连接数,可以直观的了解路由器的资源使用情况. 接口状态,直观的显示当前网卡的状态,物理连接是否正常,网络

统一流控服务开源:基于.Net Core的流控服务

原文:统一流控服务开源:基于.Net Core的流控服务 先前有一篇博文,梳理了流控服务的场景.业界做法和常用算法 统一流控服务开源-1:场景&业界做法&算法篇 最近完成了流控服务的开发,并在生产系统进行了大半年的验证,稳定可靠.今天整理一下核心设计和实现思路,开源到Github上,分享给大家 https://github.com/zhouguoqing/FlowControl  一.令牌桶算法实现 先回顾一下令牌桶算法示意图 随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=10

HDU 3549 Flow Problem ( 最大流 -EK 算法)

C++,G++的读取速度差距也太大了 Flow Problem 题意:n,m表示n个点m条有向带权边 问:从1-n最大流多少 裸最大流,拿来练手,挺不错的 #include <iostream> #include <cstdlib> #include <cstdio> #include <cstring> #include <queue> #include <algorithm> const int N = 210; #define

iOS开发UI篇—自定义瀑布流控件(蘑菇街数据刷新操作)

iOS开发UI篇—自定义瀑布流控件(蘑菇街数据刷新操作) 一.简单说明 使用数据刷新框架: 该框架提供了两种刷新的方法,一个是使用block回调(存在循环引用问题,_ _weak),一个是使用调用. 问题:在进行下拉刷新之前,应该要清空之前的所有数据(在刷新数据这个方法中). 移除正在显示的cell: (1)把字典中的所有的值,都从屏幕上移除 (2)清除字典中的所有元素 (3)清除cell的frame,每个位置的cell的frame都要重新计算 (4)清除可复用的缓存池. 该部分的代码如下: 1