【问题标题】:How can I count the number of events in a given interval?如何计算给定时间间隔内的事件数?
【发布时间】:2018-01-24 07:23:27
【问题描述】:

我需要知道不同事件发生的频率。例如,过去 15 分钟内发生了多少 HTTP 请求。因为可能有大量事件(数百万),所以必须使用有限的内存量。

Java 中是否有任何 util 类可以做到这一点?

如何在 Java 中实现这个 self?

理论使用代码如下:

FrequencyCounter counter = new FrequencyCounter( 15, TimeUnit.Minutes );
...
counter.add();
...
int count = counter.getCount();

编辑:它必须是一个实时值,每分钟可以更改数千次,并且每分钟会查询数千次。基于数据库或文件的解决方案是不可能的。

【问题讨论】:

  • 您还有其他限制吗? (并发方面?)
  • 如果您的应用程序输出了一些日志并且我确定确实如此,为什么您没有另一个进程(实际上可以位于另一台服务器上,只要它可以访问日志文件)分析这些日志以提取此信息?如果您不需要实时获取此信息,您可以想象触发一种批处理作业,该作业每天执行一次计算,或者在负载不高时(例如在夜间)每天执行几次等)
  • 您可以有一个在午夜重置的计数器,并在数据库中输入每分钟的请求数。然后,您需要计算的只是间隔开始和结束时分钟的分数,其余的可以计算出来。这也可以通过每 x 秒存储一次计数来进一步优化,您可以用分钟和更小的间隔计算计数,因此您只需计算 x 秒。
  • @Allan 它必须是实时的。抱歉忘记写了。
  • @GalAbra 是的,需要并发/线程安全。但我认为同步应该解决这个问题。一定不能高并发。

标签: java algorithm counter performancecounter


【解决方案1】:

这是我对这种计数器的实现。默认精度的内存使用量少于 100 字节。内存使用量与事件计数无关。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A counter that counts events within the past time interval. All events that occurred before this interval will be
 * removed from the counter.
 */
public class FrequencyCounter {

    private final long          monitoringInterval;

    private final int[]         details;

    private final AtomicInteger currentCount = new AtomicInteger();

    private long                startInterval;

    private int                 total;

    /**
     * Create a new instance of the counter for the given interval.
     * 
     * @param interval the time to monitor/count the events.
     * @param unit the time unit of the {@code interval} argument
     */
    FrequencyCounter( long interval, TimeUnit unit ) {
        this( interval, unit, 16 );
    }

    /**
     * Create a new instance of the counter for the given interval.
     * 
     * @param interval the time to monitor/count the events.
     * @param unit the time unit of the {@code interval} argument
     * @param precision the count of time slices for the for the measurement
     */
    FrequencyCounter( long interval, TimeUnit unit, int precision ) {
        monitoringInterval = unit.toMillis( interval );
        if( monitoringInterval <= 0 ) {
            throw new IllegalArgumentException( "Interval mus be a positive value:" + interval );
        }
        details = new int[precision];
        startInterval = System.currentTimeMillis() - monitoringInterval;
    }

    /**
     * Count a single event.
     */
    public void increment() {
        checkInterval( System.currentTimeMillis() );
        currentCount.incrementAndGet();
    }

    /**
     * Get the current value of the counter.
     * 
     * @return the counter value
     */
    public int getCount() {
        long currentTime = System.currentTimeMillis();
        checkInterval( currentTime );
        long diff = currentTime - startInterval - monitoringInterval;

        double partFactor = (diff * details.length / (double)monitoringInterval);
        int part = (int)(details[0] * partFactor);
        return total + currentCount.get() - part;
    }

    /**
     * Check the interval of the detail counters and move the interval if needed.
     * 
     * @param time the current time
     */
    private void checkInterval( final long time ) {
        if( (time - startInterval - monitoringInterval) > monitoringInterval / details.length ) {
            synchronized( details ) {
                long detailInterval = monitoringInterval / details.length;
                while( (time - startInterval - monitoringInterval) > detailInterval ) {
                    int currentValue = currentCount.getAndSet( 0 );
                    if( (total | currentValue) == 0 ) {
                        // for the case that the counter was not used for a long time
                        startInterval = time - monitoringInterval;
                        return;
                    }
                    int size = details.length - 1;
                    total += currentValue - details[0];
                    System.arraycopy( details, 1, details, 0, size );
                    details[size] = currentValue;
                    startInterval += detailInterval;
                }
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    我认为实现这一点的最佳方法是使用另一个“计时”线程。
    如果您担心内存量,可以为eventsCounter 的大小添加一个阈值(Integer.MAX_VALUE 似乎是自然的选择)。

    这是一个实现的示例,它也是线程安全的:

    public class FrequencyCounter {
    
        private AtomicInteger eventsCounter = new AtomicInteger(0);
        private int timeCounter;
        private boolean active;
    
        public FrequencyCounter(int timeInSeconds) {
            timeCounter = timeInSeconds;
            active = true;
        }
    
        // Call this method whenever an interesting event occurs
        public int add() {
            if(active) {
                int current;
                do {
                    current = eventsCounter.get();
                } while (eventsCounter.compareAndSet(current, current + 1));
    
                return current + 1;
            }
            else return -1;
        }
    
        // Get current number of events
        public int getCount() {
            return eventsCounter.get();
        }
    
        // Start the FrequencyCounter
        public void run() {
            Thread timer = new Thread(() -> {
                while(timeCounter > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    timeCounter --;
                }
                active = false;
            });
            timer.start();
        }
    }
    

    【讨论】:

      【解决方案3】:

      计划的执行器服务怎么样。

      class TimedValue{
          int startValue;
          int finishedValue;
          TimedValue(int start){
               startValue = start;
          }
      }
      
      List<TimedValue> intervals = new CopyOnWriteArrayList<>();
      
      //then when starting a measurement.
      TimeValue value = new TimedValue();
      
      //set the start value. 
      Callable<TimedValue> callable = ()->{
          //performs the task.
      
          value.setValueAtFinish(getCount());
          return value;
      }
      
      ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
      
      ScheduledFuture<TimedValue> future = executor.schedule(
                                             callable, 
                                             TimeUnit.MINUTES, 
                                             15);
      
      executor.schedule(()->itervals.add(
                              future.get(),
                              TimeUnit.MINUTES, 
                              future.getDelay(TimeUnit.MINUTES
                            );
      

      这是一个有点复杂的方法。

      我可能只有一个List&lt;LoggedValues&gt; 并以固定速率在该列表中累积值。然后可以在您想知道间隔时对其进行检查。

      【讨论】:

        猜你喜欢
        • 2017-09-02
        • 2018-07-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-15
        • 1970-01-01
        • 2012-03-26
        相关资源
        最近更新 更多