Fixed-Window Rate Limiting: Java and Redis Implementation Guide
The article explains the fixed‑window rate‑limiting algorithm, details a thread‑safe Java implementation, shows how to realize distributed limiting with Redis Lua scripts, compares advantages and drawbacks, provides performance benchmarks, and offers practical usage scenarios and production recommendations.
Algorithm Overview
The fixed‑window rate‑limiting algorithm divides time into equal‑length windows (e.g., 1 second). Each window maintains an independent counter; incoming requests increment the counter, and requests exceeding the configured threshold are rejected. When a window expires, the counter resets to zero.
Critical Issue
Because the counter does not consider request distribution across adjacent windows, a burst that straddles the window boundary can temporarily double the allowed traffic (e.g., 5 requests at 0.9 s and another 5 at 1.1 s result in 10 requests within 0.2 s).
1. Single‑Machine Thread‑Safe Implementation
Key design points:
Atomic operations using AtomicInteger and AtomicLong ensure thread‑safe updates of the counter and window start time.
CAS ( compareAndSet) prevents multiple threads from resetting the window simultaneously.
A lock‑free approach avoids the overhead of synchronized, yielding over 30% higher throughput.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class FixedWindowRateLimiter {
private final int maxRequests; // maximum requests per window
private final long windowMillis; // window size in milliseconds
private final AtomicInteger counter; // current window counter
private final AtomicLong windowStart; // start time of current window
public FixedWindowRateLimiter(int maxRequests, long windowMillis) {
this.maxRequests = maxRequests;
this.windowMillis = windowMillis;
this.counter = new AtomicInteger(0);
this.windowStart = new AtomicLong(System.currentTimeMillis());
}
public boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
long startTime = windowStart.get();
// check if we have entered a new window
if (currentTime - startTime > windowMillis) {
// CAS to reset window without race conditions
if (windowStart.compareAndSet(startTime, currentTime)) {
counter.set(0); // reset counter
}
}
// increment counter and verify limit
return counter.incrementAndGet() <= maxRequests;
}
}Example usage:
public class Main {
public static void main(String[] args) throws InterruptedException {
FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(5, 1000); // 5 requests per second
for (int i = 0; i < 10; i++) {
boolean allowed = limiter.tryAcquire();
System.out.println("Request " + (i + 1) + ": " + (allowed ? "allowed" : "rejected"));
Thread.sleep(200); // simulate interval between requests
}
}
}2. Distributed Implementation with Redis
2.1 Redis Lua Script
-- KEYS[1]: rate‑limit key (e.g., "rate_limit:user123")
-- ARGV[1]: window size in seconds
-- ARGV[2]: limit per window
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local current = tonumber(redis.call("GET", key) or "0")
if current >= limit then
return 0 -- limit exceeded
else
redis.call("INCR", key)
if current == 0 then
redis.call("EXPIRE", key, window)
end
return 1 -- allowed
end2.2 Java Wrapper for the Script
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
public class RedisFixedWindowLimiter {
private final StringRedisTemplate redisTemplate;
private final DefaultRedisScript<Long> script;
public RedisFixedWindowLimiter(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
this.script = new DefaultRedisScript<>(
"local key = KEYS[1]
" +
"local window = tonumber(ARGV[1])
" +
"local limit = tonumber(ARGV[2])
" +
"local current = tonumber(redis.call('GET', key) or '0')
" +
"if current >= limit then return 0
" +
"else
" +
" redis.call('INCR', key)
" +
" if current == 0 then redis.call('EXPIRE', key, window) end
" +
" return 1
" +
"end",
Long.class);
}
public boolean isAllowed(String key, int windowSec, int limit) {
Long result = redisTemplate.execute(
script,
Collections.singletonList(key),
String.valueOf(windowSec),
String.valueOf(limit)
);
return result != null && result == 1;
}
}2.3 Spring MVC Controller Example
@RestController
public class ApiController {
@Autowired
private RedisFixedWindowLimiter limiter;
@GetMapping("/api")
public String handleRequest() {
boolean allowed = limiter.isAllowed("rate_limit:ip_192.168.1.1", 1, 5);
return allowed ? "Request succeeded" : "Too many requests";
}
}3. Advantages and Disadvantages
Advantages
Simple implementation: only INCR and EXPIRE commands, minimal code.
Low memory consumption: each key stores a single counter.
High performance: Redis single‑command operation can reach tens of thousands QPS.
Disadvantages
Critical burst issue: window switch may allow a temporary double‑threshold spike.
Insufficient precision: cannot smooth traffic, bursts may overload the system.
Cannot aggregate across windows: unable to count requests over a sliding period (e.g., last 10 seconds).
4. Typical Use Cases
Short‑term burst control, such as limiting SMS verification code requests (once per minute).
Non‑critical APIs where exact precision is not required.
Rapid deployment: a quick temporary solution when rate limiting must be added urgently.
5. Production Recommendations
For high‑precision requirements, switch to a sliding‑window algorithm (e.g., Sentinel implementation).
For bursty traffic, consider a token‑bucket algorithm like Guava RateLimiter.
Monitor rate‑limit trigger counts and adjust thresholds dynamically.
6. Performance Benchmarks
Standalone Java version: ~100 k QPS on a 4‑core, 8 GB server.
Redis‑backed version: ~20 k QPS with a same‑datacenter Redis instance.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
