11. DefaultController&RateLimiterController

DefaultController

DefaultController 是默认使用的流量效果控制器,直接拒绝超出阈值的请求。当 QPS 超过限流规则配置的阈值,新的请求就会被立即拒绝,抛出 FlowException。适用于对系统处理能力明确知道的情况下,比如通过压测确定阈值。实际上我们很难测出这个阈值,因为一个服务可能部署在硬件配置不同的服务器上,并且随时都可能调整部署计划。

public class DefaultController implements TrafficShapingController {

    private static final int DEFAULT_AVG_USED_TOKENS = 0;

    private double count;
    private int grade;

    public DefaultController(double count, int grade) {
        this.count = count;
        this.grade = grade;
    }

    @Override
    public boolean canPass(Node node, int acquireCount) {
        return canPass(node, acquireCount, false);
    }

    @Override
    public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        //当前通过线程数或者node统计的qps数
        int curCount = avgUsedTokens(node);
        //当前数+请求数>阀值数,禁止通过(prioritized默认值为false)
        if (curCount + acquireCount > count) {
            if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
                long currentTime;
                long waitInMs;
                currentTime = TimeUtil.currentTimeMillis();
                waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
                if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
                    node.addWaitingRequest(currentTime + waitInMs, acquireCount);
                    node.addOccupiedPass(acquireCount);
                    sleep(waitInMs);

                    // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
                    throw new PriorityWaitException(waitInMs);
                }
            }
            return false;
        }
        return true;
    }

    private int avgUsedTokens(Node node) {
        if (node == null) {
            return DEFAULT_AVG_USED_TOKENS;
        }
        return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
    }

    private void sleep(long timeMillis) {
        try {
            Thread.sleep(timeMillis);
        } catch (InterruptedException e) {
            // Ignore.
        }
    }
}

 

RateLimiterController

Sentinel 匀速流控效果是漏桶算法结合虚拟队列等待机制实现的,可理解为存在一个虚拟的队列,请求在队列中排队通过,每(count/1000)毫秒可通过一个请求。虚拟队列的好处在于队列非真实存在,多核 CPU 多个请求并行通过时也可以通过,也就是说,实际通过的 QPS 会超过限流阈值的 QPS,但不会超很多。

要配置限流规则使用匀速通过效果控制器 RateLimiterController,则必须配置限流阈值类型为 GRADE_QPS,并且阈值要少于等于 1000。

此外匀速限流器可以使用于削峰填谷。场景:派单系统每隔3S搜集一定区域的订单和司机,然后调用最优匹配系统,适配出最优订单+司机组合。使用Elastic-job每隔3s触发一次,具体操作每个城市启动一个线程调用接口。100多个城市基本上同时调用最优化匹配系统,导致系统压力巨大。这里就可以考虑用匀速限流器实现,将这100个城市有序的匀速的分散,发生请求,有序也保证了每个城市都是间隔3s。

我们来看一下RateLimiterController 的构造方法

public class RateLimiterController implements TrafficShapingController {
    //请求在虚拟队列中的最大等待时间,默认500毫秒
    private final int maxQueueingTimeMs;
    //限流QPS阀值
    private final double count;
    //最近一个请求通过的时间,用于计算下一个请求的预期通过时间
    private final AtomicLong latestPassedTime = new AtomicLong(-1);

    public RateLimiterController(int timeOut, double count) {
        this.maxQueueingTimeMs = timeOut;
        this.count = count;
    }
}

RateLimiterController 实现的 canPass 方法源码如下

    @Override
    public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        //....
        // 假设1s之内限制200个请求,则间隔时间为5ms(count=200 acquireCount=1 costTime=5) 
        long currentTime = TimeUtil.currentTimeMillis();
        long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
        // 当前请求预期通过的时间= 请求通过的间隔时间+最近一个请求通过的时间
        long expectedTime = costTime + latestPassedTime.get();
        // 预期时间<=当前时间,则放行
        if (expectedTime <= currentTime) {
            //存在并发问题,可能导致超过实际的限流阀值。但为性能考虑,误差可以忽略 
            latestPassedTime.set(currentTime);
            return true;
        } else {
            //计算等待时间,超过最大等待时间则直接拒绝
            long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
            if (waitTime > maxQueueingTimeMs) {
                return false;
            } else {
                try {
                    //重置latestPassedTime,二次校验
                    long oldTime = latestPassedTime.addAndGet(costTime);
                    waitTime = oldTime - TimeUtil.currentTimeMillis();
                    if (waitTime > maxQueueingTimeMs) {
                        //如果拒绝的话,回滚一个间隔 
                        latestPassedTime.addAndGet(-costTime);
                        return false;
                    }
                    // 等待一段时间再放行
                    if (waitTime > 0) {
                        Thread.sleep(waitTime);
                    }
                    return true;
                } catch (InterruptedException e) {
                }
            }
        }
        return false;
    }

long costTime = Math.round(1.0 * (acquireCount) / count * 1000);  这里要特别注意一下,匀速限流阀值(count)<=1000才有意义 ,如果count>1000&count<2000,则限流效果和1000是一样的。如果count>2000,则限流间隔costTime=0,实际上就是不限流。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值