Xây dựng rate limiting (giới hạn tốc độ) bằng Redis
Tuyệt, dưới đây là hướng dẫn tích hợp tính năng Rate Limiting bằng Redis trong Spring Cloud Gateway, giúp bạn bảo vệ hệ thống khỏi việc bị spam request, DDoS, hoặc lạm dụng API từ một số client nhất định.
🚦 Rate Limiting là gì?#
Rate limiting là giới hạn số lượng request từ một client trong một khoảng thời gian. Ví dụ: không cho một IP gửi quá 100 request/phút.
Spring Cloud Gateway hỗ trợ tính năng này qua RedisRateLimiter, một phần của Spring Cloud Gateway Filters.
🧱 Kiến trúc cơ bản#
Client → Gateway (RedisRateLimiter) → Backend | └── Kết nối tới Redis (lưu thông tin IP + thời gian)
1. Thêm dependency vào pom.xml#
`
2. Cài đặt Redis#
Bạn có thể chạy Redis bằng Docker:
docker run -d -p 6379:6379 --name redis redis
Hoặc cài Redis trên local tùy theo hệ điều hành.
3. Cấu hình Redis trong application.yml#
` spring: redis: host: localhost port: 6379
cloud: gateway: routes: - id: user-service uri: http://localhost:8081 predicates: - Path=/api/users/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 5 redis-rate-limiter.burstCapacity: 10 key-resolver: "#{@ipKeyResolver}" `
replenishRate: số request cho phép mỗi giây (ví dụ: 5 request/giây)burstCapacity: số request tối đa được xử lý tức thời (buffer)
4. Định nghĩa KeyResolver để giới hạn theo IP#
` @Configuration public class RateLimiterConfig {
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> {
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(ip);
};
}
} `
- Mỗi IP sẽ có riêng bộ đếm (counter) request trong Redis.
5. Redis lưu gì trong quá trình rate limit?#
Spring sử dụng Redis để lưu:
Key: tên route + keyResolver (ví dụ:
user-service:192.168.1.5)TTL: dựa trên thời gian của replenish rate
Giá trị: số request còn lại
✅ Kết quả#
Nếu IP gửi >10 request trong 1 giây: Redis trả về
429 Too Many RequestsCác IP khác vẫn hoạt động bình thường
Giới hạn được đặt theo route, IP, hoặc user ID nếu dùng JWT
🔒 Nâng cao#
Bạn có thể:
Giới hạn theo user (phân tích JWT và dùng keyResolver theo
sub)Kết hợp Spring Security để giới hạn theo role
Theo dõi log Redis bằng
redis-cli monitorhoặc tích hợp Grafana/Prometheus
Dưới đây là hướng dẫn chuyên sâu về cách:
✅ Tích hợp Swagger với Gateway để test Rate Limit
🚫 Chặn IP vượt giới hạn nhiều lần
📊 Tạo API hiển thị trạng thái Rate Limit hiện tại
1. ✅ Tích hợp Swagger UI để test Gateway và Rate Limit#
🧱 Cách làm:#
Thêm Swagger vào từng service backend (ví dụ: user-service)
Tạo route
/swagger-ui/**trong Gateway để truy cập
⚙️ Cấu hình route Swagger trong application.yml:#
spring: cloud: gateway: routes: - id: user-service-swagger uri: http://localhost:8081 predicates: - Path=/swagger-ui/** filters: - RewritePath=/swagger-ui/(?<path>.*), /${path}
Bây giờ bạn có thể truy cập:
http://localhost:8080/swagger-ui/index.html
📝 Mẹo: Với nhiều service, thêm route tương ứng cho từng Swagger URL.
2. 🚫 Chặn IP nếu vượt giới hạn quá nhiều lần#
✅ Cách làm:#
Sử dụng Redis để đếm số lần một IP bị từ chối (HTTP 429)
Nếu vượt quá ngưỡng (ví dụ: 5 lần trong 1 phút), thêm IP đó vào danh sách đen (blacklist)
🧠 Bổ sung vào JwtAuthenticationFilter hoặc Filter tùy chỉnh:#
` @Component public class IpBlockFilter implements GlobalFilter {
private static final String BLOCKED_IP_KEY = "blocked::";
private static final String RATE_LIMIT_REJECT_COUNT = "reject::";
private static final int MAX_REJECTS = 5;
@Autowired
private ReactiveStringRedisTemplate redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return redisTemplate.hasKey(BLOCKED_IP_KEY + ip)
.flatMap(isBlocked -> {
if (isBlocked) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
// Nếu response là 429, tăng bộ đếm
exchange.getResponse().beforeCommit(() -> {
if (exchange.getResponse().getStatusCode() == HttpStatus.TOO_MANY_REQUESTS) {
return redisTemplate.opsForValue()
.increment(RATE_LIMIT_REJECT_COUNT + ip)
.then(redisTemplate.expire(RATE_LIMIT_REJECT_COUNT + ip, Duration.ofMinutes(1)))
.then(redisTemplate.opsForValue().get(RATE_LIMIT_REJECT_COUNT + ip))
.flatMap(count -> {
if (Integer.parseInt(count) >= MAX_REJECTS) {
return redisTemplate.opsForValue()
.set(BLOCKED_IP_KEY + ip, "1", Duration.ofMinutes(10));
}
return Mono.empty();
});
}
return Mono.empty();
});
return chain.filter(exchange);
});
}
} `
3. 📊 API hiển thị trạng thái Rate Limit của người dùng#
Ví dụ API: /rate-limit/status#
` @RestController @RequestMapping("/rate-limit") public class RateLimitController {
@Autowired
private ReactiveStringRedisTemplate redisTemplate;
@GetMapping("/status")
public Mono<Map<String, String>> getStatus(ServerHttpRequest request) {
String ip = request.getRemoteAddress().getAddress().getHostAddress();
String redisKey = "request_rate_limiter:{user-service}." + ip;
return redisTemplate.opsForValue()
.get(redisKey)
.map(value -> {
Map<String, String> status = new HashMap<>();
status.put("ip", ip);
status.put("info", value);
return status;
})
.defaultIfEmpty(Map.of("message", "No rate limit data found"));
}
} `
🔍 Lưu ý: Key trong Redis được Gateway tạo theo định dạng
{routeId}.{key}, nên bạn cần biếtrouteIdvà định dạngkeyResolver.