Implementation and application of Java current limiting algorithm (counter, leaky bucket, token bucket, sliding window)

1. What is the current limiting algorithm

Current limiting is a protection measure for the system. ie 限制流量请求的频率(每秒处理多少个请求). Generally speaking, when the request traffic exceeds the bottleneck of the system, the excess request traffic is discarded to ensure the availability of the system. That is to say, if you don't let it in, if you let it in, you will be guaranteed to provide services.

2. Counter algorithm

1 Overview

The counter adopts a simple counting operation and is automatically cleared after a period of time.

2. Java implements counter algorithm

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); // 3秒清空一次

        //模拟无限请求从天而降降临
        while (true) {
    
    
            try {
    
    
                //判断计数器
                semaphore.acquire();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            //如果准许响应,打印一个ok
            System.out.println("ok");
        }
    }
}

Result: a group of 3 ok presents, blocked until the next count cycle

3. Advantages and disadvantages

It is very simple to implement.

The control strength is too simple. If 3 times are limited within 1 second, then if the 3 times have been used up within the first 100ms, the next 900ms will only be in a blocked state and will be wasted in vain.

4. Application

There are few scenarios where counter current limiting is used, because its processing logic is not flexible enough.

The most common scenario may be web login password verification and wrong number 冻结一段时间of entries.

If the website requests to use the counter, the malicious attacker eats the traffic count in the first 100ms, so that all subsequent normal requests are blocked, and the entire service is easily brought down.

3. Leaky Bucket Algorithm

1 Overview

The leaky bucket algorithm caches the request in the bucket and serves the process 匀速处理. 超出桶容量的部分丢弃.

The leaky bucket algorithm is mainly used to protect the internal processing business and ensure its stable and rhythmic processing requests, however 无法根据流量的波动弹性调整响应能力. In reality, similar service halls with limited capacity have opened fixed service windows.

insert image description here

2. Java implements leaky bucket algorithm

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. Advantages and disadvantages

It effectively blocks external requests and protects internal services from overload.
Internal services are executed at a uniform speed, unable to cope with traffic peaks, and unable to flexibly handle unexpected tasks.
The task is discarded when it times out. In reality, it may be necessary to maintain the cache queue for a period of time.

4. Application

Current limiting in nginx is a typical application of the leaky bucket algorithm. The configuration example is as follows:

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;
		}
}

4. Token Bucket Algorithm

1 Overview

The token bucket algorithm can be regarded as an upgrade of the leaky bucket algorithm. It can not only limit the traffic one step, but also solve the problem that the leaky bucket cannot be elastically scaled to process requests. Reflected in reality, access control cards are issued at the gates of similar service halls. The release is at a uniform speed. When the request is small, 令牌可以缓存起来it is used for one-time batch acquisition when the traffic bursts. The internal service window is unlimited.

insert image description here

2. Java implements the token bucket algorithm

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());
        }
    }
}

Pay attention to the rhythm of the results!
Flood peaks 0-2 were executed quickly, indicating that 3 tokens were temporarily stored in the bucket, which effectively dealt with the flood peaks.
Flood peaks 3 and 4 were executed at intervals, resulting in effective current limiting. Daily requests were executed at a uniform speed, and the second wave of flood peaks
was evenly spaced
Coming, the same as the first time
. After the request passed, the tokens were finally issued evenly, and no increase after accumulating to 3

3. Application

The gateway in springcloud can be configured with a token bucket to implement current limiting control. The case is as follows:

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

Five, sliding window algorithm

1 Overview

The sliding window can be understood as that 细分之后的计数器the counter roughly limits the number of visits within 1 minute, and the sliding window current limit divides 1 minute into multiple segments, which not only requires that the number of requests in the entire 1 minute is less than the upper limit, but also requires that the number of requests for each segment is also be less than the upper limit. It is equivalent to splitting the original counting cycle into multiple segments. more refined.
insert image description here

2. Java implements sliding window algorithm

