SpringBoot整合Redis限流

通过一个完整的Spring Boot项目演示如何用Redis实现简单的API限流功能。我们将从零开始搭建项目。

一、环境准备

1.1 开发环境要求

  • JDK 1.8+
  • IntelliJ IDEA(推荐)
  • Redis 5.0+(本地安装)
  • Postman(测试用)

1.2 创建SpringBoot项目

使用IDEA创建新项目,选择:

  • Spring Web
  • Spring Data Redis (Lettuce)
  • Lombok

二、基础配置

2.1 添加Redis配置

yaml 复制代码
# application.yml
spring:
  redis:
    host: localhost
    port: 6379

2.2 Redis配置类

java 复制代码
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

三、核心代码实现

3.1 令牌桶算法Lua脚本

在resources目录创建rate_limiter.lua

lua 复制代码
-- 令牌桶算法实现
local key = KEYS[1]
local now = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local rate = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

-- 获取当前令牌数
local data = redis.call("HMGET", key, "tokens", "last_time")
local tokens = tonumber(data[1]) or capacity
local last_time = tonumber(data[2]) or now

-- 计算新增令牌
local delta = math.floor((now - last_time) * rate)
if delta > 0 then
    tokens = math.min(tokens + delta, capacity)
    last_time = now
end

-- 判断是否允许请求
local result = 0
if tokens >= requested then
    tokens = tokens - requested
    result = 1
end

-- 更新存储
redis.call("HMSET", key, "tokens", tokens, "last_time", last_time)
redis.call("EXPIRE", key, 86400)

return result

3.2 限流服务类

java 复制代码
@Service
@RequiredArgsConstructor
public class RateLimitService {
    private final RedisTemplate<String, Object> redisTemplate;
    
    // 加载Lua脚本
    private static final DefaultRedisScript<Long> REDIS_SCRIPT;
    static {
        REDIS_SCRIPT = new DefaultRedisScript<>();
        REDIS_SCRIPT.setLocation(new ClassPathResource("rate_limiter.lua"));
        REDIS_SCRIPT.setResultType(Long.class);
    }

    /**
     * 尝试获取令牌
     * @param key 限流key
     * @param capacity 桶容量
     * @param rate 令牌生成速率(个/秒)
     * @param requested 请求令牌数
     * @return 是否允许请求
     */
    public boolean tryAcquire(String key, int capacity, double rate, int requested) {
        Long result = redisTemplate.execute(
                REDIS_SCRIPT,
                Collections.singletonList(key),
                Instant.now().getEpochSecond(),
                capacity,
                rate,
                requested
        );
        return result != null && result == 1;
    }
}

3.3 自定义限流注解

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    String key() default "";       // 限流key前缀
    int capacity() default 100;    // 桶容量
    double rate() default 10.0;    // 令牌生成速率
    int permits() default 1;       // 每次请求消耗令牌数
}

3.4 切面实现

java 复制代码
@Aspect
@Component
@RequiredArgsConstructor
public class RateLimitAspect {
    private final RateLimitService rateLimitService;

    @Around("@annotation(rateLimit)")
    public Object checkLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        String key = buildKey(joinPoint, rateLimit);
        boolean allow = rateLimitService.tryAcquire(
                key,
                rateLimit.capacity(),
                rateLimit.rate(),
                rateLimit.permits()
        );
        if (!allow) {
            throw new RuntimeException("请求太频繁,请稍后再试!");
        }
        return joinPoint.proceed();
    }

    private String buildKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return "rate_limit:" + 
               (rateLimit.key().isEmpty() ? 
                signature.getMethod().getName() : rateLimit.key());
    }
}

四、使用示例

4.1 创建测试Controller

java 复制代码
@RestController
public class TestController {

    @RateLimit(key = "testApi", capacity = 5, rate = 1)
    @GetMapping("/test")
    public String testApi() {
        return "请求成功!当前时间:" + LocalDateTime.now();
    }
}

4.2 异常处理

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<String> handleLimit(RuntimeException ex) {
        return ResponseEntity.status(429).body(ex.getMessage());
    }
}

五、测试验证

5.1 使用Postman测试

  1. 快速连续访问 http://localhost:8080/test
  2. 观察响应结果变化:

正常响应:

json 复制代码
"请求成功!当前时间:2024-05-20T15:30:45.123"

限流响应:

json 复制代码
{
    "status": 429,
    "message": "请求太频繁,请稍后再试!"
}

5.2 测试结果预期

请求次数 结果
1-5 成功
6-10 部分失败
后续请求 每秒1次成功

六、常见问题解答

Q1:Redis宕机了怎么办?

A:可添加本地限流作为备用方案(推荐使用Guava的RateLimiter)

Q2:如何区分不同用户?

A:在key中加入用户ID:@RateLimit(key = "user_#{userId}")

Q3:如何修改限流规则?

A:直接修改注解参数即可立即生效

七、完整项目结构

复制代码
rate-limiter-demo
├── src/main/java
│   └── com
│       └── example
│           ├── config/RedisConfig.java
│           ├── service/RateLimitService.java
│           ├── aspect/RateLimitAspect.java
│           ├── annotation/RateLimit.java
│           ├── controller/TestController.java
│           └── exception/GlobalExceptionHandler.java
├── src/main/resources
│   ├── rate_limiter.lua
│   └── application.yml
相关推荐
AlienZHOU4 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
李昊哲小课6 小时前
Spring Boot SSE 实时推送教程
spring boot·websocket·flux·sseemitter
狗头大军之江苏分军6 小时前
《潮水漫过十七岁》开学了
后端
苏三说技术6 小时前
如何看待GPT-6在UP主众测中碾压夺冠?它是现在最强大模型吗?
后端
mldong7 小时前
一份 JSON,一条能跑的审批流:把报销流程送上工作流引擎
后端·架构
wno7047 小时前
Spring Boot WebFlux增删改查
java·spring boot·后端
Captaincc7 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
aramae8 小时前
模拟实现strlen()函数 (C语言)
c语言·开发语言·后端
行百里er9 小时前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring cloud·监控
IT_陈寒9 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端