-- KEYS[1] = bucket key
-- ARGV[1] = capacity, ARGV[2] = refill_rate (tokens/sec)
-- ARGV[3] = now_ms, ARGV[4] = requested tokens
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now_ms = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local state = redis.call('HMGET', KEYS[1], 'tokens', 'last_refill')
local tokens = tonumber(state[1])
local last_refill = tonumber(state[2])
if tokens == nil then
tokens = capacity
last_refill = now_ms
end
local elapsed = math.max(0, now_ms - last_refill) / 1000.0
tokens = math.min(capacity, tokens + (elapsed * refill_rate))
local allowed = 0
local retry_after_ms = 0
if tokens >= requested then
tokens = tokens - requested
allowed = 1
else
local deficit = requested - tokens
retry_after_ms = math.ceil((deficit / refill_rate) * 1000)
end
redis.call('HSET', KEYS[1], 'tokens', tokens, 'last_refill', now_ms)
-- expire idle buckets after they would fully refill
redis.call('PEXPIRE', KEYS[1], math.ceil((capacity / refill_rate) * 1000) + 1000)
return { allowed, retry_after_ms }
package com.example.ratelimit;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Jedis;
import java.util.List;
import java.util.Arrays;
public class TokenBucketRateLimiter {
public record Decision(boolean allowed, long retryAfterSeconds) {}
private final JedisPool pool;
private final int capacity;
private final double refillRate;
private final String scriptSha;
public TokenBucketRateLimiter(JedisPool pool, int capacity, double refillRate, String luaScript) {
this.pool = pool;
this.capacity = capacity;
this.refillRate = refillRate;
try (Jedis jedis = pool.getResource()) {
this.scriptSha = jedis.scriptLoad(luaScript);
}
}
public Decision tryAcquire(String clientId) {
String key = "rl:bucket:" + clientId;
long now = System.currentTimeMillis();
try (Jedis jedis = pool.getResource()) {
Object raw = jedis.evalsha(
scriptSha,
List.of(key),
Arrays.asList(
String.valueOf(capacity),
String.valueOf(refillRate),
String.valueOf(now),
"1"));
@SuppressWarnings("unchecked")
List<Long> result = (List<Long>) raw;
boolean allowed = result.get(0) == 1L;
long retryAfter = (long) Math.ceil(result.get(1) / 1000.0);
return new Decision(allowed, retryAfter);
}
}
}
package com.example.ratelimit;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
public class RateLimitFilter implements Filter {
private TokenBucketRateLimiter limiter;
@Override
public void init(FilterConfig config) {
this.limiter = (TokenBucketRateLimiter)
config.getServletContext().getAttribute("rateLimiter");
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String clientId = resolveClientId(request);
TokenBucketRateLimiter.Decision decision = limiter.tryAcquire(clientId);
if (!decision.allowed()) {
response.setStatus(429); // Too Many Requests
response.setHeader("Retry-After", String.valueOf(decision.retryAfterSeconds()));
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"rate_limit_exceeded\"}");
return;
}
chain.doFilter(req, res);
}
private String resolveClientId(HttpServletRequest request) {
String apiKey = request.getHeader("X-Api-Key");
if (apiKey != null && !apiKey.isBlank()) {
return "key:" + apiKey;
}
return "ip:" + request.getRemoteAddr();
}
}
This snippet implements per-client API throttling using the token bucket algorithm, applied as a Servlet Filter that sits in front of protected endpoints. A token bucket models a fixed-capacity reservoir that refills at a steady rate: each request removes one token, and requests are only allowed when at least one token remains. Unlike a fixed window counter, the bucket allows short bursts (up to the capacity) while still enforcing a sustained average rate, which makes it a good fit for real-world API traffic that arrives unevenly.
The core state lives in Redis because rate limits must be shared across every application instance behind a load balancer. RateLimiterLua holds the atomic decision logic as an embedded Lua script. Running the refill-and-consume calculation server-side matters: reading the bucket, computing the refill, and decrementing must happen as one indivisible operation, otherwise two concurrent requests could both read the same token count and both succeed. Redis executes a Lua script atomically, so the check-and-decrement becomes a single round trip with no race window. The script derives the number of tokens to add from the elapsed time since last_refill, clamps the total to capacity, and returns both whether the request is allowed and how long until the next token is available.
In TokenBucketRateLimiter, the Java side wraps the script with EVALSHA for efficiency, passing the refillRate, capacity, and current time as arguments. It returns a small Decision record carrying the allow flag and a suggested Retry-After value. Keys are namespaced per client so buckets never collide, and Redis TTLs let idle buckets expire on their own without a separate cleanup job.
Finally, RateLimitFilter bridges HTTP and the limiter. It extracts a client identity (an API key header, falling back to remote address), calls tryAcquire, and on rejection writes a 429 Too Many Requests with a Retry-After header rather than passing the request down the chain. Allowed requests continue via chain.doFilter. Placing this logic in a filter keeps throttling orthogonal to business code and lets it protect many endpoints at once. A subtle trade-off worth noting: identifying clients by IP is spoofable and breaks behind shared NAT, so production systems usually prefer authenticated keys.
Related snips
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
Channels (mpsc) for message passing between threads
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
json.array! @posts do |post|
json.cache! ['v1', post], expires_in: 1.hour do
json.id post.id
json.title post.title
json.excerpt post.excerpt
json.published_at post.published_at
Fragment caching for expensive JSON serialization
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
Share this code
Here's the card — post it anywhere.