Shell如何高效打印大文件中首列出现次数超K次的所有行
大体积日志按首列出现次数筛选行的高性能方案
基础信息
源文件内容
$ cat log.txt key1 1654684897 1 3 d d key1 1654684897 1 3 d 2038 key1 1654684997 1 3 c c key1 1654684997 1 3 c 2038 key1 1654684997 1 3 c 2071 key2 1654684897 1 3 d d key2 1654684897 1 3 d 2039 key3 1654684997 1 3 c c key3 1654684997 1 3 c 2038 key3 1654684997 1 3 c 2071
原有实现
$ cat log.txt|awk '{print $1}' | sort | uniq -c |awk '$1>2{print $2}' |xargs -I{} grep -E {} log.txt
该方案在小文件下能得到正确结果,阈值K=2时会输出首列key出现次数大于2的所有行:key1出现5次、key3出现3次保留,key2出现2次被过滤。但处理GB级以上大文件时耗时极长,核心瓶颈有三个:
- 重复读取文件+正则匹配开销:第一次遍历文件统计key,第二次通过grep逐行正则匹配符合条件的key,双倍IO开销之外,正则匹配本身的计算成本也很高;如果符合条件的key多,xargs还会反复启动grep进程,额外消耗资源
- 全量排序开销:管道中
sort命令是最大的性能杀手,大文件排序会占用大量内存,内存不足时会读写临时磁盘文件,数据量越大排序的耗时占比越高,极端场景下能占总耗时的90%以上 - 多进程切换开销:整条命令串联了cat、awk、sort、uniq、xargs、grep多个独立进程,频繁的进程上下文切换会积少成多带来不必要的性能损耗
期望输出
key1 1654684897 1 3 d d key1 1654684897 1 3 d 2038 key1 1654684997 1 3 c c key1 1654684997 1 3 c 2038 key1 1654684997 1 3 c 2071 key3 1654684997 1 3 c c key3 1654684997 1 3 c 2038 key3 1654684997 1 3 c 2071
最优实现方案
99%的日志场景下,直接用单awk命令即可实现性能的数量级提升,命令如下:
awk 'NR==FNR{cnt[$1]++; next} cnt[$1]>2' log.txt log.txt
逻辑说明
- awk会顺序读取两次log.txt文件:第一次读取时仅做计数,用数组
cnt存储每个首列key的出现次数,不输出任何内容;第二次读取时逐行判断当前行首列key的计数值,大于阈值2就直接打印当前行 - 性能优势:
- 全程仅启动awk一个进程,没有多进程切换开销,也不需要反复调用grep做正则匹配
- 完全去掉了全量排序逻辑,省去了排序带来的大量内存、临时磁盘IO开销
- 对文件的两次读取都是顺序读,是磁盘IO性能最高的模式,实际执行速度基本接近磁盘的原生读取速度
- 阈值调整非常简单,如果需要筛选出现次数大于N的key,只需要把命令末尾的
2改成对应数值即可,比如阈值改为10就写成cnt[$1]>10。
如果遇到极端场景:首列去重后的key数量大到内存无法存储(比如几十亿个独立key),可以再采用分块统计+二次筛选的方案,但常规线上日志处理场景下,上面的awk方案已经是性能最优解,比原有管道方案快数倍到数十倍,文件体积越大性能差距越明显。
内容的提问来源于stack exchange,提问作者achilles666
相关产品推荐
相关产品推荐

