接口限流(本地及分布式)

/ 后端 / 没有评论 / 307浏览

###阿里的Sentinel进行分布式限流,请看之前文章~

1本地限流,根据注解切面(Guava)

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>xxx</version>
        </dependency>

创建自定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LocalLimiting {
    // 默认每次放入桶中的token
    double limitNum() default 20;
    //名称
    String name() default "";
    //是否根据用户token拦截
    boolean userRateLimit() default false;
}

创建aop:

@Order(-10000)
@Aspect
@Component
public class WfLocalLimitingAop {

    private volatile Map<String, RateLimiter> RATE_LIMITER = new ConcurrentHashMap<>();

    @Pointcut("@annotation(localLimiting)")
    public void pointCut(LocalLimiting localLimiting) {
    }

    @Around("pointCut(localLimiting)")
    public Object around(ProceedingJoinPoint point, LocalLimiting localLimiting) throws Throwable {
        String limitName = localLimiting.name();
        if (StringUtils.isBlank(limitName)) {
            limitName = point.getSignature().toShortString();
        }
        boolean userRateLimit = localLimiting.userRateLimit();
        if (userRateLimit) {
            Object userId = getRequest().getAttribute(TokenCheckInterceptor.LOGIN_USER_KEY);
            if (userId == null) {
                userId = WebHttpUtils.getRequestIP(getRequest());
            }
            String source = getRequest().getHeader("t-source");
            if (source == null) {
                source = String.valueOf(WfCurrentMemberUtil.getSourseByUserAgent());
            }
            limitName += userId + source;
        }

        RateLimiter rateLimiter = RATE_LIMITER.computeIfAbsent(limitName, key -> RateLimiter.create(localLimiting.limitNum()));
        if (rateLimiter.tryAcquire()) {
            return point.proceed();
        } else {
            return NoRepeatSubmitInterceptor.checkReturnError(point);
        }
    }

    public static HttpServletRequest getRequest() {
        try {
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        } catch (Exception e) {
            return null;
        }
    }
}

2.分布式限流,根据注解切面(Redisson)

依赖:

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>xxxx</version>
        </dependency>

配置redisson:

@Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        config.setCodec(new JsonJacksonCodec());
        config.useSingleServer().
                setDatabase(10).
                setAddress("redis://" + hostname + ":" + port).
                setPassword(password).
                setRetryInterval(5000).
                setTimeout(10000).
                setConnectionMinimumIdleSize(8).
                setConnectionPoolSize(16).
                setConnectTimeout(10000);
        return Redisson.create(config);
    }

创建自定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLimiting {
    //名称
    String name() default "";

    //是否根据用户token拦截
    boolean userRateLimit() default false;

    //每次给的令牌数
    long rate() default 10;

    //时间周期,单位秒
    long rateInterval() default 1;
}

创建工具类:

@Component
public class RedissonLockUtil {

    @Qualifier("getRedisson")
    @Autowired(required = false)
    private RedissonClient redissonClient;

    private static RedissonClient client;

    private static final String KEY_PREFIX = "Rate_Limit:";

    @PostConstruct
    public void init() {
        client = redissonClient;
    }

    public static RRateLimiter getRateLimit(String name, long rate, long rateInterval) {
        RRateLimiter rateLimiter = client.getRateLimiter(KEY_PREFIX + name);
        rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, RateIntervalUnit.SECONDS);
        return rateLimiter;
    }

    //可重入锁
    public static RLock getLock(String key) {
        return client.getLock(key);
    }


    private static HttpServletRequest getRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        return request;
    }
}

创建aop:

@Order(-99999)
@Aspect
@Component
public class WfRedisLimitingAop {

    @Pointcut("@annotation(redisLimiting)")
    public void pointCut(RedisLimiting redisLimiting) {
    }

    @Around("pointCut(redisLimiting)")
    public Object around(ProceedingJoinPoint point, RedisLimiting redisLimiting) throws Throwable {
        String limitName = redisLimiting.name();
        boolean userRateLimit = redisLimiting.userRateLimit();
        if (StringUtils.isBlank(limitName)) {
            limitName = point.getSignature().toShortString();
        }
        if (userRateLimit) {
            Object userId = getRequest().getAttribute(TokenCheckInterceptor.LOGIN_USER_KEY);
            if (userId == null) {
                userId = WebHttpUtils.getRequestIP(getRequest());
            }
            String source = getRequest().getHeader("t-source");
            if (source == null) {
                source = String.valueOf(WfCurrentMemberUtil.getSourseByUserAgent());
            }
            limitName += userId + source;
        }
        RRateLimiter rateLimit = RedissonLockUtil.getRateLimit(limitName, redisLimiting.rate(), redisLimiting.rateInterval());
        if (rateLimit.tryAcquire()) {
            return point.proceed();
        } else {
            return NoRepeatSubmitInterceptor.checkReturnError(point);
        }
    }

    public static HttpServletRequest getRequest() {
        try {
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        } catch (Exception e) {
            return null;
        }
    }
}

3动态url限流,根据mysql动态加载(Redisson)

1.创建表

CREATE TABLE `wf_rate_limit_config` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(50) NOT NULL COMMENT '需要限流的请求地址',
  `user_rate_limit` tinyint(255) NOT NULL DEFAULT '0' COMMENT '0不拦截用户  1拦截用户  维度(用户id或IP 客户端)',
  `rate` int(255) NOT NULL DEFAULT '1' COMMENT '每次的令牌数量',
  `rate_interval` int(255) NOT NULL DEFAULT '1' COMMENT '时间周期(秒)',
  `client` varchar(20) NOT NULL DEFAULT '' COMMENT '多个逗号分隔  1 (ios) 2 (android) 3 (公众号) 4(PC) 6 (小程序) 7(安卓tv)',
  `client_type` tinyint(255) NOT NULL DEFAULT '2' COMMENT '0不区分客户端  1区分部分客户端 2全部区分',
  `remark` varchar(100) NOT NULL DEFAULT '' COMMENT '备注',
  `status` tinyint(255) NOT NULL DEFAULT '0' COMMENT '0无效  1有效',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

