Rate Limiting in Spring Boot: Counter, Leaky & Token Buckets using Guava & Baidu
This article explains three classic rate‑limiting algorithms—counter, leaky bucket, and token bucket—illustrates their principles, compares their behavior, and provides practical Spring Boot implementations using Google Guava’s RateLimiter and Baidu’s ratelimiter‑spring‑boot‑starter, including configuration, code samples, and performance testing.
Environment: springboot2.3.9 + Guava30.1.1-jre
Rate Limiting Algorithms
Generally there are two rate‑limiting algorithms—leaky bucket and token bucket—plus a simple counter method.
Counter
Using a counter for rate limiting is straightforward: set a QPS limit (e.g., 100 requests per second). When the first request arrives, start a 1‑second window; each subsequent request increments an AtomicLong counter. If the counter reaches the threshold, further requests are rejected until the window expires and the counter resets to zero. This approach can cause a "burst" problem where early requests consume the quota and later requests are all blocked.
Leaky Bucket
The leaky bucket algorithm controls the rate at which data leaves a bucket, smoothing bursty traffic. It can be visualized as a bucket that receives incoming requests (water) and releases them at a constant rate; excess water overflows and is dropped, effectively rejecting excess requests.
The leaky bucket reliably controls access speed; requests exceeding the configured rate are denied.
Token Bucket
The token bucket algorithm adds tokens to a bucket at a constant rate. A request can proceed only if it successfully takes a token; otherwise it is rejected. Conceptually, it is the opposite of the leaky bucket: tokens are added ("water in") and requests consume them ("water out").
After understanding the three algorithms, we explore how to apply them in a project.
Use Google Guava library RateLimiter
Guava's RateLimiter implements a token‑bucket algorithm. It issues tokens at a fixed rate; a thread must acquire a token before proceeding. RateLimiter does not support clustered environments, which require external stores like Redis.
Dependency:
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency></code>Goal: allow only 3 requests per second.
<code>@RestController
@RequestMapping("/products")
public class ProductController {
private final RateLimiter rateLimiter = RateLimiter.create(5.0);
@GetMapping("/{id}")
public ResponseEntity<R> queryProducts(@PathVariable("id") String id) throws Exception {
if (rateLimiter.tryAcquire(1)) {
TimeUnit.MILLISECONDS.sleep(200);
return new ResponseEntity<>(R.success("查询商品【" + id + "】成功"), HttpStatus.OK);
}
return new ResponseEntity<>(R.failure("你访问的太快了"), HttpStatus.INTERNAL_SERVER_ERROR);
}
}</code>Testing with JMeter 5.4.1 (100 concurrent threads, 2 loops) shows the rate limit in action.
Interface configuration screenshot:
Test results screenshot:
RateLimiter related methods illustration:
Use Baidu's ratelimiter‑spring‑boot‑starter
ratelimiter‑spring‑boot‑starter provides single‑node rate limiting for Spring Boot services; it can be integrated into Spring Cloud projects as well.
Limiting dimensions : node‑level, method‑level, service‑level.
Node‑level: each service instance applies its own rules.
Method‑level: each HTTP method+URI can have separate rules.
Service‑level: a global rule for all requests to the instance; both service‑level and method‑level are evaluated, and any rejection blocks the request.
Dependency:
<code><dependency>
<groupId>com.baidubce.formula</groupId>
<artifactId>ratelimiter-spring-boot-starter</artifactId>
<version>2.1.1.1</version>
</dependency></code>Application configuration:
<code>spring:
application:
name: ratelimiter
---
formula:
ratelimiter:
enabled: true
ratelimiters:
- effectiveLocation: /products/q/**
effectiveType: 1
enabled: true
httpMethod: GET
limiterType: 1
threshold: 5</code>Note:
spring.application.namemust be set; the
sourcefield is undocumented.
API example:
<code>@GetMapping("/q/{id}")
public ResponseEntity<R> queryProduct(@PathVariable("id") String id) throws Exception {
TimeUnit.MILLISECONDS.sleep(200);
return new ResponseEntity<>(R.success("查询商品【" + id + "】成功"), HttpStatus.OK);
}</code>Failed requests return HTTP 429 Too Many Requests.
Core filter class in Baidu’s tool (RateLimiterEffectiveFilter.java) is shown below.
Key methods such as
waitForPermitand
waitForPermissionare illustrated.
HttpUtil#isBlockException can be overridden to customize the block response.
<code>public static boolean isBlockException(HttpServletResponse response, Exception e) throws IOException {
if (e instanceof BlockException) {
response.setStatus(429);
response.setContentType("application/json; charset=utf-8");
response.setCharacterEncoding("UTF-8");
response.getWriter().print("{\"code\": -1, \"message\": \"你的请求太快了\"}");
response.flushBuffer();
return true;
} else {
return false;
}
}</code>After modifying and testing, the custom behavior works as expected.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.