一次解决 Skywalking 写入延迟问题的过程

2,442 阅读8分钟

背景

接入 Skywalking 后的几个月里,写入能力一直比较拉稀,大概在 kafka 流量 40MB/s、ES 写入量 3w/s 左右时无论如何也写不进去了。

由于之前解决 ES 的性能问题时咨询了阿里云,当时阿里云给出来的建议是使用基于 ES6.7 版本的日志增强版,并开启写入高可用功能。听信了他们的谗言后都依着照做了,结果是性能压根没有任何提高。每天晚高峰 Kafka 的堆积量达到了 4 亿条左右,有时候整了一夜到了早高峰还没有消费完。

问题探究

目前主要有两个问题:

  1. kafka 堆积
  2. ES 写入能力差

kafka 堆积

从 Kafka 的监控来看,一旦写入的流量加大, 读取的流量会进一步降低:

截屏2021-04-25 下午1.09.10.png

这种现象可以引出两个猜测方向:

  1. ES 在写入压力达到一定阈值时,写入能力会逐渐下降,所以消费速度降低
  2. Kafka 服务器在写入流量增加时不堪重负,消费速度降低

究竟是哪个原因不好确定,但最有可能的是 ES 的问题,从之前的经验来看,ES 拉胯的可能性最大。

ES 写入性能

ES 每到写入 3w/s 左右时,性能会逐渐下降。从日志观察,同样数量的批次,写入延迟由平时的每批 300ms,增加到 800ms。

而此时看 ES 的服务监控,CPU 和 Load 都很低,远远没有达到瓶颈,IO 也是。

所以问题来了,ES 没有达到瓶颈,写入延迟是谁增加的?于是把目光放在了 客户端 → ES 服务器的链路上来,链路上有两个东西,一个是 ES 的接入地址,一个是「写入高可用」组件。针对前者,ES 的服务器有多台,而接入地址只有一个,但接入地址通常是一个非常简单的负载均衡,瓶颈的可能性比较小。针对后者,我们发现写入高可用实际上是做了一层解析,将写入完全异步化,同时会根据数据的 shard 来直接请求到对应的节点,减少路由转发,由此提高性能。阿里云 ES 的写入高可用架构图如下:

截屏2021-04-25 下午1.17.51.png

可以看到写入高可用是一个「异步数据处理组件」,也就是说实际上与客户端交互的是这个组件。写入发生延迟很可能是它产生的,导致实际的写入压力压根没有传递到 ES 节点。

基于这点,本想把它关掉做实验,但是又想到 ES7.0 之后做了很多关于写入的优化,并且所谓的「增强版ES」实际的使用体验非常令人不适,性能拉胯不说,还 压根没有办法扩容容量。

于是最后我选择了更换到 ES 7.10 实例。

更换 ES 实例

更换到 ES 7.10 实例后,可以看到写入能力有了突飞猛进的提高,写入延迟大大降低,Kafka 的读取流量随着提高到 60M/s。

但好日子还没到,Kafka 的读取流量到 60M/s 就不再提高了,而写入流量在逐步放开后会达到 100M/s,但消费流量只有 60M/s。在反复确认 ES 节点没有压力,响应也很快后,最终把目光停在了 kafka 上。

定位 Kafka 问题

Skywalking OAP 拉取 Kafka 的消息后,会同步写入到 ES 的异步组件。如果消费慢了只有两个可能:

  1. 拉取的动作慢了。
  2. 消费消息的动作慢了。这个动作中包括对消息的处理、异步写入 ES 的组件,而 ES 异步组件有可能产生阻塞(否则无法对流量进行控制了)

为了排除拉取消息慢的可能,线上使用 arthas 统计了每次拉取消息的方法(monitor -c 5 *KafkaConsumer poll)的时间,结果如下: 截屏2021-04-21 下午6.52.21.png

比较幸运,这里确实是问题所在。 从图中可以看到拉取消息的平均延迟是 600~900ms,而有时候又非常快(35.62ms),所以瓶颈是在拉取消息这里。而这又引发两种可能:

  1. Kakfa 客户端拉取消息的策略有问题,是否可以通过修改 Kafka 客户端的拉取配置提高。
  2. Kafka 服务端的性能问题。

