利特爾法則
利特爾法則派生於排隊論,用以下數學公式表示:
L = λW
L 系統中存在的平均請求數量。
λ 請求有效到達速率。例如:5/s 表示每秒有5個請求到達系統。
W 請求在系統中的平均等待執行時間。
排隊論:研究服務系統中排隊現象隨機規律的學科,探究排隊有關的數量指標的概率規律性。
場景
我們先假設一個店鋪員工調整場景。
前提
-
每個客戶一次只買一隻炸雞;
-
每位員工製作一個炸雞需要1分鐘。
-
客戶買炸雞時等待時間越短,體驗越好。
如果你是一家炸雞店老闆,今年受疫情影響需要對店裡的員工進行調整,你會如何處理?
這個問題本質就是員工利用率與客戶體驗之間的權衡。
-
為了讓客戶保持極佳體驗,需要保持員工數量或增加員工;
-
為避免資源浪費,控制人力成本,需要裁減空閒員工。
假設店裡目前有3名員工。你如何進行員工調整決策。我們分析以下幾種情形。
當 平均客流量 = 3人/分鐘 客戶等待時間稍短,體驗良好,並且員工工作都是飽和。此時不需要調整。
當 平均客流量 < 3人/分鐘 客戶等待時間稍短,體驗良好,但是始終有一個員工在打醬油,此時可以考慮減裁一人。
當 平均客流量 > 3人/分鐘 客戶5,6,7等待時間延長體驗稍差,此時可以根據實際情況增加員工。
平均每分鐘客流量 ≈ 員工數 為最佳。
執行緒池
其實執行緒池處理也算是一個排隊模型。簡化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
兩組迭代請求,一次提交10個任務,執行緒數為10
兩組迭代請求,一次提交10個任務,執行緒數為50
上面測試比較片面。現實應根據系統長期平均指標進行調整。
總結
利特爾法則應用場景很多。歡迎大家留言交流!