likes
comments
collection
share

Hytrix线程池配置解析

作者站长头像
站长
· 阅读数 44

背景

公司项目使用Fegin来进行相互之间的调用,有时并发量较高的时候会出现没有足够的线程处理Fegin请求,导致Fegin报错

java.util.concurrent.RejectedExecutionException: Rejected command because thread-pool queueSize is at rejection threshold.

分析

把上面这句话翻译一下是:拒绝执行该操作,因为线程池队列大小已经到达了拒绝阈值。

而报错的类是 com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler$HystrixContextSchedulerWorker.schedule(HystrixContextScheduler.java:103)

hystrix是一个SpringCloud的熔断器,当你使用Fegin进行远程调用时就可以用到这个熔断器

你如果不想使用这个熔断器也可以将下面这个配置为false,默认情况下也是不启动的

feign.hystrix.enabled=true

先来看下整个hystrix运行的原理,了解一下为什么会出现这个问题

Hytrix线程池配置解析

  1. 构造一个HystrixCommandHystrixobservableccommand对象

  2. 执行命令

  3. 是否缓存了响应?

  4. 断路器是否打开?

  5. 线程池/队列/信号量是否已满?

  6. HystrixObservableCommand.construct()HystrixCommand.run ()

  7. 计算断路器是否健康

  8. 执行Fallback逻辑

  9. 返回成功的响应

我们这次就看第五步的实现原理,也就是当断路器没有被打开的时候,线程池是怎么样限制Fegin的请求调用

资源隔离

在hystrix中有一个资源隔离的机制,原理则是将一个大的模块划分成一个个小的模块,每个模块之间相互隔离,当模块A出现问题,它不会耗尽所有资源而是只会消耗自己模块的资源,其他模块还可以继续使用.

在微服务的环境下,我们经常一个服务会调用多个服务,资源隔离机制就可以保证,在被调用服务中某一个服务出现了问题不会耗尽所有的资源让其他服务也没有办法被调用.

hystrix可以使用两个机制来实现服务隔离,线程池/信号量,默认是线程池。

线程池如何配置

有关的配置项一共有下面这几个

Hytrix线程池配置解析

  1. coreSize 核心线程数 默认10
  2. maximumSize 最大线程数 默认10
  3. maxQueueSize 线程池阻塞队列的长度 默认-1使用的是SynchronousQueue, 当设置大于0时使用LinkedBlockingQueue

源代码:HystrixConcurrencyStrategygetBlockingQueue方法

public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
    /*
     * We are using SynchronousQueue if maxQueueSize <= 0 (meaning a queue is not wanted).
     * <p>
     * SynchronousQueue will do a handoff from calling thread to worker thread and not allow queuing which is what we want.
     * <p>
     * Queuing results in added latency and would only occur when the thread-pool is full at which point there are latency issues
     * and rejecting is the preferred solution.
     */
    if (maxQueueSize <= 0) {
        return new SynchronousQueue<Runnable>();
    } else {
        return new LinkedBlockingQueue<Runnable>(maxQueueSize);
    }
  1. queueSizeRejectionThreshold 队列拒绝阈值,人为的队列大小,即使队列未达到maxQueueSize也会被拒绝 默认5

我们再复习一下线程池的执行机制

Hytrix线程池配置解析

queueSizeRejectionThreshold值在线程池的配置中是没有的,也就是Hystrix一定会对它和当前阻塞队列线程数进行一个判断,来返回是否拒绝执行。

我们找到打印最上面报错的源码,就能知道queueSizeRejectionThreshold是在哪里和阻塞队列中线程数量进行判断的

public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
    if (threadPool != null) {
        if (!threadPool.isQueueSpaceAvailable()) {
            throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold.");
        }
    }
    return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action), delayTime, unit);
}


public boolean isQueueSpaceAvailable() {
    if (queueSize <= 0) {
        // we don't have a queue so we won't look for space but instead
        // let the thread-pool reject or not
        return true;
    } else {
        return threadPool.getQueue().size() < properties.queueSizeRejectionThreshold().get();
    }
}

可以看到当maxQueueSize小于等于0的时候,返回的是true,走到worker.schedule方法中,由线程池来判断是否继续执行 当maxQueueSize大于0时,对当前阻塞队列中的数据量和queueSizeRejectionThreshold进行比较,小于返回true继续由线程池执行,大于等于则返回false,接着报错。

也就是说只有在maxQueueSize大于0的时候,queueSizeRejectionThreshold才有用

下面根据实际的测试,我们看看会产生什么样的结果

  1. coreSize = 5,maxQueueSize = 10

    运行第11个线程会被拒绝,因为maxQueueSize大于0, queueSizeRejectionThreshold 生效,阻塞队列中只能放5个线程,核心线程数 + 阻塞拒绝阈值是10,注意如果按这样配置maximumSize设多大都不会有效果,因为在交给线程池执行之前任务就已经被拒绝了

  2. coreSize = 5, maxQueueSize = 5,queueSizeRejectionThreshold = 20,maximumSize = 10

    第16个线程会被拒绝,因为maxQueueSize小于queueSizeRejectionThreshold并且大于0,每次都会交给线程池去判断是否执行,最多能执行的个数就是 阻塞队列数 + 最大线程数,这里没有计算核心线程数是因为最大线程数其中就包括了核心线程数

  3. coreSize = 5,queueSizeRejectionThreshold = 20,maximumSize = 10

    第11个线程会被拒绝,因为maxQueueSize默认值是-1小于等0,使用的是同步阻塞队列不会存放线程,queueSizeRejectionThreshold也就不会生效,最多能处理的任务数量就是最大线程数

  4. coreSize = 5,maxQueueSize = 10,queueSizeRejectionThreshold = 10,maximumSize = 10

    第16个线程会被拒绝,因为maxQueueSize不小于0,queueSizeRejectionThreshold会生效,

    threadPool.getQueue().size() < properties.queueSizeRejectionThreshold().get()

    这段代码直接决定能否是否放入线程池执行,而线程池最大能执行的数量是 maxQueueSize + maximumSize = 20,大于queueSizeRejectionThreshold的值,不会出现交给线程池,线程池无法执行的情况。

  5. coreSize=64, maximumSize=200, maxQueueSize=800

    由于maxQueueSize大于0,queueSizeRejectionThreshold会生效,也就是阻塞队列中只能放5个,再多放就会报错,那么执行到第64 + 5 + 1 = 70个线程会报错,maximumSize设置的值不会生效

综上所述:如果我们修改了coreSize,一定也要修改maximumSize的值,因为最大线程数必须要比核心线程数大。想要扩大 maxQueueSize 的值也要把 queueSizeRejectionThreshold 跟着一起变更,并且当 queueSizeRejectionThreshold 比 maximumSize 大的时候,maximumSize设置的值才有意义。

参考:

zhuanlan.zhihu.com/p/161522189

tech.meituan.com/2020/04/02/…

docs.steeltoe.io/api/v3/circ…

转载自:https://juejin.cn/post/7247345890628419642
评论
请登录