基于「你永远可以相信 kafka」的原则,我首先尝试修改 Kafka 的拉取策略配置:

  1. max.poll.records:每批最大消息条数,调整到 5000。
  2. max.poll.interval.ms:每次拉取消息的最大间隔,避免间隔时间过长导致 Kafka 认为该服务器不可用
  3. fetch.max.bytes: 每批消息允许的最大 bytes 数,调整到 256M。
  4. max.partition.fetch.bytes: 每个 partition 每次允许的最大 bytes 数,调整到 50M。因为从 kafka 的消费堆积情况来看,只有某几个 partition 的堆积最为严重。
  5. OAP 服务的配置减半,节点数翻倍。希望可以降低每个节点的 partition 数分担一下消费压力,提高消费流量。

调整的基本思路是提高单次吞吐量,降低来回次数。

效果十分拔群,一通调整后消费速度从 55M/s 一路降到 38M/s 左右。看起来催得越急,给的越慢。最后实在没办法联系了阿里云,经过与阿里云的艰难沟通,坚决拒绝升配之后,终于定位到了问题:

截屏2021-04-25 下午1.48.00.png

他反馈的问题如下:

问题一:有 rebalance 现象

这个是胡扯,我反复查了客户端日志没有发生任何 rebalance。可能是共享实例他看到别人的实例上去了。

问题二:部分线程开始读冷数据,冷数据是磁盘不是 SSD

与部分 partition 堆积最严重的现象匹配。但这个现象是互为因果,堆积 → 点位滞后 → 读冷数据 → 消费慢 → 堆积,无法定位根因。

问题三:用户发送消息过于碎片化,导致服务端性能有所下降

这个比较像话了,因为 Skywalking Agent 在发送消息时,确实没配置过发送消息的策略。阿里云也确实定位到了磁盘的 IO 已经到上限了:

截屏2021-04-25 下午1.51.46.png

截屏2021-04-25 下午1.52.49.png

于是我修改了 Kafka 发送消息的策略:

  1. linger.ms: 每批消息的最长等待时间,调整到 1000ms,默认是消息来了就发送。
  2. batch.size: 每批消息的最大体积,调整到 512k,默认是消息来了就发送。

(顺便一提,我们二开过的 Skywalking Agent,所有配置项都是即时生效的,无需等待应用重启)

第二天观察早高峰,发现消费流量终于与写入流量完美一致,堆积也消失了:

截屏2021-04-25 下午1.56.24.png

截屏2021-04-26 下午2.05.14.png

于是我们也得知了限制消费能力的根因,一个是写入碎片化导致 Kafka 服务器疲于应对,一个是磁盘 IO 的限制可能会使读取速度进一步变慢。

进一步优化 ES 存储

彻底解决了堆积的问题后,把目光又转到了对 ES 的优化,更换到 ES7 后性能有了长足的提高,对链路数据的聚合和分析能力也上了一个台阶,因此我配置了一个看板:

截屏2021-04-25 下午15954.png

该看板可以从服务视角、链路类型视角、链路名称视角查看整体的链路数据,根据看板上又优化了几点:

  1. 过滤了 RocketMQ 的追踪链路(阿里云商业版 RocketMQ 独有特性)。
  2. 过滤了 Druid 的 getConnection 链路。
  3. 过滤了 MySQL 的链路、Redisson 的链路。
  4. 限制了异步线程的上传数量,包括 parallelStream() 时创建的大量链路。

同时又调整链路的索引模板,将压缩算法从默认改为「best_compress」,又节省 35% 左右的存储空间,此时 CPU 还有大量余量,压缩算法构不成压力。

通过观察,每天的链路数据量从每日 3.2T 左右降到了 1.2T(-65%),随后我将索引过期时间从 24h 延长到了 7 天。后续流量增加后观察,如果空间不够了而处理能力足够,则可以不改 ES 节点配置只提高磁盘空间,以节约成本。

总结

通过这次解决了几个月以来 skywalking 链路数据延迟数小时至一晚上的问题,并基本彻底解决了 ES 的存储空间性能问题。

最终 Kafka 的消费能力提高了最低 300%,ES 存储成本降低了 25%,存储能力提高了 700%,链路的可见性延迟从几小时缩短到了几分钟。

这次优化告诉我们

  1. 不要在反复持续优化后还是一团糟的东西上继续尝试。应该主动放弃并选择其他方向,例如放弃诱人但无用的增强版,使用 ES 7.10。
  2. 使用科学的方法进行优化,抓大放小。如果一开始我就优化缩减 ES 写入数据量,最后依然不会解决任何问题。
  3. 使用 Arthas 之类的工具快速定位排查方向,可以节省大量的时间,并得到真正的知识。