/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
/** * This class maintains a thread safe hash map that has timestamp-based buckets followed by a string for a key, and a * counter for a value. each time the increment() method is called it adds the key if it does not exist, increments its * value and returns it. a maintenance thread cleans up keys that are prefixed by previous timestamp buckets.
*/ publicclass TimeBucketCounter {
privatestaticfinal Log log = LogFactory.getLog(TimeBucketCounter.class); privatestaticfinal StringManager sm = StringManager.getManager(TimeBucketCounter.class);
/** * Map to hold the buckets
*/ privatefinal ConcurrentHashMap<String,AtomicInteger> map = new ConcurrentHashMap<>();
/** * Milliseconds bucket size as a Power of 2 for bit shift math, e.g. 16 for 65_536ms which is about 1:05 minute
*/ privatefinalint numBits;
/** * Ratio of actual duration to config duration
*/ privatefinaldouble ratio;
/** * The future allowing control of the background processor.
*/ private ScheduledFuture<?> maintenanceFuture; private ScheduledFuture<?> monitorFuture; privatefinal ScheduledExecutorService executorService; privatefinallong sleeptime;
/** * Creates a new TimeBucketCounter with the specified lifetime. * * @param bucketDuration duration in seconds, e.g. for 1 minute pass 60 * @param executorService the executor service which will be used to run the maintenance
*/ public TimeBucketCounter(int bucketDuration, ScheduledExecutorService executorService) {
this.executorService = executorService;
int durationMillis = bucketDuration * 1000;
int bits = 0; int pof2 = nextPowerOf2(durationMillis); int bitCheck = pof2; while (bitCheck > 1) {
bitCheck = pof2 >> ++bits;
}
/** * Increments the counter for the passed identifier in the current time bucket and returns the new value. * * @param identifier an identifier for which we want to maintain count, e.g. IP Address * * @return the count within the current time bucket
*/ publicfinalint increment(String identifier) {
String key = getCurrentBucketPrefix() + "-" + identifier;
AtomicInteger ai = map.computeIfAbsent(key, v -> new AtomicInteger()); return ai.incrementAndGet();
}
/** * Calculates the current time bucket prefix by shifting bits for fast division, e.g. shift 16 bits is the same as * dividing by 65,536 which is about 1:05m. * * @return The current bucket prefix.
*/ publicfinalint getCurrentBucketPrefix() { return (int) (System.currentTimeMillis() >> this.numBits);
}
publicint getNumBits() { return numBits;
}
/** * The actual duration may differ from the configured duration because it is set to the next power of 2 value in * order to perform very fast bit shift arithmetic. * * @return the actual bucket duration in milliseconds
*/ publicint getActualDuration() { return (int) Math.pow(2, getNumBits());
}
/** * Returns the ratio between the configured duration param and the actual duration which will be set to the next * power of 2. We then multiply the configured requests param by the same ratio in order to compensate for the added * time, if any. * * @return the ratio, e.g. 1.092 if the actual duration is 65_536 for the configured duration of 60_000
*/ publicdouble getRatio() { return ratio;
}
/** * Returns the ratio to the next power of 2 so that we can adjust the value.
*/ staticdouble ratioToPowerOf2(int value) { double nextPO2 = nextPowerOf2(value); return Math.round((1000 * nextPO2 / value)) / 1000d;
}
/** * Returns the next power of 2 given a value, e.g. 256 for 250, or 1024, for 1000.
*/ staticint nextPowerOf2(int value) { int valueOfHighestBit = Integer.highestOneBit(value); if (valueOfHighestBit == value) { return value;
}
return valueOfHighestBit << 1;
}
/** * When we want to test a full bucket duration we need to sleep until the next bucket starts. * * @return the number of milliseconds until the next bucket
*/ publiclong getMillisUntilNextBucket() { long millis = System.currentTimeMillis(); long nextTimeBucketMillis = ((millis + (long) Math.pow(2, numBits)) >> numBits) << numBits; long delta = nextTimeBucketMillis - millis; return delta;
}
/** * Sets isRunning to false to terminate the maintenance thread.
*/ publicvoid destroy() { // Stop our thread if (monitorFuture != null) {
monitorFuture.cancel(true);
monitorFuture = null;
} if (maintenanceFuture != null) {
maintenanceFuture.cancel(true);
maintenanceFuture = null;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.