危险的Hystrix线程池

  • 时间:
  • 浏览:4
  • 来源:大发快3_快3平台代理_大发快3平台代理

本文介绍Hystrix多任务管理器 池的工作原理和参数配置,指出所处的难题并提供规避方案,阅读本文前要对Hystrix有一定的了解。

文本讨论的内容,基于hystrix 1.5.18:

    <dependency>
      <groupId>com.netflix.hystrix</groupId>
      <artifactId>hystrix-core</artifactId>
      <version>1.5.18</version>
    </dependency>

多任务管理器 池和Hystrix Command之间的关系

当hystrix command的隔离策略配置为多任务管理器 ,也只是execution.isolation.strategy设置为THREAD时,command中的代码会装入去去多任务管理器 池里执行,跟发起command调用的多任务管理器 隔选泽离开。摘要官方wiki如下:

execution.isolation.strategy

This property indicates which isolation strategy HystrixCommand.run() executes with, one of the following two choices:

THREAD — it executes on a separate thread and concurrent requests are limited by the number of threads in the thread-pool

SEMAPHORE — it executes on the calling thread and concurrent requests are limited by the semaphore count

有一一两个多多多线上的服务,往往会有什么都有有hystrix command分别用来管理不同的内控 依赖。 并且 有几次hystrix多任务管理器 池所处呢,什么command跟多任务管理器 池的对应关系又是咋样的呢,是一对一吗?

答案是不一定,command跟多任务管理器 池可以 做到一对一,但通常还会 ,受到HystrixThreadPoolKey和HystrixCommandGroupKey这两项配置的影响。

优先采用HystrixThreadPoolKey来标识多任务管理器 池,可能性没人配置HystrixThreadPoolKey没人就使用HystrixCommandGroupKey来标识。command跟多任务管理器 池的对应关系,看过HystrixCommandKey、HystrixThreadPoolKey、HystrixCommandGroupKey这有一一两个多多多参数的配置。

获取多任务管理器 池标识的代码如下,可以 看过跟我的描述是一致的:

    /*
     * ThreadPoolKey
     *
     * This defines which thread-pool this command should run on.
     *
     * It uses the HystrixThreadPoolKey if provided, then defaults to use HystrixCommandGroup.
     *
     * It can then be overridden by a property if defined so it can be changed at runtime.
     */
    private static HystrixThreadPoolKey initThreadPoolKey(HystrixThreadPoolKey threadPoolKey, HystrixCommandGroupKey groupKey, String threadPoolKeyOverride) {
        if (threadPoolKeyOverride == null) {
            // we don't have a property overriding the value so use either HystrixThreadPoolKey or HystrixCommandGroup
            if (threadPoolKey == null) {
                /* use HystrixCommandGroup if HystrixThreadPoolKey is null */
                return HystrixThreadPoolKey.Factory.asKey(groupKey.name());
            } else {
                return threadPoolKey;
            }
        } else {
            // we have a property defining the thread-pool so use it instead
            return HystrixThreadPoolKey.Factory.asKey(threadPoolKeyOverride);
        }
    }

Hystrix会保证同有一一两个多多多多任务管理器 池标识只会创建有一一两个多多多多任务管理器 池:

    /*
     * Use the String from HystrixThreadPoolKey.name() instead of the HystrixThreadPoolKey instance as it's just an interface and we can't ensure the object
     * we receive implements hashcode/equals correctly and do not want the default hashcode/equals which would create a new threadpool for every object we get even if the name is the same
     */
    /* package */final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>();

    /**
     * Get the {@link HystrixThreadPool} instance for a given {@link HystrixThreadPoolKey}.
     * <p>
     * This is thread-safe and ensures only 1 {@link HystrixThreadPool} per {@link HystrixThreadPoolKey}.
     *
     * @return {@link HystrixThreadPool} instance
     */
    /* package */static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) {
        // get the key to use instead of using the object itself so that if people forget to implement equals/hashcode things will still work
        String key = threadPoolKey.name();

        // this should find it for all but the first time
        HystrixThreadPool previouslyCached = threadPools.get(key);
        if (previouslyCached != null) {
            return previouslyCached;
        }

        // if we get here this is the first time so we need to initialize
        synchronized (HystrixThreadPool.class) {
            if (!threadPools.containsKey(key)) {
                threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
            }
        }
        return threadPools.get(key);
    }

