一、Pinpoint是什么,为啥要关心它在高并发下的表现

不少做后端开发的朋友应该都遇到过这种情况:线上系统突然卡得像蜗牛,用户刷页面转半天圈,订单提交半天没反应,甚至还出了重复下单的问题。这时候要快速定位问题,靠打印日志一条一条翻,基本要翻到天荒地老。这时候Pinpoint就能派上用场了,它是一款专门用来追踪系统调用链路的工具,简单说就是能帮你画出用户请求从前端到后端、再到数据库的完整“路线图”,每一步花了多少时间、有没有报错,一眼就能看清楚。 但Pinpoint本身也是个需要部署运行的服务,要是线上系统用户突然暴增,比如电商大促、直播带货爆单,系统进入高并发状态,这时候Pinpoint自己会不会“扛不住”?会不会因为它卡了反而影响业务?或者它能不能正常追踪到所有请求,帮我们定位问题?这就是这篇文章要讲的核心内容。 先给大家举个最常见的应用场景:某电商平台做618大促,当天的订单量是平时的20倍,所有业务系统都处于高并发状态,这时候Pinpoint要能正常工作,才能帮开发团队快速定位“下单接口慢”“库存扣减超时”这类问题,要是Pinpoint自己先崩了,开发团队就只能摸黑排查,损失会非常大。

二、Pinpoint在高并发场景下的常见性能问题

Pinpoint的工作流程大概是这样的:业务系统(被追踪的应用)上装一个探针(Agent),探针会收集每个请求的链路数据,然后把数据发给Pinpoint的收集端(Collector),收集端再把数据存到存储里(一般用HBase),最后我们通过Pinpoint的Web端查看链路。高并发的时候,每个环节都可能出问题,我给大家列几个最常见的。

2.1 探针(Agent)占用业务系统资源过多

探针是装在业务系统里的,相当于业务系统的“小跟班”,要是这个小跟班太“胖”,占了业务系统的CPU、内存,那反而会拖慢业务。比如平时业务系统的CPU占用是30%,装了探针后涨到50%,高并发的时候,业务本身的CPU占用就会到80%以上,探针再占一部分,很可能就会触发系统的负载过高预警。 举个真实的例子:某互联网公司的一个下单服务,平时运行正常,装了Pinpoint探针后,在大促当天,CPU占用直接冲到95%,导致大量请求超时。后来排查发现,探针的采样率设成了100%,也就是每个请求都要收集,高并发的时候,探针要处理几十万条请求的收集,自然就占了大量资源。

2.2 收集端(Collector)处理能力不足

收集端是接收所有探针发过来的数据的,高并发的时候,探针发过来的数据量会暴增,要是收集端的配置不够,或者部署的节点太少,就会出现数据堆积、丢失的情况。比如平时收集端每秒能处理1万条数据,高并发的时候每秒要处理10万条,那收集端就会“忙不过来”,要么把数据丢了,要么处理速度变慢,导致Web端查看链路的时候延迟很高。

2.3 存储(HBase)读写性能跟不上

Pinpoint的链路数据最终要存在HBase里,高并发的时候,收集端每秒要往HBase写大量数据,要是HBase的配置不够,比如节点太少、内存不足、磁盘IO不够,就会出现写入超时、数据丢失的情况。比如某公司的Pinpoint存储用的是3节点的HBase,大促的时候,写入延迟从平时的几毫秒涨到了几百毫秒,导致收集端的数据堆积了几个G,最后只能重启收集端清理数据。

三、Pinpoint高并发场景下的优化方案

针对上面说的这些问题,我们可以从探针、收集端、存储、部署架构这几个方面来优化,我给大家说几个实用的方法,还有具体的配置示例。

3.1 探针(Agent)的优化

探针的优化核心是减少对业务系统的资源占用,最常用的方法就是调整采样率。采样率就是探针收集请求的比例,比如采样率设成10%,就是10个请求里收集1个,这样就能大大减少探针的工作量。

3.1.1 调整采样率

技术栈:Java(因为Pinpoint主要用于Java应用的追踪,示例统一用Java) 首先,找到探针的配置文件pinpoint.config,一般在探针的根目录下,找到采样率的配置项:

# 探针采样率配置,默认是1000,这里的1000代表1000个请求里采样1个,也就是0.1%的采样率
profiler.sampling.rate=1000
# 采样类型,有rate(按比例)、count(按数量)等,这里用rate
profiler.sampling.type=rate

比如高并发的时候,我们可以把采样率调到1000,也就是0.1%的采样率,这样探针收集的数据量就会减少1000倍,资源占用自然就降下来了。要是平时需要更细的追踪,再调到100(1%)或者10(10%)就行。 另外,还可以配置“非重要请求”不采样,比如静态资源请求、健康检查请求,这些请求对业务影响不大,不用追踪,这样也能减少探针的工作量。配置方法是在pinpoint.config里加上:

# 配置不采样的请求路径,支持正则表达式
profiler.exclude.path=.*healthcheck.*,.*static.*

3.1.2 减少探针的日志输出

探针默认会输出很多调试日志,高并发的时候,这些日志会占用磁盘IO和CPU,我们可以把探针的日志级别调到WARN或者ERROR,只输出重要的日志。找到探针的log4j配置文件log4j.properties,修改日志级别:

# 把日志级别从DEBUG调到WARN
log4j.rootLogger=WARN, stdout

3.2 收集端(Collector)的优化

收集端的优化核心是提高处理能力,最常用的方法是扩容和配置优化。

3.2.1 扩容收集端节点

Pinpoint的收集端是支持集群部署的,高并发的时候,我们可以多部署几个收集端节点,然后用负载均衡(比如Nginx)把探针发过来的数据分摊到不同的收集端节点上,这样每个节点的压力就小了。 举个Nginx的配置示例,用来做收集端的负载均衡:

