性能调优日记

27 阅读4分钟

前言

本文用于记录小白第一次性能调优,仅供参考,欢迎交流

Part 1

测试场景

receive集群group1(两个receive节点)上单个代理,100个tcp连接,2s-10s之间随机推送报文数据

实际结果

100个连接经过ELB负载均衡后均匀的分配在了两个receive节点上,但是只有单个analyze节点在执行数据策略链

企业微信截图_17679253796825.png

企业微信截图_17679283158513.png

具体原因

  1. 在receive解析完数据进行推送时,指定了key为proxyID,在本次测试场景中,同一个代理虽然存在100个tcp连接推送数据,但是共享一个代理id,导致所有的推送数据进入同一个分区,最终被analyze节点3所持有的消费者长期消费。
  2. 单节点的消费者数量为5个,3个节点共计15个消费者,而数据传输topic中的分区数为10,导致部分消费者无法获取到分区,处于空闲状态。

image.png

image.png

企业微信截图_17679369141470.png

原理解析

  1. 在Kafka中,生产者在推送数据时,分区数不变的情况下,如果指定了key,相同key的数据一定分配到同一个partition(同一key的消息在单个分区内有序)。
  2. 在同一个消费者组(consumer group)中,一个分区(partition)在同一时刻只能被该组内的一个消费者(consumer)消费,且消费组内分区与消费者的持有关系保持固定。当有消费者加入或退出消费组时,触发rebalance,持有关系会重新分配。

调整措施

  1. 调整消费者与分区的数量关系,concurrency * 节点数 <= 分区数。
  2. 从业务角度出发,推送数据时移除key,设备传输过程中的业务分包或网络分包,在receive节点已经完成组包以及解析,接受时间也已固定,因此具体由哪个节点执行后续的匹配、计算、过滤、转发并无差异。

Part 2

测试场景

  • 两个receive集群group1和group2(每个集群包含两个节点)
  • 每个集群一个代理,每个代理200个tcp连接以及10个测点
  • 单个连接1s-3s之间随机推送报文数据,每个数据报文10个测点全量匹配,共计转发10次

实际结果

  • 经过约1个小时左右的压测,analyze会出现OOM的情况,同时消费组中部分消费者掉线
  • analyze节点消费速度偏慢
  • 其他服务服务器指标偏高,但未触及水位线

image.png

image.png

具体原因

  1. 通过查看GC日志发现,GC频繁且实际能释放的空间极少 image.png
  2. 导出heap_dump文件后通过MAT进行解析,发现某个线程池占据了约1.5G的内存(此时服务堆内存设置2G),通过对线程池outgoing引用的分析发现,主要内存占用还是在线程池的任务队列中 image.png
  3. 通过path to GC Roots找到强引用路径,定位阻碍GC存在内存泄漏的的线程池为executeDataStrategyKafka-Executor,引用的起点是Kafka的消费者 image.png
  4. 最终找到实际的罪魁祸首@Async和@KafkaListener的混合使用,同时消费速度跟不上生产速度,单次批量拉取500条消息,封装为任务进入等待队列,导致线程池中积压了大量解析后的数据,最终撑爆了内存 image.png image.png
  5. 服务设定的是批量拉取(单次poll最多拉取500条数据),消费者将数据基于proxyId分组后通过并行流进行处理,单个分组内的消息同步消费,消费过程中涉及到服务调用,数据转发等网络I/O。因此,在只有两个代理的压测场景下,并行流实际只有两个线程在工作,最终单次poll的处理时间超过了kafka默认的单次最大调用时间(5分钟),导致消费者被踢下线 image.png

原理解析

  1. 线程池必须显示设定队列大小,同时要基于业务考量队列大小的合理性,避免因为部分业务产生OOM导致整个服务不可用。
  2. 并行流(parallelStream)默认使用ForkJoin线程池(线程数为CPU核心数-1),且该线程池被CompletableFuture、Arrays等方法共用,容易导致业务之间相互阻塞。同时并行流更适用于简易的CPU密集计算,而非I/O密集型操作
  3. 在使用消息队列时,消费者端禁止开启自动提交(例如当前情况,大量消息从kafka转到任务队列中积压,OOM后导致数据全部丢失)
  4. 若kafka消费者未定时发送心跳(默认3s)或者单次poll的处理时间超标(默认5分钟),均会导致消费者从消费组中移除;Full GC耗时过长会导致消费者心跳发送卡死,同样出现离线

调整措施

  1. 移除消费者与kafka之间的异步线程池
  2. 并行流调整为CompletableFuture + 专属线程池
  3. 关闭kafka消费自动提交,改为手动提交