Hystrix多任务管理器 池参数一览

  • coreSize 核心多任务管理器 数量
  • maximumSize 最大多任务管理器 数量
  • allowMaximumSizeToDivergeFromCoreSize 允许maximumSize大于coreSize,没人配了你是什么值coreSize才有意义
  • keepAliveTimeMinutes 超过你是什么时间多于coreSize数量的多任务管理器 会被回收,没人maximumsize大于coreSize,你是什么值才有意义
  • maxQueueSize 任务队列的最大大小,当多任务管理器 池的多任务管理器 多任务管理器 还会 工作,只是能创建新的多任务管理器 的事先,新的任务会进到队列里等待的图片
  • queueSizeRejectionThreshold 任务队列中存储的任务数量超过你是什么值,多任务管理器 池拒绝新的任务。这跟maxQueueSize从前是一回事,只是受限于hystrix的实现土办法 maxQueueSize没人动态配置,什么都有有有了你是什么配置。

根据给定的多任务管理器 池参数猜测多任务管理器 池表现

可以 看过hystrix的多任务管理器 池参数跟JDK多任务管理器 池ThreadPoolExecutor参数很像但又不一样,即便是全部地看过文档,仍然你可以迷惑。不过无妨,先来猜猜几种配置下的表现。

coreSize = 2; maxQueueSize = 10

多任务管理器 池中常驻有一一两个多多多多任务管理器 。新任务提交到多任务管理器 池,有空闲多任务管理器 则直接执行,并且 入队等待的图片 的图片 。等待的图片 队列中的任务数=10时,拒绝接受新任务。

coreSize = 2; maximumSize = 5; maxQueueSize = -1

多任务管理器 池中常驻有一一两个多多多多任务管理器 。新任务提交到多任务管理器 池,有空闲多任务管理器 则直接执行,没人空闲多任务管理器 时,可能性当前多任务管理器 数小于5则创建有一一两个多多多新的多任务管理器 用来执行任务,并且 拒绝任务。

coreSize = 2; maximumSize = 5; maxQueueSize = 10

你是什么配置下从官方文档中可能性看没得来实际表现会是咋样的。猜测有如下某种可能性:

  • 可能性一。多任务管理器 池中常驻有一一两个多多多多任务管理器 。新任务提交到多任务管理器 池,有一一两个多多多多任务管理器 中含空闲则直接执行,并且 入队等待的图片 的图片 。当有一一两个多多多多任务管理器 还会 工作且等待的图片 队列中的任务数=10时,现在并且刚开始了了为新任务创建多任务管理器 ,直到多任务管理器 数量为5,此时现在并且刚开始了了拒绝新任务。从前语录,对资源敏感型的任务比较友好,这也是JDK多任务管理器 池ThreadPoolExecutor的行为。

  • 可能性二。多任务管理器 池中常驻有一一两个多多多多任务管理器 。新任务提交到多任务管理器 池,有空闲多任务管理器 则直接执行,没人空闲多任务管理器 时,可能性当前多任务管理器 数小于5则创建有一一两个多多多新的多任务管理器 用来执行任务。当多任务管理器 数量达到两个且还会 工作时,任务入队等待的图片 的图片 。等待的图片 队列中的任务数=10时,拒绝接受新任务。从前语录,对延迟敏感型的任务比较友好。

某种情形还会 可能性,从文档中无法选泽究竟咋样。

并发情形下Hystrix多任务管理器 池的真正表现

本节中,通过测试来看看多任务管理器 池的行为究竟会咋样。

