Redis Rate Limiter Implementation with AOP Annotation Extraction and Expiration Support
This article explains how to extract AOP annotation code, implements a Redis-based rate limiter using Redisson in Java, analyzes the underlying Lua script, and extends the utility to support automatic expiration of the limiter, providing complete code examples and detailed explanations.
The article begins by showing how an AOP annotation can invoke the RedisUtils.rateLimiter method to perform rate limiting, followed by a brief code snippet that demonstrates the call:
// 调用Reids工具类的rateLimiter 方法
long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);It then presents the full RedisUtils class, which defines a static rateLimiter method that obtains a Redisson RRateLimiter , configures it with trySetRate , and returns the available permits or -1L on failure.
public class RedisUtils {
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
/**
* 限流
* @param key 限流key
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔
* @return -1 表示失败
*/
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits();
} else {
return -1L;
}
}
}The article analyzes the trySetRate implementation inside Redisson, showing that it ultimately executes a Lua script via commandExecutor.evalWriteNoRetryAsync . The Lua script uses hsetnx to store the rate, interval, and type fields in a Redis hash, ensuring fields are only set if they do not already exist.
@Override
public boolean trySetRate(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
return get(trySetRateAsync(type, rate, rateInterval, unit));
}
@Override
public RFuture
trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
return commandExecutor.evalWriteNoRetryAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);" +
"redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);" +
"return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);",
Collections.singletonList(getRawName()), rate, unit.toMillis(rateInterval), type.ordinal());
}Each part of the Lua script is explained: it attempts to set the rate , interval , and type fields in the hash, using hsetnx so existing fields are left unchanged.
Since the original implementation does not provide automatic expiration for the limiter, the article adds an expire call. The updated rateLimiter method now accepts expirationTimeInSeconds and isExpire parameters, and if expiration is requested, it invokes rateLimiter.expire(expirationTimeInSeconds, TimeUnit.SECONDS) before attempting to acquire permits.
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval,
long expirationTimeInSeconds, boolean isExpire) {
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
if (isExpire) {
rateLimiter.expire(expirationTimeInSeconds, TimeUnit.SECONDS);
}
if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits();
} else {
return -1L;
}
}Finally, the article invites readers to comment on any issues in the code, encouraging community discussion and improvement.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
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.