并发限流与计算限流的使用与比较

一、引言

        最近有并发较高影响服务稳定性,之前博主分享过自己封装的计算限流工具,github地址:

GitHub - SongTing0711/count-limit        

        主要是针对计算资源如:CPU、内存等,使用的其实是加权计数器的限流算法,对于并发流量比较高的场景,其实可以用。但是在计数的过程中要进行资源数量的扣减和归还,这个其实在高并发的时候是多了一层逻辑处理的,而且这种限流是在在乎之前的流量处理结果。

        如果并发场景消耗的资源比较少其实直接使用滑动窗口、令牌桶等限流算法更加适合,也就是只用于高并发和消耗内存、cpu少的场景,其次这种不能进行分布式的限流,因为他的令牌桶是基于本地内存。

二、RateLimiter使用

        这里采用的是谷歌的RateLimiter作为底层限流封装,是谷歌基于令牌桶限流算法进行封装的工具类,但是在业务开发过程中要考虑几个问题

1、封装的限流器初始化

2、限流器对应的限流事件

3、默认的限流速率

4、事件可能不需要限流了

        第一个问题要有初始化方法

        第二个问题要考虑不管是接口还是mq接收处理事件,不可能一个限流器给所有的接口和mq使用,所以需要将事件与限流器做一个键值对,这里博主使用了ConcurrentHashMap

        第三个问题默认其实随便设置一个就好了,但是需要能改动,这里设置跟随配置进行重置

        第四个问题要对事件是否使用限流器进行设置,默认使用

@Slf4j
@Component
public class RateLimiterControl {

    public static int default_rate = 20;

    public static ConcurrentHashMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();

    public static ConcurrentHashMap<String, Boolean> useFlowMap = new ConcurrentHashMap<>();

    public synchronized static void initRate(String key, Integer rate) {
        RateLimiter rateLimiter = rateLimiterMap.get(key);
        if (rateLimiter == null) {
            //首次启动key对应的限流器
            rateLimiter = RateLimiter.create(rate);
            rateLimiterMap.put(key, rateLimiter);
            useFlowMap.put(key, true);
            log.info("create new RateLimiter key:{}, is {}", key, rate);
            return;
        }
        if (rate != rateLimiter.getRate()) {
            //参数变更
            log.info("Reset RateLimiter, new Rate is {}", rate);
            rateLimiter.setRate(rate);
        }
    }

    /**
     * 请求令牌桶
     *
     * @return 是否允许
     */
    public static boolean tryAcquire(String key) {
        if (!useFlowMap.containsKey(key)) {
            initRate(key, default_rate);
        }
        if (!useFlowMap.get(key)) {
            //如果这个key事件不使用限流器
            return true;
        }
        return rateLimiterMap.get(key).tryAcquire();
    }

    //设置是否使用限流器
    public static void setEnable(String key, Boolean value) {
        useFlowMap.put(key, value);
    }

    @Value("${default.rate:200}")
    public void setDefaultRate(Integer rate) {
        default_rate = rate;
    }


}

        以mq作为例子,这里是以topic加event作为一个事件进行限流,以topic也可以,其实就是限流的维度到底要什么。

        关键在于限流不通过的处理,这里使用的是停一下然后抛出异常,然后借助mq的重试机制继续处理。其实一共有几种:

1、直接return(这种需要消息是会很快进来,丢掉没关系的,一般是心跳之类)

2、等待一段时间之后抛出异常(适用于mq重试机制)

3、直接抛出异常(接口和mq都可以,看业务能不能接受)

4、等待一段时间之后递归调用(这种很不建议,最坏的情况下会递归栈不断加深导致oom栈溢出,或者在oom之前出现过多死循环,耗尽线程池资源)

@ConsumeTopic(topic = "TP_FEED", eventCode = "EC_FEEDBACK_STATUS_CHANGE", log = true)
public class FeedbackListener implements TopicListener<FeedbackEvent> {

  /**
   * 接收消息.
   */
  public void onMessage(MonsterMessage<FeedbackEvent> message) throws Exception {
    if (!RateLimiterControl.tryAcquire("TP_FEED_EC_FEEDBACK_STATUS_CHANGE")) {
      Thread.sleep(10);
      throw new Exception();
    }
  }
}

三、对比

工具 限流算法 是否支持集群限流 适用限流
count_limit 加权计数器

CPU、内存、并发

需要防止资源的过度消耗,在乎之前流量的处理结果

RateLimiterControl 令牌桶

并发

不在乎进入的请求处理结果

猜你喜欢

转载自blog.csdn.net/m0_69270256/article/details/130341584