还是你是什么配置:

coreSize = 2; maximumSize = 5; maxQueueSize = 10

大家通过不断提交任务到hystrix多任务管理器 池,并且 在任务的执行代码中使用CountDownLatch占住多任务管理器 来模拟测试,代码如下:

public class HystrixThreadPoolTest {

  public static void main(String[] args) throws InterruptedException {
    final int coreSize = 2, maximumSize = 5, maxQueueSize = 10;
    final String commandName = "TestThreadPoolCommand";

    final HystrixCommand.Setter commandConfig = HystrixCommand.Setter
        .withGroupKey(HystrixCommandGroupKey.Factory.asKey(commandName))
        .andCommandKey(HystrixCommandKey.Factory.asKey(commandName))
        .andCommandPropertiesDefaults(
            HystrixCommandProperties.Setter()
                .withExecutionTimeoutEnabled(false))
        .andThreadPoolPropertiesDefaults(
            HystrixThreadPoolProperties.Setter()
                .withCoreSize(coreSize)
                .withMaximumSize(maximumSize)
                .withAllowMaximumSizeToDivergeFromCoreSize(true)
                .withMaxQueueSize(maxQueueSize)
                .withQueueSizeRejectionThreshold(maxQueueSize));

    // Run command once, so we can get metrics.
    HystrixCommand<Void> command = new HystrixCommand<Void>(commandConfig) {
      @Override protected Void run() throws Exception {
        return null;
      }
    };
    command.execute();
    Thread.sleep(100);

    final CountDownLatch stopLatch = new CountDownLatch(1);
    List<Thread> threads = new ArrayList<Thread>();

    for (int i = 0; i < coreSize + maximumSize + maxQueueSize; i++) {
      final int fi = i + 1;

      Thread thread = new Thread(new Runnable() {
        public void run() {
          try {
            HystrixCommand<Void> command = new HystrixCommand<Void>(commandConfig) {
              @Override protected Void run() throws Exception {
                stopLatch.await();
                return null;
              }
            };
            command.execute();
          } catch (HystrixRuntimeException e) {
            System.out.println("Started Jobs: " + fi);
            System.out.println("Job:" + fi + " got rejected.");
            printThreadPoolStatus();
            System.out.println();
          }
        }
      });
      threads.add(thread);
      thread.start();
      Thread.sleep(100);

      if(fi == coreSize || fi == coreSize + maximumSize || fi == coreSize + maxQueueSize ) {
        System.out.println("Started Jobs: " + fi);
        printThreadPoolStatus();
        System.out.println();
      }
    }

    stopLatch.countDown();

    for (Thread thread : threads) {
      thread.join();
    }

  }

  static void printThreadPoolStatus() {
    for (HystrixThreadPoolMetrics threadPoolMetrics : HystrixThreadPoolMetrics.getInstances()) {
      String name = threadPoolMetrics.getThreadPoolKey().name();
      Number poolSize = threadPoolMetrics.getCurrentPoolSize();
      Number queueSize = threadPoolMetrics.getCurrentQueueSize();
      System.out.println("ThreadPoolKey: " + name + ", PoolSize: " + poolSize + ", QueueSize: " + queueSize);
    }

  }

}

执行代码得到如下输出:

// 任务数 = coreSize。此时coreSize个多任务管理器

在工作
Started Jobs: 2
ThreadPoolKey: TestThreadPoolCommand, PoolSize: 2, QueueSize: 0

// 任务数 > coreSize。此时仍然没人coreSize个多任务管理器

,多于coreSize的任务进入等待的图片

的图片
队列,没人创建新的多任务管理器

  
Started Jobs: 7
ThreadPoolKey: TestThreadPoolCommand, PoolSize: 2, QueueSize: 5

// 任务数 = coreSize + maxQueueSize。此时仍然没人coreSize个多任务管理器

,多于coreSize的任务进入等待的图片

