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

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

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

定义策略接口

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

实现单机限流策略

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();
        }
    }
}

实现分布式限流策略

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";
    }
}

修改切面逻辑

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();
    }
}

在切面逻辑中,我们注入了 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";
    }
}

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

配置限流策略

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

// 单机限流配置
@Bean
public RateLimitStrategy localRateLimitStrategy() {
    return new LocalRateLimitStrategy();
}

// 分布式限流配置
@Bean
public RateLimitStrategy distributedRateLimitStrategy(RedisTemplate<String, Object> redisTemplate) {
    return new DistributedRateLimitStrategy(redisTemplate);
}

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

相关推荐

  1. 使用策略模式实现 Spring 分布式单机

    2024-04-21 07:54:03       34 阅读
  2. 使用 Redis + Lua 实现分布式

    2024-04-21 07:54:03       39 阅读
  3. 【微服务】分布式如何实现

    2024-04-21 07:54:03       65 阅读
  4. golang版本使用令牌桶算法来实现策略

    2024-04-21 07:54:03       55 阅读
  5. 学习小记-使用Redis的令牌桶算法实现分布式

    2024-04-21 07:54:03       27 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-21 07:54:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-21 07:54:03       101 阅读
  3. 在Django里面运行非项目文件

    2024-04-21 07:54:03       82 阅读
  4. Python语言-面向对象

    2024-04-21 07:54:03       91 阅读

热门阅读

  1. 笔记wife_assistant

    2024-04-21 07:54:03       25 阅读
  2. 23种设计模式之结构型模式篇

    2024-04-21 07:54:03       27 阅读
  3. linux命令ar使用说明

    2024-04-21 07:54:03       125 阅读
  4. core_v4.2

    core_v4.2

    2024-04-21 07:54:03      122 阅读
  5. 深入Git配置

    2024-04-21 07:54:03       71 阅读
  6. rk3568 ubuntu修改IP地址

    2024-04-21 07:54:03       35 阅读
  7. Android 13 - Media框架(33)- ACodec(九)

    2024-04-21 07:54:03       85 阅读
  8. TCP三次握手的原因

    2024-04-21 07:54:03       131 阅读
  9. 深度学习基础——卷积神经网络的基础模块

    2024-04-21 07:54:03       39 阅读
  10. 基于Kubernetes集群1.27.3构建ElasticSearch-7集群

    2024-04-21 07:54:03       37 阅读