• 如何用利特尔法则调整线程池大小


    利特尔法则

    利特尔法则派生于排队论,用以下数学公式表示:

    L = λW

    L 系统中存在的平均请求数量。

    λ 请求有效到达速率。例如:5/s 表示每秒有5个请求到达系统。

    W 请求在系统中的平均等待执行时间。

    排队论:研究服务系统中排队现象随机规律的学科,探究排队有关的数量指标的概率规律性。

    场景

    我们先假设一个店铺员工调整场景。

    前提

    • 每个客户一次只买一只炸鸡;

    • 每位员工制作一个炸鸡需要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

    上面测试比较片面。现实应根据系统长期平均指标进行调整。

    总结

    利特尔法则应用场景很多。欢迎大家留言交流!

  • 相关阅读:
    Ubantu 查看系统资源占用
    C do {...} while (0) 在宏定义中的作用
    Redis架构设计
    Ubantu 安装boost环境
    Ubuntu 安装谷歌拼音输入法
    Ubuntu C/C++开发环境的安装和配置
    ubuntu 14.04英文环境设置成中文
    自己动手写可视化软件(代码已开源)
    探秘Tomcat——连接篇
    探秘Tomcat——连接器和容器的优雅启动
  • 原文地址:https://www.cnblogs.com/sky233/p/12968741.html
Copyright © 2020-2023  润新知