Have you ever come across a situation that you need to do rate limiting to an api. In this article we look at one of the ways rate limiting can be done.
We would limit access to an api and make only 1 call to api within 2 seconds.
This class would use threads and give an option to the caller whether to wait for the api call to finish or just let the throller call the api.
import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * API has limits on number of concurrent connections and the number of calls * that can send within a minutes. This controller acts as a throttler to the * api. * * @author T Tak * */ @Component public class ApiCallsThrottler { private static final Logger logger = LogManager.getLogger(); @Autowired private Api api; // 3 threads private ExecutorService tasksExecutor; private volatile long lastTimeExecution = System.currentTimeMillis(); private ScheduledExecutorService infoScheduler; @PostConstruct public void init() { logger.info("Init ApiCallsThrottler"); BasicThreadFactory factoryNormal = new BasicThreadFactory.Builder().namingPattern("normalPriothread-%d") .build(); tasksExecutor = Executors.newFixedThreadPool(3, factoryNormal); BasicThreadFactory infoSchedulerFactory = new BasicThreadFactory.Builder().namingPattern("apiinfoscheduler-%d") .build(); infoScheduler = Executors.newSingleThreadScheduledExecutor(infoSchedulerFactory); // initial delay = 1 minute, and then each 1 minutes infoScheduler.scheduleWithFixedDelay(ApiCallsThrottler.this::printQueueDetails, 1, 1, TimeUnit.MINUTES); } protected void printQueueDetails() { try { printThreadPoolDetails((ThreadPoolExecutor) tasksExecutor); } catch (Exception e) { logger.error("couldn't print request executor details : {}", e, e); } } private void printThreadPoolDetails(ThreadPoolExecutor threadPoolExecutor) { int activeCount = threadPoolExecutor.getActiveCount(); long taskCount = threadPoolExecutor.getTaskCount(); long completedTaskCount = threadPoolExecutor.getCompletedTaskCount(); long tasksToDo = taskCount - completedTaskCount - activeCount; logger.info("tasks in queue : {}, sending now : {} , apx total requests send till now : {}", tasksToDo, activeCount, completedTaskCount); } public Future<ApiRequest> sendRequest(ApiRequest sendrequestTask) { sendrequestTask.setQueueSubmitTime(Instant.now()); return tasksExecutor.submit(() -> sendrequestNoReciept(sendrequestTask)); } private ApiRequest sendrequestNoReciept(ApiRequest task) throws InterruptedException { task.setQueueProcessingStartTime(Instant.now()); if (System.currentTimeMillis() - lastTimeExecution < 2000) { logger.info("NP :Waiting 2000 milis to send request due to quantity limits! {} ", task.getTaskId()); Thread.sleep(2000); logger.info("NP :Now sending request! {} ", task.getTaskId()); } lastTimeExecution = System.currentTimeMillis(); api.sendRequest(task.getTaskId()); logger.info("NP :request sent! {} ", task.getTaskId()); task.setQueueProcessingFinishTime(Instant.now()); return task; } @PreDestroy public void destroy() { System.out.println("destroy thread"); // NOSONAR shutdownAnyExecutor(Arrays.asList(tasksExecutor, infoScheduler)); tasksExecutor = null; } private void shutdownAnyExecutor(List<ExecutorService> executorServices) { // shutdown all for (ExecutorService each : executorServices) { if (each != null) { // wait 3 second for closing all threads System.out.println("Destroy : shutdown scheduler"); // NOSONAR each.shutdown(); } } // wait for termination for (ExecutorService each : executorServices) { if (each != null) { try { // wait 60 second for closing all threads // requester should be able to do all the task before shutdown System.out.println("Destroy : Await Termination for max 60 seconds"); // NOSONAR each.awaitTermination(180, TimeUnit.SECONDS); // try again and let go if (!each.isTerminated()) { System.out.println(// NOSONAR "Destroy : even after 60 seconds wait the scheduler has not terminated, try shutdownNow() and exit"); // NOSONAR each.shutdownNow(); } else { System.out.println("Destroy : Scheduler terminated successfully"); // NOSONAR } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } public static class ApiRequest { private int taskId; private int sendRetry = 0; private boolean processing; /** indicator, that request was sent */ private boolean requestSent = false; // queue tracking private Instant queueSubmitTime; private Instant queueProcessingStartTime; private Instant queueProcessingFinishTime; private String token; private String errorText; public int getSendRetry() { return sendRetry; } public void setSendRetry(int sendRetry) { this.sendRetry = sendRetry; } public boolean isProcessing() { return processing; } public void setProcessing(boolean processing) { this.processing = processing; } public boolean isRequestSent() { return requestSent; } public void setRequestSent(boolean requestSent) { this.requestSent = requestSent; } public Instant getQueueSubmitTime() { return queueSubmitTime; } public void setQueueSubmitTime(Instant queueSubmitTime) { this.queueSubmitTime = queueSubmitTime; } public Instant getQueueProcessingStartTime() { return queueProcessingStartTime; } public void setQueueProcessingStartTime(Instant queueProcessingStartTime) { this.queueProcessingStartTime = queueProcessingStartTime; } public Instant getQueueProcessingFinishTime() { return queueProcessingFinishTime; } public void setQueueProcessingFinishTime(Instant queueProcessingFinishTime) { this.queueProcessingFinishTime = queueProcessingFinishTime; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } public String getErrorText() { return errorText; } public void setErrorText(String errorText) { this.errorText = errorText; } public int getTaskId() { return taskId; } public void setTaskId(int taskId) { this.taskId = taskId; } }
Dependencies needed :
org.springframework
spring-core
5.3.10
org.apache.commons
commons-lang3
3.12.0
org.apache.logging.log4j
log4j-core
2.14.1
javax.annotation
javax.annotation-api
1.3.2
org.springframework
spring-context
5.3.10
does not compile
I have added dependencies to the post. That should help you compile. And look at how you can use spring in your project