# 定义收集端的集群,包含3个节点
upstream pinpoint_collector {
    server collector1:9994; # 收集端1的地址和端口
    server collector2:9994; # 收集端2的地址和端口
    server collector3:9994; # 收集端3的地址和端口
    # 负载均衡算法用轮询,也可以用加权轮询、最少连接等
    least_conn;
}

server {
    listen 9994; # 监听探针发数据的端口
    location / {
        proxy_pass http://pinpoint_collector; # 把请求转发到收集端集群
    }
}

这样探针只需要把数据发给Nginx的地址,Nginx会自动把数据分摊到不同的收集端节点上,提高整体的处理能力。

3.2.2 调整收集端的线程池配置

收集端的线程池配置决定了它能同时处理多少请求,高并发的时候,我们可以把线程池的大小调大,提高处理能力。找到收集端的配置文件pinpoint-collector.properties,修改线程池的配置:

# 接收数据的线程池大小,默认是10,高并发的时候调到100
collector.receiver.thread.pool.size=100
# 处理数据的线程池大小,默认是20,高并发的时候调到200
collector.worker.thread.pool.size=200

3.3 存储(HBase)的优化

Pinpoint的存储用的是HBase,HBase的优化核心是提高读写性能,最常用的方法是扩容和配置优化。

3.3.1 扩容HBase节点

HBase是分布式存储,节点越多,读写性能越好,高并发的时候,我们可以把HBase的节点从3个扩容到5个或者更多,提高存储的读写能力。

3.3.2 调整HBase的配置

找到HBase的配置文件hbase-site.xml,修改以下配置:

<!-- 把HBase的堆内存调到8G,根据服务器的配置调整,一般设为服务器内存的一半 -->
<property>
    <name>hbase.regionserver.heapsize</name>
    <value>8g</value>
</property>
<!-- 把HBase的写入缓存调到256M,提高写入性能 -->
<property>
    <name>hbase.regionserver.hlog.blocksize</name>
    <value>268435456</value> <!-- 256M,单位是字节 -->
</property>
<!-- 把HBase的读取缓存调到512M,提高读取性能 -->
<property>
    <name>hfile.block.cache.size</name>
    <value>0.5</value> <!-- 占堆内存的比例,0.5就是50% -->
</property>

另外,还可以给HBase的表做预分区,因为Pinpoint的表是按时间戳分区的,高并发的时候,预分区可以让数据更均匀地分布在不同的节点上,提高读写性能。比如给Pinpoint的Span表(存链路数据的表)做预分区:

# 进入HBase的Shell
hbase shell
# 创建Span表,预分区成10个,分区键是时间戳
create 'pinpoint-span', {NAME => 'cf', COMPRESSION => 'SNAPPY'}, {SPLITS => ['1000000000000', '2000000000000', '3000000000000', '4000000000000', '5000000000000', '6000000000000', '7000000000000', '8000000000000', '9000000000000']}

3.4 部署架构的优化

除了上面的优化,还可以调整Pinpoint的部署架构,比如把Pinpoint的服务和业务服务分开部署,不要把Pinpoint的收集端、存储和业务服务装在同一台服务器上,避免互相抢资源。比如业务服务用10台服务器,Pinpoint的收集端用3台服务器,HBase用5台服务器,这样各自的资源都能得到保障。

四、Pinpoint优化的优缺点和注意事项

4.1 优化方案的优缺点

4.1.1 优点

这些优化方案的优点很明显,首先是能大大提高Pinpoint在高并发场景下的稳定性,不会因为Pinpoint自己的问题影响业务;其次是能减少Pinpoint对业务系统的资源占用,让业务系统能更专注于处理请求;最后是能提高Pinpoint的链路追踪能力,高并发的时候也能正常收集链路数据,帮开发团队快速定位问题。

4.1.2 缺点

这些优化方案也有一些缺点,比如调整采样率会导致部分链路数据丢失,要是采样率设得太低,可能会漏掉一些问题的链路;扩容收集端和HBase节点会增加部署和维护的成本,需要更多的服务器资源;配置优化需要一定的技术经验,要是配置不当,可能会导致Pinpoint的性能反而下降。

4.2 注意事项

在优化的时候,有几个注意事项要提醒大家: 第一,采样率的调整要根据实际情况来,不能为了减少资源占用就把采样率设得太低,一般高并发的时候设成0.1%到1%就够了,要是平时需要更细的追踪,再调回来; 第二,优化的时候要逐步调整,不要一次性改很多配置,比如先调整采样率,观察一段时间,要是没问题再调整收集端的配置,这样出了问题也能快速定位; 第三,要定期监控Pinpoint的运行状态,比如收集端的CPU、内存占用,HBase的读写延迟,探针的资源占用,要是发现异常,及时调整; 第四,不要把Pinpoint的服务和业务服务混在一起部署,一定要分开,避免互相影响。

五、文章总结

Pinpoint是一款非常实用的链路追踪工具,能帮开发团队快速定位系统问题,但在高并发场景下,要是不做优化,很可能会出现性能问题,甚至影响业务。这篇文章给大家介绍了Pinpoint在高并发场景下的常见性能问题,以及从探针、收集端、存储、部署架构这几个方面的优化方案,还有具体的配置示例。 总的来说,优化Pinpoint的核心是“减少资源占用、提高处理能力”,通过调整采样率、扩容节点、优化配置等方法,就能让Pinpoint在高并发场景下正常工作,帮开发团队解决问题。要是大家在实际使用中遇到了其他问题,也可以根据自己的业务场景,调整优化方案,找到最适合自己的配置。