并行脚本进程

并行脚本进程

我想解析 apache 访问日志的 IP。我使用了以下代码,但花了近 90 秒。

grep "^$CLIENT_IP" /var/log/http/access.log > /tmp/access-$CLIENT_IP.log

然后我尝试了如下替代方案。

sed -i -e "/^$CLIENT_IP/w /tmp/access-$CLIENT_IP.log" -e '//d' /var/log/http/access.log

即使这也花了 60 多秒。

有 1200 个 IP 需要解析。我想知道有什么方法可以实现并行性来减少运行时间。

答案1

我假设您在所有 IP 地址的 shell 循环中执行此操作,可能使用来自文本文件的 IP 地址。是的,如果对sed每个grepIP 地址进行一次调用,那么速度会很慢。

sed相反,如果您仔细准备,您可能会侥幸使用一次。

首先,我们必须创建一个脚本,我们从包含 IP 地址的sed文件中执行此操作,每行一个地址:ip.list

sed -e 'h' \
    -e 's/\./\\./g' \
    -e 's#.*#/^&[[:blank:]]/w /tmp/access-#' \
    -e 'G' \
    -e 's/\n//' \
    -e 's/$/.log/' ip.list >ip.sed

这个sed东西对于每个 IP 地址来说,

  1. 将地址复制到“保留空间”(中的额外缓冲区sed)。
  2. .将“模式空间”(输入行)更改为\.(为了正确匹配点,您的代码没有执行此操作)。
  3. 前置^和附加[[:blank:]]/w /tmp/access-到模式空间。
  4. 将未修改的输入行从保留空间附加到模式空间,中间有换行符。
  5. 删除该换行符。
  6. 附加.log到行尾(并隐式输出结果)。

对于包含以下内容的文件

127.0.0.1
10.0.0.1
10.0.0.100

这将创建sed脚本

/^127\.0\.0\.1[[:blank:]]/w /tmp/access-127.0.0.1.log
/^10\.0\.0\.1[[:blank:]]/w /tmp/access-10.0.0.1.log
/^10\.0\.0\.100[[:blank:]]/w /tmp/access-10.0.0.100.log

请注意,您必须在 IP 地址后匹配一个空白字符(空格或制表符),否则 的日志条目10.0.0.100将进入该/tmp/access-10.0.0.1.log文件。你的代码忽略了这一点。

然后可以在您的日志文件上使用它(无循环):

sed -n -f ip.sed /var/log/http/access.log

我从未测试过从同一个sed脚本写入 1200 个文件。如果它不起作用,请尝试以下awk变体。


类似的解决方案awk包括首先将 IP 地址读入数组,然后将它们与每一行进行匹配。这需要一次awk调用:

awk 'FNR == NR  { list[$1] = 1; next }
     $1 in list { name = $1 ".log"; print >>name; close name }' ip.list /var/log/http/access.log

在这里,我们awk同时给出IP列表和日志文件。当NR == FNR我们知道我们仍在读取第一个文件(列表)时,我们将 IP 号list作为键添加到关联数组中,并继续下一行输入。

如果FNR == NR条件不成立,我们将从第二个文件(日志文件)中读取内容,并测试输入行的第一个字段是否是关键字段list(这是纯字符串比较,而不是正则表达式匹配) 。如果是,我们将该行附加到适当命名的文件中。

我们必须小心关闭输出文件,否则可能会用完打开的文件描述符。因此,将会有大量的打开和关闭文件用于附加,但它仍然比awk每个 IP 地址调用(或任何实用程序)一次要快。


我有兴趣知道这些东西是否适合您以及大概的运行时间可能是多少。我仅在极小的数据集上测试了这些解决方案。


当然,我们可以同意您的想法,通过grep在系统上并行抛出多个eg实例来暴力破解它:

忽略我们没有正确匹配 IP 地址中的点这一事实,我们可能会这样做

xargs -P 4 -n 100 sh -c '
    for n do
        grep "^$n[[:blank:]]" /var/log/http/access.log >"/tmp/access-$n.log"
    done' sh <ip.list

在这里,xargs将一次从ip.list文件中给出最多 100 个 IP 地址到一个简短的 shell 脚本。它将安排四个并行的脚本调用。

简短的 shell 脚本:

for n do
    grep "^$n[[:blank:]]" /var/log/http/access.log >"/tmp/access-$n.log"
done

这只会迭代在xargs命令行上提供的 100 个 IP 地址,并应用grep与您使用的命令几乎相同的命令,不同之处在于将有四个循环并行运行。

增加-P 4-P 16与您拥有的 CPU 数量相关。加速可能不是线性的,因为每个并行实例grep都会从同一磁盘读取和写入。

除了-P标记 to之外xargs,这个答案中的所有内容都应该能够在任何 POSIX 系统上运行。-P的标志是xargs非标准的,但在 GNUxargs和 BSD 系统中实现。

答案2

对于各种方法: https://stackoverflow.com/questions/9066609/fastest-possible-grep

除此之外,如果您经常这样做,那么 SSD 可能是您的最佳选择。接触高清是此类事情的杀手锏。

您有大量不同的 grep 需要运行。制作一个脚本,将脚本命令(例如,每个核心一个)启动到后台,然后跟踪它们何时完成,因为它们完成了启动更多命令。

当我这样做时,我可以让所有 12 个核心以 100% CPU 使用率运行,但您可能会发现您的资源限制是其他的。鉴于您的所有作业都需要相同的文件,如果您不在 SSD 上,您可能需要复制该文件,这样它们就不会共享。

答案3

如果/var/log/http/access.log大于 RAM 并且因此无法缓存,那么并行运行更多进程可能是access.log多次读取的一个很好的替代方案 - 特别是如果您有多个内核。这将为grep每个 IP 并行运行一个(+ 几个帮助包装进程)。

pargrep() {
    # Send standard input to grep with different match strings in parallel
    # This command would be enough if you only have 250 match strings
    parallel --pipe --tee grep ^{} '>' /tmp/access-{}.log ::: "$@"
}
export -f pargrep
# Standard input is tee'ed to several pargreps.
# Each pargrep gets 250 match strings and thus starts 250 processes.
# For 1200 ips this starts 3600 processes taking around 1 GB RAM,
# but it reads access.log only once
cat /var/log/http/access.log |
  parallel --pipe --tee -N250 pargrep {} :::: ips

相关内容