实现一个简单的漏斗限流

  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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值