• 使用策略模式实现 Spring 分布式和单机限流


    我们可以使用策略模式来统一单机限流和分布式限流的实现,提高代码的可扩展性和可维护性。

    思路是定义一个 RateLimitStrategy 接口,并分别实现单机限流策略 LocalRateLimitStrategy分布式限流策略 DistributedRateLimitStrategy。在 AOP 切面中,根据配置决定使用哪种限流策略。

    定义策略接口

    public interface RateLimitStrategy {
        boolean tryAcquire(String key, double qps, long timeout, TimeUnit timeUnit);
    }
    
    • 1
    • 2
    • 3

    实现单机限流策略

    import com.google.common.util.concurrent.RateLimiter;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.TimeUnit;
    
    public class LocalRateLimitStrategy implements RateLimitStrategy {
    
        private final Map<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();
    
        @Override
        public boolean tryAcquire(String key, double qps, long timeout, TimeUnit timeUnit) {
            RateLimiter limiter = rateLimiters.computeIfAbsent(key, k -> RateLimiter.create(qps));
            if (timeout > 0) {
                return limiter.tryAcquire(timeout, timeUnit);
            } else {
                return limiter.tryAcquire();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    实现分布式限流策略

    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.script.RedisScript;
    
    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    public class DistributedRateLimitStrategy implements RateLimitStrategy {
    
        private final RedisTemplate<String, Object> redisTemplate;
    
        public DistributedRateLimitStrategy(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        @Override
        public boolean tryAcquire(String key, double qps, long timeout, TimeUnit timeUnit) {
            long window = timeUnit.toSeconds(timeout);
            List<String> keys = Collections.singletonList(key);
    
            String luaScript = buildLuaScript();
            RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
            Long currentCount = redisTemplate.execute(redisScript, keys, Collections.singletonList(window), Collections.singletonList(qps));
    
            return currentCount <= qps;
        }
    
        private String buildLuaScript() {
           return "local key = KEYS[1]\n" +
                    "local window = tonumber(ARGV[1])\n" +
                    "local qps = tonumber(ARGV[2])\n" +
                    "local current = redis.call('incrBy', key, 1)\n" +
                    "if current == 1 then\n" +
                    "    redis.call('expire', key, window)\n" +
                    "end\n" +
                    "if current > qps then\n" +
                    "    return redis.call('decrBy', key, 1)\n" +
                    "else\n" +
                    "    return current\n" +
                    "end";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    修改切面逻辑

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.util.concurrent.TimeUnit;
    
    @Aspect
    @Component
    public class RateLimitAspect {
    
        @Autowired
        private RateLimitStrategy rateLimitStrategy;
    
        @Around("@annotation(rateLimitAnnotation)")
        public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimitAnnotation) throws Throwable {
            String key = joinPoint.getSignature().toLongString();
            double qps = rateLimitAnnotation.qps();
            long timeout = rateLimitAnnotation.timeout();
            TimeUnit timeUnit = rateLimitAnnotation.timeUnit();
    
            boolean acquired = rateLimitStrategy.tryAcquire(key, qps, timeout, timeUnit);
            if (!acquired) {
                throw new RuntimeException("Rate limit exceeded");
            }
    
            return joinPoint.proceed();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在切面逻辑中,我们注入了 RateLimitStrategy 的实现类。根据配置决定使用单机限流还是分布式限流策略。

    使用示例

    @RestController
    public class DemoController {
    
        @Autowired
        private RateLimitStrategy rateLimitStrategy;
    
        @GetMapping("/test")
        @ApiRateLimit(qps = 10, timeout = 60, timeUnit = TimeUnit.SECONDS)
        public String test() {
            return "hello world";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在使用时,我们只需要在方法上标注 @RateLimit 注解即可,而不需要关心底层使用的是单机限流还是分布式限流。

    配置限流策略

    在 Spring 配置中,我们可以根据需求注入不同的 RateLimitStrategy 实现类:

    // 单机限流配置
    @Bean
    public RateLimitStrategy localRateLimitStrategy() {
        return new LocalRateLimitStrategy();
    }
    
    // 分布式限流配置
    @Bean
    public RateLimitStrategy distributedRateLimitStrategy(RedisTemplate<String, Object> redisTemplate) {
        return new DistributedRateLimitStrategy(redisTemplate);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过使用策略模式,我们将限流算法与具体的限流策略解耦,提高了代码的可扩展性和可维护性。未来如果需要新的限流策略,只需要实现 RateLimitStrategy 接口并配置即可,无需修改核心的限流逻辑。

  • 相关阅读:
    从一次Kafka宕机说起(JVM hang)
    wy的leetcode刷题记录_Day25
    ChartDirector 7.1 for .NET Crack
    Stable Diffusion绘画
    零刻SER8 AMD 8845Hs Ryzen AI 本地部署大语言模型教程!
    干货丨产品的可行性分析要从哪几个方面入手?
    WINDOWS核心编程--Windows程序内部运行机制
    阿里云2核2G服务器e系列租用优惠价格182元性能测评
    【算法可视化】图论专题
    C语言之文件操作【万字详解】
  • 原文地址:https://blog.csdn.net/mbh12333/article/details/137880117