事件中心服务新增定时消息功能:
定时消息的链路为:定时消息接口(pubsub) --> TiDB、RocketMQ --> 定时消息执行服务(Consumer) --> 定时消息回调接口 --> Kafka
本次压测及调优,只针对定时消息接口、定时消息执行服务(Consumer)、定时消息回调接口。
压测意图:
测试提交定时消息接口在不同 QPS 时的 RT 情况
观测pubsub服务在负载情况下的资源使用情况
测试consumer的消费能力、得出消息积压与Consumer数量的比例
测试定时消息回调接口的QPS峰值
场景描述:
pubsub单实例,最终调整配置和生产环境一致
模拟固定QPS对提交定时消息接口进行压测:
堆积一定数量的定时消息:
观测指标:
pubsub服务器:
机器CPU使用情况
查看线程级别 user, system 情况
主机 load
GC 情况
内存增长情况
线程情况
接口耗时(通过压测平台监控查看)
定时消息执行服务:
consumer TPS
consumer 服务监控
consumer 主机监控
pubsub服务器:
消息延迟情况
pubsub服务2c-6g,单实例;Consumer服务2c-6g,单实例
定时消息提交接口并发数1000,思考时间为1000ms,模拟QPS为1000的场景,平均RT在好几秒,吞吐量只有200左右
单个消费者TPS只有60左右
pubsub服务及Consumer服务资源使用率均保持在很低水平
pubsub服务young GC频繁
离预期相距甚远,预期是可以承受上万条消息积压仍能保持毫秒级别的误差,所以优化目标为:
在pubsub服务实例上ping TiDB、RocketMQ服务地址,延迟均为5ms内,先排除网络原因。
使用相同的压测脚本,尝试压了测试环境的pubsub服务,测试环境的压测结果很正常,QPS达到预期设置的1000,平均RT在10ms内,测试环境使用的是MySQL,生产环境使用的是新申请的阿里云服务器搭建的TiDB,怀疑是TiDB服务端配置或者其他原因导致影响性能。
将生产环境上pubsub使用的TiDB换为同网络环境的MySQL,压测情况并无变化,排除。
RocketMQ服务在相同网段进行过压测,压测结果正常,排除。
由于测试环境和生产环境压测结果大相径庭,很长一段时间的排查方向都指向了生产环境k8s的网络、组件间的延迟等,于是又把pubsub部署至生产环境的一台物理机上,结果任然保持不变。
客观测试了一下测试环境和生产环境的网络差异:
并发数 1,思考时间 1000ms
测试环境:
生产环境:
分析
每次接口提交,大致产生的跨进程交互如下:
内容 | 测试环境耗时 | 生产环境耗时 | |
---|---|---|---|
施压机到pubsub主机 | 0.18 | 0.6 | |
pubsub主机到tidb,开启事务 | 0.06 | 4.9 | |
pubsub主机到tidb,运行 sql | 0.06 | 4.9 | |
pubsub主机到rocketMQ,生产消息 | 0.3 | 4 | |
pubsub主机到tidb,提交事务 | 0.06 | 4.9 | |
总计 | 0.66 | 19.3 | |
本场景压测RT平均值 | 7.98 | 40.93 |
结论:
由于测试环境的物理距离问题,天然有至少5倍左右的性能差异,所以测试环境的压测结果超出预期的理想,得益于各组件的网络低延迟,但是生产环境的网络延迟属于正常范围,还需进一步排查。
使用 Arthas 的 trace 命令,对接口的主要逻辑方法进行耗时追踪:
发现真正执行逻辑的代码只耗时13ms左右,但是执行方法之前,有一个500多ms的耗时。
使用skywalking观察耗时很长的请求,结果如下:
结论相同,在执行SQL前,有一个很长的等待时间,初步定位到问题,怀疑是数据库连接池的问题。
pom文件中添加:
<dependency>
<groupId>io.micrometergroupId>
<artifactId>micrometer-registry-prometheusartifactId>
<scope>runtimescope>
dependency>
添加配置:
# 对外暴露所有监控指标
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
prometheus:
enabled: true # 激活普罗米修斯
health:
show-details: always # 健康值总是展示细节
metrics:
export:
prometheus:
enabled: true # 指标允许被导出
启动服务后,访问:/actuator/prometheus
可以看到默认连接池最大数量是10,大量并发情况下,获取连接池时,耗费了大量的时间。
场景一至场景四:并发数 500,思考时间 400 ms
pubbsub 调整内容:
分析
pubsub 调整内容:
第一次压测:
第二次压测:
分析:
pubsub 调整内容:
分析:
pubsub 调整内容:
分析:
尝试提升压力,观察 RT 情况:并发数 500,思考时间 400 ms
分析:
并发数 500,思考时间 300 ms
分析
并发数 500,思考时间 200 ms
pubsub 调整内容:
分析:
CPU已打满,继续增加线程数无提升,线程数已接近理想状态值。
CPU核数、Tomcat线程数、数据库连接池数比例约为:
1:100:100
将pubsub服务CPU核数修改为:8c,Tomcat线程数、数据库连接池数修改为:800
初始状态:
将消费者增加至20个:
消费服务资源使用情况:
定时消息回调接口:
消费者TPS:
可以看到增加消费者后,消费能力明显提升,但是个服务资源使用率都未达到上限。
继续增加Consumer数量,TPS并没有提升。
(这里测试消费能力,可以只修改读队列数)
消费服务实例数 | 单个服务消费者数量 | 消费者总数 | 消费TPS |
---|---|---|---|
1 | 20 | 3000左右 | |
1 | 40 | 40 | 3000左右 |
2 | 20 | 40 | 5000左右 |
2 | 40 | 80 | 5000左右 |
以上压测过程中,pubsub服务和消费者服务的资源使用率都只维持在中等水平,单个消费者实例上消费者数量达到40时,出现个别报错:
第一次出现这个异常,刚开始没仔细看异常信息,以为是pubsub回调接口处理不过来了,没有在意,造成了一个排查疏漏,走了一些弯路。
消费服务实例数 | 单个服务消费者数量 | 消费者总数 | 消费TPS |
---|---|---|---|
1 | 20 | 20 | 3000左右 |
1 | 40 | 40 | 3000左右 |
2 | 20 | 40 | 5000左右 |
4 | 20 | 80 | 8500左右 |
5 | 20 | 100 | 9500左右 |
6 | 20 | 120 | 9500左右 |
消费服务实例数 | 单个服务消费者数量 | 消费者总数 | 消费TPS |
---|---|---|---|
4 | 30 | 120 | 9000左右 |
5 | 30 | 150 | 12000左右 |
由于每一轮压测成本过高,已经达到预期,没有继续进行后续压测。
消费者在30*4时,再次出现大量异常:ConnectionPoolTimeoutException
初步结论:
增加消费者数量和 Topic队列数,可以有效提升消费者TPS
目前单个消费服务配置20~30个消费者达到消费峰值,但是消费服务和pubsub服务的资源使用还远远没到瓶颈。
消费服务内有http客户端,怀疑是瓶颈在http连接池上,而之前出现的异常:ConnectionPoolTimeoutException,正式由于http客户端使用了连接池,从连接池内获取http连接超时导致的。
消费程序中的http客户端使用的是apache提供的CloseableHttpClient,有两个线程池核心参数:
maxTotal默认是200,defaultMaxPerRoute默认是50,而消费程序中只会调用一个接口,所以实际只有50个连接是可用的,将defaultMaxPerRoute修改为200。
消费者TPS有提升,不再有报错信息,消费者服务CPU消耗有提升。
对Consumer进行了几个参数的调试,包括:consumeThreadMax、consumeThreadMin、consumeMessageBatchMaxSize、pullBatchSize,其中consumeThreadMax、consumeThreadMin两个参数修改后对消费能力有较明显提升,其余两个对于顺序消费场景无明显提升。
consumeThreadMax、consumeThreadMin默认为20,修改为40:
CPU消耗有提升,加大consumeThreadMin后,消费者TPS可以瞬间达到消费峰值
继续加大consumeThreadMin后,消费TPS出现下滑。
Tips:Consumer客户端会根据消费线程的压力,动态增加消费线程: