当系统并发流量过大时,可能会导致系统不堪重负,导致整个服务不可用。对于这种场景,一般的解决办法是:如果流量超过这个量,我们就拒绝提供服务,这样我们的服务就不会挂掉。当然,限流虽然可以保护系统不被压垮,但是对于限流的用户来说会很不爽。所以限流实际上是一种有损的解决方案。但是,与完全不可用相比,有损服务是最好的解决方案。除了上面提到的限流使用场景,限流设计还可以防止恶意请求流量和恶意攻击。因此,限流的基本原理是通过限制并发访问/请求或一个时间窗口内请求的速率来保护系统。一旦达到限制速率,可以拒绝服务(跳转到错误页面或通知资源没了)、排队或Waiting(秒杀、下单)、降级(返回口袋数据或默认数据或默认数据、如商品详情页的库存默认为有货)。一般互联网公司常见的限流包括:限制总并发数(如数据库连接池、线程池)、限制瞬时并发数(使用nginx的limit_conn模块来限制瞬时并发数),限制时间窗口内的平均速率(如Guava的RateLimiter,nginx的limit_req模块,限制每秒的平均速率);other对远程接口调用的速率和MQ的消费速率有限制。另外还可以根据网络连接数、网络流量、CPU或内存负载等进行限流,有了缓存,加上上限流量,在处理高并发的时候也能从容应对。不用担心瞬时流量导致系统挂掉或雪崩,最终不是没有服务而是损坏服务;但是限流需要评估好,不能乱用,否则在一些正常的流量中会出现一些奇怪的问题,导致用户体验不好,造成用户流失。常见的限流算法滑动窗口发送方和接收方都会维护一个数据帧的序列,称为一个窗口。发送方的窗口大小由接收方决定。目的是为了控制发送速度,以免接收方缓冲区过大导致溢出。同时,控制流量也可以避免网络拥塞。下图中4、5、6号数据帧已经发送,但是还没有收到相关的ACK,7、8、9号帧等待发送。可以看出发送端的窗口大小为6,这是接收端通知的。此时如果发送方收到4号ACK,则窗口左边缘向右缩小,窗口右边缘向右扩大。这时,窗口向前“滑动”,即数据帧10也可以发送了。滑动窗口演示地址Leakybucket(控制传输速率Leakybucket)漏桶算法的思想是不断地往桶里灌水,不管灌水速度是大还是小,水都会以一定的速度漏出固定利率;水桶满了,水就会溢出来;水桶本身以恒定的速度向下漏水,并且水以恒定的速度从上方进入桶。水桶不满时,可加入上面的水。一旦水满了,上面的水就不能加了。桶满是算法中的一个关键触发条件(即异常流量的判定条件)。在这种情况下,如何处理从上面流下来的水有两种方法。然后放掉上面的水。溢出的塔顶水直接丢弃。特点漏水率固定即使出现突发注水(突然加大注水量),漏水率也是固定的令牌桶(可以解决突发流量)令牌桶算法是网络流量整形(TrafficShaping)和rateRateLimiting中最常用的算法之一。通常,令牌桶算法用于控制发送到网络的数据量并允许发送突发数据。令牌桶是存储固定容量的令牌(token)的桶,令牌以固定的速率添加到桶中;令牌桶算法实际上由三部分组成:两流一桶,分别是令牌流、数据流和令牌桶。令牌流和令牌桶系统会以一定的速度产生令牌,放入令牌桶中。令牌桶可以想象成一个缓冲区(这种数据结构可以使用队列),当缓冲区填满时,新生成的令牌将被丢弃。这里有两个变量很重要:第一个是令牌生成的速率,通常称为速率。比如我们设置rate=2,即每秒产生2个token,即每1/2秒产生一个token;二是令牌桶的大小,一般称为burst。比如我们设置burst=10,即令牌桶最多只能放10个令牌。数据流数据流是进入系统的真实流量。对于http接口,如果平均调用2次/秒,则认为速率为2次/s。可能会出现三种情况:数据流的速率等于令牌流的速率。在这种情况下,每个传入的数据包或请求都可以对应一个令牌,然后无延迟地通过队列;数据流的速率小于令牌流的速率。通过队列的数据包或请求只消耗部分令牌,剩余的令牌将累积在令牌桶中,直到桶满为止。剩余的令牌可以在突发请求中消耗。数据流的速率大于令牌流的速率。这意味着桶中的令牌将很快耗尽。服务将中断一段时间。如果数据包或请求继续到达,则会发生数据包丢失或响应拒绝。例如,在前面的例子中,生成令牌的速率和令牌桶的大小分别为rate=2,burst=10,那么系统可以承受的突发请求速率为10次/s,平均请求速率为2次/秒。三种场景中的最后一种是该算法的核心,非常准确,实现起来非常简单,对服务器的压力可以忽略不计,因此被广泛使用,值得学习和利用。特性令牌可累积:buckets桶中令牌的最大个数为b,表示最大可累积令牌数允许突发流量:桶中令牌最多可累积n个(b<=n<=0),此时如果同时有n个突发请求到达,这n个请求是限流算法,可以同时处理例如下面这个场景,模拟20个客户端请求。为了减少访问的压力,我们使用Semaphore来限制请求的流量。publicclassSemaphoreTest{publicstaticvoidmain(String[]args){//线程池ExecutorServiceexec=Executors.newCachedThreadPool();//只有5个线程可以同时访问finalSemaphoresemp=newSemaphore(5);//模拟20次客户端访问for(intindex=0;index<20;index++){finalintNO=index;Runnablerun=newRunnable(){publicvoidrun(){try{//获取权限semp.acquire();System.out.println("正在访问:"\+否);Thread.sleep((long)(Math.random()*10000));//访问后释放semp.release();}catch(InterruptedExceptione){}}};exec.执行(运行);}//退出线程池exec.shutdown();}}Guava的RateLimiter实现Guava中RateLimiter有两种实现:Bursty和WarmUpbursty是基于令牌桶的算法实现,如RateLimiterrateLimiter=RateLimiter.create(permitPerSecond);//创建突发实例rateLimiter.acquire();//获得1个许可,当token数量不够时,会阻塞,直到获得。引入jar包
