【问题标题】:Rate Limiter not working correctly速率限制器无法正常工作
【发布时间】:2016-12-04 17:12:09
【问题描述】:

我正在编写一个算法来接收消息,然后确定它们是否符合既定的消息传递速率。

例如,在任何 50 秒的窗口中发送的消息不超过 5 条。因此,此窗口必须是滚动窗口。

我已经从this post. 实现了这个令牌桶算法但是,我无法让它始终如一地工作。它通过了一些测试用例,但没有通过其他测试用例,这让我觉得这里隐藏了一个逻辑问题。

这是我目前所拥有的:

public class Messaging {
//times in millis
double time_before;
double time_now;
double now;
double time_passed;

double allowance;

//constants
static double per = 50000; // 50 seconds
static double rate = 5; //5 messages

public Messaging(){
    time_before = System.currentTimeMillis();
    allowance = rate;
}

public void onEvent(){
    time_now = System.currentTimeMillis();
    time_passed = time_now - time_before;
    time_before = time_now;

    allowance += time_passed * (rate / per);

    if (allowance > rate){
        allowance = rate;
        System.out.println("Reset Allowance");
    }

    if (allowance < 1.0){
        System.out.println("Discard");
    }else{
        System.out.println("Forward message");
        allowance -= 1.0;
    }


}

但这不起作用!

public static void main(String[] args) {
    Messaging orders = new Messaging();
    for (int i = 0; i < 10; i++) {
        orders.onEvent();
        try {
            Thread.sleep(5000);
        } catch (Exception ex) {

        }        
    }
}

运行上面的代码会得到:

Forward message. Time: 1469830426910
Forward message. Time: 1469830431912
Forward message. Time: 1469830436913
Forward message. Time: 1469830441920
Forward message. Time: 1469830446929
Forward message. Time: 1469830451937
Forward message. Time: 1469830456939
Forward message. Time: 1469830461952
Forward message. Time: 1469830466962
Discard. Time: 1469830471970
Total time passed: 50067

为什么只丢弃最后一条消息?不应该将津贴减少到足以在第 5 条消息后自动失败吗?

我希望获得有关此特定实施的帮助。实际实现将使用没有队列等的专有语言。

【问题讨论】:

  • 我们无法提供任何信息,因为您的输出没有时间戳。我们没有办法知道发生了什么。一个建议:将过滤代码与时间戳解耦,这样无论调试如何,您都可以为其提供一组可重复的测试数据。这将让您逐步完成代码并弄清楚这一点。
  • @JimGarrison 我添加了时间戳输出。我正在测试不同的变体,所以现在只需使用 Thread.sleep 来模拟每 x 秒出现的消息。单步执行代码我可以告诉allowance += time_passed * (rate / per); 行是造成问题的原因,但是由于我在许多实现中都看到了相同的行,所以我想知道我是否在我的特定做错了什么或者这个算法没有为滑动窗口工作。
  • 这不是他的意思——我目前正在写一个答案,希望能解释一下。

标签: java algorithm message-queue


【解决方案1】:

对于速率受限的滑动窗口,您需要将每条消息连同其时间戳一起排队。这样,当队列已满时,您只需丢弃任何新消息。当队列末尾的消息在队列中的停留时间超过了分配的时间时,它们就会离开队列,您就有空间容纳更多新消息。

class MessageBuffer {
    class Message {
        double timestamp;
        String value; // Can be any type you need it to be

        public Message(double timestamp, String value) {
            this.timestamp = timestamp;
            this.value = value;
        }
    }

    static final double WINDOW_SIZE = 5;
    static final double TIME_LIMIT = 50000;

    Queue<Message> messages = new ArrayDeque<>(WINDOW_SIZE);

    public void onEvent(String message) {
        double now = System.currentTimeMillis();

        // If the queue has messages in them that are no longer in the sliding window,
        // remove them from the queue
        while (messages.size() > 0
            && messages.peek().timestamp + TIME_LIMIT > now)
            messages.remove();

        // If there is room in the queue, process this message, otherwise discard it
        if (messages.size() < WINDOW_SIZE) {
            System.out.println("Forward message: " + message);
            messages.add(new Message(now, message));
        } else {
            System.out.println("Discard message: " + message);
        }
    }
}

如果没有这个时间戳信息,您就无法知道消息何时离开滑动窗口,因此您无法知道您的窗口是否已满。仅供参考,您链接到的示例是一个近似值,实际上可以将您限制为 50 秒内少于 5 条消息。

两个小问题:

  • 在面向对象的世界中,类是事物,而不是动作。在大多数情况下,你的类名应该是一个描述它是什么的名词(MessageBuffer),而不是一个描述它做什么的动词(Messaging)。
  • 变量now不应该是成员变量——当你第一次分配它时,它确实代表当前时间,但是一旦方法完成,并且调用了另一个方法,它的值已过时 - 它实际上并不代表当前时间。

【讨论】:

  • 这是有道理的。有没有办法在不使用 Queue 对象的情况下做到这一点?例如,如果您只能访问 HashMap -esque 数据结构?我之所以这样问,是因为最终的实现不会使用 Java,而是使用一种没有 Queue 对象的专有语言——或者实际上是一个数组对象(所以我不能真正构建自己的)。它的主要功能是多索引数组查找——基本上是可以有多个键的 HashMap。
  • 虽然奇​​怪的是该语言没有数组类型,但您可以将 hashmap 视为一个数组 - 只需使用数字索引作为键。然后你可以在上面实现一个队列。这为您提供了一个选择,尽管比近似方法涉及更多的工作。我会看看我是否可以让你最初使用的方法起作用。
【解决方案2】:

您使用的算法是一个近似值 - 平均而言,它将为您提供您正在寻找的速率。在original post 中,作者声称:

'allowance' 最多以每秒 5/8 个单位的速度增长,即每八秒最多五个单位。转发的每条消息都会扣除一个单位,因此每 8 秒发送的消息不能超过 5 条。

这并不完全正确 - 如果津贴从最大值开始,比如 5,它会以每秒 5/8 个单位增长,然后每发送一条消息,都会减去 1。所以余量以每秒 3/8 个单位的速度减少,从 5 开始,我们可以在限制发生之前发送大约 10 条消息。

如果您在一段时间内收到的消息没有您的节流速度那么快,那么它会增加限额。然后,当消息加快速度时,您有一段短暂的时间,您可能最终会在限制开始之前处理2 * rate 消息。如果您将循环更改为执行 20 次迭代,而不是 10 次,您最终会看到节流的行为与您预期的一样。或者,如果您一开始就将余量设置为 0,而不是速率,它会立即节流。

【讨论】:

  • 也就是说,没有办法在没有队列的情况下对滚动时间窗口进行严格的速率限制?
  • 可以将其设为严格的上限 - 不要将 allowance 设置为 rate,而是将其设置为 1。不过,这也会使 平均 费率远低于您的期望费率。最好的情况是,你有 5 条消息进来,相隔 10 秒。他们都熬过去了。最坏的情况,你有 49 秒的休息时间,然后在 1 秒内收到 5 条消息,其中 4 条将被丢弃。您的问题很好地证明了这种近似值的缺点。
猜你喜欢
  • 1970-01-01
  • 2015-02-12
  • 1970-01-01
  • 2013-07-26
  • 1970-01-01
  • 2021-04-06
  • 1970-01-01
  • 1970-01-01
  • 2020-06-24
相关资源
最近更新 更多