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
相关推荐
野犬寒鸦6 分钟前
从零起步学习并发编程 || 第四章:synchronized底层源码级讲解及项目实战应用案例
java·服务器·开发语言·jvm·后端·学习·面试
计算机毕设VX:Fegn08958 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
没差c9 小时前
springboot集成flyway
java·spring boot·后端
三水不滴9 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
编程彩机10 小时前
互联网大厂Java面试:从Java SE到大数据场景的技术深度解析
java·大数据·spring boot·面试·spark·java se·互联网大厂
笨蛋不要掉眼泪10 小时前
Spring Boot集成LangChain4j:与大模型对话的极速入门
java·人工智能·后端·spring·langchain
像少年啦飞驰点、11 小时前
零基础入门 Spring Boot:从“Hello World”到可上线微服务的完整学习指南
java·spring boot·微服务·编程入门·后端开发
indexsunny12 小时前
互联网大厂Java面试实战:从Spring Boot到微服务架构的技术问答解析
java·spring boot·redis·微服务·kafka·jwt·flyway
sheji341613 小时前
【开题答辩全过程】以 基于SpringBoot的疗养院管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端