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.

Top Architect
Top Architect
Top Architect
Comprehensive Guide to Rate Limiting in Backend Applications Using Guava, Redis, and Nginx

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.

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.

BackendJavaredisGuava
Top Architect
Written by

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.

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.