【问题标题】:Why is printing data to a file considerably slower with awk than with the shell?为什么使用 awk 将数据打印到文件比使用 shell 慢得多?
【发布时间】:2013-11-26 20:37:22
【问题描述】:

我已经使用了大约 3 年的 shell 脚本,它启动 tail -F -n0 /path/to/LOGFILE,解析和格式化输出并将其转储到管道分隔的文件中。但是,我们已经从每天几千条日志行增加到每天几百万条日志行,并且脚本已经开始占用大量内存和 CPU。

我最近用awk 替换了所有数据解析逻辑,这在测试中似乎在解析数据时要快很多数量级。为了测试新的 awk 代码,我尝试使用 awk 代码和 shell 代码通过逻辑(约 600 万行)抽取一整天的日志,不出所料,awk 代码在约 10 秒内提取了约 92K 相关行,而shell 代码花了超过 15 分钟来做同样的事情。但是,如果我使用完全相同的代码,而不是 cat /path/to/file|awk '...' 而不是 tail -F -n0 /path/to/file|awk '...,那么将文本写入文件的时间会有很大的延迟,最多约 2-3 分钟,而不是约 0.5 分钟-1.0 秒,当它通过 shell 代码时。

Shell 代码(是的,我知道 Shell 代码有多丑):

outDir="/opt/redacted/logs/allerrors/"
tail -F -n0 /opt/redacted/logs/manager.log|while read -a logLine;do
if [[ "${logLine[2]}" == "E" ]];then
  if [[ "${logLine[7]:0:1}" == "@" ]];then
    echo "${logLine[0]}|${logLine[1]}|${logLine[6]}|${logLine[7]}|${logLine[@]:8:${#logLine[@]}}" >> ${outDir}allerrors.${logLine[0]//\//.}.log
  else
    echo "${logLine[0]}|${logLine[1]}|${logLine[6]}|NULL|${logLine[@]:7:${#logLine[@]}}" >> ${outDir}allerrors.${logLine[0]//\//.}.log
  fi
fi

完成

awk 代码:

  outDir="/opt/redacted/logs/allerrors/"
  tail -F -n0 /opt/redacted/logs/manager.log|awk -v dir=$outDir '{OFS="|"}
{
  if ($3 == "E")
  {
    file="allerrors."$1".log"
    gsub("/",".",file)
    if ($8 ~ /@/)
      print $1,$2,$7,$8,substr($0, index($0,$9)) >> dir file
    else {if ($8 !~ /@/)
      print $1,$2,$7,"NULL",substr($0, index($0,$8)) >> dir file
    }
  }
}'

需要明确的是,如果我使用 cat 而不是拖尾文件,则两组代码都可以工作并创建相同的输出,但是使用 awk 代码,直到它出现大约 2-3 分钟后,我才能在输出文件中看到结果在日志中,而 shell 版本只需要几秒钟。

【问题讨论】:

  • 这可能是因为awk 正在缓冲输入而read 不是。你为什么使用tail -F 而不是仅仅提供文件作为awk 的输入(即既不是cat 也不是tail,而是简单地读取):awk ... /opt/redacted/logs/manager.log
  • 我认为这可能与行缓冲有关,但我在概念上理解得不够好,无法解决它。 .我在将尾部管道输送到 grep 时遇到了问题,但是 grep 有一个 --line-buffer 标志(或类似的东西)可以修复它。 awk 有类似的东西吗?至于为什么尾巴,它是一个日志文件,当它达到一定大小时会翻转。但我希望在实时(ish)时间完成解析。我不知道如何让请求实时读取文件并在文件滚动时跟进,这可能吗?

标签: bash shell optimization awk logparser


【解决方案1】:

awk 默认缓冲,而sh 没有。这增加了 awk 的吞吐量,但也增加了延迟。

只需将fflush(); 添加到您的 awk 代码中即可强制刷新缓冲区:

  outDir="/opt/redacted/logs/allerrors/"
  tail -F -n0 /opt/redacted/logs/manager.log|awk -v dir=$outDir '{OFS="|"}
{
  if ($3 == "E")
  {
    file="allerrors."$1".log"
    gsub("/",".",file)
    if ($8 ~ /@/)
      print $1,$2,$7,$8,substr($0, index($0,$9)) >> dir file
    else {if ($8 !~ /@/)
      print $1,$2,$7,"NULL",substr($0, index($0,$8)) >> dir file
    }
    fflush();
  }
}'

【讨论】:

  • 是的,做到了,但我必须使用flush(stdout) 才能使其工作。
【解决方案2】:
tail -f -n0 filename 

阻塞直到文件有新的 I/O,所以正如已经建议的那样,这是一个输入问题,与 awk 进程中的 stdout 缓冲有关。但是,如果日志文件在 2 分钟内零更改,则 awk I/O 缓冲不能完全考虑时间延迟。

试试这个来展示行缓冲的效果:

while true                    
do                        
echo -n 'this is a string'
sleep 5                   
echo ' add a newline'     
done | awk '{print $0}'   

【讨论】:

    猜你喜欢
    • 2014-03-23
    • 2021-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    • 2011-04-29
    • 1970-01-01
    相关资源
    最近更新 更多