All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ghp.request.limiter.impl.SlideWindowCountLimiter Maven / Gradle / Ivy

package com.ghp.request.limiter.impl;

import com.ghp.request.annotation.RequestLimit;
import com.ghp.request.limiter.Limiter;

import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLongArray;

/**
 * @author ghp
 * @title 滑动窗口计数限流器
 * @description
 */
public class SlideWindowCountLimiter implements Limiter {

    /**
     * 映射接口的计数器
     * key 为接口的名称,value是接口对应的计数器
     */
    private Map counterMap = new ConcurrentHashMap<>();

    private class SlidingWindowCounter {
        /**
         * 每秒可以处理的请求数量(也就是 QPS)
         */
        private final int qps;
        /**
         * 滑动窗口的大小
         */
        private final int interval;
        /**
         * 记录每一个滑块的请求数量
         */
        private LinkedList sliders;
        /**
         * 记录每一个滑块的时间戳
         */
        private LinkedList timestamps;
        /**
         * 记录窗口中请求的总数量
         */
        private int count;
        /**
         * 滑块的数量
         */
        private final int sliderNumber;


        public SlidingWindowCounter(int qps, int interval, int slider) {
            this.qps = qps;
            this.interval = interval;
            this.sliderNumber = interval / slider;
            this.sliders = new LinkedList<>();
            this.timestamps = new LinkedList<>();
        }

        /**
         * 判断是否允许请求
         *
         * @return true-允许 false-不允许
         */
        public boolean allowRequest() {
            long now = System.currentTimeMillis();

            // 移除过期的滑块
            for (int i = 0; i < sliders.size(); i++) {
                long timestamp = timestamps.isEmpty() ? 0 : timestamps.get(i);
                if (now - timestamp <= interval) {
                    // 由于sliders中是按照时间戳排序的,当发现第一个滑块未过期,后面的就都不会过期
                    break;
                }
                count -= sliders.removeFirst();
                timestamps.removeFirst();
            }

            // 判断请求数量是否超过阈值
            if ((count + 1) > qps) {
                return false;
            }
            // 未超过阈值,更新窗口中的滑块,已经当前窗口的最大请求数
            int lastCount = sliders.isEmpty() ? 0 : sliders.peekLast();
            if (sliders.size() < sliderNumber) {
                // 窗口中滑块数量未满,则直接新增滑块
                sliders.addLast(1);
                timestamps.addLast(now);
            } else {
                // 窗口中滑块数量已满,则直接更新最后一个滑块
                sliders.set(sliderNumber - 1, lastCount + 1);
                timestamps.set(sliderNumber - 1, now);
            }
            count++;
            return true;
        }
    }


    /**
     * 限流
     *
     * @param methodName
     * @param requestLimitAnnotation
     * @return true-发生限流 false-未限流
     */
    @Override
    public boolean limit(String methodName, RequestLimit requestLimitAnnotation) {
        int qps = requestLimitAnnotation.qps();
        int interval = requestLimitAnnotation.interval();
        int slider = requestLimitAnnotation.slider();
        SlidingWindowCounter slidingWindowCounter = counterMap.computeIfAbsent(methodName, key ->
                new SlidingWindowCounter(qps, interval, slider));
        return !slidingWindowCounter.allowRequest();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy