计数器
package com.itheima.limit; import java.util.concurrent.*; 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(); } //如果准许响应,打印一个ok System.out.println("ok"); } } } 3)结果分析 3个ok一组呈现,到下一个计数周期之前被阻断 4)优缺点 实现起来非常简单。 控制力度太过于简略,假如1s内限制3次,那么如果3次在前100ms内已经用完,后面的900ms将只能处于阻塞状态,白白浪费掉。 5)应用 使用计数器限流的场景较少,因为它的处理逻辑不够灵活。最常见的可能在web的登录密码验证,输入错误次数冻结一段时间的场景。如果网站请求使用计数器,那么恶意攻击者前100ms吃掉流量计数,使得后续正常的请求被全部阻断,整个服务很容易被搞垮。
漏桶算法
package com.itheima.limit; import java.util.concurrent.*; 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); //无数个请求,i 可以理解为请求的编号 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(); } } } } 3)结果分析 image-20200604162448941 put任务号按照顺序入桶 执行任务匀速的1s一个被处理 因为桶的容量只有3,所以1-3完美执行,4被溢出丢弃,5正常执行 4)优缺点 有效的挡住了外部的请求,保护了内部的服务不会过载 内部服务匀速执行,无法应对流量洪峰,无法做到弹性处理突发任务 任务超时溢出时被丢弃。现实中可能需要缓存队列辅助保持一段时间 5)应用 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 com.itheima.limit; import java.util.concurrent.*; 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()); } } } 3)结果分析 image-20200604162855096 注意结果出现的节奏! 洪峰0-2迅速被执行,说明桶中暂存了3个令牌,有效应对了洪峰 洪峰3,4被间隔性执行,得到了有效的限流 日常请求被匀速执行,间隔均匀 第二波洪峰来临,和第一次一样 请求过去后,令牌最终被均匀颁发,积累到3个后不再上升 4)应用 springcloud中gateway可以配置令牌桶实现限流控制,案例如下: 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
标签:令牌,简单,System,算法,semaphore,println,public,out From: https://www.cnblogs.com/wscp/p/18111557