的图片
队列,没人创建新的多任务管理器

  
Started Jobs: 12
ThreadPoolKey: TestThreadPoolCommand, PoolSize: 2, QueueSize: 10

// 任务数 > coreSize + maxQueueSize。此时仍然没人coreSize个多任务管理器

,等待的图片

的图片
队列已满,新增任务被拒绝 
Started Jobs: 13
Job:13 got rejected.
ThreadPoolKey: TestThreadPoolCommand, PoolSize: 2, QueueSize: 10

Started Jobs: 14
Job:14 got rejected.
ThreadPoolKey: TestThreadPoolCommand, PoolSize: 2, QueueSize: 10

Started Jobs: 15
Job:15 got rejected.
ThreadPoolKey: TestThreadPoolCommand, PoolSize: 2, QueueSize: 10

Started Jobs: 16
Job:16 got rejected.
ThreadPoolKey: TestThreadPoolCommand, PoolSize: 2, QueueSize: 10

Started Jobs: 17
Job:17 got rejected.
ThreadPoolKey: TestThreadPoolCommand, PoolSize: 2, QueueSize: 10

全部的测试代码,参见这里

可以 看过Hystrix多任务管理器 池的实际表现,跟事先的某种猜测还会 同,跟JDK多任务管理器 池的表现不同,跟另某种合理猜测只是通。当maxSize > coreSize && maxQueueSize != -1的事先,maxSize你是什么参数根本就不起作用,多任务管理器 数量永远我太多 超过coreSize,对于的任务入队等待的图片 的图片 ,队列满了,就直接拒绝新任务。

不得不说,这是某种你可以疑惑的,非常危险的,容易配置错误的多任务管理器 池表现。

JDK多任务管理器 池ThreadPoolExecutor

继续分析Hystrix多任务管理器 池的原理事先,先来复习一下JDK中的多任务管理器 池。

只说跟本文讨论的内容相关的参数:

  • corePoolSize核心多任务管理器 数,maximumPoolSize最大多任务管理器 数。你是什么有一一两个多多多参数跟hystrix多任务管理器 池的coreSize和maximumSize含义是一致的。
  • workQueue任务等待的图片 的图片 队列。跟hystrix不同,jdk多任务管理器 池的等待的图片 的图片 队列还会 指定大小,只是前要使用方提供有一一两个多多多BlockingQueue。
  • handler当多任务管理器 池无法接受任务时的处里器。hystrix是直接拒绝,jdk多任务管理器 池可以 定制。

可以 看过,jdk的多任务管理器 池使用起来更加灵活。配置参数的含义也十分清晰,没人hystrx多任务管理器 池里面allowMaximumSizeToDivergeFromCoreSize、queueSizeRejectionThreshold你是什么奇奇怪怪你可以疑惑的参数。

关于jdk多任务管理器 池的参数配置,参加如下jdk源码:


    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

没人在跟hystrix多任务管理器 池对应的参数配置下,jdk多任务管理器 池的表现会咋样呢?

corePoolSize = 2; maximumPoolSize = 5; workQueue = new ArrayBlockingQueue(10); handler = new ThreadPoolExecutor.DiscardPolicy()

这里不再测试了,直接给出答案。多任务管理器 池中常驻有一一两个多多多多任务管理器 。新任务提交到多任务管理器 池,有一一两个多多多多任务管理器 中含空闲则直接执行,并且 入队等待的图片 的图片 。当有一一两个多多多多任务管理器 还会 工作且等待的图片 队列中的任务数=10时,现在并且刚开始了了为新任务创建多任务管理器 ,直到多任务管理器 数量为5,此时现在并且刚开始了了拒绝新任务。

相关逻辑涉及的源码贴在下面。值得一提的是,jdk多任务管理器 池从不根据等待的图片 的图片 任务的数量来判断等待的图片 的图片 队列有无已满,只是直接调用workQueue的offer土办法 ,可能性workQueue接受了那就入队等待的图片 的图片 ,并且 执行拒绝策略。

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

