限流算法

限流是对系统的一种保护措施。即限制流量请求的频率(每秒处理多少个请求)。一般来说,当请求流量超过系统的瓶颈,则丢弃掉多余的请求流量,保证系统的可用性。即要么不放进来,放进来的就保证提供服务。

计数器

计数器采用简单的计数操作,到一段时间节点后自动清零

package cache;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class Counter {
	public static void main(String[] args) {
		//计数器,这里用信号量实现
		final Semaphore semaphore = new Semaphore(3);
		
		ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
		service.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				semaphore.release(3);
			}
		}, 3000, 3000, TimeUnit.MILLISECONDS);
		
		
		//模拟无数个请求从天而降
		while(true) {
			try {
				semaphore.acquire();
			}catch(InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("ok");
		}
	}
}

优缺点

  • 实现起来非常简单。
  • 控制力度太过于简略,假如1s内限制3次,那么如果3次在前100ms内已经用完,后面的900ms将只能处于阻
    塞状态,白白浪费掉。

使用计数器限流的场景较少,因为它的处理逻辑不够灵活。最常见的可能在web的登录密码验证,输入错误次数冻结一段时间的场景。如果网站请求使用计数器,那么恶意攻击者前100ms吃掉流量计数,使得后续正常的请求被全部阻断,整个服务很容易被搞垮。

漏桶算法

漏桶算法将请求缓存在桶中,服务流程匀速处理。超出桶容量的部分丢弃。漏桶算法主要用于保护内部的处理业务,保障其稳定有节奏的处理请求,但是无法根据流量的波动弹性调整响应能力。现实中,类似容纳人数有限的服务大厅开启了固定的服务窗口。

package cache;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Barrel {
	public static void main(String[] args) {
		//桶,用阻塞队列实现,容量为3
		final LinkedBlockingQueue<Integer> que = new LinkedBlockingQueue<>(3);
		
		//定时器,相当于服务的窗口,2s处理一个
		ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
		service.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				int v = que.poll();
				System.out.println("处理:"+v);
			}
		}, 2000, 2000, TimeUnit.MILLISECONDS);
		
		int i = 0;
		while(true) {
			i++;
			try {
				System.out.println("put:"+i);
				//如果是put,会一直等待桶中有空闲位置,不会丢弃
				//que.put(i);
				//等待1s如果进不了桶,就溢出丢弃
				que.offer(i,1000,TimeUnit.MILLISECONDS);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
}

优缺点

  • 有效的挡住了外部的请求,保护了内部的服务不会过载
  • 内部服务匀速执行,无法应对流量洪峰,无法做到弹性处理突发任务
  • 任务超时溢出时被丢弃。现实中可能需要缓存队列辅助保持一段时间

应用

nginx中的限流是漏桶算法的典型应用,配置案例如下:

http {
    #$binary_remote_addr 表示通过remote_addr这个标识来做key,也就是限制同一客户端ip地址。
	#zone=one:10m 表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息。    
	#rate=1r/s 表示允许相同标识的客户端每秒1次访问    
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    server {
        location /limited/ {
        #zone=one 与上面limit_req_zone 里的name对应。
		#burst=5 缓冲区,超过了访问频次限制的请求可以先放到这个缓冲区内,类似代码中的队列长度。
		#nodelay 如果设置,超过访问频次而且缓冲区也满了的时候就会直接返回503,如果没有设置,则所有请求会等待排队,类似代码中的put还是offer。  
        limit_req zone=one burst=5 nodelay;
    }
}

令牌桶

令牌桶算法可以认为是漏桶算法的一种升级,它不但可以将流量做一步限制,还可以解决漏桶中无法弹性伸缩处理请求的问题。体现在现实中,类似服务大厅的门口设置门禁卡发放。发放是匀速的,请求较少时,令牌可以缓存起来,供流量爆发时一次性批量获取使用。而内部服务窗口不设限。

package cache;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class Token {

	public static void main(String[] args) throws InterruptedException {
		//令牌桶,信号量实现,容量为3
		final Semaphore semaphore = new Semaphore(3);
		
		//定时器,1s一个,匀速颁发令牌
		ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
		service.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				if(semaphore.availablePermits() < 3) {
					semaphore.release();
				}
				System.out.println("令牌数:"+semaphore.availablePermits());
			}
		}, 1000, 1000, TimeUnit.MILLISECONDS);
		
		//等待,等候令牌桶储存
		Thread.sleep(5);
		
		//模拟洪峰5个请求,前3个迅速响应,后两个排队
		for (int i = 0; i < 5; i++) {
			semaphore.acquire();
			System.out.println("洪峰:"+i);
		}
		
		//模拟日常请求,2s一个
		for (int i = 0; i < 3; i++) {
			Thread.sleep(1000);
			semaphore.acquire();
			System.out.println("日常:"+i);
			Thread.sleep(1000);
		}
		
		//再次洪峰
		for (int i = 0; i < 5; i++) {
			semaphore.acquire();
			System.out.println("洪峰:"+i);
		}
		
		//检查令牌桶的数量
		for (int i = 0; i < 5; i++) {
			Thread.sleep(2000);
			System.out.println("令牌剩余:"+semaphore.availablePermits());
		}
	}
}

