作者
兴丰
阿里云Elasticsearch团队
高级开发工程师
ELK是Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称,也称为Elastic Stack。Elasticsearch是一个搜索和分析引擎。Logstash是服务器端数据处理管道,能够同时从多个来源采集数据、转换数据,然后将数据发送到Elasticsearch。Kibana提供了图形和图表对数据进行可视化。用户通常在ELK前引入消息队列比如Kafka进行数据缓冲,提升系统的可靠性。
通常的系统架构和数据处理流程如下:
阿里云Elasticsearch已经支持完整的ELK组件一键部署,配合Kafka等消息队列,使用顺畅。
Houston, We Have A Problem
此前,某客户反馈,ES集群写入报错“es_rejected_execution_exception”,写入速度上不去,kafka堆积严重。登录监控查看后,发现ES节点的write队列堆积严重,cpu使用率很低,节点的load非常高。
首先登录ECS执行top查看cpu情况
各cpu wait都非常高,超过20%。
查看当前磁盘的状态,从iostat的信息来看,磁盘的写入量不高,延迟虽然稍微有点大,但基本符合预期。联系块存储的值班同学,反馈云盘的后端各项指标正常。
进一步查看load高的进程和线程是哪些。
这次有了新的发现,这些线程基本都属于同一个进程,就是Elasticsearch所在的进程。这些线程都处于D状态,ps命令输出的linux的进程状态有下面几种:
D uninterruptible sleep (usually IO)
R running or runnable (on run queue)
S interruptible sleep (waiting for an event to complete)
T stopped by job control signal
t stopped by debugger during the tracing
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z defunct ("zombie") process, terminated but not reaped by its parent
linux的top命令在计算负载时包含了处于D和R状态的进程和线程,那么,load高的原因就很明确了——ES有很多线程阻塞在IO中。
真相大白
用jstack获取Es进程,随便搜了几个线程id, 发现全是write线程,而且都阻塞在translog的sync函数上。
根据ES官方文档的介绍 ,对于每一个bulk请求,每个shard都会调用系统的fsync,保证translog持久化到磁盘上,避免硬件故障导致数据丢失。
此问题看上去应该是每个shard每次requst分到的条数太小,频繁触发fsync导致的。用户提到logstash消费kafka然后采用bulk推送,每次3000条,每个doc 1KB大小。但是这个描述和kibana中的监控不符合,不少索引每秒只有几十篇doc写入,如果每个索引都是bulk 3000条,则曲线不会是下面这种平滑的曲线,应该是非常明显的锯齿状,波峰是3000,波谷是0。
在和用户仔细沟通后发现,所有索引的源数据都混在Kafka的同一个topic里面,logstash每次bulk数据包含几十个索引,总共上百个shard,每个shard在每次请求只能分到几十条doc,导致write线程平均每处理几十条doc就要fsync translog一次,延迟再低的SSD也支持不住。
解决方案
通常,对于一般的性能问题没有加节点解决不了的,如果有,那就升级节点规格,但费用成本较高。
临时方案
- 调大bulk条数,控制每个请求在10MB到30MB之间,增加每次shard分到的doc条数,间接降低fsync的频率
- 适当增加写入队列大小,避免集群偶尔抖动导致队列快速塞满而报错
- 修改索引setting,将translog索引的刷新(flush)改为异步
修改参数如下,除了translog必须为aysnc,其他的可按需调整。
"settings": { "index": { "refresh_interval": "60s", "translog": { "flush_threshold_size": "2gb", "sync_interval": "100s", "durability": "async" }, "merge": { "policy": { "segments_per_tier": "30", "max_merged_segment": "512m" } } }}
终极方案 & 最佳实践
需要把不同索引的数据拆分到不同的Kafka topic中,和上面的临时方案结合效果更佳。
- 避免每个shard在每次bulk请求分到的doc太少,产生频繁的fsync
- 当上游数据产生太快,ES集群性能不足或者故障恢复、索引重建时,可以按优先级消费topic,推送指定索引到ES,提高整个系统的稳定性
实行效果
该客户采用了临时方案,吞吐很快就上去了,节点load也降下来了,kafka的堆积数据很快消费完,不再有堆积。
以前写入50k/s会产生瓶颈,现在峰值可达到100k/s,从cpu和磁盘io的监控来看,整体还有相当大的提升空间。
虽然translog改为异步后,写入性能得到了极大的提升,但write线程从节点队列获取每个shard的请求数据时需要加锁,每次获取数据条数太少,频繁的加锁会有不少cpu消耗在自旋锁上。如果索引按topic拆分,写入性能还将有进一步的提升。