org.ak.trafficController.messaging.mem.DynamicSettings Maven / Gradle / Ivy
package org.ak.trafficController.messaging.mem;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ak.trafficController.messaging.exception.ThresholdReachedException;
/**
* This class handles dynamic settings of flow of messages.
* This class helps {@code InMemoryQueueTuner} to tune as per configuration set by user.
* As name suggest, this enables dynamic settings like increasing and decreasing consumer count on basis of number of messages.
* Actual queue will be wrapped inside the settings.
*
* Consider we have the queue and production is very fast. If we do not have more consumer, we will soon end up to a situation where we can have out of memory etc.
* For this, this class can be configured to choose a high limit post which if messages in queue increases to, a new consumer is tried.
*
* If post increasing consumer, the number of messages start reducing or when we had just a flare where we had lot of messages, there is a need to reduce the consumers
* which is handled once low limit is encountered.
*
* We can reduce or increase this count to an extent where we do not have any consumer or we end up having so many consumers which itself causing slowness.
* This is also handled by minimum consumer count and maximum count.
*
* Also, this class allows queue to throw exception or ignore taking more messages.
* Also, post a threshold, it can be configured to get clean itself if the messages can be allowed to.
* @author Amit Khosla
*
* @param Type of queue.
*/
public class DynamicSettings { //We can later think of making it decorator
static Logger log = Logger.getLogger(DynamicSettings.class.getName());
/**
* Cleaners that will be used in case max threshold is reached and configured to wipe out all data, these consumers will be passed the queue to let it get saved.
*/
private List>> cleaners = new ArrayList<>();
private InMemoryQueue queue;
public InMemoryQueue getQueue() {
return queue;
}
/**
* Setting queue to be worked upon.
* @param queue Queue which needs to be worked upon
* @return this object to make it easy to work on
*/
public DynamicSettings setQueue(InMemoryQueue queue) {
this.queue = queue;
return this;
}
/**
* Should throw exception post retries.
* @return true if configured so
*/
public Boolean getShouldThrowExceptionPostRetry() {
return shouldThrowExceptionPostRetry;
}
/**
* Set if exception should be thrown to producers if threshold is reached and configured amount of retries are also taken.
* @param shouldThrowExceptionPostRetry Boolean value to set
* @return this object to make it easy to work on
*/
public DynamicSettings setShouldThrowExceptionPostRetry(Boolean shouldThrowExceptionPostRetry) {
this.shouldThrowExceptionPostRetry = shouldThrowExceptionPostRetry;
return this;
}
/**
* Maximum direct consumers which can be created. This is maximum till what we can increase.
*/
private Integer maxDirectConsumer=10;
/**
* Minimum Direct consumers to which we can reduce.
*/
private Integer minDirectConsumer=1;
/**
* Maximum batch consumers which can be created.
*/
private Integer maxBatchConsumer=10;
/**
* Minimum batch consumers which can be reduced to.
*/
private Integer minBatchConsumer=1;
/**
* High limit when a new consumer addition will be tried.
*/
private Long highLimitWhenToIncreaseConsumer=500L;
/**
* Low limit when a consumer can be shut down as the production is now slow or consumption is good enough.
*/
private Long lowLimitWhenToDecreseConsumer=50L;
/**
* Threshold limit post which it is considered as dangerous situation.
*/
private Long thresholdWhenNoMoreItemsShouldBeHandled=5000L;
/**
* If set, once the threshold is reached we can clear the queue.
*/
private boolean shouldClearQueueAtThreshold;
/**
* Should stop adding if already threshold reached.
*/
private boolean shouldStopAddingAtThreshold;
/**
* Should throw exception when threshold is reached at time of adding new item.
*/
private boolean shouldThrowExceptionWhenThresholdAtAdd;
/**
* Should retry producer till threshold not recovered.
*/
private boolean shouldRetrySenderTillThresholdNotRecovered;
/**
* Milliseconds to wait for retry.
*/
private Long waitForRetryThresholdLimit=100L;//milliseconds to wait for
/**
* Number of retries while waiting.
*/
private Long numberOfRetriesToWait=100L;
/**
* Should throw exception post retry or just reject.
*/
private boolean shouldThrowExceptionPostRetry;
/**
* Sleep for configured amount.
* @param sleepTime Sleep time for which we want to sleep
*/
public static void sleep(Long sleepTime) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
log.log(Level.FINE, e, ()->"Could not sleep");
}
}
/**
* Maximum direct consumers which can be created. A new consumer will be added automatically once high limit is reached. This is maximum till what we can increase.
* @return Maximum direct consumer
*/
public Integer getMaxDirectConsumer() {
return maxDirectConsumer;
}
/**
* Set the number of maximum direct consumers which can be created.
* A new consumer will be added automatically once high limit is reached.
* This is maximum till what we can increase.
* @param maxDirectConsumer Maximum direct consumer
* @return This object for further modifications easily
*/
public DynamicSettings setMaxDirectConsumer(Integer maxDirectConsumer) {
this.maxDirectConsumer = maxDirectConsumer;
return this;
}
/**
* Minimum direct consumers which can be reduced to.
* A consumer is stopped automatically once count is less than low limit.
* This is minimum till what we can reduce.
* @return Minimum direct consumer count
*/
public Integer getMinDirectConsumer() {
return minDirectConsumer;
}
/**
* Setter of mimimum direct consumers till which consumers count be reduced to.
* A consumer is stopped automatically once count is less than low limit.
* This is minimum till what we can reduce.
* @param minDirectConsumer Minimum direct consumer count to be set
* @return This object for further modifications easily
*/
public DynamicSettings setMinDirectConsumer(Integer minDirectConsumer) {
this.minDirectConsumer = minDirectConsumer;
return this;
}
/**
* Maximum batch consumers which can be created. A new consumer will be added automatically once high limit is reached. This is maximum till what we can increase.
* @return Maximum batch consumer count
*/
public Integer getMaxBatchConsumer() {
return maxBatchConsumer;
}
/**
* Set the number of maximum batch consumers which can be created.
* A new consumer will be added automatically once high limit is reached.
* This is maximum till what we can increase.
* @param maxBatchConsumer Maximum batch consumer
* @return This object for further modifications easily
*/
public DynamicSettings setMaxBatchConsumer(Integer maxBatchConsumer) {
this.maxBatchConsumer = maxBatchConsumer;
return this;
}
/**
* Minimum batch consumers which can be reduced to.
* A consumer is stopped automatically once count is less than low limit.
* This is minimum till what we can reduce.
* @return Minimum batch consumer count
*/
public Integer getMinBatchConsumer() {
return minBatchConsumer;
}
/**
* Setter of minimum batch consumers till which consumers count be reduced to.
* A consumer is stopped automatically once count is less than low limit.
* This is minimum till what we can reduce.
* @param minBatchConsumer Minimum direct consumer count to be set
* @return This object for further modifications easily
*/
public DynamicSettings setMinBatchConsumer(Integer minBatchConsumer) {
this.minBatchConsumer = minBatchConsumer;
return this;
}
/**
* Get high limit when increase of consumer is tried. If the number of messages in queue is greater than this value a new consumer will be tried.
* @return High limit when to increase consumer
*/
public Long getHighLimitWhenToIncreaseConsumer() {
return highLimitWhenToIncreaseConsumer;
}
/**
* Set high limit when increase of consumer is tried. If the number of messages in queue is greater than this value a new consumer will be tried.
* @param highLimitWhenToIncreaseConsumer High limit when increase of consumer should be tried
* @return This object to easily configure urther
*/
public DynamicSettings setHighLimitWhenToIncreaseConsumer(Long highLimitWhenToIncreaseConsumer) {
this.highLimitWhenToIncreaseConsumer = highLimitWhenToIncreaseConsumer;
return this;
}
/**
* Get low limit when decrease of consumer is tried. If number of messages in queue is lower than this value, a consumer can be tried to shut down.
* @return Low limit
*/
public Long getLowLimitWhenToDecreseConsumer() {
return lowLimitWhenToDecreseConsumer;
}
/**
* Set low limit when decrease of consumer is tried. If number of messages in queue is lower than this value, a consumer can be tried to shut down.
* @param lowLimitWhenToDecreseConsumer Low limit which should be taken in consideration
* @return This object to easily configure further
*/
public DynamicSettings setLowLimitWhenToDecreseConsumer(Long lowLimitWhenToDecreseConsumer) {
this.lowLimitWhenToDecreseConsumer = lowLimitWhenToDecreseConsumer;
return this;
}
/**
* Threshold when more item should not be added.
* @return Threshold value
*/
public Long getThresholdWhenNoMoreItemsShouldBeHandled() {
return thresholdWhenNoMoreItemsShouldBeHandled;
}
/**
* Threshold when more item should not be added.
* @param thresholdWheNoMoreItemsShouldBeHandled Threshold value to be configured to
* @return This object to further configure.
*/
public DynamicSettings setThresholdWhenNoMoreItemsShouldBeHandled(Long thresholdWheNoMoreItemsShouldBeHandled) {
this.thresholdWhenNoMoreItemsShouldBeHandled = thresholdWheNoMoreItemsShouldBeHandled;
return this;
}
/**
* Should clear the queue if threshold reached.
* @return should clear flag
*/
public Boolean getShouldClearQueueAtThreshold() {
return shouldClearQueueAtThreshold;
}
/**
* Configures to if should clear queue at threshold.
* @param shouldClearQueueAtThreshold boolean value
* @return This object for further configure
*/
public DynamicSettings setShouldClearQueueAtThreshold(Boolean shouldClearQueueAtThreshold) {
this.shouldClearQueueAtThreshold = shouldClearQueueAtThreshold;
return this;
}
/**
* Should stop adding at threshold.
* @return should stop adding at threshold
*/
public Boolean getShouldStopAddingAtThreshold() {
return shouldStopAddingAtThreshold;
}
/**
* Setter of should stop adding at threshold.
* @param shouldStopAddingAtThreshold value to be set
* @return This object to make it easy to further update this object.
*/
public DynamicSettings setShouldStopAddingAtThreshold(Boolean shouldStopAddingAtThreshold) {
this.shouldStopAddingAtThreshold = shouldStopAddingAtThreshold;
return this;
}
/**
* Is retry enabled while adding if threshold is reached.
* @return Retry enabled status
*/
public Boolean getShouldRetrySenderTillThresholdNotRecovered() {
return shouldRetrySenderTillThresholdNotRecovered;
}
/**
* Setter of retry enabled while adding if threshold is reached.
* @param shouldPauseSenderTillThresholdNotRecovered value to be set
* @return This object for easy further modification
*/
public DynamicSettings setShouldRetrySenderTillThresholdNotRecovered(Boolean shouldPauseSenderTillThresholdNotRecovered) {
this.shouldRetrySenderTillThresholdNotRecovered = shouldPauseSenderTillThresholdNotRecovered;
return this;
}
/**
* Wait time for retries.
* @return wait time for retries
*/
public Long getWaitForRetryThresholdLimit() {
return waitForRetryThresholdLimit;
}
/**
* Set wait time for retry.
* @param waitForRetryThresholdLimit Wait for retry value to be set
* @return This object for further modifications or use
*/
public DynamicSettings setWaitForRetryThresholdLimit(Long waitForRetryThresholdLimit) {
this.waitForRetryThresholdLimit = waitForRetryThresholdLimit;
return this;
}
/**
* Number of retries while adding at threshold.
* @return Number of retries
*/
public Long getNumberOfRetriesToWait() {
return numberOfRetriesToWait;
}
/**
* Setter of Number of retries while adding at threshold.
* @param numberOfRetriesToWait Number of retries
* @return This object for further modifications or use
*/
public DynamicSettings setNumberOfRetriesToWait(Long numberOfRetriesToWait) {
this.numberOfRetriesToWait = numberOfRetriesToWait;
return this;
}
/**
* This method handles the increase/decrease of consumers.
* A tuner calls this method periodically.
*/
public void adjust() {
Long numberOfItemsInQueue = queue.getNumberOfItemsInQueue();
if (numberOfItemsInQueue > highLimitWhenToIncreaseConsumer) {
increaseConsumerIfPossible();
tryIfCanFreeUp();
} else if (numberOfItemsInQueue < lowLimitWhenToDecreseConsumer) {
decreaseConsumerIfPossible();
}
}
/**
* It verifies if it can free up messages based on configurations and condition.
*/
protected void tryIfCanFreeUp() {
if (shouldClearQueueAtThreshold && queue.getNumberOfItemsInQueue() > thresholdWhenNoMoreItemsShouldBeHandled) {
this.queue.clear(getCleaners());
}
}
/**
* Get registered cleaners.
* @return Attached cleaners
*/
protected Consumer>[] getCleaners() {
if (cleaners.size() > 0) {
Consumer>[] arr = new Consumer[cleaners.size()];
for (int i=0;i>[]) cleaners.toArray();
}
return null;
}
/**
* Increase consumers if high limit is reached and we still have number of consumers less than total current consumers.
*/
protected void increaseConsumerIfPossible() {
if (queue.isDirectConsumerSet()) {
int directConsumers = queue.getDirectConsumerCount();
if (directConsumers < maxDirectConsumer) {
queue.incrementDirectConsumer();
}
}
if (queue.isBatchConsumerSet()) {
int batchConsumers = queue.getBatchConsumerCount();
if (batchConsumers < maxBatchConsumer) {
queue.incrementBatchConsumer();
}
}
}
/**
* Decrease consumer if low limit attained back and we still have more consumers than minimum consumers.
*/
protected void decreaseConsumerIfPossible() {
if (queue.isDirectConsumerSet()) {
int directConsumers = queue.getDirectConsumerCount();
if (directConsumers > minDirectConsumer) {
queue.decrementDirectConsumer();
}
}
if (queue.isBatchConsumerSet()) {
int batchConsumers = queue.getBatchConsumerCount();
if (batchConsumers > minBatchConsumer) {
queue.decrementBatchConsumer();
}
}
}
/**
* Should throw exception when threshold reached in add process.
* @return Status of shouldThrowExceptionWhenThresholdAtAdd
*/
public Boolean getShouldThrowExceptionWhenThresholdAtAdd() {
return shouldThrowExceptionWhenThresholdAtAdd;
}
/**
* Setter of Should throw exception when threshold reached in add process.
* @param shouldThrowExceptionWhenThresholdAtAdd Value to be set
* @return This object for further use
*/
public DynamicSettings setShouldThrowExceptionWhenThresholdAtAdd(Boolean shouldThrowExceptionWhenThresholdAtAdd) {
this.shouldThrowExceptionWhenThresholdAtAdd = shouldThrowExceptionWhenThresholdAtAdd;
return this;
}
/**
* This method is responsible of adding data in queue.
* This method also retries and/or throws exception as per configuration.
* If addition is not allowed and no exception should be thrown, returns false.
* @param item Item to be added to queue
* @return True if successfully add item in queue
*/
public boolean addItemInQueue(T item) {
return processAddItem(this.queue::add, item);
}
/**
* Checks if add item allowed and add the item to queue.
* @param consumer Consumer which will actually add item in queue
* @param item Item to be added
* @param type of item to be added
* @return true if successfully added to queue
*/
protected boolean processAddItem(Consumer consumer, K item) {
if(addStillAllowed()){
consumer.accept(item);
return true;
}
return false;
}
/**
* This method is responsible of adding data in list in queue.
* This method also retries and/or throws exception as per configuration.
* If addition is not allowed and no exception should be thrown, returns false.
* @param list List containing data to be saved in queue
* @return True if successfully added to queue
*/
public boolean addItemsInQueue(Collection list) {
return processAddItem(this.queue::addAllFromCollection, list);
}
/**
* This method tells if we can still add in the queue.
* @return true if allowed
*/
protected boolean addStillAllowed() {
if (queue.getNumberOfItemsInQueue() >= this.thresholdWhenNoMoreItemsShouldBeHandled) {
log.fine("Threshold reached.");
if (this.shouldThrowExceptionWhenThresholdAtAdd) {
throw new ThresholdReachedException();
} else if (this.shouldStopAddingAtThreshold) {
return handleStopAddingAtThreshold();
}
}
return true;
}
/**
* This method is to handle in situation where we need to continue even when threshold reached.
* To continue, we can try again for given retries or directly return false.
* In case of waiting is allowed, this method will throw {@code ThresholdReachedException} in case all retries are over and still threshold is there.
* @return true if post retries items in queue lowers to less than threshold
*/
protected boolean handleStopAddingAtThreshold() {
if (!shouldRetrySenderTillThresholdNotRecovered) {
log.fine("wait is not applicable. Returning false");
return false;
}
if (waitForRetriesIfAny()) {
log.fine("wait for retries succeed. Returning true");
return true;
}
if (shouldThrowExceptionPostRetry) {
log.fine("wait for retries failed. Throwing exception as configured.");
throw new ThresholdReachedException("Threshold Reached and after waits also, cannot recover.");
}
log.fine("all retries done. Now no waiting required. Sending back with false.");
return false;
}
/**
* This method waits for configured number and for given time if allowed to wait.
* Post each retry, it checks if we threshold is now cooled off.
* @return true if we can add more data
*/
protected boolean waitForRetriesIfAny() {
for (long i=0;i addPreCleanHandler(Consumer> preCleanHandler) {
this.cleaners.add(preCleanHandler);
return this;
}
/**
* Shutdown underlying queue.
*/
public void shutdown() {
this.queue.shutdown();
}
}