在大数据量高并发访问时,常常会出现服务或接口面对暴涨的请求而不可用的状况,甚至引起连锁反映致使整个系统崩溃。此时你须要使用的技术手段之一就是限流,当请求达到必定的并发数或速率,就进行等待、排队、降级、拒绝服务等。
对通常的限流场景来讲它具备两个维度的信息:
时间:限流基于某段时间范围或者某个时间点,也就是咱们常说的“时间窗口”,好比对每分钟、每秒钟的时间窗口作限定
资源:基于可用资源的限制,好比设定最大访问次数,或最高可用链接数nginx
上面两个维度结合起来看,限流就是在某个时间窗口对资源访问作限制,好比设定每秒最多100个访问请求。但在真正的场景里,咱们不止设置一种限流规则,而是会设置多个限流规则共同做用。
主要的几种限流规则以下:
redis
对于图中链接数和QPS)限流来讲,咱们可设定IP维度的限流,也能够设置基于单个服务器的限流。算法
在真实环境中一般会设置多个维度的限流规则,好比设定同一个IP每秒访问频率小于10,链接数小于5,再设定每台机器QPS最高1000,链接数最大保持200。更进一步,咱们能够把某个服务器组或整个机房的服务器当作一个总体,设置更high-level的限流规则,这些全部限流规则都会共同做用于流量控制。spring
对于“传输速率”你们都不会陌生,好比资源的下载速度。有的网站在这方面的限流逻辑作的更细致,好比普通注册用户下载速度为100k/s,购买会员后是10M/s,这背后就是基于用户组或者用户标签的限流逻辑。数据库
黑白名单是各个大型企业应用里很常见的限流和放行手段,并且黑白名单每每是动态变化的。举个例子,若是某个IP在一段时间的访问次数过于频繁,被系统识别为机器人用户或流量攻击,那么这个IP就会被加入到黑名单,从而限制其对系统资源的访问,这就是咱们俗称的“封IP”。
咱们平时见到的爬虫程序,好比说爬知乎上的美女图片,或者爬券商系统的股票分时信息,这类爬虫程序都必须实现更换IP的功能,以防被加入黑名单。有时咱们还会发现公司的网络没法访问12306这类大型公共网站,这也是由于某些公司的出网IP是同一个地址,所以在访问量太高的状况下,这个IP地址就被对方系统识别,进而被添加到了黑名单。使用家庭宽带的同窗们应该知道,大部分网络运营商都会将用户分配到不一样出网IP段,或者时不时动态更换用户的IP地址。
白名单就更好理解了,至关于御赐金牌在身,能够自由穿梭在各类限流规则里,畅行无阻。好比某些电商公司会将超大卖家的帐号加入白名单,由于这类卖家每每有本身的一套运维系统,须要对接公司的IT系统作大量的商品发布、补货等等操做。编程
分布式区别于单机限流的场景,它把整个分布式环境中全部服务器当作一个总体来考量。好比说针对IP的限流,咱们限制了1个IP每秒最多10个访问,无论来自这个IP的请求落在了哪台机器上,只要是访问了集群中的服务节点,那么都会受到限流规则的制约。缓存
从上面例子不难看出,咱们最好将限流信息保存在一个“中心化”的组件上,这样它就能够获取到集群中全部机器的访问状态,目前有两个比较主流的限流方案:安全
网关层限流 将限流规则应用在全部流量的入口处
中间件限流 将限流信息存储在分布式环境中某个中间件里(好比Redis缓存),每一个组件均可以从这里获取到当前时刻的流量统计,从而决定是拒绝服务仍是放行流量
sentinel,springcloud生态圈为微服务量身打造的一款用于分布式限流、熔断降级等组件服务器
说到限流,至少咱们须要对限流的底层原理有个大体的了解,才好更深刻的进行学习,下面咱们挑选令牌桶算法、漏桶算法、滑动窗口和计数器算法来讲一下网络
Token Bucket令牌桶算法是目前应用最为普遍的限流算法,顾名思义,它有如下两个关键角色:
令牌 获取到令牌的Request才会被处理,其余Requests要么排队要么被直接丢弃
桶 用来装令牌的地方,全部Request都从这个桶里面获取令牌
用图简单描述以下
主要涉及到2个过程:
这个流程涉及到令牌生成器和令牌桶,前面咱们提到过令牌桶是一个装令牌的地方,既然是个桶那么必然有一个容量,也就是说令牌桶所能容纳的令牌数量是一个固定的数值。
对于令牌生成器来讲,它会根据一个预约的速率向桶中添加令牌,好比咱们能够配置让它以每秒100个请求的速率发放令牌,或者每分钟50个。注意这里的发放速度是匀速,也就是说这50个令牌并不是是在每一个时间窗口刚开始的时候一次性发放,而是会在这个时间窗口内匀速发放。
在令牌发放器就是一个水龙头,假如在下面接水的桶子满了,那么天然这个水(令牌)就流到了外面。在令牌发放过程当中也同样,令牌桶的容量是有限的,若是当前已经放满了额定容量的令牌,那么新来的令牌就会被丢弃掉。
每一个访问请求到来后,必须获取到一个令牌才能执行后面的逻辑。假如令牌的数量少,而访问请求较多的状况下,一部分请求天然没法获取到令牌,那么这个时候咱们能够设置一个“缓冲队列”来暂存这些多余的令牌。
缓冲队列实际上是一个可选的选项,并非全部应用了令牌桶算法的程序都会实现队列。当有缓存队列存在的状况下,那些暂时没有获取到令牌的请求将被放到这个队列中排队,直到新的令牌产生后,再从队列头部拿出一个请求来匹配令牌。
当队列已满的状况下,这部分访问请求将被丢弃。在实际应用中咱们还能够给这个队列加一系列的特效,好比设置队列中请求的存活时间,或者将队列改造为PriorityQueue,根据某种优先级排序,而不是先进先出。
Leaky Bucket,又是个桶,限流算法是跟桶杠上了,那么漏桶和令牌桶有什么不一样呢?咱们来看图说话:
漏桶算法的前半段和令牌桶相似,可是操做的对象不一样,令牌桶是将令牌放入桶里,而漏桶是将访问请求的数据包放到桶里。一样的是,若是桶满了,那么后面新来的数据包将被丢弃。
漏桶算法的后半程是有鲜明特点的,它永远只会以一个恒定的速率将数据包从桶内流出。打个比方,若是我设置了漏桶能够存放100个数据包,而后流出速度是1s一个,那么无论数据包以什么速率流入桶里,也无论桶里有多少数据包,漏桶能保证这些数据包永远以1s一个的恒定速度被处理。
漏桶 vs 令牌桶的区别
根据它们各自的特色不难看出来,这两种算法都有一个“恒定”的速率和“不定”的速率。令牌桶是以恒定速率建立令牌,可是访问请求获取令牌的速率“不定”,反正有多少令牌发多少,令牌没了就干等。而漏桶是以“恒定”的速率处理请求,可是这些请求流入桶的速率是“不定”的。
从这两个特色来讲,漏桶的自然特性决定了它不会发生突发流量,就算每秒1000个请求到来,那么它对后台服务输出的访问速率永远恒定。而令牌桶则不一样,其特性能够“预存”必定量的令牌,所以在应对突发流量的时候能够在短期消耗全部令牌,其突发流量处理效率会比漏桶高,可是导向后台系统的压力也会相应增多。
根据图示,咱们将时间窗口的限流原理拆解描述一下其过程:
黑色的大框就是时间窗口,咱们设定窗口时间为5秒,它会随着时间推移向后滑动。咱们将窗口内的时间划分为五个小格子,每一个格子表明1秒钟,同时这个格子还包含一个计数器,用来计算在当前时间内访问的请求数量。那么这个时间窗口内的总访问量就是全部格子计数器累加后的数值。
好比说,咱们在每一秒内有5个用户访问,第5秒内有10个用户访问,那么在0到5秒这个时间窗口内访问量就是15。若是咱们的接口设置了时间窗口内访问上限是20,那么当时间到第六秒的时候,这个时间窗口内的计数总和就变成了10,由于1秒的格子已经退出了时间窗口,所以在第六秒内能够接收的访问量就是20-10=10个。
滑动窗口其实也是一种计算器算法,它有一个显著特色,当时间窗口的跨度越长时,限流效果就越平滑。打个比方,若是当前时间窗口只有两秒,而访问请求所有集中在第一秒的时候,当时间向后滑动一秒后,当前窗口的计数量将发生较大的变化,拉长时间窗口能够下降这种状况的发生几率
那么有了上面的基础理论以后,咱们来简单总结下目前经常使用的限流方案有哪些呢?
提及Guava你们必定不陌生,它是Google出品的一款工具包,咱们常常用它作一些集合操做好比Lists.newArrayList()等,它最先源于2007的"Google Collections Library"项目。Guava不甘于将本身平凡的一辈子都耗费在Collections上面,因而乎它开始了转型,慢慢扩展了本身在Java领域的影响力,从反射工具、函数式编程、安全验证、数学运算等等方面,都提供了响应的工具包
在限流领域中,Guava也贡献了一份绵薄之力,在其多线程模块下提供了以RateLimiter为首的几个限流支持类,可是做用范围仅限于“当前”这台服务器,也就是说Guawa的限流是单机的限流,跨了机器或者jvm进程就无能为力了
好比说,目前我有2台服务器[Server 1,Server 2],这两台服务器都部署了一个登录服务,假如我但愿对这两台机器的流量进行控制,好比将两台机器的访问量总和控制在每秒20之内,若是用Guava来作,只能独立控制每台机器的访问量<=10。
尽管Guava不是面对分布式系统的解决方案,可是其做为一个简单轻量级的客户端限流组件,很是适合来说解限流算法
在整个分布式系统中,若是有这么一个“一夫当关,万夫莫开”的角色,非网关层莫属。服务网关,做为整个分布式链路中的第一道关卡,承接了全部用户来访请求,所以在网关层面进行限流是一个很好的切入点
网关层限流的架构思考
若是咱们将这个系统的模型想象成为一个漏斗模型的话,抽象出来大概以下面的结构:
上面是一个最普通的流量模型,从上到下的路径依次是:
为何说它是一个漏斗模型,由于流量自上而下是逐层递减的,在网关层汇集了最多最密集的用户访问请求,其次是后台服务。
而后通过后台服务的验证逻辑以后,刷掉了一部分错误请求,剩下的请求落在缓存上,若是缓存中没有数据才会请求漏斗最下方的数据库,所以数据库层面请求数量最小(相比较其余组件来讲数据库每每是并发量能力最差的一环,阿里系的MySQL即使通过了大量改造,单机并发量也没法和Redis、Kafka之类的组件相比)
若是在上面这个漏斗模型中作流量限制,网关层首当其冲对不对?由于它是整个访问链路的源头,是全部流量途径的第一站。目前主流的网关层有以软件为表明的Nginx,还有Spring Cloud中的Gateway和Zuul这类网关层组件,也有以硬件+软件为表明的F5(F5价钱贵到你怀疑人生)
在系统架构中,Nginx的代理与路由转发是其做为网关层的一个很重要的功能,因为Nginx天生的轻量级和优秀的设计,让它成为众多公司的首选,Nginx从网关这一层面考虑,能够做为最前置的网关,抵挡大部分的网络流量,所以使用Nginx进行限流也是一个很好的选择,在Nginx中,也提供了经常使用的基于限流相关的策略配置,后续咱们将会使用简单的案例进行说明
对于分布式环境来讲,无非是须要一个相似中心节点的地方存储限流数据。打个比方,若是我但愿控制接口的访问速率为每秒100个请求,那么我就须要将当前1s内已经接收到的请求的数量保存在某个地方,而且可让集群环境中全部节点都能访问。那咱们能够用什么技术来存储这个临时数据呢?
那么想必你们都能想到,必然是redis了,利用Redis过时时间特性,咱们能够轻松设置限流的时间跨度(好比每秒10个请求,或者每10秒10个请求)。同时Redis还有一个特殊技能–脚本编程,咱们能够将限流逻辑编写成一段脚本植入到Redis中,这样就将限流的重任从服务层彻底剥离出来,同时Redis强大的并发量特性以及高可用集群架构也能够很好的支持庞大集群的限流访问。【reids + lua】
除了上面介绍的几种方式之外,目前也有一些开源组件提供了相似的功能,好比Sentinel就是一个不错的选择。Sentinel是阿里出品的开源组件,而且包含在了Spring Cloud Alibaba组件库中,Sentinel提供了至关丰富的用于限流的API以及可视化管控台,能够很方便的帮助咱们对限流进行治理
从架构维度考虑限流设计
在真实的项目里,不会只使用一种限流手段,每每是几种方式互相搭配使用,让限流策略有一种层次感,达到资源的最大使用率。在这个过程当中,限流策略的设计也能够参考前面提到的漏斗模型,上宽下紧,漏斗不一样部位的限流方案设计要尽可能关注当前组件的高可用。以我参与的实际项目为例,好比说咱们研发了一个商品详情页的接口,经过手机淘宝导流,app端的访问请求首先会通过阿里的mtop网关,在网关层咱们的限流会作的比较宽松,等到请求经过网关抵达后台的商品详情页服务以后,再利用一系列的中间件+限流组件,对服务进行更加细致的限流控制