限流算法的介绍与代码实现

1,032 阅读6分钟

这是我参与8月更文挑战的第28天,活动详情查看: 8月更文挑战

限流在我们生活中也听到的比较多,比如去景区旅游时,需要提前预约景区的门票,其实这也是种限流措施,在软件中如果访问量比较大的时候,系统无法承受住一下比较大的访问流量,如果我们不对出现这样的问题做一些限流操作的话,那我们的系统可能就会造成整个系统不可用,限流是对系统的一种保护措施,当流量请求访问超过一定数量的时候,就需要限制流量的请求,将多余的请求抛弃掉,来保障系统的可用性,限流在网关中用的比较多,网关需要限制过多的流量打到后端请求,造成后端的应用程序无法支持那么大的流量请求可宕掉,常用的限流算法有:计数器、漏桶算法、令牌桶、滑动窗口这几种限流算法。

计数器

  • 概述

对指定时间段内访问的次数做一个计数,当访问量达到限制的次数之后,在该时间段内后面的访问就不允许访问,只能等到下一个时间段才可以访问。这种算法实现起来简单,但是当在一个时间段内,有大量的请求来的时候,会在瞬间丢弃大量的请求,例如1秒限制访问5次,如果前5次在0.1秒内都处理完成后,后面的0.9秒都没法请求访问,服务也处理空闲状态,这样就造成资源白白的浪费。 采用计数器的场景,在发送验证码的时候用的比较多,防止恶意刷验证码,造成验证码资源浪费,这个时候一般就会限制1分钟内只能够发送一次,当输入次数达到一次的次数后,就会冻结账号的登录,隔一段时间后才可以减冻,重新登录。

  • 算法实现介绍

在这里实现的时候借助java并发包里的Semaphore类来实现,在代码中开启一个线程,每1秒钟重置Semaphore的次数,然后采用while循环来模拟数据不停的请求,当获取到次数后,在执行后续的请求,没有拿到次数就阻塞等待下一个周期

  • 简易代码
    final Semaphore semaphore = new Semaphore(5);

        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                semaphore.release(5);
            }
        }, 1000, 1000, TimeUnit.MILLISECONDS);

        //模拟无数个请求从天而降
        while (true) {
          //判断计数器
          semaphore.acquire();
    }

漏桶算法

  • 概述

漏桶算法就像我们平时接水的水桶一样,在下面有一个小洞,可以将水匀速的漏出,当我们将这个桶放到水龙头下接水的时候,当水龙头开的比较大时,很快就将水桶接满,这个时候水就会溢出,当水龙头水比较小或者没有时,下面的洞会慢慢的把水桶里的水漏干。这里借用网上的一张图 081225378155003.png 这种算法可以有效的挡住外部的请求,当请求超过我桶的容量时,就将多余的请求扔掉,但是因为内部是匀速请求,当出现业务高峰期时,会将多余的请求扔掉,无法应对流量高峰期,当业务高峰期时会扔掉很多请求,对业务不太友好。     漏桶算法在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; 
        } 
    }
  • 算法实现介绍

才用LinkedBlockingQueue来模拟桶的容量,然后采用一个线程来每隔一秒钟将LinkedBlockingQueue队列中的元素取出代表请求被执行,然后采用while循环来不停的往LinkedBlockingQueue队列中添加元素,如果LinkedBlockingQueue队列没有满就可以添加成功,当队列满了就无法继续添加数据,就将该元素扔掉。

  • 简易代码
        //桶,用阻塞队列实现,容量为5
        final LinkedBlockingQueue<Integer> que = new LinkedBlockingQueue(5);
        //定时器,相当于服务的窗口,1s处理一个
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate((Runnable) () -> {
            int v = que.poll();
            System.out.println("处理:" + v);
        }, 1000, 1000, TimeUnit.MILLISECONDS);
        int i = 0;
        while (true) {
            i++;
              System.out.println("put:" + i);
              que.offer(i, 1000, TimeUnit.MILLISECONDS);
        }

令牌桶

  • 概述

令牌桶是漏桶算法的一种升级,漏桶是限制桶漏出的大小,而令牌就是限制水龙头每次产出的水的流速,然后将水龙头的水流入到桶中,桶可以根据水的多少来扩容,然后后面的请求不停的从桶中舀出水来处理。这里选用网上的一张图片来描述该算法的实现逻辑 src=http___image.mamicode.com_info_201604_20180110190723245115.png&refer=http___image.mamicode.jpg 在实际应用中SpringCloud的网关可以配置令牌桶算法来实现限流控制

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
  • 算法实现介绍

实现的时候也采用Semaphore来做一个桶,设置桶的最大容量,然后开启一个线程匀速的增加Semaphore桶的容量,当小于指定容量的时候才往里面存放令牌,然后模拟请求,从Semaphore获取信号,当获取到后才进行后续的执行逻辑,没有获取到就一直等待,直到有令牌产生。

  • 简易代码
        final Semaphore semaphore = new Semaphore(3);
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(() -> {
            if (semaphore.availablePermits() < 3) {
                semaphore.release();
            } 
        }, 1000, 1000, TimeUnit.MILLISECONDS);

滑动窗口

  • 概述

滑动窗口可以理解为将一个周期内的限制次数拆分为多个片段,每个片段的处理请求都要小于该段的请求上限,对于请求的控制更加精细,采用网上的一张图,大家应该一看就比较明白,每次窗口不停的往前滑动 18d8bc3eb13533fa0e6012a26c24561943345b62.jpg

滑动窗口算法在TCP协议中发包读包过程中,用的比较多