可以 看过hystrix多任务管理器 池的配置参数跟jdk多任务管理器 池是非常像的,从名字到含义,都基本一致。

为什么会么会

事实上hystrix的多任务管理器 池,只是在jdk多任务管理器 池的基础上实现的。相关代码如下:


    public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
        final ThreadFactory threadFactory = getThreadFactory(threadPoolKey);

        final boolean allowMaximumSizeToDivergeFromCoreSize = threadPoolProperties.getAllowMaximumSizeToDivergeFromCoreSize().get();
        final int dynamicCoreSize = threadPoolProperties.coreSize().get();
        final int keepAliveTime = threadPoolProperties.keepAliveTimeMinutes().get();
        final int maxQueueSize = threadPoolProperties.maxQueueSize().get();
        final BlockingQueue<Runnable> workQueue = getBlockingQueue(maxQueueSize);

        if (allowMaximumSizeToDivergeFromCoreSize) {
            final int dynamicMaximumSize = threadPoolProperties.maximumSize().get();
            if (dynamicCoreSize > dynamicMaximumSize) {
                logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " +
                        dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ".  Maximum size will be set to " +
                        dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value");
                return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
            } else {
                return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
            }
        } else {
            return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
        }
    }

    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);
        }
    }

既然hystrix多任务管理器 池基于jdk多任务管理器 池实现,为什么会么会在如下有一一两个多多多基本一致的配置上,行为却不一样呢?

//hystrix
coreSize = 2; maximumSize = 5; maxQueueSize = 10

//jdk
corePoolSize = 2; maximumPoolSize = 5; workQueue = new ArrayBlockingQueue(10); handler = new ThreadPoolExecutor.DiscardPolicy()

jdk在队列满了事先会创建多任务管理器 执行新任务直到多任务管理器 数量达到maximumPoolSize,而hystrix在队列满了事先直接拒绝新任务,maximumSize这项配置成了摆设。

原因 就在于hystrix判断队列有无满有无要拒绝新任务,没人通过jdk多任务管理器 池在判断,只是当事人判断的。参见如下hystrix源码:

    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();
        }
    }

    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);
    }

可以 看过hystrix在队列大小达到maxQueueSize时,根本我太多 往底层的ThreadPoolExecutor提交任务。ThreadPoolExecutor也就没人可能性判断workQueue可以 offer,更没人创建新的多任务管理器 了。

为什么会么会办

对用惯了jdk的ThreadPoolExecutor的人来说,再用hystrix的确容易出错,笔者就曾在多个重要线上服务的代码里看过过错误的配置,称一声危险的hystrix多任务管理器 池不为过。

那为什么会么会办呢?

配置的事先规避难题

一齐配置maximumSize > coreSize,maxQueueSize > 0,像下面从前,是不行了。

coreSize = 2; maximumSize = 5; maxQueueSize = 10

妥协一下,可能性对延迟比较看重,配置maximumSize > coreSize,maxQueueSize = -1。从前在任务多的事先,我太多 有等待的图片 的图片 队列,直接创建新多任务管理器 执行任务。

coreSize = 2; maximumSize = 5; maxQueueSize = -1

可能性对资源比较看重, 不希望创建太多多任务管理器 ,配置maximumSize = coreSize,maxQueueSize > 0。从前在任务多的事先,会进等待的图片 的图片 队列,直到有多任务管理器 空闲可能性超时。

coreSize = 2; maximumSize = 2; maxQueueSize = 10

在hystrix上修复你是什么难题

技术上是可行的,有什么都有有方案可以 做到。但Netflix可能性发表声明不再维护hystrix了,这条路也就不通了,除非维护当事人的hystrix分支版本。

Reference

https://github.com/Netflix/Hystrix/wiki/Configuration

https://github.com/Netflix/Hystrix/issues/1589

https://github.com/Netflix/Hystrix/pull/1670