java單機接口限流處理方案詳解

對單機服務做接口限流的處理方案

簡單說就是設定某個接口一定時間隻接受固定次數的請求,比如/add接口1秒最多接收100次請求,多的直接拒絕,這個問題很常見,場景也好理解,直接上代碼:

/**
 * 單機限流
 */
@Slf4j
public class FlowLimit {

 //接口限流上限值和限流時間緩存
    private static Cache<String, AtomicLong> localCache = CacheBuilder.newBuilder().maximumSize(100)
            .expireAfterWrite(1000, TimeUnit.MILLISECONDS).build();

 //每個接口的上限緩存
    private static Map<String, Long> maxFlowLimitMap = new ConcurrentHashMap<>();

    private static final FlowLimit instance = new FlowLimit();

 //這塊的目的是初始化每個接口的上限,下面的變量:apiFlowLimitConfigure 
 //實際使用的時候應該是從db或者其他地方獲取設置的每個接口的限流上限值,
 //這樣可以動態的調整接口上限,比如直接修改db,不用發佈,就可以調整接口限流值
    static {
        new ScheduledThreadPoolExecutor(1, runnable -> {
            Thread thread = new Thread(runnable, "api-flowLimit-configure");
//            thread.setDaemon(true);
            return thread;
        }).scheduleAtFixedRate(() -> {
            try {
                String apiFlowLimitConfigure = "{\"doAdd\":100}";  //表示/doAdd接口1秒接受100次請求
                Map mapObj = JSONObject.parseObject(apiFlowLimitConfigure, Map.class);
                if(mapObj != null){
                    mapObj.forEach((key, value) -> {
                        if(value != null){
                            instance.setMaxFlowLimit(key.toString(), new Long(value.toString()));
                        }else{
                            log.warn(key + " - 設置接口限流發現限流值為空,設置默認值");
                            instance.setMaxFlowLimit(key.toString(), 100L);
                        }
                    });
                }
            } catch (Exception e) {
                log.error("設置接口限流出現異常{}", e);
            }
        }, 0, 3, TimeUnit.SECONDS);
    }

    public static FlowLimit getInstance() {
        return instance;
    }

    private FlowLimit setMaxFlowLimit(String key, Long maxFlowLimit) {
        maxFlowLimitMap.put(key, maxFlowLimit);
        return this;
    }

    public Boolean isAvailable(String key) {
        return checkAvailable(key, 1L);
    }

    public Boolean isAvailable(String key, Long incrNum) {
        return checkAvailable(key, incrNum);
    }

    private Boolean checkAvailable(String key, Long incrNum){
        Long maxFlowLimit = maxFlowLimitMap.get(key);
        if (null == maxFlowLimit || maxFlowLimit == 0) {
            return true;
        }
        if (incrAndGet(key, incrNum) <= maxFlowLimit.longValue()) {
            return true;
        } else {
            return false;
        }
    }

    private long incrAndGet(String key, final long n) {
        try {
            return localCache.get(key, new Callable<AtomicLong>() {
                @Override
                public AtomicLong call() throws Exception {
                    return new AtomicLong(0);
                }
            }).addAndGet(n);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return 0;
    }

    public long get(String key) {
        return incrAndGet(key, 0);
    }

}

上面這個就是單機限流邏輯,代碼不難,感覺沒必要使用ConcurrentHashMap,不過感覺無所謂瞭
這段代碼隻需要加在需要限流的接口前面:

@GetMapping("doAdd")
public Boolean doAdd(){
    FlowLimit instance = FlowLimit.getInstance(); //單例獲取
    //查看當前的/doAdd接口是否觸發瞭限流
    Boolean flowLimitFlag = instance.isAvailable("doAdd");
    if(!flowLimitFlag){
        log.warn("觸發限流,拒絕請求");
        return false;
    }
    //doAdd()
    return true;
}

調用實例如上

上面這個限流其實是有一定問題的:比如你限定10秒鐘1000次,在第9.9秒的時候,突然進來1000個請求,然後第10.1秒的時候,攻擊者,又進來1000次請求,這樣,0.2秒之內,進來2000次請求。。。
所以這個時候就需要令牌桶或者其他算法瞭,其他算法後面再寫

沒怎麼仔細測試,有問題歡迎提出,共同學習

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: