The rate limit is defined as the maximum number of requests that can be made within a specific time frame. For example, an API might have a rate limit of 100 requests per minute. This means that a client can make at most 100 requests within any given one - minute interval.
When exposing an API to external clients, rate limiting helps prevent abuse. It ensures that no single client can flood the API with requests, which could lead to resource exhaustion and denial - of - service for other clients.
In a system with limited resources such as database connections or network bandwidth, rate limiting can be used to control the rate at which requests access these resources. This helps maintain the stability and performance of the system.
Rate limiting can be used in conjunction with billing systems. By tracking the number of requests made by a client within a certain period, it becomes easier to calculate usage - based fees.
import java.time.Instant
import java.util.concurrent.ConcurrentHashMap
class FixedWindowRateLimiter(private val limit: Int, private val windowDurationMs: Long) {
// Map to store the request count for each window
private val windowCounters = ConcurrentHashMap<Long, Int>()
fun allowRequest(): Boolean {
// Get the current window timestamp
val currentWindow = Instant.now().toEpochMilli() / windowDurationMs
// Get or initialize the counter for the current window
val counter = windowCounters.compute(currentWindow) { _, count -> count?.inc() ?: 1 }
return counter <= limit
}
}
// Example usage
fun main() {
val rateLimiter = FixedWindowRateLimiter(limit = 5, windowDurationMs = 1000)
repeat(10) {
if (rateLimiter.allowRequest()) {
println("Request $it is allowed")
} else {
println("Request $it is blocked")
}
Thread.sleep(200)
}
}
In this code, we define a FixedWindowRateLimiter
class. The allowRequest
method checks if the current window’s request count is below the limit. If it is, the request is allowed; otherwise, it is blocked.
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
class TokenBucketRateLimiter(private val capacity: Int, private val rate: Double) {
private val tokens = AtomicInteger(capacity)
private val lastRefillTime = AtomicLong(System.currentTimeMillis())
private fun refill() {
val now = System.currentTimeMillis()
val elapsedTime = now - lastRefillTime.get()
val newTokens = (elapsedTime * rate / 1000).toInt()
if (newTokens > 0) {
tokens.updateAndGet { currentTokens ->
val updatedTokens = currentTokens + newTokens
if (updatedTokens > capacity) capacity else updatedTokens
}
lastRefillTime.set(now)
}
}
fun allowRequest(tokensNeeded: Int): Boolean {
refill()
return tokens.getAndUpdate { currentTokens ->
if (currentTokens >= tokensNeeded) currentTokens - tokensNeeded else currentTokens
} >= tokensNeeded
}
}
// Example usage
fun main() {
val rateLimiter = TokenBucketRateLimiter(capacity = 10, rate = 1.0)
repeat(15) {
if (rateLimiter.allowRequest(1)) {
println("Request $it is allowed")
} else {
println("Request $it is blocked")
}
Thread.sleep(200)
}
}
In this code, the TokenBucketRateLimiter
class has a refill
method that adds tokens to the bucket based on the elapsed time and the rate. The allowRequest
method first refills the bucket and then checks if there are enough tokens to process the request.
Rate limiting is an essential technique in modern software development, and Kotlin provides a flexible and powerful environment for implementing rate limiters. By understanding the core concepts, typical usage scenarios, and best practices, you can effectively control the rate of requests in your applications, protecting your resources and ensuring the stability and performance of your systems.