guava限流器与redis限流器

文章目录

  • 参考
  • 原理
  • 实例

参考

guava rate Limite原理参考:https://www.cnblogs.com/fnlingnzb-learner/p/13086185.html

原理

guava的限流器,可以理解为并发包中的信号量。通过tryAcquire方式获取有限的令牌(即你要限制的qps),获取到就可以进入Controller中执行。但是只适用于单机版本的限流器,对于集群限流器只能使用redis去实现。
上述是在进入方法前做限流拦截,因此结合拦截器去做限流是最合适的。

关于限流配置:
将预先设置的限流策略配置在DynamicConfig(放置在配置文件中,服务启动时会解析配置文件获取到限流策略,做的更好的是一种动态配置。提供refresh接口,服务器上放一个root权限才能写的配置文件,root权限是为了业务安全。root用户修改后调用refresh接口将新策略刷入该动态配置)

实例

@Slf4j
public class RateLimitInterceptor extends HandlerInterceptorAdapter {
	// 为了避免反复生成限流器对象,https://juejin.cn/post/6844904057128091655 并发包中的map防止并发更新失败
    private Map<String, RateLimiter> limiterMap = new ConcurrentHashMap<>();

    private static final int ACQUIRE_WAIT_TIME = 100;

    @Autowired
    private ResponseUtil responseUtil;

    @Resource
    private RedisTemplate<String, String> redisClient;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //每条限流规则是一个item。
        for (RateLimitRule item : DynamicConfig.limitRule) {
            //获取单条限流规则中所有限流维度
            Set<String> limitDimension = item.getLimitDimension();

            //提取这次请求中限流纬度特征,拼成本次请求的特征key
            StringBuffer sb = new StringBuffer();
            if (limitDimension.contains(“sysetemID”)) {
                sb.append("systemId=");
                sb.append(ThreadInfoCache.getSystemId());
            }
            
            if (limitDimension.contains(entityNameKey)) {
                sb.append("entityName=");
                String entityName=StringUtils.isEmpty(request.getParameter("entityName"))
                        ?"unknow":request.getParameter("entityName");
                sb.append(entityName);
            }

            if (limitDimension.contains("method")) {
                sb.append("method=");
                sb.append(ThreadLocalCache.get(OpenApiContextConstant.Method));
            }
            
            if(StringUtils.isEmpty(sb.toString())){
                continue;
            }

            if(!tryLimit(sb.toString(),item.getLimitQps(),item.getRuleType(),item.getCustomKey())){
                responseUtil.noAuthResponse(response, ResponeCode.OVER_LIMIT_ERROR,ResponeCode.OVER_LIMIT_ERROR);
                return false;
            }
        }

        return true;
    }

    private boolean tryLimit(String requestLimitKey,int confLimitQps,int confLimitType,String confCustomRuleLimitKey){
        if(LimitTypeEnum.LOCAL_LIMIT.code==confLimitType){
            return tryLocalLimit(requestLimitKey+confLimitQps, confLimitQps);
        }
        return tryAllLimit(requestLimitKey, confLimitQps);
    }

    //本地限流根据特征key和对应qps缓存一个guavaLimit在map中,map中的key是特征key加qps,这样缓存规则变了之后,就会产生一个新的guavaLimit。
    private boolean tryLocalLimit(String key,int qps){
	    // guava的限流器限制
        RateLimiter limitRate = getLimitRate(key, qps);
        return limitRate.tryAcquire(ACQUIRE_WAIT_TIME, TimeUnit.MILLISECONDS);
    }

    //集群限流使用redis
    private boolean tryAllLimit(String key,int qps){
        String limitKey = key + System.currentTimeMillis() / 1000;
        //limitKey=key;
        Long current = redisClient.opsForValue().increment(limitKey,1);
        //为了避免单次设置超时时间可能出现的失败,造成死key,同时也为了避免频繁更新redis,默认设置三次重试确保成功
        if(current<4){
        	// https://www.runoob.com/redis/keys-expire.html
        	// expire刷新过期时间的命令
            redisClient.expire(limitKey,120,TimeUnit.SECONDS);
        }
        return current<qps;
    }

    private RateLimiter getLimitRate(String key,int qps)  {
        if (limiterMap.containsKey(key)) {
            return limiterMap.get(key);
        }
        RateLimiter rateLimiter = RateLimiter.create(qps);
        limiterMap.putIfAbsent(key, rateLimiter);
        return limiterMap.get(key);
    }

    @Override
    public void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
    }
}

限流器配置

[{
        "limitDimension": ["systemId", "method", "entityName", "tenant"],
        "limitQps": 30,
        "ruleType": 2
}, {
        "customKey": "systemId=aIDmethod=search",
        "limitDimension": ["systemId", "method"],
        "limitQps": 2,
        "ruleType": 2
}]

第一条规则代表:全局限流,对于每个systemId的每个method的每个entityName的每个tenant限制qps为30。

第二条规则代表:全局限流,对于systemId为A的租户且请求方法为search的所有请求限制qps为30。

你可能感兴趣的:(Java后端技术栈,#,进阶语法与原理,redis,redis,java,数据库)