慌了,居然被问到怎么做高并发系统的限流

来源:uee.me/cDuRD

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。本文结合作者的一些经验介绍限流的相关概念、算法和常规的实现方式。

缓存

缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪。使用缓存不单单能够提升系统访问速度、提高并发访问量,也是保护数据库、保护系统的有效方式。大型网站一般主要是“读”,缓存的使用很容易被想到。

在大型“写”系统中,缓存也常常扮演者非常重要的角色。比如累积一些数据批量写入,内存里面的缓存队列(生产消费),以及HBase写数据的机制等等也都是通过缓存提升系统的吞吐量或者实现系统的保护措施。甚至消息中间件,你也可以认为是一种分布式的数据缓存。

降级

服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别,面临不同的异常等级执行不同的处理。根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务。

根据服务范围:可以砍掉某个功能,也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好。

限流

限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。

比如:延迟处理,拒绝处理,或者部分拒绝处理等等。

限流的算法

常见的限流算法有:计数器、漏桶和令牌桶算法。

计数器

计数器是最简单粗暴的算法。比如某个服务最多只能每秒钟处理100个请求。我们可以设置一个1秒钟的滑动窗口,窗口中有10个格子,每个格子100毫秒,每100毫秒移动一次,每次移动都需要记录当前服务请求的次数。

内存中需要保存10次的次数。可以用数据结构LinkedList来实现。格子每次移动的时候判断一次,当前访问次数和LinkedList中最后一个相差是否超过100,如果超过就需要限流了。

很明显,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

示例代码如下:

//服务访问次数,可以放在Redis中,实现分布式系统的访问计数
Long counter = 0L;
//使用LinkedList来记录滑动窗口的10个格子。
LinkedList<Long> ll = new LinkedList<Long>();

public static void main(String[] args)
{
    Counter counter = new Counter();

    counter.doCheck();
}

private void doCheck()
{
    while (true)
    {
        ll.addLast(counter);

        if (ll.size() > 10)
        {
            ll.removeFirst();
        }

        //比较最后一个和第一个,两者相差一秒
        if ((ll.peekLast() - ll.peekFirst()) > 100)
        {
            //To limit rate
        }

        Thread.sleep(100);
    }
}

漏桶算法

漏桶算法即leaky bucket是一种非常常用的限流算法,可以用来实现流量整形(Traffic Shaping)和流量控制(Traffic Policing)。贴了一张维基百科上示意图帮助大家理解:

漏桶算法的主要概念如下:

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;
  • 如果桶是空的,则不需流出水滴;
  • 可以以任意速率流入水滴到漏桶;
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

漏桶算法比较好实现,在单机系统中可以使用队列来实现(.Net中TPL DataFlow可以较好的处理类似的问题,你可以在这里找到相关的介绍),在分布式环境中消息中间件或者Redis都是可选的方案。

令牌桶算法

令牌桶算法是一个存放固定容量令牌(token)的桶,按照固定速率往桶里添加令牌。令牌桶算法基本可以用下面的几个概念来描述:

  • 令牌将按照固定的速率被放入令牌桶中。比如每秒放10个。
  • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝。
  • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上。
  • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

如下图:

令牌算法是根据放令牌的速率去控制输出的速率,也就是上图的to network的速率。to network我们可以理解为消息的处理程序,执行某段业务或者调用某个RPC。

漏桶和令牌桶的比较

令牌桶可以在运行时控制和调整数据处理的速率,处理某时的突发流量。放令牌的频率增加可以提升整体数据处理的速度,而通过每次获取令牌的个数增加或者放慢令牌的发放速度和降低整体数据处理速度。而漏桶不行,因为它的流出速率是固定的,程序处理速度也是固定的。更多算法相关:算法聚合

整体而言,令牌桶算法更优,但是实现更为复杂一些。

限流算法实现

Guava

Guava是一个Google开源项目,包含了若干被Google的Java项目广泛依赖的核心库,其中的RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

1. 常规速率:

创建一个限流器,设置每秒放置的令牌数:2个。返回的RateLimiter对象可以保证1秒内不会给超过2个令牌,并且是固定速率的放置。达到平滑输出的效果

public void test()
{
    /**
     * 创建一个限流器,设置每秒放置的令牌数:2个。速率是每秒可以2个的消息。
     * 返回的RateLimiter对象可以保证1秒内不会给超过2个令牌,并且是固定速率的放置。达到平滑输出的效果
     */
    RateLimiter r = RateLimiter.create(2);

    while (true)
    {
        /**
         * acquire()获取一个令牌,并且返回这个获取这个令牌所需要的时间。如果桶里没有令牌则等待,直到有令牌。
         * acquire(N)可以获取多个令牌。
         */
        System.out.println(r.acquire());
    }
}

