三种限流算法的实现和比较

三种限流算法的实现,我们在讲到hystrix或者sentinel的时候,绕不开一个问题,那就是他们的底层是如何实现限流。今天借这个机会来讲讲他们的底层实现。

关于服务降级限流一篇比较好的文章:服务降级限流

限流算法一: 滑动时间窗口限流算法

这个有点类似于TCP的滑动窗口,也是类似于一个滑动窗口里面最多有多少个请求,比如我们每分钟最多只能通过1w个请求,我们在设计实现的时候,只需要维持滑动窗口大小为1w,超过了这个数量我们直接拒绝。实现如下,我们采用队列来实现。这里的代码由 这个大佬 提供,以下是基于单机版的,如果是基于分布式的,我们可以借助redis来实现。


import java.time.LocalTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 滑动时间窗口限流工具
 * 本限流工具只适用于单机版,如果想要做全局限流,可以按本程序的思想,用redis的List结构去实现
 *
 * @author: https://www.cnblogs.com/dijia478/p/13807826.html
 * @date 2020-10-13 10:53
 */
public class SlideWindow {

    /** 队列id和队列的映射关系,队列里面存储的是每一次通过时候的时间戳,这样可以使得程序里有多个限流队列 */
    private volatile static Map<String, List<Long>> MAP = new ConcurrentHashMap<>();

    private SlideWindow() {}

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            // 任意10秒内,只允许2次通过
            System.out.println(LocalTime.now().toString() + SlideWindow.isGo("ListId", 2, 10000L));
            // 睡眠0-10秒
            Thread.sleep(1000 * new Random().nextInt(10));
        }
    }

    /**
     * 滑动时间窗口限流算法
     * 在指定时间窗口,指定限制次数内,是否允许通过
     *
     * @param listId     队列id
     * @param count      限制次数
     * @param timeWindow 时间窗口大小
     * @return 是否允许通过
     */
    public static synchronized boolean isGo(String listId, int count, long timeWindow) {
        // 获取当前时间
        long nowTime = System.currentTimeMillis();
        // 根据队列id,取出对应的限流队列,若没有则创建
        List<Long> list = MAP.computeIfAbsent(listId, k -> new LinkedList<>());
        // 如果队列还没满,则允许通过,并添加当前时间戳到队列开始位置
        if (list.size() < count) {
            list.add(0, nowTime);
            return true;
        }

        // 队列已满(达到限制次数),则获取队列中最早添加的时间戳
        Long farTime = list.get(count - 1);
        // 用当前时间戳 减去 最早添加的时间戳
        if (nowTime - farTime <= timeWindow) {
            // 若结果小于等于timeWindow,则说明在timeWindow内,通过的次数大于count
            // 不允许通过
            return false;
        } else {
            // 若结果大于timeWindow,则说明在timeWindow内,通过的次数小于等于count
            // 允许通过,并删除最早添加的时间戳,将当前时间添加到队列开始位置
            list.remove(count - 1);
            list.add(0, nowTime);
            return true;
        }
    }
}

限流算法二:漏桶算法

关于漏桶算法和令牌桶算法的区别和使用方法还有计数算法的优劣比较:

几种限流算法的优劣式比较

public class LeakyBucket {
        public long timeStamp = System.currentTimeMillis();  // 当前时间
        public long capacity; // 桶的容量
        public long rate; // 水漏出的速度
        public long water; // 当前水量(当前累积请求数)

        public boolean grant() {
            long now = System.currentTimeMillis();
            // 先执行漏水,计算剩余水量
            water = Math.max(0, water - (now - timeStamp) * rate); 
            
            timeStamp = now;
            if ((water + 1) < capacity) {
                // 尝试加水,并且水还未满
                water += 1;
                return true;
            } else {
                // 水满,拒绝加水
                return false;
        }
    }
}

限流算法三:令牌桶算法

/**
 * @author https://www.jianshu.com/p/f76730be59e0
 * @since 18/1/3 上午9:45.
 * 限流器
 */
public class RateLimiter{

    private volatile int token;
    private final int originToken;

    private static Unsafe unsafe = null;
    private static final long valueOffset;
    private final Object lock = new Object();

    static {
        try {
            // 应用开发中使用unsafe对象必须通过反射获取
            Class<?> clazz = Unsafe.class;
            Field f = clazz.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(clazz);
            valueOffset = unsafe.objectFieldOffset(RateLimiter.class.getDeclaredField("token"));
        } catch (Exception ex) {throw new Error(ex);}
    }

    public RateLimiter(int token){
        this.originToken = token;
        this.token = token;
    }

    /**
     * 获取一个令牌
     */
    public boolean acquire(){
        int current = token;
        if(current<=0){
            // 保证在token已经用光的情况下依然有获取竞争的能力
            current = originToken;
        }

        long expect = 1000;// max wait 1s
        long future = System.currentTimeMillis()+expect;
        while(current>0){
            if(compareAndSet(current, current-1)){
                return true;
            }
            current = token;
            if(current<=0 && expect>0){
                // 在有效期内等待通知
                synchronized (lock){
                    try {
                        lock.wait(expect);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                current = token;
                if(current<=0){
                    current = originToken;
                }
                expect = future - System.currentTimeMillis();
            }
        }
        return false;
    }

    private boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    /**
     * 补充令牌
     */
    public void supplement(final ExecutorService executorService){
        this.token = originToken;
        executorService.execute(() -> {
            synchronized (lock){
                lock.notifyAll();
            }
        });
    }

}

怎么在面试中把这些原理融入到你的项目中,关注我,给你答案。

三种限流算法的实现和比较_第1张图片

你可能感兴趣的:(实习日记,漏桶算法,令牌桶算法实现,滑动窗口算法,限流算法,令牌桶算法)