运行结果:

洪峰0-2迅速被执行,说明桶中暂存了3个令牌,有效应对了洪峰
洪峰3,4被间隔性执行,得到了有效的限流
日常请求被匀速执行,间隔均匀
第二波洪峰来临,和第一次一样
请求过去后,令牌最终被均匀颁发,积累到3个后不再上升

应用

springcloudgateway可以配置令牌桶实现限流控制,案例如下:

cloud:
   gateway:
     routes:
     ‐ id: limit_route
       uri: http://localhost:8080/test
       filters:
       ‐ name: RequestRateLimiter
         args:
          #限流的key,ipKeyResolver为spring中托管的Bean,需要扩展KeyResolver接口  
           key‐resolver: '#{@ipResolver}'
           #令牌桶每秒填充平均速率,相当于代码中的发放频率
           redis‐rate‐limiter.replenishRate: 1
           #令牌桶总容量,相当于代码中,信号量的容量
           redis‐rate‐limiter.burstCapacity: 3

滑动窗口

滑动窗口可以理解为细分之后的计数器,计数器粗暴的限定1分钟内的访问次数,而滑动窗口限流将1分钟拆为多个段,不但要求整个1分钟内请求数小于上限,而且要求每个片段请求数也要小于上限。相当于将原来的计数周期做了多个片段拆分。更为精细。

实现:

package cache;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class Token {

	public static void main(String[] args) throws InterruptedException {
		//令牌桶,信号量实现,容量为3
		final Semaphore semaphore = new Semaphore(3);
		
		//定时器,1s一个,匀速颁发令牌
		ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
		service.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				if(semaphore.availablePermits() < 3) {
					semaphore.release();
				}
				System.out.println("令牌数:"+semaphore.availablePermits());
			}
		}, 1000, 1000, TimeUnit.MILLISECONDS);
		
		//等待,等候令牌桶储存
		Thread.sleep(5);
		
		//模拟洪峰5个请求,前3个迅速响应,后两个排队
		for (int i = 0; i < 5; i++) {
			semaphore.acquire();
			System.out.println("洪峰:"+i);
		}
		
		//模拟日常请求,2s一个
		for (int i = 0; i < 3; i++) {
			Thread.sleep(1000);
			semaphore.acquire();
			System.out.println("日常:"+i);
			Thread.sleep(1000);
		}
		
		//再次洪峰
		for (int i = 0; i < 5; i++) {
			semaphore.acquire();
			System.out.println("洪峰:"+i);
		}
		
		//检查令牌桶的数量
		for (int i = 0; i < 5; i++) {
			Thread.sleep(2000);
			System.out.println("令牌剩余:"+semaphore.availablePermits());
		}
	}
}

模拟零零散散的请求,会造成每个片里均有计数,总数达到上限后,不再响应,限流生效

再模拟突发的流量请求,会造成单片流量计数达到上限,不再响应而被限流

应用

滑动窗口算法,在tcp协议发包过程中被使用。在web现实场景中,可以将流量控制做更细化处理,解决计数器模型控制力度太粗暴的问题。

posted @ 2022-02-10 22:39  请务必优秀  阅读(132)  评论(0编辑  收藏  举报