import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Window {
    
    
    //整个窗口的流量上限,超出会被限流
    final int totalMax = 5;
    //每片的流量上限,超出同样会被拒绝,可以设置不同的值
    final int sliceMax = 5;
    //分多少片
    final int slice = 3;
    //窗口,分3段,每段1s,也就是总长度3s
    final LinkedList<Long> linkedList = new LinkedList<>();
    //计数器,每片一个key,可以使用HashMap,这里为了控制台保持有序性和可读性,采用TreeMap
    Map<Long,AtomicInteger> map = new TreeMap();
    //心跳,每1s跳动1次,滑动窗口向前滑动一步,实际业务中可能需要手动控制滑动窗口的时机。
    ScheduledExecutorService service = Executors.newScheduledThreadPool(1);

    //获取key值,这里即是时间戳(秒)
    private Long getKey(){
    
    
        return System.currentTimeMillis()/1000;
    }

    public Window(){
    
    
        //初始化窗口,当前时间指向的是最末端,前两片其实是过去的2s
        Long key = getKey();
        for (int i = 0; i < slice; i++) {
    
    
            linkedList.addFirst(key-i);
            map.put(key-i,new AtomicInteger(0));
        }
        //启动心跳任务,窗口根据时间,自动向前滑动,每秒1步
        service.scheduleAtFixedRate(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                Long key = getKey();
                //队尾添加最新的片
                linkedList.addLast(key);
                map.put(key,new AtomicInteger());

                //将最老的片移除
                map.remove(linkedList.getFirst());
                linkedList.removeFirst();

                System.out.println("step:"+key+":"+map);;
            }
        },1000,1000,TimeUnit.MILLISECONDS);
    }

    //检查当前时间所在的片是否达到上限
    public boolean checkCurrentSlice(){
    
    
        long key = getKey();
        AtomicInteger integer = map.get(key);
        if (integer != null){
    
    
            return integer.get() < totalMax;
        }
        //默认允许访问
        return true;
    }

    //检查整个窗口所有片的计数之和是否达到上限
    public boolean checkAllCount(){
    
    
        return map.values().stream().mapToInt(value -> value.get()).sum()  < sliceMax;
    }

    //请求来临....
    public void req(){
    
    
        Long key = getKey();
        //如果时间窗口未到达当前时间片,稍微等待一下
        //其实是一个保护措施,放置心跳对滑动窗口的推动滞后于当前请求
        while (linkedList.getLast()<key){
    
    
            try {
    
    
                Thread.sleep(200);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        //开始检查,如果未达到上限,返回ok,计数器增加1
        //如果任意一项达到上限,拒绝请求,达到限流的目的
        //这里是直接拒绝。现实中可能会设置缓冲池,将请求放入缓冲队列暂存
        if (checkCurrentSlice() && checkAllCount()){
    
    
            map.get(key).incrementAndGet();
            System.out.println(key+"=ok:"+map);
        }else {
    
    
            System.out.println(key+"=reject:"+map);
        }
    }


    public static void main(String[] args) throws InterruptedException {
    
    
        Window window = new Window();
        //模拟10个离散的请求,相对之间有200ms间隔。会造成总数达到上限而被限流
        for (int i = 0; i < 10; i++) {
    
    
            Thread.sleep(200);
            window.req();
        }
        //等待一下窗口滑动,让各个片的计数器都置零
        Thread.sleep(3000);
        //模拟突发请求,单个片的计数器达到上限而被限流
        System.out.println("---------------------------");
        for (int i = 0; i < 10; i++) {
    
    
            window.req();
        }

    }
}

Simulating scattered requests will cause counts in each chip. After the total number reaches the upper limit, no response will be made and the current limit will take effect.
Simulating bursty traffic requests will cause the traffic count of a single chip to reach the upper limit. is current limited in response.

3. Application

The sliding window algorithm is used in the process of sending packets in the tcp protocol. In real web scenarios, the flow control can be fine-tuned to solve the problem that the control of the counter model is too rough.

References

https://blog.csdn.net/oneknows/article/details/119442358
https://blog.csdn.net/zhanghanlun/article/details/104383038
https://blog.csdn.net/chyh741/article/details/124085421

Guess you like

Origin blog.csdn.net/A_art_xiang/article/details/132045440