上面代码执行的结果如下图,基本是0.5秒一个数据。拿到令牌后才能处理数据,达到输出数据或者调用接口的平滑效果。acquire()的返回值是等待令牌的时间,如果需要对某些突发的流量进行处理的话,可以对这个返回值设置一个阈值,根据不同的情况进行处理,比如过期丢弃。

2. 突发流量:

突发流量可以是突发的多,也可以是突发的少。首先来看个突发多的例子。还是上面例子的流量,每秒2个数据令牌。如下代码使用acquire方法,指定参数。

System.out.println(r.acquire(2));
System.out.println(r.acquire(1));
System.out.println(r.acquire(1));
System.out.println(r.acquire(1));

得到如下类似的输出。

如果要一次新处理更多的数据,则需要更多的令牌。代码首先获取2个令牌,那么下一个令牌就不是0.5秒之后获得了,还是1秒以后,之后又恢复常规速度。这是一个突发多的例子,如果是突发没有流量,如下代码:

System.out.println(r.acquire(1));
Thread.sleep(2000);
System.out.println(r.acquire(1));
System.out.println(r.acquire(1));
System.out.println(r.acquire(1));

得到如下类似的结果:

等了两秒钟之后,令牌桶里面就积累了3个令牌,可以连续不花时间的获取出来。处理突发其实也就是在单位时间内输出恒定。这两种方式都是使用的RateLimiter的子类SmoothBursty。另一个子类是SmoothWarmingUp,它提供的有一定缓冲的流量输出方案。

/**
* 创建一个限流器,设置每秒放置的令牌数:2个。速率是每秒可以210的消息。
* 返回的RateLimiter对象可以保证1秒内不会给超过2个令牌,并且是固定速率的放置。达到平滑输出的效果
* 设置缓冲时间为3秒
*/
RateLimiter r = RateLimiter.create(2,3,TimeUnit.SECONDS);

