• SLA中QPS、TP999等概念


    一、SLA概念

    参考文档:SLA

    二、TP999

    1.1 概念

    • 接口的响应TP99线的时间

    • TP99指的是满足百分之九十九的网络请求所需要的最低耗时。同理TP999指的满足千分之九百九十九的网络请求所需要的最低耗时,这里的百分比按耗时由小到大的序列截取。

    • 举个例子:有四次请求耗时分别为:

    10ms,1000ms,100ms,2ms,由小到大排序后为 2ms,10ms,100ms,1000ms

    计算TP50:4次请求中,99%的请求数为4*0.5,进位取整也就是2次,满足这全部2次请求的的最低耗时为10ms,也就是TP99的答案是10ms。

    计算TP99:4次请求中,99%的请求数为4*0.99,进位取整也就是4次,满足这全部4次请求的的最低耗时为1000ms,也就是TP99的答案是1000ms。

    可以发现,TP后的数字越大,对耗时的容忍性越低,这个下限取决于那个最耗时的请求,有点像木桶效应~

    1.2 作用

    可以定制rpc的timeout时间,如果tp999一般在500ms,那么调用方就可以设置timeout = 1500ms左右

    三、QPS

    3.1 基本概念

    1、含义:即每秒的响应请求数,也即是最大吞吐能力。

    ⚠️:【是1s能处理的请求数量,不是1s可以打进来多少请求】,即我提供的接口方法的最大吞吐量

    3.2 公式:

    QPS = 最大的工作线程数 / avg

    • 最大连接数

      minWorkerThreads(默认即可)最少的工作线程数10流量存在明显的高低峰的场景下,可以根据服务实际情况调大,避免服务出现不断动态地创建和销毁线程
      maxWorkerThreads(默认即可)最大的工作线程数256
      workQueueSize(默认即可)线程池队列大小0
      selectorThreads(默认即可)select线程数4
      keepAliveTime线程池空闲时,线程存活的时间30s

      a、是接口1s可以支持的最大连接数量。和接口使用的框架、通信协议有关

      b、mt使用的是thrift的rpc,底层是netty。maxWorkerThreads = 256

      c、dubbo、springcloud也有自己相应的最大工作线程数

    • avg

      a、一般就是接口的平均响应时间,可以等效理解为RT(接口请求的一次往返时间)

      b、流量不高的时候,接口avg可能是100ms。但是高并发的时候,avg可能会高2-3倍

      eg:

      ​ 1、接口是rpc并发查询下游数据,然后内存处理,返回。正常时,rpc并发查询下游是30个请求,我们配置的mdp核心线程数是32,完全够用,所以avg = 100ms

      ​ 2、当并发高了,33请求并发查询下游数量。1-32的请求,还是100ms,但是第33个请求来了,可能第1个请求刚好处理完了或正在处理,或者基本上刚开始处理。所以第33个请求处理完成需要100ms,整体需要100ms[1-32] + 100ms[33] = 200ms

      ​ 3、所以,并发高了,avg肯定会变大。可以采取并发请求的数量 / 核心线程数 * 之前的avg【一般就是扩大2-3倍】

      c、接口avg的影响因素,一般分为

      ​ 1、代码问题【for循环嵌套过多、处理逻辑繁琐等】

      ​ 2、mdp线程池核心的数量,并发处理度

      ​ 3、查询db,sql语句的快慢、索引是否添加、甚至是mysql的连接耗时等

      ​ 4、适当的让少一些流量进来,也能提升avg-流量削峰(不好的请求、redis同一个人的请求拒绝)

      ​ 5、使用缓存数据、读缓存等也能提高接口的性能

      d、数据库连接池 和 线程池处理请求的区别

      ⚠️:mysql不是安装在你appkey代码跑的服务器上,mysql有自己的服务器集群。

    3.3 数据库连接池 和 线程池处理请求区别

    一、数据库连接池
    此处连接池逻辑和线程池逻辑类似,初始化的时候初始化常驻链接,每过来一个新的连接,执行如下逻辑:
    mdp.zebra[0].maxPoolSize=100
    mdp.zebra[0].initialPoolSize=30

    1.连接数是否小于常驻连接数,若小于则拿出来用recycle,否则进入步骤二;

    2.连接数是否小于最大连接数,若小于则创建新连接,用后close,否则进入步骤三;

    3.等待其他连接关闭,等待的时间是否小于最大等待时长,若小于则创建新连接,用后close,否则抛出GetConnectionTimeoutException

    二、线程池
    首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。

    如果workerCount【线程数量】 < corePoolSize,则创建并启动一个线程来执行新提交的任务。

    如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。

    如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。

    如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

    • 综上所述:

    一台机器的QPS = 256 / 100ms = 2560。5台机器 = 2560 * 5 = 12800

    ⚠️这里的256,是你整个服务的最大工作线程。256不一定都给你这个接口方法用

    所以,一台机器的此方法QPS = 256 /10 / 100ms = 250左右 , 6台机器 1500 QPS左右

    • 线程核心大小: QPS / (1000 / avg)
    • 队列类型:流量大且流量均匀。比如QPS稳定在800-1000,且线程池大小够用,则建议使用同步队列synchronousQueue
      流量不大且波动较大比如QPS10-1000,则使用阻塞队列LinkedBlockQueue
    • 队列长度:max(200,3*线程数)

    3.4 粗估QPS的计算公式【不知道avg的情况下】

    • 正常情况下avg可以根据访问db的sql时间大概能推断出来,但是有时候不知道avg,此时可以使用下面公式:
    • pv访问量*0.8 / 864000 * 0.2 = 集群QPS

    3.5队列类型和大小

    • 同步队列
      • 任务多、流量均匀。比如QPS均匀在800-1000波动,当线程数足够时,建议使用SynchronousQueue队列
    • 阻塞队列
      • 任务数不固定,流量不均匀。比如流量在10-1000之间波动,建议使用LinkedBlockQueue
    • 长度:max{3*coreSize, 300}。阻塞队列建议在800-1000

    3.6 线程数量

    核心线程数量 = QPS / (1000ms / avg)

    最大线程数 = 2 * 核心线程数

    3.7 实战

    @Configuration
    public class ThreadPoolConfig {
        private static ThreadPoolExecutor queryBlackListExecutor = new ThreadPoolExecutor(
                16, 32, 60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(800),
                new DefineThreadFactory("queryBlackListExecutor"),
                new ThreadPoolExecutor.AbortPolicy()
        );
        
        @Bean
        public static ThreadPoolExecutor queryBlackListExecutor() {
            return queryBlackListExecutor;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    public class DefineThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
    
        private final ThreadGroup group;
    
        private final AtomicInteger threadNumber = new AtomicInteger(1);
    
        private final String  namePrefix;
    
        public DefineThreadFactory(String biz) {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" + biz + "-" + poolNumber.getAndIncrement() + "-thread-";
        }
    
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(), 0);
            if (t.isDaemon()) {
                t.setDaemon(false);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    搭建FTP服务器备份vCenter
    垃圾分类资讯易语言代码
    IK学习笔记(2)——TwoBones IK
    在CentOS7下构建TeamSpeak服务器并增加网易云点歌插件
    淘宝sku详情
    ElasticSearch 、Kibana安装
    Plink常见命令 --bfile --freq--recode --make-bed
    QT学习笔记1-Hello, QT
    2分钟明白异或运算
    [爬虫练手]学校院系专业整理
  • 原文地址:https://blog.csdn.net/tmax52HZ/article/details/133218190