利特尔法则派生于排队论,用如下数学公式表示:java
L = λWide
L 系统中存在的平均请求数量。测试
λ 请求有效到达速率。例如:5/s 表示每秒有5个请求到达系统。this
W 请求在系统中的平均等待执行时间。线程
排队论:研究服务系统中排队现象随机规律的学科,探究排队有关的数量指标的几率规律性。3d
咱们先假设一个店铺员工调整场景。code
每一个客户一次只买一只炸鸡;blog
每位员工制做一个炸鸡须要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
上面测试比较片面。现实应根据系统长期平均指标进行调整。
利特尔法则应用场景不少。欢迎你们留言交流!