while (true) {
    /**
     * acquire()获取一个令牌,并且返回这个获取这个令牌所需要的时间。如果桶里没有令牌则等待,直到有令牌。
     * acquire(N)可以获取多个令牌。
     */
    System.out.println(r.acquire(1));
    System.out.println(r.acquire(1));
    System.out.println(r.acquire(1));
    System.out.println(r.acquire(1));

输出结果如下图,由于设置了缓冲的时间是3秒,令牌桶一开始并不会0.5秒给一个消息,而是形成一个平滑线性下降的坡度,频率越来越高,在3秒钟之内达到原本设置的频率,以后就以固定的频率输出。

图中红线圈出来的3次累加起来正好是3秒左右。这种功能适合系统刚启动需要一点时间来“热身”的场景。

Nginx

对于Nginx接入层限流可以使用Nginx自带了两个模块:

  • 连接数限流模块ngx_http_limit_conn_module
  • 漏桶算法实现的请求限流模块ngx_http_limit_req_module

1. ngx_http_limit_conn_module

我们经常会遇到这种情况,服务器流量异常,负载过大等等。对于大流量恶意的攻击访问,会带来带宽的浪费,服务器压力,影响业务,往往考虑对同一个ip的连接数,并发数进行限制。

ngx_http_limit_conn_module 模块来实现该需求。该模块可以根据定义的键来限制每个键值的连接数,如同一个IP来源的连接数。并不是所有的连接都会被该模块计数,只有那些正在被处理的请求(这些请求的头信息已被完全读入)所在的连接才会被计数。

我们可以在nginx_conf的http{}中加上如下配置实现限制:

#限制每个用户的并发连接数,取名one
limit_conn_zone $binary_remote_addr zone=one:10m;

#配置记录被限流后的日志级别,默认error级别
limit_conn_log_level error;
#配置被限流后返回的状态码,默认返回503
limit_conn_status 503;

然后在server{}里加上如下代码:

#限制用户并发连接数为1
limit_conn one 1;

然后我们是使用ab测试来模拟并发请求:

ab -n 5 -c 5 http://10.23.22.239/index.html

得到下面的结果,很明显并发被限制住了,超过阈值的都显示503:

另外刚才是配置针对单个IP的并发限制,还是可以针对域名进行并发限制,配置和客户端IP类似。

#http{}段配置
limit_conn_zone $ server_name zone=perserver:10m;
#server{}段配置
limit_conn perserver 1;

2. ngx_http_limit_req_module

上面我们使用到了ngx_http_limit_conn_module 模块,来限制连接数。那么请求数的限制该怎么做呢?这就需要通过ngx_http_limit_req_module 模块来实现,该模块可以通过定义的键值来限制请求处理的频率。

特别的,可以限制来自单个IP地址的请求处理频率。限制的方法是使用了漏斗算法,每秒固定处理请求数,推迟过多请求。如果请求的频率超过了限制域配置的值,请求处理会被延迟或被丢弃,所以所有的请求都是以定义的频率被处理的。

在http{}中配置

#区域名称为one,大小为10m,平均处理的请求频率不能超过每秒一次。

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

在server{}中配置

#设置每个IP桶的数量为5
limit_req zone=one burst=5;

上面设置定义了每个IP的请求处理只能限制在每秒1个。并且服务端可以为每个IP缓存5个请求,如果操作了5个请求,请求就会被丢弃。

使用ab测试模拟客户端连续访问10次:

ab -n 10 -c 10 http://10.23.22.239/index.html

如下图,设置了通的个数为5个。一共10个请求,第一个请求马上被处理。第2-6个被存放在桶中。由于桶满了,没有设置nodelay因此,余下的4个请求被丢弃。

Image placeholder
yuyuy
未设置
  38人点赞

没有讨论,发表一下自己的看法吧

推荐文章
漫画 |《程序员十二时辰》,居然是这样的!内容过于真实 …

作者:纯洁的微笑漫画:法小四据说程序员的一天是这样渡过….7:00开始新的一天起床缓冲中,已经进行……6%回想昨晚不该又High到2点7:10闹钟响到第6次的时候,终于鼓起勇气起床。其实我也不想那么晚

预案三板斧的限流大法

限流策略:多维防御+纵深防御 限流能力 限流是针对请求的各种特征,多维防御+纵深防御,从而限制流量,实现对服务端资源的合理使用。这里的特征是指一个请求所包含的各种信息,包括但不限于IP、Header、

1000亿文本信息,高并发MD5查询,这么大数据量的业务怎么弄?

==提问== 沈老师,你好,想请教一个身份证信息检索的问题。公司有一个每秒5万并发查询的业务,(假设)根据身份证MD5查询身份证信息,目前有1000亿条数据,纯文本存储,前几天看你写LevelDB,请

高并发业务场景下的秒杀解决方案 (初探)

文章简介 本文内容是对并发业务场景出现超卖情况而写的一片解决方案。主要是利用到了Redis中的队列技术。 超卖介绍 所谓的超卖,就是我们的售卖量大于了物品的库存量。该情况一般出现在电商系统中促销类的业

高并发设计笔记

基础篇 高并发系统:它的通用设计方法是什么? 高并发系统设计的三种通用方法:Scale-out、缓存和异步。 这三种方法可以在做方案设计时灵活地运用,但它不是具体实施的方案,而是三种思想,在实际运用中

高并发设计笔记

基础篇 高并发系统:它的通用设计方法是什么? 高并发系统设计的三种通用方法:Scale-out、缓存和异步。 这三种方法可以在做方案设计时灵活地运用,但它不是具体实施的方案,而是三种思想,在实际运用中

Redis为什么是单线程、及高并发快的3大原因详解

Redis的高并发和快速原因 1.redis是基于内存的,内存的读写速度非常快; 2.redis是单线程的,省去了很多上下文切换线程的时间; 3.redis使用多路复用技术,可以处理并发的连接。非阻塞

高并发设计笔记(续篇)

感谢大家对上篇的阅读和点赞,由于内容比较多,选择分开记录。 如果想深入了解,还是建议去购买学习该门课程《高并发系统设计》 分布式服务篇系统架构:每秒1万次请求的系统要做服务化拆分吗?了解实际业务中会

高并发下的接口幂等性解决方案!

一、背景我们实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返回一样的结果。例如:前端重复提交选中的数据,应该后台只产生对应这个数据的一个反应结果。我们发起一笔付款请求,应该只扣用户账户一

96秒100亿!如何抗住双11高并发流量?

今年双11全民购物狂欢节进入第十一个年头,1分36秒,交易额冲到100 亿 !比2018年快了近30 秒,比2017年快了近1分半!这个速度再次刷新天猫双11成交总额破100亿的纪录。那么如何抗住双1

阿里支付宝架构师:谈谈我眼中的高并发架构【好文】

来源:my.oschina.net/u/3772106/blog/1793561前言高并发经常会发生在有大活跃用户量,用户高聚集的业务场景中,如:秒杀活动,定时领取红包等。为了让业务可以流畅的运行并且

架构师眼中的高并发架构

前言高并发经常发生在有大活跃用户量和用户高聚集的业务场景中,如:秒杀活动、定时领取红包等。为了让业务可以流畅的运行并且给用户一个好的交互体验,我们需要根据业务场景预估达到的并发量等因素,来设计适合自己

这 20 多个高并发编程必备的知识点,你都会吗?

转载自并发编程网–ifeve.comhttp://ifeve.com/%e9%ab%98%e5... 一、前言借用Java并发编程实践中的话”编写正确的程序并不容易,而编写正常的并发程序就更难了”,相

使用 Proxy 突破网管的限制

背景 公司网络限制了网易云音乐的使用,网页和客户端皆不可使用,虽然QQ音乐没有禁用(难道网管对网易有偏见?),但是实在觉得QQ的推荐功能不如网易的,遂决定想法子突破网管的限制。 尝试 首先想到的是通过

【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!

Java并发基础常见面试题总结 1.什么是线程和进程? 1.1.何为进程? 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

GoldenDB ,一个已经全面支撑银行核心系统的国产数据库

摘要:沿用、并存还是替代,一直是银行核心系统数据库转型重点思考的问题。四大行目前主要采用的是沿用与并存的数据库产品战略,在确保稳定的大前提下对新兴数据库技术进行探索研究和实践。相对而言,股份制银行在这

分片技术如何解决区块链系统的可伸缩性问题?

区块链技术的应用可能将改变组织存储数据和执行分布式事务的方式。即使在公共网络上,区块链也可以保证所有参与者都以安全、可靠和可验证的方式访问记录。但是区块链有一个非常明显的限制:可伸缩性。随着交易数量的

共享内存在不同系统的应用与优劣详解

共享内存是一种使计算机程序能够同时共享内存资源以实现更高性能和更少冗余数据副本的技术。共享系统内存可以在单处理器系统、并行多处理器或集群微处理器上运行。对于分布式系统会有一些差异,但共享内存也可以其上

当Kubernetes成为云操作系统的标准应用,AWS也亮出了“杀手锏”!

作为云计算领域的排头兵,AWS一直是“老大哥”形象,他的一举一动都牵动着无数人的神经。AWS不仅在云计算领域投入时间早、运行时间长、客户多,在无服务器、容器以及现代化应用工具开发方面,也是当之无愧的引

全球“黑客大赛”冠军霸气讲述:我是如何让50个文件一起骗过AI安防系统的?

大数据文摘出品来源:medium编译:邢畅、张睿毅、钱天培你有没有想过当黑客呢?破解手机密码,黑入公司系统,甚至…控制全球电脑。打住打住!违法犯罪的念头显然不能有。再退一步讲,咱也不一定有这本事。尤其

从300万行到50万行代码,遗留系统的微服务改造

在传统企业甚至互联网企业中往往存在大量的遗留系统,这些遗留系统大多都能够正常工作,有的可能还运行着关键业务或者持有核心数据。但是,大部分遗留系统通常经常存在技术陈旧、代码复杂、难以修改等特点。笔者曾经

详解 | 阿里怎么做双11全链路压测?

导读:全链路压测是阿里的首创,本文将从工作内容、操作过程、运行总结等多个方向来介绍下阿里内部典型电商活动(如双11准备),以给大家展示一个完整的压测流程,帮助更多的企业和用户更好的完成性能测试。前言关

多云时代下大规模数据库管理,该怎么做?

中国经济的高速发展是世界各国人们有目共睹的,随着数字经济时代的到来,金融数据库的管理,也随之面对一系列的新技术与挑战。云计算一直以来被认为是极具颠覆性的技术力量,随着云计算的应用普及,越来越为企业重视

Flutter高内聚组件怎么做?闲鱼打造开源高效方案!

fish_redux是闲鱼技术团队打造的开源flutter应用开发框架,旨在解决页面内组件间的高内聚、低耦合问题。开源地址:https://github.com/alibaba/fish-redux从

当金融科技遇上云原生,蚂蚁金服是怎么做安全架构的?

蚂蚁金服在过去十五年重塑支付改变生活,为全球超过十二亿人提供服务,这些背后离不开技术的支撑。在2019杭州云栖大会上,蚂蚁金服将十五年来的技术沉淀,以及面向未来的金融技术创新和参会者分享。我们将其中的