2.定时异步加载限流规则到缓存,避免实时加载增加请求时间

    @Scheduled(fixedDelay = 1000 * 60, initialDelay = 1000 * 3)
    public void rateLimit() {
        try {
            wfRateLimitService.refresh();
        } catch (Exception e) {
            log.error("刷新规则错误", e);
        }
    }
@Service
public class WfRateLimitService {
    private Logger log = LoggerFactory.getLogger(WfRateLimitService.class);
    public final static Map<String, WfRateLimitConfig> CACHE_RATELIMIT_MAP = new ConcurrentHashMap<>();

    @Resource
    WfRateLimitConfigMapper wfRateLimitConfigMapper;

    public void refresh() {
        List<WfRateLimitConfig> allList = wfRateLimitConfigMapper.getWfRateLimitConfigAllListByParam(null);
        if (allList.size() < 1) {
            return;
        }
        CACHE_RATELIMIT_MAP.clear();
        allList.forEach(l -> {
            Integer clientType = l.getClientType();
            if (clientType == 1) {
                String client = l.getClient();
                if (StringUtils.isNotBlank(client)) {
                    try {
                        String[] split = client.split(",");
                        if (split.length > 0) {
                            l.setClientList(Arrays.asList(split));
                        }
                    } catch (Exception e) {
                        log.error("切分客户端来源失败", e);
                    }
                }
            }
            CACHE_RATELIMIT_MAP.put(l.getUrl(), l);
        });
    }
}

3.动态切面控制请求限流

@Order(-99999)
@Aspect
@Component
public class WfDynamicRedisLimitingAop {

    @Pointcut("@annotation(getMapping)")
    public void pointCutGet(GetMapping getMapping) {
    }

    @Pointcut("@annotation(postMapping)")
    public void pointCutPost(PostMapping postMapping) {
    }

    @Pointcut("@annotation(requestMapping)")
    public void pointCutRequest(RequestMapping requestMapping) {
    }

    @Around("pointCutRequest(requestMapping)")
    public Object aroundRequest(ProceedingJoinPoint point, RequestMapping requestMapping) throws Throwable {
        return around(point);
    }

    @Around("pointCutGet(getMapping)")
    public Object aroundGet(ProceedingJoinPoint point, GetMapping getMapping) throws Throwable {
        return around(point);
    }

    @Around("pointCutPost(postMapping)")
    public Object aroundPost(ProceedingJoinPoint point, PostMapping postMapping) throws Throwable {
        return around(point);
    }

    public Object around(ProceedingJoinPoint point) throws Throwable {
        Map<String, WfRateLimitConfig> cacheRatelimitMap = WfRateLimitService.CACHE_RATELIMIT_MAP;
        if (cacheRatelimitMap.size() < 1) {
            return point.proceed();
        }
        HttpServletRequest request = getRequest();
        if (request == null) {
            return point.proceed();
        }
        String requestURI = request.getRequestURI();
        WfRateLimitConfig wfRateLimitConfig = cacheRatelimitMap.get(requestURI);
        if (wfRateLimitConfig == null) {
            return point.proceed();
        }
        String limitName = requestURI;

        //用户维度
        Integer userRateLimit = wfRateLimitConfig.getUserRateLimit();
        if (userRateLimit == 1) {
            Object userId = getRequest().getAttribute(TokenCheckInterceptor.LOGIN_USER_KEY);
            if (userId == null) {
                userId = WebHttpUtils.getRequestIP(getRequest());
                if ("0:0:0:0:0:0:0:1".equals(userId)) {
                    userId = "localhost";
                }
            }
            limitName += ":" + userId;
        }

        //客户端来源
        Integer clientType = wfRateLimitConfig.getClientType();
        switch (clientType) {
            case 0://不区分客户端
                break;
            case 1://区分部分客户端
                String currentSource = getClientSource();
                List<String> clientList = wfRateLimitConfig.getClientList();
                if (CollectionUtils.isEmpty(clientList)) {
                    return point.proceed();
                }
                if (!clientList.contains(currentSource)) {
                    return point.proceed();
                }
                limitName += "-" + getClientSource();
                break;
            case 2://全部区分
                limitName += "-" + getClientSource();
                break;
        }

        Integer rate = wfRateLimitConfig.getRate();
        Integer rateInterval = wfRateLimitConfig.getRateInterval();
        if (rate < 1 || rateInterval < 1) {
            return point.proceed();
        }
        RRateLimiter rateLimit = RedissonLockUtil.getRateLimit(limitName, wfRateLimitConfig.getRate(), wfRateLimitConfig.getRateInterval());
        if (rateLimit.tryAcquire()) {
            return point.proceed();
        } else {
            return NoRepeatSubmitInterceptor.checkReturnError(point);
        }
    }

    public String getClientSource() {
        HttpServletRequest request = getRequest();
        if (request == null) {
            return "0";
        }
        String source = getRequest().getHeader("t-source");
        if (source == null) {
            source = String.valueOf(WfCurrentMemberUtil.getSourseByUserAgent());
        }
        return source;
    }

    public static HttpServletRequest getRequest() {
        try {
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        } catch (Exception e) {
            return null;
        }
    }
}

4.删除无用限流规则 手动执行脚本删除无用redis规则