【问题标题】:Approximate count of events over given time frame给定时间范围内的大致事件计数
【发布时间】:2013-09-04 14:12:52
【问题描述】:

我想要一种有效的方法来计算给定时间范围内重复事件的(近似)计数。

示例: 我正在尝试从主机重复下载文件。它通常可以正常工作,但有时会在网络拥塞时发生错误。我不在乎这些单一的错误。不过,每隔一段时间,主机就会离线,所以我所有的尝试都失败了。在这种情况下,我想自动停止我的程序重试。

所以我需要找出在过去 x 分钟内发生了多少错误。当数字低于某个阈值时,什么也不会发生。当它在上面时,我想采取行动。计数不必 100% 准确,只要准确到足以告诉我是否达到阈值即可。

一个简单但无效 (O(n)) 的方法是只存储事件的时间戳,然后对于每个新事件,通过迭代它们并比较时间戳来确定先前事件的数量 (直到达到时间框架)。 [旁白] 我想这就是 SQL 引擎对WHERE timestamp BETWEEN NOW() AND INTERVAL X MINUTES 所做的事情,除非它们在列上有索引。 [/旁白]

我想要一个具有恒定 (O(1)) 复杂性的解决方案。到目前为止,我认为我会保留一个事件计数器,每次事件都会增加 1。我还将存储最近发生的时间戳。然后,当一个新事件发生时,通过一些数学魔法,我可以使用当前时间和存储的时间戳来减少计数器,以大致反映过去 x 分钟内发生了多少事件。

很遗憾,我的数学技能无法胜任这项任务。有人可以提供一些提示吗?

【问题讨论】:

  • 相关 - Design a datastructure to return the number of connections to a web server in last 1 minute。如果间隔是固定的,您可以使用基于队列的方法。如果间隔的选项数量很少,则可以有多个指针进入队列,每个间隔一个。或者基于计数的方法应该有效。
  • 对于程序的特定运行来说,“X 分钟”是常数吗?或者您是否有时想知道过去 10 分钟内发生了多少错误,而其他时候想知道过去 30 分钟内发生了多少错误?
  • x 是一个常数。但是,需要在不同的时间范围内跟踪不同类型的事件。

标签: algorithm math data-structures


【解决方案1】:

如果您只是要对最近 x 分钟内的故障计数设置阈值,为什么不将故障时间戳存储在容量等于阈值的循环缓冲区中呢?插入显然是 O(1),为了检查是否有足够的失败,测试最近最少插入的时间戳是否在最近 x 分钟内。

【讨论】:

  • 我不明白这将如何反映阈值。让我概述一下我对这种方法的理解:我有一个缓冲区和第二个变量 $index 保存最近插入的位置。我需要索引变量才能进行有效的插入。为了获取最近插入的时间戳,我可以在$index - 1 处获取该字段,但这并不能告诉我缓冲区之前是否已完全填充(根据阈值的要求)。
  • @t.heintz 测试最近 x 分钟内是否有 F 次故障与测试 Fth 最近的故障是否最多在 x 分钟前相同。您想检查($index + 1) % F 处的字段,该字段将被下一次失败覆盖。
  • 啊,我明白了,我将“最近”误读为“最近”。那么它当然很有意义!十分优雅。唯一的问题是我可能想要一个高阈值,这需要保留一个大缓冲区。
【解决方案2】:

解决此问题的一种简单方法是设置一个阈值计数器,每次 错误 将其加一,并在每次 ok 下载时重置为零。这将跟踪连续失败的下载次数,可能足以解决您的问题。

或者,您可以做某种移动平均线。以下代码是执行此操作的简单方法:

errorRate = errorRate * 0.8
if (error) {
   errorRate = errorRate + 0.2
}

这给出了这样的进展:

Download#   Status  errorRate
     1      ok      0.000
     2      ok      0.000        <=
     3      error   0.200        <= Low rate of errors
     4      ok      0.160        <= 
     5      ok      0.128        
     6      error   0.302        
     7      error   0.442        
     8      ok      0.354
     9      ok      0.283
    10      ok      0.226
    11      error   0.381
    12      error   0.505
    13      error   0.603
    14      ok      0.483
    15      error   0.586
    16      error   0.669        <= High rate of errors shows 
    17      ok      0.535
    18      ok      0.428
    19      ok      0.343
    20      ok      0.274
    21      ok      0.219        <= goes back down after some ok downloads
    etc..

您可以使用因子 0.8 和 0.2 来获得您喜欢的进度

【讨论】:

  • 我给出的例子不是实际的用例。在实际用例中,在每次成功事件后重置错误计数器是不够的。我确实喜欢你的移动平均线方法。然而,它确实需要更新每个事件的错误率,而不仅仅是错误。可能没问题,但我很确定有一个解决方案需要更少的计算。
  • Alternativ - 每次发生错误时向计数器添加一个,然后将此计数器每分钟减少 20 % 或类似
  • 这已经接近了我最初的想法,即按照自上次错误以来经过的时间按比例减少错误计数。事实上,它可能只是这样工作:$error_count = $error_count * (1 - ((time() - $last_timestamp) / $time_interval))
【解决方案3】:

以@ebbe-m-pedersen 的 cmets 为基础,这就是 PHP 中使用 Redis 作为数据存储的解决方案的样子:

function error_handler() {
    $threshold = 100; // how many errors may occur
    $timeframe = 60 * 5; // 5 minutes, how many minutes may pass
    $now = time();

    // get error stats from redis
    $key_base = 'errors:';
    $count = $redis->get($key_base . 'count'); // calculated count
    $last = $redis->get($key_base . 'last'); // timestamp

    // calculate damping factor
    $rate = ($now - $last) / $timeframe;
    $rate = min($rate, 1.0); // $rate may not be larger than 1
    $rate = 1 - $rate; // we need the inverse for multiplying

    // calculate new error count
    $count = (int)($count * $rate);

    if ($count > $threshold) {
        // take action
    }

    // increase error
    $count++;

    // write error stats back to redis
    $redis->set($key_base . 'count', $count);
    $redis->set($key_base . 'last'. $now);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 2020-05-05
    • 2017-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多