利特爾法則
利特爾法則派生于排隊論,用以下數(shù)學(xué)公式表示:
L 系統(tǒng)中存在的平均請求數(shù)量。
λ 請求有效到達速率。例如:5/s 表示每秒有5個請求到達系統(tǒng)。
W 請求在系統(tǒng)中的平均等待執(zhí)行時間。
排隊論:研究服務(wù)系統(tǒng)中排隊現(xiàn)象隨機規(guī)律的學(xué)科,探究排隊有關(guān)的數(shù)量指標的概率規(guī)律性。
場景
我們先假設(shè)一個店鋪員工調(diào)整場景。
前提
每個客戶一次只買一只炸雞;
每位員工制作一個炸雞需要1分鐘。
客戶買炸雞時等待時間越短,體驗越好。
如果你是一家炸雞店老板,今年受疫情影響需要對店里的員工進行調(diào)整,你會如何處理?
這個問題本質(zhì)就是員工利用率與客戶體驗之間的權(quán)衡。
為了讓客戶保持極佳體驗,需要保持員工數(shù)量或增加員工;
為避免資源浪費,控制人力成本,需要裁減空閑員工。
假設(shè)店里目前有3名員工。你如何進行員工調(diào)整決策。我們分析以下幾種情形。
當(dāng) 平均客流量 = 3人/分鐘 客戶等待時間稍短,體驗良好,并且員工工作都是飽和。此時不需要調(diào)整。

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

當(dāng) 平均客流量 > 3人/分鐘 客戶5,6,7等待時間延長體驗稍差,此時可以根據(jù)實際情況增加員工。

平均每分鐘客流量 ≈ 員工數(shù) 為最佳。
線程池
其實線程池處理也算是一個排隊模型。簡化Java線程池處理模型如下:
線程池任務(wù)執(zhí)行大致階段:提交 --> 入隊列或直接執(zhí)行 ---> 實際執(zhí)行

任務(wù)提交頻率:每秒任務(wù)提交數(shù);
任務(wù)隊列等待平均耗時:任務(wù)隊列等待總耗時除以實際執(zhí)行數(shù);
任務(wù)實際執(zhí)行平均耗時:任務(wù)實際運行總耗時除以實際執(zhí)行數(shù);
任務(wù)執(zhí)行平均耗時:任務(wù)隊列等待平均耗時加任務(wù)實際執(zhí)行平均耗時;
我們可以根據(jù)以下指標來評估調(diào)整線程池參數(shù)
線程池中平均任務(wù)數(shù) = 任務(wù)提交頻率 * 任務(wù)執(zhí)行平均耗時
線程等待耗時與響應(yīng)時間比率 = 任務(wù)隊列等待總耗時 / (任務(wù)隊列等待總耗時 + 任務(wù)實際執(zhí)行總耗時)
當(dāng) 線程等待耗時與響應(yīng)時間比率 過高,說明任務(wù)排隊較多,評估當(dāng)前線程池大小是否合理,結(jié)合系統(tǒng)負載進行相應(yīng)調(diào)整。
當(dāng) 線程池中平均任務(wù)數(shù) < 目前線程池大小 應(yīng)適當(dāng)減少線程數(shù)量。
當(dāng) 系統(tǒng)平均處理任務(wù)數(shù) > 目前線程池大小 在這種情況下,先評估當(dāng)前系統(tǒng)是否有能力支撐更大的線程數(shù)量(如CPU數(shù),內(nèi)存等),然后再進行調(diào)整。
代碼片段
@Slf4j
public class MonitoredThreadPoolExecutor extends ThreadPoolExecutor {
//任務(wù)提交成功時間
private final ConcurrentHashMap<Runnable, Long> timeOfRequest = new ConcurrentHashMap<>();
//任務(wù)實際開始執(zhí)行時間
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
//上一個任務(wù)提交成功時間
private long lastArrivalTime;
// 任務(wù)實際執(zhí)行總數(shù)
private final AtomicInteger numberOfRequestsRetired = new AtomicInteger();
// 任務(wù)提交總數(shù)
private final AtomicInteger numberOfRequests = new AtomicInteger();
// 任務(wù)實際執(zhí)行總耗時
private final AtomicLong totalServiceTime = new AtomicLong();
// 任務(wù)在隊列等待總耗
private final AtomicLong totalPoolTime = new AtomicLong();
// 新任務(wù)提交總耗時
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個任務(wù),線程數(shù)為1

兩組迭代請求,一次提交10個任務(wù),線程數(shù)為10

兩組迭代請求,一次提交10個任務(wù),線程數(shù)為50

上面測試比較片面?,F(xiàn)實應(yīng)根據(jù)系統(tǒng)長期平均指標進行調(diào)整。
總結(jié)
利特爾法則應(yīng)用場景很多。歡迎大家留言交流!