Implementing Rate Limiting in Java Spring Applications Using Guava, Redis, and Nginx
This article explains why rate limiting is needed for high‑traffic Java services, reviews common throttling techniques such as Hystrix, Sentinel, token‑bucket algorithms, and then provides multiple concrete implementations—including Guava RateLimiter, Redis counters, interceptor configuration, and Tomcat connector settings—complete with code samples.
1. Rate Limiting Operations
Why Rate Limit
Rate limiting prevents malicious rapid refreshes of an interface, protects services that run on external servers without hardware upgrades, and avoids crashes caused by excessive concurrent requests.
Common Rate‑Limiting Methods
Netflix Hystrix
Alibaba Sentinel (open‑source)
Queue, thread pool, message queue (Kafka), middleware, and Sentinel strategies such as direct reject, warm‑up, and smooth queuing
Technical approaches include caching duplicate requests, using load balancers like Nginx, caching hot data in Redis or Elasticsearch, and employing connection pools.
Business approaches may add user interaction queues.
2. Application‑Level Rate Limiting Implementations
Method 1 – Google Guava Token‑Bucket : Use Guava's RateLimiter to implement SmoothBursty or SmoothWarmingUp throttling.
<!-- Java project core library dependency -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency> package com.citydo.dialogue.controller;
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
// 10 permits per second
private RateLimiter limiter = RateLimiter.create(10.0);
@GetMapping("/test/{name}")
public String test(@PathVariable("name") String name) {
double acquire = limiter.acquire();
System.out.println("--------" + acquire);
if (acquire >= -1e-6 && acquire <= 1e-6) {
return name;
} else {
return "操作太频繁"; // Too frequent
}
}
}This behaves like QPS control: when the request rate exceeds a threshold, the limiter blocks or rejects excess calls.
Method 2 – Redis Counter : Increment a Redis key (e.g., IP+timestamp) on each request and set an expiration time.
package com.citydo.dialogue.config;
import com.citydo.dialogue.service.AccessLimitInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AccessLimitInterceptor())
.addPathPatterns("/**");
}
} package com.citydo.dialogue.service;
import com.citydo.dialogue.entity.AccessLimit;
import com.citydo.dialogue.utils.IpUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;
public class AccessLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate
redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
AccessLimit al = hm.getMethodAnnotation(AccessLimit.class);
if (al == null) return true;
int limit = al.limit();
int sec = al.sec();
String key = IpUtil.getIpAddr(request) + request.getRequestURI();
Integer count = redisTemplate.opsForValue().get(key);
if (count == null) {
redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);
} else if (count < limit) {
redisTemplate.opsForValue().set(key, count + 1, sec, TimeUnit.SECONDS);
} else {
output(response, "请求太频繁!");
return false;
}
}
return true;
}
private void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.getOutputStream().write(msg.getBytes("UTF-8"));
}
}The interceptor can be driven by a custom annotation:
import java.lang.annotation.*;
@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
int limit() default 5; // max requests
int sec() default 5; // time window in seconds
}Redis configuration to avoid serialization issues:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate
redisTemplate(RedisConnectionFactory factory) {
RedisTemplate
template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}Method 3 – Distributed Rate Limiting : Use Redis+Lua scripts or Nginx+Lua for atomic throttling across multiple nodes.
Method 4 – Pooling Techniques : Limit total resources such as database connections or thread pools; excess requests wait or throw exceptions.
Method 5 – Tomcat Connector Settings (example parameters):
maxThreads : maximum request‑handling threads
maxConnections : peak simultaneous connections, excess are queued
acceptCount : queue size for incoming connections when all threads are busy
Method 6 – AtomicLong Counter for a single endpoint:
try {
if (atomic.incrementAndGet() > limit) {
// reject request
} else {
// process request
}
} finally {
atomic.decrementAndGet();
}Method 7 – Guava Cache with Time Window :
LoadingCache
counter = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(new CacheLoader
() {
@Override
public AtomicLong load(Long seconds) {
return new AtomicLong(0);
}
});
long limit = 1000;
while (true) {
long currentSeconds = System.currentTimeMillis() / 1000;
if (counter.get(currentSeconds).incrementAndGet() > limit) {
System.out.println("限流了: " + currentSeconds);
continue;
}
// business logic
}All the above techniques aim to control request throughput and protect backend services from overload.
References: CSDN article, WeChat public‑account posts, and the GitHub repository springboot_current_limiting .
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.