實現一個簡單的漏斗限流

  1. 限流:系統能力有限或出現有惡意請求時,需要組織部分請求,防止系統壓力過大造成宕機。也就是在規定時間內一個操作只能執行有限次數,超出就是非法行爲。

    1. 簡單限流:通過維護某個時間區間,判斷改時間區間內發生的次數。
    2. 漏斗限流:在每次試圖處理請求前,先計算和上一次請求的間隔,並恢復該部分的空間。只有在空間允許的情況下才會放行該請求。
    3. redis不能直接使用以下代碼思路,因爲無法保證操作的原子性,如果爲了原子性加鎖又會降低性能。Redis中提供了Redis-Cell,該模塊使用了漏斗算法,並提供了原子的限流指令。使用非常簡單,指令爲:
      cl.throttle  userId:actionId capacity operation time needCapacity 
      
      返回值包括請求結果,漏斗容量,剩餘容量,重試時間,以及漏斗完全空出來的剩餘時間。
  2. 簡單代碼實現

package com.xliu.chapter1;

import java.sql.Time;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author liuxin
 * @version 1.0
 * @date 2020/4/22 18:44
 */
public class FunnelRateLimeter {

    static class Funnel {
        int capatity;
        float leakingRate;
        int leftQuota;
        long leakingTs;

        public Funnel(int capatity, float leakingRate) {
            this.capatity = capatity;
            this.leakingRate = leakingRate;
            this.leftQuota = capatity;
            this.leakingTs = System.currentTimeMillis();
        }

        void makeSpace() {
            long nowTs = System.currentTimeMillis();
            long deltaTs = nowTs - leakingTs;
            int deltaQuota = (int) (deltaTs * leakingRate);
            //間隔時間很久,溢出了
            if (deltaQuota < 0) {
                this.leftQuota = capatity;
                this.leakingTs = nowTs;
                return;
            }
            if (deltaQuota < 1) {
                return;
            }
            this.leftQuota += deltaQuota;
            this.leakingTs = nowTs;
            if (this.leftQuota > this.capatity) {
                this.leftQuota = this.capatity;
            }
        }

        boolean watering(int quota) {
            makeSpace();
            if (this.leftQuota >= quota) {
                this.leftQuota -= quota;
                return true;
            }
            return false;
        }
    }

    private Map<String, Funnel> funnels = new HashMap<>();

    public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakingRate) {
        String key = String.format("%s:%s", userId, actionKey);
        Funnel funnel = funnels.get(key);
        if (funnel == null) {
            funnel = new Funnel(capacity, leakingRate);
            funnels.put(key, funnel);
        }
        return funnel.watering(1);
    }

    public static void main(String[] args) {
        FunnelRateLimeter funnelRateLimeter = new FunnelRateLimeter();
        String userId = "liuxin";
        String actionId = "do";
        float leakingRate = 5.0f / 1000;
        int capacity = 5;
        for (int i = 0; i < 22; i++) {
            boolean result = funnelRateLimeter.isActionAllowed(userId, actionId, capacity, leakingRate);
            System.out.println("第" + i + "次 :" + result);
            if (!result) {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  1. 實驗結果,參數限定爲每一秒只能執行5次,運行結果可以看到第六次請求被拒絕了,等到200ms,恢復了1空間後,第7次請求成功和預期結果一致。
0次 :true1次 :true2次 :true3次 :true4次 :true5次 :false6次 :false7次 :true8次 :false9次 :false10次 :true11次 :false12次 :false13次 :true14次 :false15次 :false16次 :true17次 :false18次 :false19次 :true20次 :false21次 :false
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章