使用Redis实现接口限流

Api接口需要实现限流,例如每分钟可以请求60次,超过频率则提示:接口请求频繁。业务本身使用了redisson作为Redis的客户端,所以采用官方提供的RRateLimiter来实现接口限流。

1. maven依赖引入

 
        org.redisson
        redisson
        3.17.3
 

2. redis配置 

spring:
    redisson:
    database: 1
    address: redis://127.0.0.1:6379
    password:
    connectionPoolSize: 8
    connectionMinimumIdleSize: 3
    subscriptionConnectionPoolSize: 6
    subscriptionConnectionMinimumIdleSize: 1

 Redis的客户端接入代码如下:

@Configuration
public class RedisSingleConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private int dataBase;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        String redisUrl = String.format("redis://%s:%s", host + "", port + "");
        config.useSingleServer().setTimeout(1000)
                .setRetryAttempts(3)
                .setRetryInterval(1500)  //ms
                .setAddress(redisUrl);
        if (StringUtils.isNotBlank(password)) {
            config.useSingleServer().setPassword(password);
        }
        SingleServerConfig singleConfig = config.useSingleServer()
                .setAddress(redisUrl)
                .setPingConnectionInterval(1000);//心跳检测,定时与redis连接,可以防止一段时间过后,与redis的连接断开
        singleConfig.setPassword(password);
        config.useSingleServer().setDatabase(dataBase);
        return Redisson.create(config);
    }
}

3. redisson客户单配置 

@Component
public class RedissonClientConfig {

    @Value("${spring.redisson.address}")
    private String address;

    @Value("${spring.redisson.database}")
    private Integer dataBase;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer().setAddress(address)
                .setDatabase(dataBase);
        return Redisson.create(config);
    }
}

4. 自定义限流注解 

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiRateLimiter {

    String name();

    long rate() default 180;

    long rateInterval() default 60;
}

5. 定义简单的工厂模式 

public class RateLimiterFactory {
    private volatile static RateLimiterFactory SINGLETON;

    private RateLimiterFactory() {
    }


    private final ConcurrentHashMap rateLimiterMap = new ConcurrentHashMap<>();

    public static RateLimiterFactory getSingleton() {
        if (null == SINGLETON) {
            synchronized (RateLimiterFactory.class) {
                if (null == SINGLETON) {
                    SINGLETON = new RateLimiterFactory();
                }
            }
        }
        return SINGLETON;
    }

    public RRateLimiter get(String name, long rate,long rateInterval) {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("rate name can not be empty");
        }
        if (rate < 0) {
            throw new IllegalArgumentException("rate must be >0");
        }
        RRateLimiter rRateLimiter = rateLimiterMap.get(name);
        if (null == rRateLimiter) {
            RedissonClient redissonClient = (RedissonClient) SpringContextUtil.getBean("redissonClient");
            rRateLimiter = redissonClient.getRateLimiter(name);
            //根据业务选择 RateType 参数
            rRateLimiter.setRate(RateType.OVERALL, rate, rateInterval, RateIntervalUnit.SECONDS);
            rateLimiterMap.put(name, rRateLimiter);
        }
        return rRateLimiter;
    }
}

 6. 定义切面

@Slf4j
@Component
@Aspect
public class ApiRateLimitAspect {

    @Pointcut("@annotation(ApiRateLimiter类路径)")
    public void rateLimitPoint() {
    }

    @Around("rateLimitPoint()")
    public Object aroundAop(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        ApiRateLimiter apiRateLimiter = AnnotationUtils.getAnnotation(method, ApiRateLimiter.class);
        if (null == apiRateLimiter) {
            return joinPoint.proceed();
        }
        long rate = apiRateLimiter.rate();
        long rateInterval = apiRateLimiter.rateInterval();
        String name = apiRateLimiter.name();
        RRateLimiter newRankLimiter  = RateLimiterFactory.getSingleton().get(name, rate,rateInterval );
        //根据业务选择 acquire 以及 tryAcquire
        newRankLimiter.acquire();
        return joinPoint.proceed();
    }

7. 编写接口进行限流测试 

    @ApiRateLimiter(name = "ratelimitApi", rate = 5, rateInterval = 60)
    @RequestMapping(value = "/rate/limit", method = RequestMethod.POST)
    public RspInfoBO rateLimit(ApplyTrialReqBO reqInfoBO) {
        RspInfoBO res = new RspInfoBO();
        return res;
    }

如果使用的是阿里云的Redis套件,需要设置阿里云Redis参数 script_check_enable参数为0

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