当前位置: 首页 > 工具软件 > aizuda > 使用案例 >

aizuda-limit限流学习

羊舌新荣
2023-12-01

注解 @Target @Retention

@Target @Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(value = {ElementType.METHOD}) 只能表注在方法上

注释将由编译器记录在类文件中,并在运行时由 VM 保留,因此可以反射性地读取它们
@Retention(value = RetentionPolicy.RUNTIME)

切入类

/**
 * 速率限制拦截切面处理类
 * <p>
 * 尊重知识产权,CV 请保留版权,爱组搭 http://aizuda.com 出品
 *
 * @author 青苗
 * @since 2021-11-16
 */
@Aspect
@AllArgsConstructor
public class RateLimitAspect {
   

}

切入注解方法

 @Around("@annotation(com.aizuda.limiter.annotation.RateLimit)")
    public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        final String classMethodName = MethodUtils.getClassMethodName(method);
   
       
    }

获取执行速率限制注解,缓存反射信息

RATE_LIMIT_MAP.computeIfAbsent(classMethodName, k -> method.getAnnotation(RateLimit.class));

限流具体实现

	 private MethodMetadata buildMethodMetadata(ProceedingJoinPoint joinPoint) {
	        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
	        Method method = signature.getMethod();
	        String classMethodName = MethodUtils.getClassMethodName(method);
	        RateLimit rateLimit = this.getRateLimit(method, classMethodName);
	        return new RateLimitMethodMetaData(method, joinPoint::getArgs, rateLimit);
	    }
    
    MethodMetadata methodMetadata = this.buildMethodMetadata(pjp);
    if (rateLimitHandler.proceed(methodMetadata)) {
        return pjp.proceed();
    } else {
        throw new RateLimitException(StringUtils.hasLength(rateLimit.message()) ? rateLimit.message() :
                "current limiting rule triggered");
    }
}

初始化 redisscript 执行方法

 RateLimit rateLimit = methodMetadata.getAnnotation();
  private static RedisScript<Long> REDIS_SCRIPT_RATE_LIMIT = null;
        if (null == REDIS_SCRIPT_RATE_LIMIT) {
            REDIS_SCRIPT_RATE_LIMIT = RedisScript.of(this.luaScript(), Long.class);
        }
         /**
     * Lua 限流脚本
     */
    public String luaScript() {
        return "local key = KEYS[1];" +
                "local count = tonumber(ARGV[1]);" +
                "local interval = tonumber(ARGV[2]);" +
                "local current = tonumber(redis.call('get', key) or \"0\") " +
                "if current + 1 > count then return 0 " +
                "else redis.call(\"INCRBY\", key, \"1\") redis.call(\"expire\", key, interval) return current + 1 end";
    }

唯一标识构建

  // SpEL Key 解析
    String parseKey = Optional.ofNullable(spELKey)
            .filter(StringUtils::hasLength)
            .map(str -> {
                Method method = methodMetadata.getMethod();
                Object[] args = methodMetadata.getArgs();
                return this.parser(spELKey, method, args);
            }).orElse("");


    if (useDefaultStrategy) {
        key.append(defaultKeyGenerateStrategy.getKey(methodMetadata, parseKey));
    }
     public final static String TYPE = "aizuda-default";

    @Override
    public String getType() {
        return TYPE;
    }
     @Override
    public String getKey(MethodMetadata methodMetadata, String parseKey) {
        String result = methodMetadata.getClassMethodName();
        if (StringUtils.hasLength(parseKey)) {
            result += ":" + parseKey;
        }
        return result;
    }

预留扩展接口 实现

概括 : 该接口 通过 注解上的 strategy 遍历 通过 每个元素 对应的type 去执行 getkey 拼接后 返回 用户也可以着急实现该接口进行自定义扩展

 /**
     * 限制策略
     */
  String[] strategy() default {};
  private final List<IKeyGenerateStrategy> keyGenerateStrategyList;
   // 组装自定义策略
        if (strategy.length > 0) {
            for (String str : strategy) {
                keyGenerateStrategyList.stream()
                        .filter(t -> Objects.equals(t.getType(), str))
                        .findFirst()
                        .map(rateLimitStrategy -> rateLimitStrategy.getKey(methodMetadata, parseKey))
                        .filter(StringUtils::hasLength)
                        .ifPresent(currentGenerateKey -> {
                                    if (key.length() > 0) {
                                        key.append(":");
                                    }
                                    key.append(currentGenerateKey);
                                }
                        );
            }
        }

执行脚本 lua 脚本

