Spring AOP实现分布式限流

高并发系统中,限流是必要的,不然一会就被人刷爆了。我介绍如何使用Spring AOP结合Redis实现分布式限流功能。

为什么需要限流?

在高并发场景下,系统可能会面临以下问题:

  1. 流量洪峰突发流量导致系统过载
  2. 资源耗尽数据库连接池、线程池等资源被耗尽
  3. 服务降级:保护核心服务(系统主动关掉非核心功能,优先保住核心功能
  4. 用户体验:防止系统崩溃,提供错误提示,比如“当前排队人数过多,请稍后再试”

限流策略选择

常见的限流算法有:

  • 令牌桶算法平滑限流,允许突发流量
  • 漏桶算法:严格限制请求速率
  • 计数器算法简单但不够平滑

我们用计数器算法结合Redis来简单实现分布式限流。

AOP限流实现原理

核心思路

  1. AOP切面:拦截标注@RateLimit注解的方法
  2. Redis存储:使用Redis记录请求计数
  3. Lua脚本:保证原子性操作
  4. 用户隔离:按用户ID或IP进行限流

架构设计

请求 → AOP拦截器 → Redis限流 → 业务方法

代码实现详解

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    String key() default "rate_limit";  // 限流Key前缀
    int time() default 60;            // 时间窗口(秒)
    int count() default 100;          // 允许的请求数
}

AOP切面实现

@Slf4j
@Aspect
@Component
public class RateLimitAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private DefaultRedisScript<Long> redisScript;

    @PostConstruct
    public void init() {
        redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
    }

    @Around("@annotation(com.campus.annotation.RateLimit)")
    public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        RateLimit rateLimit = method.getAnnotation(RateLimit.class);

        if (rateLimit != null) {
            String key = rateLimit.key();
            int time = rateLimit.time();
            int count = rateLimit.count();

            // 组合 Key: rate_limit:方法名:用户ID
            String combineKey = key + ":" + method.getName() + ":" + BaseContext.getCurrentId();
            List<String> keys = Collections.singletonList(combineKey);

            // 执行 Lua 脚本
            Long number = stringRedisTemplate.execute(redisScript, keys, String.valueOf(count), String.valueOf(time));

            if (number != null && number == 0) {
                // 超过限流
                throw new RuntimeException("访问过于频繁,请稍后再试");
            }
            log.info("限制请求'{}',当前请求'{}',缓存key'{}", count, number.intValue(), key);
        }

        return pjp.proceed();
    }
}

Lua脚本实现(计数器算法

-- limit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = tonumber(ARGV[2])

local is_exists = redis.call("EXISTS", key)
if is_exists == 1 then
    local current = tonumber(redis.call("GET", key))
    if current and current + 1 > limit then
        return 0
    end
    redis.call("INCR", key)
else
    redis.call("SET", key, "1")
    redis.call("EXPIRE", key, expire_time)
end

return 1

使用方法

添加注解

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @RateLimit(key = "user_login", time = 60, count = 5)
    @Override
    public User login(String username, String password) {
        // 登录逻辑
    }
}

配置Redis

spring:
  redis:
    host: localhost
    port: 6379

后续优化

令牌桶算法优化

当前实现是简单的计数器算法,可以升级为令牌桶算法

-- token_bucket.lua
local key = KEYS[1]
local rate = tonumber(ARGV[1])  -- 生成速率(令牌/秒)
local capacity = tonumber(ARGV[2])  -- 桶容量
local now = tonumber(ARGV[3])  -- 当前时间戳

local last_time = redis.call("HGET", key, "last_time") or now
local tokens = redis.call("HGET", key, "tokens") or capacity

local passed = now - last_time
local new_tokens = math.floor(passed * rate)
tokens = math.min(tokens + new_tokens, capacity)

if tokens > 0 then
    tokens = tokens - 1
    redis.call("HSET", key, "tokens", tokens)
    redis.call("HSET", key, "last_time", now)
    return 1
else
    return 0
end

动态限流

根据系统负载动态调整限流参数:

@RateLimit(key = "dynamic_limit", time = "${limit.time}", count = "${limit.count}")

避坑

  • Key 命名:一定要在切面里强制加上模块前缀,否则以后 Redis 里全是乱七八糟的 Key,排查起来能累死。
  • 别写死异常:建议定义一个统一的业务异常,前端接收后能做更优雅的处理,而不是随便穿弹一个 RuntimeException。
  • 进阶优化:目前是固定窗口的方法,有可能会遇到流量边界问题(比如刚好卡着时间窗口刷),后续可以无缝替换 Lua 脚本,用 ZSET 实现滑动窗口。

总结

我们详细了解基于Spring AOPRedis分布式限流实现,虽然简单,但是在小型项目中绝对够用,也能无缝切换更好的限流方法。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