Shell脚本为JSON文件添加Elasticsearch索引耗时过长求优化
批量JSON转Elasticsearch Bulk格式的性能优化方案
问题背景
我们有大量JSON文件(单文件含20万条记录,总计5000个/80GB),需要转成Elasticsearch Bulk格式后导入。当前脚本逐行处理单文件耗时约30分钟,而导入仅需20秒,核心瓶颈在JSON转Bulk格式的处理环节,需优化该逻辑。
当前处理逻辑:逐行读取JSON,为每行生成一条索引元数据行,格式如下:
{"index":{"_id":$guid,"_index" : "demoindex", "_type" : "usage"}} $row
核心瓶颈分析
- 进程开销过大:逐行循环中每次调用
awk提取GUID,20万条记录会启动20万次awk进程,性能损耗极大。 - 磁盘IO频繁:每次处理一行都执行两次
echo >>追加写入临时文件,磁盘小IO次数过多。
优化方案
方案一:用AWK一次性处理全文件(核心优化)
用AWK代替Shell逐行循环,全程仅启动一次AWK进程,批量生成Bulk格式并写入文件,效率提升数十倍。
替换原脚本中的while read循环部分:
# 替换原while read循环 echo "Parsing $feed" awk -F '"' '{ printf "{\"index\": {\"_index\": \"demoindex\", \"_id\": \"%s\", \"_type\": \"usage\"}}\n%s\n", $4, $0 }' "$feed" > "$FEED_TMP_DIR/$feed"
方案二:跳过临时文件,流式导入(进一步节省IO)
如果不需要保留转换后的Bulk文件,可直接通过管道将AWK输出传给curl,跳过磁盘写入步骤:
echo "Parsing and importing $feed" awk -F '"' '{ printf "{\"index\": {\"_index\": \"demoindex\", \"_id\": \"%s\", \"_type\": \"usage\"}}\n%s\n", $4, $0 }' "$feed" | curl -s -H "Content-Type: application/x-ndjson" -XPOST 'localhost:9200/demoindex/usage/_bulk?' --data-binary @- >> $FEEDER_LOG
方案三:并行处理多文件(进阶优化)
若服务器CPU、内存及Elasticsearch集群负载允许,可并行处理多个文件,进一步缩短总耗时。示例用xargs实现4个文件并行处理(可根据服务器性能调整-P后的数值):
cd $FEED_DIR # 替换原for循环 ls -1 *.json | xargs -P 4 -I {} bash -c ' feed="{}" echo "Processing $feed" awk -F '"'"'"'"'"'"'"'"'{ printf "{\"index\": {\"_index\": \"demoindex\", \"_id\": \"%s\", \"_type\": \"usage\"}}\n%s\n", $4, $0 }'"'"'"'"'"'"'"'"' "$feed" | curl -s -H "Content-Type: application/x-ndjson" -XPOST "localhost:9200/demoindex/usage/_bulk?" --data-binary @- >> '"$FEEDER_LOG"' echo "Completed $feed" '
其他辅助优化
- 调整Elasticsearch索引参数:导入前关闭刷新间隔,减少索引开销,导入完成后恢复:
# 导入前设置 curl -XPUT -H 'Content-Type: application/json' 'localhost:9200/demoindex/_settings' -d '{ "number_of_replicas": 0, "refresh_interval": "-1" }' # 所有文件导入完成后恢复 curl -XPUT -H 'Content-Type: application/json' 'localhost:9200/demoindex/_settings' -d '{ "number_of_replicas": 1, # 改为你原有的副本数 "refresh_interval": "30s" # 改为你原有的刷新间隔 }'
- 安全遍历文件:替换
for feed in $(ls -1 *.json)为for feed in *.json,避免文件名含特殊字符时出错。
优化后完整脚本示例
#!/bin/bash FEED_DIR="/apps/elasticsearch/demo" FEED_TMP_DIR="/apps/elasticsearch/demo/temp" FEED_ARCHIVE="/apps/elasticsearch/demo/archive" FEEDER_LOG="/apps/log/feeder/demofeeder.log" SCRPT_HOME="/apps/bin/elasticsearch" LOCKFILE=$SCRPT_HOME/system/demofeeder.lock # 检查是否已有进程在运行 if test -f "$LOCKFILE"; then echo "demofeeder.sh is still running." echo "$LOCKFILE exists." echo "Exiting !!" exit 1; fi echo "Creating lock file: $LOCKFILE" > $FEEDER_LOG touch $LOCKFILE # 调整Elasticsearch参数以加速导入 echo -e "\n Setting index settings before indexing!\n" >> $FEEDER_LOG curl -XPUT -H 'Content-Type: application/json' 'localhost:9200/demoindex/_settings' -d '{ "number_of_replicas": 0, "refresh_interval": "-1" }' echo "Starting demoindex Feeding Data at "$(date -u)"." >> $FEEDER_LOG echo "Starting demoindex Feeding Data at "$(date -u)"." echo "Clearing Temp directory" >> $FEEDER_LOG rm -f $FEED_TMP_DIR/*.json cd $FEED_DIR for feed in *.json do echo "Parsing $feed" # 用AWK一次性处理全文件 awk -F '"' '{ printf "{\"index\": {\"_index\": \"demoindex\", \"_id\": \"%s\", \"_type\": \"usage\"}}\n%s\n", $4, $0 }' "$feed" > "$FEED_TMP_DIR/$feed" echo "Loading parsed files into elasticsearch: $FEED_TMP_DIR/$feed" curl -s -H "Content-Type: application/x-ndjson" -XPOST 'localhost:9200/demoindex/usage/_bulk?' --data-binary "@$FEED_TMP_DIR/$feed" >> $FEEDER_LOG echo "Deleting parsed json: "$FEED_TMP_DIR/$feed"" rm "$FEED_TMP_DIR/$feed" echo "Moving $feed to Archive Folder" mv $FEED_DIR/$feed $FEED_ARCHIVE/$feed done # 恢复Elasticsearch原参数 echo -e "\n Restoring index settings!\n" >> $FEEDER_LOG curl -XPUT -H 'Content-Type: application/json' 'localhost:9200/demoindex/_settings' -d '{ "number_of_replicas": 1, "refresh_interval": "30s" }' echo "Data Feeding demoindex Completed at "$(date -u)"." >> $FEEDER_LOG echo "Removing lock file: $LOCKFILE" >> $FEEDER_LOG rm -f $LOCKFILE echo "Data Feeding demoindex Completed at "$(date -u)"."
内容的提问来源于stack exchange,提问作者user1398291
相关产品推荐
相关产品推荐

