Idempotent Interfaces and Distributed Rate Limiting: Concepts, Algorithms, and Practical Implementations
This article explains the importance of interface idempotency and presents a comprehensive guide to distributed rate limiting, covering key dimensions, token‑bucket and leaky‑bucket algorithms, and concrete implementations using Guava RateLimiter, Nginx, and Redis‑Lua with Java code examples.
Interface idempotency ensures that repeated requests produce the same result, preventing side‑effects such as double charges in payment scenarios.
The core idea is to use a unique business identifier or a token combined with locking to guarantee that an update or insert operation is executed only once.
Example SQL for optimistic locking:
update set version = version +1 , xxx=${xxx} where id =${id} and version = ${version};A token mechanism can generate a token on the registration page, store it in a hidden field, and use it to acquire a distributed lock before performing the insert.
Distributed Rate Limiting
Rate limiting can be defined along several dimensions: time windows (e.g., per second or per minute), resource limits (max requests or connections), black/white lists, and the distributed environment where limits apply across all nodes.
Common algorithms:
Token Bucket
A bucket holds a configurable number of tokens that are added at a steady rate; a request proceeds only if it can take a token.
RateLimiter limiter = RateLimiter.create(2.0); // 2 tokens per secondNon‑blocking acquisition:
if (limiter.tryAcquire(count)) { log.info("success, rate={}", limiter.getRate()); } else { log.info("fail, rate={}", limiter.getRate()); }Leaky Bucket
Requests are placed in a queue (the bucket) and are released at a constant rate; excess requests are discarded when the bucket is full.
while (true) { if (bucket.isFull()) { reject(); } else { bucket.add(request); }Implementation Solutions
Guava RateLimiter (client‑side)
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency> @RestController
@Slf4j
public class Controller {
RateLimiter limiter = RateLimiter.create(2.0);
@GetMapping("/tryAcquire")
public String tryAcquire(Integer count) {
if (limiter.tryAcquire(count)) {
log.info("success, rate={}", limiter.getRate());
return "success";
} else {
log.info("fail, rate={}", limiter.getRate());
return "fail";
}
}
}Nginx Rate Limiting
limit_req_zone $binary_remote_addr zone=iplimit:20m rate=1r/s; server {
server_name www.test.com;
location /access-limit/ {
proxy_pass http://127.0.0.1:8080/;
limit_req zone=iplimit burst=2 nodelay;
}
}Redis + Lua Distributed Limiting
A Lua script runs atomically inside Redis to check the current count and increment it if the limit is not exceeded.
-- limit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local count = tonumber(redis.call('get', key) or "0")
if count + 1 > limit then
return false
else
redis.call('INCRBY', key, 1)
redis.call('EXPIRE', key, 1)
return true
endSpring integration:
@Service
@Slf4j
public class AccessLimiter {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisScript
rateLimitLua;
public void limitAccess(String key, Integer limit) {
boolean allowed = stringRedisTemplate.execute(rateLimitLua, Collections.singletonList(key), limit.toString());
if (!allowed) {
log.error("Access blocked for key={}", key);
throw new RuntimeException("Access blocked");
}
}
}An annotation‑based AOP aspect can apply the limiter to any controller method.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimiterAop {
int limit();
String methodKey() default "";
} @Aspect
@Component
public class AccessLimiterAspect {
@Autowired
private AccessLimiter accessLimiter;
@Pointcut("@annotation(com.example.AccessLimiterAop)")
public void cut() {}
@Before("cut()")
public void before(JoinPoint jp) {
MethodSignature ms = (MethodSignature) jp.getSignature();
Method method = ms.getMethod();
AccessLimiterAop anno = method.getAnnotation(AccessLimiterAop.class);
String key = StringUtils.isEmpty(anno.methodKey()) ? method.getName() : anno.methodKey();
accessLimiter.limitAccess(key, anno.limit());
}
}Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.