Understanding Interface Idempotency and Distributed Rate Limiting with Token Bucket, Leaky Bucket, Guava RateLimiter, Nginx, and Redis+Lua

This article explains the concept of interface idempotency, demonstrates how to achieve idempotent update and insert operations using version numbers and token mechanisms, and provides a comprehensive guide to distributed rate limiting—including time‑window and resource dimensions, token‑bucket and leaky‑bucket algorithms, Guava RateLimiter, Nginx configurations, and Redis‑Lua integration with Spring Boot.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding Interface Idempotency and Distributed Rate Limiting with Token Bucket, Leaky Bucket, Guava RateLimiter, Nginx, and Redis+Lua

1. Interface Idempotency

Idempotency means that multiple identical requests produce the same result without side effects; a classic example is a payment request that may be retried due to network errors, potentially causing duplicate charges if the interface is not idempotent.

The core idea is to use a unique business identifier (or token) to guarantee idempotency: check whether the identifier has been processed before executing the operation, and lock the process in concurrent scenarios.

1.1 Idempotent Update Operations

1) Update based on a unique business identifier

Use a version number to control update idempotency: the client reads the current version, submits the version together with the update, and the backend updates only if the version matches.

update set version = version + 1 ,xxx=${xxx} where id =xxx and version = ${version};

1.2 Token mechanism for update/insert idempotency

1) Operations without a unique business identifier

When entering a registration page, the backend generates a token and returns it to the front‑end hidden field. The token is used to acquire a distributed lock for the insert operation; the lock is kept until it expires, ensuring the operation is executed only once.

2. Distributed Rate Limiting

2.1 Dimensions of distributed rate limiting

Two primary dimensions are time windows (e.g., per second, per minute) and resources (e.g., maximum request count or concurrent connections). In practice, multiple rules are combined, such as limiting each IP to 10 requests per second and each server to 1000 QPS.

Additional dimensions include transmission speed (e.g., download bandwidth per user group) and black/white lists for dynamic IP blocking or privileged access.

2.2 Distributed environment considerations

Distributed rate limiting treats the whole cluster as a single entity; limits are stored in a centralized component (e.g., Redis) so that every node can read the current traffic state.

Common implementations are gateway‑level limiting and middleware limiting (e.g., using Redis to share counters).

2.3 Common algorithms

2.3.1 Token Bucket Algorithm

The token bucket has two key components: a token (granting permission to process a request) and a bucket (holding tokens). Tokens are added to the bucket at a fixed rate, up to a maximum capacity; requests consume tokens, and excess requests are queued or dropped.

When the bucket is full, new tokens are discarded.

2.3.2 Leaky Bucket Algorithm

The leaky bucket stores incoming requests in a bucket and drains them at a constant rate, ensuring a steady output flow regardless of bursty input.

Unlike the token bucket, the leaky bucket cannot handle traffic spikes without smoothing them.

2.4 Practical implementations

2.4.1 Guava RateLimiter (client‑side)

Add the Guava dependency:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

Example controller:

@RestController
@Slf4j
public class Controller {
    // two tokens per second
    RateLimiter limiter = RateLimiter.create(2.0);

    // non‑blocking limit
    @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";
        }
    }

    // blocking limit with timeout
    @GetMapping("/tryAcquireWithTimeout")
    public String tryAcquireWithTimeout(Integer count, Integer timeout) {
        if (limiter.tryAcquire(count, timeout, TimeUnit.SECONDS)) {
            log.info("success, rate={}", limiter.getRate());
            return "success";
        } else {
            log.info("fail, rate={}", limiter.getRate());
            return "fail";
        }
    }

    @GetMapping("/acquire")
    public String acquire(Integer count) {
        limiter.acquire(count);
        log.info("success, rate={}", limiter.getRate());
        return "success";
    }
}

2.4.2 Nginx rate limiting

IP‑based limiting example (limit 1 request per second):

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;
    }
}

Multi‑dimensional limiting can combine IP, server name, connection count, etc., with additional directives such as limit_conn and custom status codes.

2.4.3 Redis + Lua distributed limiting

Lua script (rateLimiter.lua) stored in Redis ensures atomic check‑and‑increment:

-- get method key
local methodKey = KEYS[1]
redis.log(redis.LOG_DEBUG, 'key is', methodKey)

-- limit passed from ARGV[1]
local limit = tonumber(ARGV[1])
local count = tonumber(redis.call('get', methodKey) or "0")

if count + 1 > limit then
    return false
else
    redis.call('INCRBY', methodKey, 1)
    redis.call('EXPIRE', methodKey, 1)
    return true
end

Spring Boot integration:

Add spring-boot-starter-data-redis, spring-boot-starter-aop, and Guava dependencies.

Configure Redis connection (e.g., spring.redis.host=localhost).

Load the Lua script with DefaultRedisScript and execute it via StringRedisTemplate.

Service class:

@Service
@Slf4j
public class AccessLimiter {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisScript<Boolean> rateLimitLua;

    public void limitAccess(String key, Integer limit) {
        boolean acquired = stringRedisTemplate.execute(rateLimitLua, Lists.newArrayList(key), limit.toString());
        if (!acquired) {
            log.error("Your access is blocked, key={}", key);
            throw new RuntimeException("Your access is blocked");
        }
    }
}

Annotation and AOP aspect to apply the limiter declaratively:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
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 joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AccessLimiterAop annotation = method.getAnnotation(AccessLimiterAop.class);
        if (annotation == null) return;
        String key = annotation.methodKey();
        int limit = annotation.limit();
        if (StringUtils.isEmpty(key)) {
            key = method.getName();
            Class<?>[] types = method.getParameterTypes();
            if (types != null) {
                String paramTypes = Arrays.stream(types).map(Class::getName).collect(Collectors.joining(","));
                key += "#" + paramTypes;
            }
        }
        accessLimiter.limitAccess(key, limit);
    }
}

Controller usage example:

@RestController
@Slf4j
public class TestController {
    @Autowired
    private AccessLimiter accessLimiter;

    @GetMapping("/test")
    @AccessLimiterAop(limit = 1)
    public String test() {
        return "success";
    }
}

These examples illustrate how to achieve reliable idempotent operations and robust distributed rate limiting across Java backend services.

Source: blog.csdn.net/qq_34886352/article/details/104694550

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Idempotency
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.