一 背景
1 调用链吞吐能力低,200w/min
2 压测时,调用链kafka堆积上亿条消息,导致调用链延迟度达到小时级别
3 压测试调用链服务full gc严重,每分钟30-50次
4 随着公司业务增长,线上调用链消息在日益增长,线上调用链为每天1T左右
二 优化前调用链架构
image优化前的项目主要具备以下功能:
-
暴露日志收集dubbo接口,接收调用链日志上报
-
消费kafka日志,多线程处理后写入到es
三 问题分析
优化前服务存在如下性能问题:
1 IO性能瓶颈
多个IO密集型功能点(kafka拉取,es写入,dubbo请求处理)耦合在一个服务中,导致IO性能成为系统性能的主要瓶颈点之一
2 Synchronized代码性能问题
代码在kafka拉取线程和写入es的线程之间交换数据的集合是传统的非线程安全的LinkedList,为了保证线程安全使用了synchronized修饰,导致方法的吞吐量上限很低
3 GC问题
在处理峰值时期,full gc次数为30-50/min,导致full gc频繁主要有如下原因:
3.1 Kafka版本过低,不能指定拉取条数,只能指定拉取大小,由于调用链日志每条日志上限为10M,所以每个partition拉取值为10M,每个topic有12个partition,导致每次会拉取上万条日志到内存中,超过jvm 大对象分配2M的阈值,直接分配到老年代,导致老年代频繁回收,出发full GC
3.2 Kafka消费服务,java对象有大量的被创建,需要被快速回收,不需要晋升等特点,jvm默认的Young,Old分配比例,及Young中的Eden Survior默认分配比例不能满足本服务对GC的要求
3.3 代码实现上,为了实现异步处理,为每个日志对象创建了一个Runnable对象,导致内存的对象翻倍,加重了GC的压力
四 解决方案
针对上面问题分析的结果,提出一下解决方案:
1 代码优化,引入disruptor代替Synchronized
2 Jvm优化,解决GC问题
代码优化
1. 为什么要使用disruptor?
调用链服务消费kafka写入ES,天然符合生产消费者模型
Disruptor高性能的异步处理框架,无锁(CAS)环形队列
Disruptor的生产消费线程完全相互透明,生产消费代码解耦
生产消费线程个数,等待策略,RingBuffer大小均由参数指定,极易扩展
2. Disruptor和ArrayBlockQueue的性能对比
主要生产消费模型:
image各个生产消费模型下性能对比:
image3. disruptor的性能为什么那么高?
生产消费线程竞争基于CAS,无锁竞争
解决CPU缓存行的伪共享问题,使部分变量的读取远高于内存读取
4. 调用链中disruptor主要参数的指定
Jvm 优化
| Tables | Are | Cool || ------------- |:-------------:| -----:|| col 3 is | right-aligned | 12 || zebra stripes | are neat | $1 |
全部参数对比
image差异参数对比:
五 优化效果
吞吐能力
image
网友评论