如何用注解优雅实现接口限流?一文详解!

基于注解的接口限流实现方案

一、前言

在高并发场景下,接口限流是保障系统稳定性的关键手段之一。它可以有效防止接口被恶意或突发流量压垮,确保核心业务的可用性。本文将介绍如何基于 Spring AOP 与 Redis 实现一个灵活、可配置的注解式接口限流组件。

二、实现目标

本方案旨在实现以下功能:

  • ✅ 注解方式配置限流策略,简单直观;
  • ⏱️ 支持自定义时间窗口与最大请求次数;
  • 提供多种 Key 解析策略(如基于用户 ID、客户端 IP、方法参数等);
  • 易于扩展,支持自定义 Key 解析器;
  • 使用 Redisson 实现高效的分布式限流。

三、系统组件设计

本限流方案主要由以下四个核心组件组成:

  1. @RateLimiter 注解:用于标注需要限流的接口方法;
  2. RateLimiterKeyResolver 接口:定义限流 Key 的解析策略;
  3. RateLimiterAspect 切面类:拦截注解方法并执行业务逻辑前进行限流检查;
  4. RateLimiterRedisDAO:封装 Redis 限流逻辑,底层基于 Redisson 实现。

四、注解定义:@RateLimiter

用于对方法进行限流控制,配置项如下:

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
    int time() default 1; // 时间窗口,默认1
    TimeUnit timeUnit() default TimeUnit.SECONDS; // 时间单位
    int count() default 100; // 最大请求次数
    String message() default ""; // 限流提示信息
    Class<? extends RateLimiterKeyResolver> keyResolver() default DefaultRateLimiterKeyResolver.class; // Key 解析器
    String keyArg() default ""; // 附加参数(可选)
}

五、限流 Key 解析器设计

定义统一接口 RateLimiterKeyResolver,用于动态生成限流 Key,实现解耦与扩展:

public interface RateLimiterKeyResolver {
    String resolver(JoinPoint joinPoint, RateLimiter rateLimiter);
}

✅ 示例实现:基于客户端 IP 的解析器

public class ClientIpRateLimiterKeyResolver implements RateLimiterKeyResolver {
    @Override
    public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
        String methodName = joinPoint.getSignature().toShortString();
        String argsStr = StrUtil.join(",", joinPoint.getArgs());
        String clientIp = ServletUtils.getClientIP();
        return SecureUtil.md5(methodName + argsStr + clientIp);
    }
}

支持扩展为基于用户、Token、路径等多种策略。


六、限流切面逻辑实现

使用 Spring AOP 实现方法拦截逻辑:

@Aspect
@Slf4j
public class RateLimiterAspect {
    private final Map<Class<? extends RateLimiterKeyResolver>, RateLimiterKeyResolver> keyResolvers;
    private final RateLimiterRedisDAO rateLimiterRedisDAO;

    public RateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) {
        this.keyResolvers = CollectionUtils.convertMap(keyResolvers, RateLimiterKeyResolver::getClass);
        this.rateLimiterRedisDAO = rateLimiterRedisDAO;
    }

    @Before("@annotation(rateLimiter)")
    public void beforePointCut(JoinPoint joinPoint, RateLimiter rateLimiter) {
        RateLimiterKeyResolver keyResolver = keyResolvers.get(rateLimiter.keyResolver());
        Assert.notNull(keyResolver, "未找到对应的 KeyResolver");

        String key = keyResolver.resolver(joinPoint, rateLimiter);
        boolean allowed = rateLimiterRedisDAO.tryAcquire(key, rateLimiter.count(), rateLimiter.time(), rateLimiter.timeUnit());

        if (!allowed) {
            log.warn("[限流触发] 方法: {}, 参数: {}", joinPoint.getSignature(), joinPoint.getArgs());
            String message = StrUtil.blankToDefault(rateLimiter.message(), GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getMsg());
            throw new ServiceException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getCode(), message);
        }
    }
}

七、Redis 限流控制实现

使用 Redisson 的 RRateLimiter 提供令牌桶限流功能:

@AllArgsConstructor
public class RateLimiterRedisDAO {
    private static final String RATE_LIMITER = "rate_limiter:%s";
    private final RedissonClient redissonClient;

    public Boolean tryAcquire(String key, int count, int time, TimeUnit timeUnit) {
        RRateLimiter rateLimiter = getRRateLimiter(key, count, time, timeUnit);
        return rateLimiter.tryAcquire();
    }

    private RRateLimiter getRRateLimiter(String key, long count, int time, TimeUnit timeUnit) {
        String redisKey = String.format(RATE_LIMITER, key);
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(redisKey);
        long interval = timeUnit.toSeconds(time);
        rateLimiter.trySetRate(RateType.OVERALL, count, interval, RateIntervalUnit.SECONDS);
        return rateLimiter;
    }
}

八、使用方式示例

在需要限流的 Controller 或 Service 方法上添加注解即可:

@RateLimiter(count = 10, time = 1, timeUnit = TimeUnit.MINUTES, keyResolver = ClientIpRateLimiterKeyResolver.class)
public String getUserInfo(String userId) {
    // 业务逻辑
    return "User Info";
}

九、总结

本文介绍了一个基于 Spring AOP 与 Redis(Redisson)实现的注解式接口限流方案。该方案具备以下优势:

  • 配置灵活:通过注解快速定义限流规则;
  • 易于扩展:支持多种 Key 生成策略,满足不同业务需求;
  • ⚡ 高效可靠:依赖 Redisson 实现分布式限流,性能优越;

适用于微服务架构下高并发接口保护场景,推荐在实际开发中推广使用。

你可能感兴趣的:(java,redis)