我希望能够有效地计算给定时间范围内重复事件的(近似)计数。
示例:我正在尝试重复从主机下载文件。它通常工作正常,但有时在网络拥塞时会发生错误。我不关心这些单一错误。但是,每隔一段时间,主机就会脱机,所以我的所有尝试都会失败。在这种情况下,我想自动停止我的程序再次尝试。
所以我需要找出最近x分钟发生了多少错误。当数字低于某个阈值时,没有任何反应。当它在上面时,我想采取行动。计数不必100%准确,只能足够准确地告诉我是否达到了阈值。
一种简单但无效(O(n)
)的方法是只存储事件的时间戳,然后为每个新事件通过迭代它们并比较它们来确定先前事件的数量。时间戳(直到达到时间范围)。 [旁白]我想这就是SQL引擎为WHERE timestamp BETWEEN NOW() AND INTERVAL X MINUTES
所做的事情,除非它们在列上有索引。 [/撤销]
我想要一个具有常量(O(1)
)复杂度的解决方案。到目前为止,我认为我将保持对每个事件增加1的事件的反击。我还将存储最近发生的时间戳。然后,当一个新的事件发生时,通过一些数学魔术,我可以使用当前时间和存储的时间戳来减少计数器,以反映过去x分钟内发生的事件数量。
不幸的是,我的数学技能不能胜任这项任务。有人可以提供一些提示吗?
答案 0 :(得分:2)
如果您要在最后x分钟内确定故障计数的阈值,为什么不将故障时间戳存储在容量等于阈值的循环缓冲区中?插入显然是O(1),并检查是否有足够的故障,测试最近插入的时间戳是否在最后x分钟内。
答案 1 :(得分:1)
解决此问题的一种简单方法是让每个错误增加一个阈值计数器,并为每个 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来获得您喜欢的进展
答案 2 :(得分:0)
基于@ ebbe-m-pedersen的评论,这就是使用Redis作为数据存储在PHP中的解决方案:
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);
}