Comprehensive Guide to Rate Limiting in Backend Applications Using Guava, Redis, and Nginx
This article explains why rate limiting is needed in high‑traffic backend services, outlines common methods such as token‑bucket with Guava, Redis‑based counters, Nginx configuration, and thread‑pool limits, and provides complete Java code examples for implementing each approach.
1. Rate‑Limiting Operations
Why rate limiting? High‑frequency requests can overwhelm an externally deployed service, especially when using WebSocket interfaces without hardware upgrades, leading to crashes. The author initially observed a throughput of about 16,000 QPS on a test server, but the local machine became unresponsive during JMeter load testing.
Common rate‑limiting techniques :
Netflix Hystrix
Alibaba Sentinel (open‑source)
General approaches: queues, threads, thread pools, message queues (Kafka), middleware, Sentinel strategies such as direct reject, warm‑up, smooth queuing, etc.
Technical level solutions :
Cache identical requests locally to block duplicates.
Use load balancers like Nginx.
Cache hot data in Redis or Elasticsearch.
Leverage connection pools; the author also mentions a WeChat public account for interview questions.
Business level solution :
Introduce interactive queuing for users.
2. Application‑Level Rate Limiting and Implementations
Method 1 – Guava Token‑Bucket Algorithm
Guava provides SmoothBursty (smooth burst) and SmoothWarmingUp (smooth warm‑up) rate limiters.
<!--Java项目广泛依赖 的核心库-->
<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;
import java.util.Collections;
@RestController
public class HomeController {
// 1 represents 10 permits per second
private RateLimiter limiter = RateLimiter.create(10.0);
//RateLimiter.create(double permitsPerSecond, long warmupPeriod, TimeUnit unit);
//RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);
@GetMapping("/test/{name}")
public String test(@PathVariable("name") String name) {
// Request a permit; if exceeded the call blocks
final double acquire = limiter.acquire();
System.out.println("--------" + acquire);
// Check if double is effectively zero
if (acquire >= (-1e-6) && acquire <= (1e-6)) {
return name;
} else {
return "操作太频繁"; // "Too many requests"
}
}
}This is similar to QPS flow control: when QPS exceeds a threshold, the system can reject requests immediately (throwing an exception or returning 404) or apply other mitigation strategies.
Method 2 – Redis Counter per Request
Increment a Redis key (e.g., IP+timestamp) on each request and set an expiration time.
Interceptor configuration :
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.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Add interceptor for all paths
registry.addInterceptor(new AccessLimitInterceptor())
.addPathPatterns("/**");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/");
}
}Interceptor implementation :
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 org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
public class AccessLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
Method method = hm.getMethod();
if (!method.isAnnotationPresent(AccessLimit.class)) {
return true;
}
AccessLimit limitAnno = method.getAnnotation(AccessLimit.class);
if (limitAnno == null) {
return true;
}
int limit = limitAnno.limit();
int sec = limitAnno.sec();
String key = IpUtil.getIpAddr(request) + request.getRequestURI();
Integer current = redisTemplate.opsForValue().get(key);
if (current == null) {
redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);
} else if (current < limit) {
redisTemplate.opsForValue().set(key, current + 1, sec, TimeUnit.SECONDS);
} else {
output(response, "请求太频繁!");
return false;
}
}
return true;
}
public void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream out = response.getOutputStream();
out.write(msg.getBytes("UTF-8"));
out.flush();
out.close();
}
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {}
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {}
}The rate‑limit annotation:
package com.citydo.dialogue.entity;
import java.lang.annotation.*;
@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
// Maximum number of accesses within the specified time window
int limit() default 5;
// Time window in seconds
int sec() default 5;
}Redis configuration to avoid serialization issues:
package com.citydo.dialogue.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
/** Resolve Redis garbled characters */
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
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
Implement atomic distributed rate limiting using Redis + Lua scripts or Nginx + Lua.
Method 4 – Pool‑Based Limiting
Limit total resources such as database connections or thread pools (e.g., a maximum of 100 connections per application).
Method 5 – Tomcat Connector Parameters
maxThreads: maximum number of request‑processing threads. maxConnections: peak simultaneous connections; excess requests are queued. acceptCount: queue size for incoming connections when all threads are busy; beyond this, connections are rejected.
Method 6 – AtomicLong for Global Concurrency Control
try {
if (atomic.incrementAndGet() > limit) {
// reject request
} else {
// process request
}
} finally {
atomic.decrementAndGet();
}Method 7 – Guava Cache for Time‑Window Counting
LoadingCache counter = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(new CacheLoader() {
@Override
public AtomicLong load(Long seconds) throws Exception {
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
}The article concludes by encouraging readers to discuss ideas, join the architect community, and obtain interview question collections from the author’s public account.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
