速率限制器无法正常工作

时间:2016-07-29 21:58:21

标签: java algorithm message-queue

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

例如,在任何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条消息之后自动失败吗?

我想帮助这个特定的实现。实际的实现将使用没有队列等的专有语言。

2 个答案:

答案 0 :(得分:0)

对于速率受限的滑动窗口,您需要将每条消息及其时间戳排队。这样,当队列已满时,您只需丢弃任何新消息。当队列末尾的消息在那里的时间超过规定的时间时,他们就会离开队列,你就有了更多新消息的空间。

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不应该是成员变量 - 当您第一次分配它时, 表示当前时间,但是一旦方法完成,并且调用另一个方法,该值是陈旧的 - 它实际上并不代表当前时间。

答案 1 :(得分:0)

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

  

&#39;津贴&#39;最多以每秒5/8单位的速度增长,即每8秒最多5个单位。转发的每条消息都会扣除一个单位,因此每八秒钟就不能发送五封以上的消息。

这不完全正确 - 如果津贴以它的最大值开始,比如5,它以每秒5/8个单位增长,然后对于每个发送的消息,减去1。所以津贴以每秒3/8个单位的速度缩小,从5开始,我们可以在节流发生之前发送大约10个消息。

如果您的消息没有以节流率的速度进入,那么它会增加限额。然后,当消息加快时,你会有一段短暂的时间,你可能会在限制开始之前处理2 * rate消息。如果你改变你的循环来进行20次迭代,而不是10次,你就可以了。我会看到最终节流的确表现得像你期望的那样。或者,如果您将allowance设置为0,而不是rate,则会立即限制。