灌篮少年 发表于 2025-4-10 08:11:57

SpringBoot 接口限流Lua脚本接合Redis 服务熔断 自界说注解 接口掩护

介绍

Spring Boot 接口限流是防止接口被频仍哀求而导致服务器负载过重或服务瓦解的一种计谋。通过限流,我们可以控制单位时间内允许的哀求次数,确保系统的稳定性。限流可以资助防止恶意哀求、掩护系统资源,并优化 API 的可用性,避免因过多哀求导致服务不可用。
Resis序列化
自界说注解

@Retention(RetentionPolicy.RUNTIME) //运行时使用
@Target({ElementType.METHOD}) // 应用到方法和类上
public @interface ApiLimitation {
    int seconds() default 5; //多少秒访问
    int maxCount() default 5; //最大次数
    //默认5秒可以访问5次
}
依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
设置文件

spring:
redis:
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器端口号
    port: 6379
    # 使用的数据库索引,默认是0
    database: 0
    # 连接超时时间
    timeout: 1800000
    # 设置密码
    # password: "123456"
    lettuce:
      pool:
      # 最大阻塞等待时间,负数表示没有限制
      max-wait: -1
      # 连接池中的最大空闲连接
      max-idle: 5
      # 连接池中的最小空闲连接
      min-idle: 0
      # 连接池中最大连接数,负数表示没有限制
      max-active: 20
拦截器

@Component
public class RequestInterceptor implements HandlerInterceptor {

    // RedisTemplate 用于与 Redis 交互
    private final RedisTemplate<Object, Object> redisTemplate;

    // 构造函数,注入 RedisTemplate
    public RequestInterceptor(RedisTemplate<Object, Object> redisTemplate) {
      this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      // 检查处理的 handler 是否是 HandlerMethod(即具体的控制器方法)
      if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取方法上的 ApiLimitation 注解
            ApiLimitation methodAnnotation = handlerMethod.getMethodAnnotation(ApiLimitation.class);

            // 如果没有 ApiLimitation 注解,则跳过限流逻辑,允许访问
            if (methodAnnotation == null) {
                return true;
            }

            // 获取注解中的配置,设置时间窗口和最大访问次数
            int time = methodAnnotation.seconds(); // 限制的时间窗口(秒)
            int count = methodAnnotation.maxCount(); // 最大请求次数

            // 获取客户端的 IP 地址
            String ip = request.getRemoteAddr();

            // 组合 key,格式为 "ip:请求路径"
            String key = ip + ":" + request.getServletPath();
            List<Object> keys = Collections.singletonList(key);

            // 创建 Redis 脚本对象,用于执行 Lua 脚本
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(limitScriptText());// 设置 Lua 脚本内容
            redisScript.setResultType(Long.class); // 设置返回值类型为 Long

            // 执行 Lua 脚本进行访问频率控制
            Long number = redisTemplate.execute(redisScript, keys, count, time);

            // 如果返回值为空或者访问次数超过最大限制,表示请求过于频繁,拒绝访问
            if (number == null || number.intValue() > count) {
                response.getWriter().write("访问频繁");// 返回 "访问频繁" 信息给客户端
                return false;// 拒绝访问
            }

            // 允许访问
            return true;
      }

      // 如果不是处理具体方法,默认允许访问
      return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    // 判断对象是否为 null 的工具方法
    public static boolean isNull(Object object) {
      return object == null;
    }

    // 返回用于限制访问频率的 Lua 脚本内容
    private String limitScriptText() {
      return "local key = KEYS\n" +
                "local count = tonumber(ARGV)\n" +
                "local time = tonumber(ARGV)\n" +
                "local current = redis.call('get', key);\n" +
                "if current and tonumber(current) > count then\n" +// 如果当前访问次数已经超过最大次数,则返回当前次数
                "    return tonumber(current);\n" +
                "end\n" +
                "current = redis.call('incr', key)\n" +// 否则,增加访问次数
                "if tonumber(current) == 1 then\n" +// 如果是第一次访问,设置 key 的过期时间
                "    redis.call('expire', key, time)\n" +// 设置过期时间,避免 Redis 中的 key 永久存在
                "end\n" +
                "return tonumber(current);";// 返回当前的访问次数
    }
}

注册拦截器

@Configuration //表示该类为配置类
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final RequestInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(interceptor).addPathPatterns("/**");
      //拦截所有的请求

//      registry.addInterceptor(interceptor)
//                .addPathPatterns("/user")//需要拦截的请求
//                .excludePathPatterns("/login");//不需要拦截的请求

    }
}
控制器

@RestController
public class UserController {

    @GetMapping("/info")
    @ApiLimitation(seconds = 5,maxCount = 2) //五秒钟只可以访问2次
    public String getInfo(){

      return "成功";
    }
}
https://i-blog.csdnimg.cn/direct/2e1d2ec61ab4423d95416b8f1dd0c372.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: SpringBoot 接口限流Lua脚本接合Redis 服务熔断 自界说注解 接口掩护