Long currentCount = redisTemplate.execute(REDIS_SCRIPT_RATE_LIMIT, Collections.singletonList(key),

返回结果
 if (null != currentCount) {
            long count = currentCount;
            if (count > 0 && count <= rateLimit.count()) {
                if (log.isDebugEnabled()) {
                    log.debug("The {}-th visit within the current limit period", count);
                }
                return true;
            }
        }
回馈
         if (rateLimitHandler.proceed(methodMetadata)) {
            return pjp.proceed();
        } else {
            throw new RateLimitException(StringUtils.hasLength(rateLimit.message()) ? rateLimit.message() :
                    "current limiting rule triggered");
        }

config 完成starter 是否开启

/*
 * 爱组搭 http://aizuda.com 低代码组件化开发平台
 * ------------------------------------------
 * 受知识产权保护,请勿删除版权申明
 */
package com.aizuda.limiter.autoconfigure;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.io.Serializable;
import java.time.Duration;

/**
 * 爱组搭安全配置
 * <p>
 * 尊重知识产权,CV 请保留版权,爱组搭 http://aizuda.com 出品
 *
 * @author 青苗
 * @since 2021-11-08
 */
@Getter
@Setter
@ConfigurationProperties(prefix = LimiterProperties.PREFIX)
public class LimiterProperties implements Serializable {

    /**
     * 配置前缀
     */
    public static final String PREFIX = "aizuda.limiter";

    /**
     * 开启限流
     */
    private boolean enableRateLimit;

    /**
     * 开启分布式锁
     */
    private boolean enableDistributedLock;

    /**
     * 分布式锁的前缀名称:默认为'aizuda-redis-lock'
     */
    private String distributedRootKey;
    /**
     * RedisLock的key失效时间
     * 默认2分钟
     * <p>
     * 例如 5s 五秒,6m 六分钟,7h 七小时,8d 八天
     */
    private Duration expireAfter;

    public boolean isEnable() {
        return this.enableRateLimit || this.enableDistributedLock;
    }
}

/*
 * 爱组搭 http://aizuda.com 低代码组件化开发平台
 * ------------------------------------------
 * 受知识产权保护,请勿删除版权申明
 */
package com.aizuda.limiter.autoconfigure;

import com.aizuda.common.toolkit.StringUtils;
import com.aizuda.limiter.aspect.DistributedLockAspect;
import com.aizuda.limiter.aspect.RateLimitAspect;
import com.aizuda.limiter.context.DefaultDistributedContext;
import com.aizuda.limiter.context.DistributedContext;
import com.aizuda.limiter.distributedlock.IDistributedLockTemplate;
import com.aizuda.limiter.distributedlock.RedisDistributedLockTemplate;
import com.aizuda.limiter.extend.IAcquireLockTimeoutHandler;
import com.aizuda.limiter.extend.IDistributedLockListener;
import com.aizuda.limiter.handler.*;
import com.aizuda.limiter.strategy.IKeyGenerateStrategy;
import com.aizuda.limiter.strategy.IpKeyGenerateStrategy;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

import java.time.Duration;
import java.util.List;
import java.util.Optional;

/**
 * 速率限制配置
 * <p>
 * 尊重知识产权,CV 请保留版权,爱组搭 http://aizuda.com 出品
 *
 * @author 青苗
 * @since 2021-11-18
 */
@Configuration
@EnableConfigurationProperties({LimiterProperties.class})
public class LimiterAutoConfiguration {

    @Bean
    public IpKeyGenerateStrategy ipRateLimitStrategy() {
        return new IpKeyGenerateStrategy();
    }

    @Bean
    public RateLimitKeyParser rateLimitKeyParser(List<IKeyGenerateStrategy> rateLimitStrategyList) {
        return new RateLimitKeyParser(rateLimitStrategyList);
    }


    /*  -------------------- 限流相关配置  --------------------  */

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableRateLimit", havingValue = "true")
    public IRateLimitHandler rateLimitHandler(ObjectProvider<RedisTemplate<String, String>> redisTemplate,
                                              RateLimitKeyParser rateLimitKeyParser) {
        return new RedisRateLimitHandler(redisTemplate.getIfAvailable(), rateLimitKeyParser);
    }

    @Bean
    @ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableRateLimit", havingValue = "true")
    public RateLimitAspect rateLimitAspect(IRateLimitHandler rateLimitHandler) {
        return new RateLimitAspect(rateLimitHandler);
    }


    /*  -------------------- 分布式锁相关配置  --------------------  */

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableDistributedLock", havingValue = "true")
    public IDistributedLockHandler distributedLockHandler(DistributedContext distributedContext,
                                                          RateLimitKeyParser rateLimitKeyParser) {
        return new RedisDistributedLockHandler(rateLimitKeyParser, distributedContext);
    }

    @Bean
    @ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableDistributedLock", havingValue = "true")
    public DistributedLockAspect distributedLockAspect(IDistributedLockHandler distributedLockHandler) {
        return new DistributedLockAspect(distributedLockHandler);
    }

    @Bean
    @ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableDistributedLock", havingValue = "true")
    public DistributedContext distributedContext(IDistributedLockTemplate distributedLockTemplate,
                                                 Optional<List<IAcquireLockTimeoutHandler>> acquireLockTimeoutHandlersOptional,
                                                 Optional<List<IDistributedLockListener>> distributedLimitListenersOptional) {
        return new DefaultDistributedContext(distributedLockTemplate, acquireLockTimeoutHandlersOptional.orElse(null),
                distributedLimitListenersOptional.orElse(null));
    }


    @Bean
    @ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableDistributedLock", havingValue = "true")
    @ConditionalOnMissingBean(IDistributedLockTemplate.class)
    public IDistributedLockTemplate iDistributedLockTemplate(LimiterProperties limiterProperties,
                                                             ObjectProvider<RedisTemplate<String, String>> redisTemplate) {
        String rootKey = Optional.ofNullable(limiterProperties.getDistributedRootKey())
                .filter(StringUtils::hasLength)
                .orElse("aizuda-redis-lock");
        Duration expireAfter = Optional.ofNullable(limiterProperties.getExpireAfter())
                .orElse(Duration.ofMinutes(2));
        return new RedisDistributedLockTemplate(redisTemplate.getIfAvailable(), rootKey, expireAfter.toMillis());
    }

}

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.aizuda.limiter.autoconfigure.LimiterAutoConfiguration
 类似资料: