作为一名项目项目经理,在项目上线之后我们经常会遇到一些参数设置的问题,比如今天要讨论的:如何合理的设置线程池大小。
线程池作为应用的关键组成部分,是应用处理业务的关键角色,而线程池大小直接关系着应用的吞吐量、处理能力、响应能力等等各项性能指标,那么线程池的大小设置为多少才是合理的呢?
按照网上各方大神给出了权威指示,线程池大小的设置跟应用的类型有关,一般系统应用分为两大类:1、CPU密集型,这种应用一般都是涉及科学计算、大数据统计等方面;2、IO密集型,这种应用是绝大多数应用的类型、比如电商网站、网关、银行系统等等等等。关于这两类应用的线程池大小设置有以下两条理论:
1、CPU密集型的应用,线程池大小为:CPU核心数+1
2、IO密集型的应用,线城池大小为:((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU核心数,其中线程等待时间+线程CPU时间就等于是线程总的总耗时,那么进一步可以演变为:(线程总耗时/线程CPU时间 )* CPU核心数
我负责的项目是互联网开放平台,实际上就是全行对外的网关,其应用性能很显然是属于第二种(IO密集型)。不过,理论归理论,要实际操作起来还有一定的难度,其中有两个问题需要我们解决:
1、如何合理的评估线程CPU时间
2、如何合理的评估线程等待时间
所幸,作为互联网开放平台这种网关性质的应用,其业务逻辑相当纯粹,所做的事情无非就是加解密、拆组报文、安全检查、服务调用、响应调用方等工作,其中除了服务调用是IO操作外,其他均为CPU操作,如果我能知道每笔交易的整个交易耗时以及服务调用耗时,那么我们就能很容易的估算出线城迟大小。更幸运的是,开放平台本身就具有监控能力,已经把每笔交易的关键要素都记录进了数据库里,以下是关键的数据库表结构的关键字段:
CREATE TABLE "MONITOR"."ESC_TRANS_LOG"
( "SERVICEID" VARCHAR2(100), //接口名称
"BUSSID" VARCHAR2(100), //接口所属的系统名称
"TRANSSTAMP1" NUMBER NOT NULL ENABLE, //接到第三方请求的时间
"TRANSSTAMP2" NUMBER NOT NULL ENABLE, //发起服务调用的时间
"TRANSSTAMP3" NUMBER NOT NULL ENABLE, //收到服务响应的时间
"TRANSSTAMP4" NUMBER NOT NULL ENABLE, //发送响应给第三方的时间
"TRANSSTAMP" TIMESTAMP (6) DEFAULT current_timestamp // 时间戳,记录接口的调用时间
)
数据库中的记录有几千万条,我在这里就不列举出来了,面对这样的数据库表,那么,
每个接口调用的线程等待时间=(TRANSSTAMP3-TRANSSTAMP2)
每个接口调用的线程总耗时=(TRANSSTAMP4-TRANSSTAMP1)
每个接口调用的线程CPU时间=每个接口的线程总耗时-每个接口的线程等待时间=(TRANSSTAMP4-TRANSSTAMP1)-(TRANSSTAMP3-TRANSSTAMP2)
现在知道了每个接口调用的线程总耗时、线程等待时间、线程CPU时间,那么线城池大小=(所有接口调用的线程总耗时/所有接口调用的线程总CPU)*CPU核心数。这个结果只能说是近似值,其中有一个问题就是每个接口的调用总数在整个调用量的比例是不同的,如果统一按照平均值处理是不合理的,最好的办法是计算出每个接口在总的交易量中的占比,然后根据(每个接口的线程总耗时/线程总CPU时间)*该接口在交易总量中的占比,最后将每个接口的所的值求和,最后得出的值才是比较精确的值。
假设有k个接口(k从1到m),a[k]是第k个接口的调用总量,n是所有的接口调用总量,c1[k]是第k个接口的总的线程总耗时,c2[k]是第k个接口的总的线程CPU耗时,p是cpu核心数量,那么,线程数量就是下面的公式(简称公式1)
(sum((a[k]/n)*(c1[k]/c2[k])))*p (k >=1 ,k<=m)
所以我们通过以下几个步骤实现我们的目标
1、计算出接口的总交易量:
with a as (SELECT count(1) cnt from ESC_TRANS_LOG t ),
2、计算出每个接口的交易总量:
b as (SELECT count(1) cnt,t.SERVICEID from ESC_TRANS_LOG t GROUP BY SERVICEID),
3、计算出每个接口总的线程总耗时、线程CPU时间:
c as(select sum((t.TRANSSTAMP4-t.TRANSSTAMP1)) cputime,sum(((t.TRANSSTAMP4-t.TRANSSTAMP1)- (t.TRANSSTAMP3-t.TRANSSTAMP2))) cpuruntime,t.SERVICEID
from ESC_TRANS_LOG t group by SERVICEID)
cputime:线程总耗时
cpuruntime:线程CPU时间
4、 根据前三步,再套用公式1,假设有16个CPU核心,我们得出的结果是:
select round ((sum((b.CNT/a.cnt)*(c.CPUTIME/c.CPURUNTIME)))*16,0)from a,b,c where b.SERVICEID = c.SERVICEID ;
以上是开放平台这类网关性质的服务器,借助数据库得出的线程计算公式,当然大部分的业务系统比这要更复杂,每个接口会有很多IO调用,比如数据库调用、接口调用,缓存服务调用、磁盘读写等等等等,面对这些业务系统,我们要做的就是做好每一个对调用的监控,尽量在日志或者数据库中记录每次调用的耗时,为日后我们的系统参数调整做好参考。