如何使用利特爾法則調整線程池大小

利特爾法則

利特爾法則派生於排隊論,用如下數學公式表示:java

L = λWide

L 系統中存在的平均請求數量。測試

λ 請求有效到達速率。例如:5/s 表示每秒有5個請求到達系統。this

W 請求在系統中的平均等待執行時間。線程

排隊論:研究服務系統中排隊現象隨機規律的學科,探究排隊有關的數量指標的機率規律性。3d

場景

咱們先假設一個店鋪員工調整場景。code

前提

  • 每一個客戶一次只買一隻炸雞;blog

  • 每位員工製做一個炸雞須要1分鐘。隊列

  • 客戶買炸雞時等待時間越短,體驗越好。

若是你是一家炸雞店老闆,今年受疫情影響須要對店裏的員工進行調整,你會如何處理?內存

這個問題本質就是員工利用率客戶體驗之間的權衡。

  1. 爲了讓客戶保持極佳體驗,須要保持員工數量或增長員工;

  2. 爲避免資源浪費,控制人力成本,須要裁減空閒員工。

假設店裏目前有3名員工。你如何進行員工調整決策。咱們分析如下幾種情形。

平均客流量 = 3人/分鐘 客戶等待時間稍短,體驗良好,而且員工工做都是飽和。此時不須要調整。

=3人/分鐘

平均客流量 < 3人/分鐘 客戶等待時間稍短,體驗良好,可是始終有一個員工在打醬油,此時能夠考慮減裁一人。

< 3人/分鐘

平均客流量 > 3人/分鐘 客戶5,6,7等待時間延長體驗稍差,此時能夠根據實際狀況增長員工。

客流量  > 3人/分鐘

平均每分鐘客流量 ≈ 員工數 爲最佳。

線程池

其實線程池處理也算是一個排隊模型。簡化Java線程池處理模型以下:

線程池任務執行大體階段:提交 --> 入隊列或直接執行 ---> 實際執行

線程池

  • 任務提交頻率:每秒任務提交數;

  • 任務隊列等待平均耗時:任務隊列等待總耗時除以實際執行數;

  • 任務實際執行平均耗時:任務實際運行總耗時除以實際執行數;

  • 任務執行平均耗時:任務隊列等待平均耗時加任務實際執行平均耗時;

咱們能夠根據如下指標來評估調整線程池參數

線程池中平均任務數 = 任務提交頻率 * 任務執行平均耗時

線程等待耗時與響應時間比率 = 任務隊列等待總耗時 / (任務隊列等待總耗時 + 任務實際執行總耗時


線程等待耗時與響應時間比率 太高,說明任務排隊較多,評估當前線程池大小是否合理,結合系統負載進行相應調整。

線程池中平均任務數 < 目前線程池大小 應適當減小線程數量。

系統平均處理任務數 > 目前線程池大小 在這種狀況下,先評估當前系統是否有能力支撐更大的線程數量(如CPU數,內存等),而後再進行調整。

代碼片斷

@Slf4j
public class MonitoredThreadPoolExecutor extends ThreadPoolExecutor {

    //任務提交成功時間
    private final ConcurrentHashMap<Runnable, Long> timeOfRequest = new ConcurrentHashMap<>();
    //任務實際開始執行時間
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();
    //上一個任務提交成功時間
    private long lastArrivalTime;

    // 任務實際執行總數
    private final AtomicInteger numberOfRequestsRetired = new AtomicInteger();
    // 任務提交總數
    private final AtomicInteger numberOfRequests = new AtomicInteger();
    // 任務實際執行總耗時
    private final AtomicLong totalServiceTime = new AtomicLong();
    // 任務在隊列等待總耗
    private final AtomicLong totalPoolTime = new AtomicLong();
    // 新任務提交總耗時
    private final AtomicLong aggregateInterRequestArrivalTime = new AtomicLong();

    public MonitoredThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                       BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    @Override
    protected void beforeExecute(Thread worker, Runnable task) {
        super.beforeExecute(worker, task);
        startTime.set(System.nanoTime());
    }

    @Override
    protected void afterExecute(Runnable task, Throwable t) {
        try {
            long start = startTime.get();
            totalServiceTime.addAndGet(System.nanoTime() - start);
            totalPoolTime.addAndGet(start - timeOfRequest.remove(task));
            numberOfRequestsRetired.incrementAndGet();
        } finally {
            if (null != t) {
                log.error(AppSystem.ERROR_LOG_PREFIX + "線程池處理異常:", Throwables.getRootCause(t));
            }
            super.afterExecute(task, t);
        }
    }

    @Override
    public void execute(Runnable task) {
        long now = System.nanoTime();
        numberOfRequests.incrementAndGet();
        synchronized (this) {
            if (lastArrivalTime != 0L) {
                aggregateInterRequestArrivalTime.addAndGet(now - lastArrivalTime);
            }
            lastArrivalTime = now;
            timeOfRequest.put(task, now);
        }
        super.execute(task);
    }
}

測試

兩組迭代請求,一次提交10個任務,線程數爲1

線程數1

兩組迭代請求,一次提交10個任務,線程數爲10

線程數10

兩組迭代請求,一次提交10個任務,線程數爲50

線程數50

上面測試比較片面。現實應根據系統長期平均指標進行調整。

總結

利特爾法則應用場景不少。歡迎你們留言交流!

相關文章
相關標籤/搜索