我想解析 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
每个grep
IP 地址进行一次调用,那么速度会很慢。
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 地址来说,
- 将地址复制到“保留空间”(中的额外缓冲区
sed
)。 .
将“模式空间”(输入行)更改为\.
(为了正确匹配点,您的代码没有执行此操作)。- 前置
^
和附加[[:blank:]]/w /tmp/access-
到模式空间。 - 将未修改的输入行从保留空间附加到模式空间,中间有换行符。
- 删除该换行符。
- 附加
.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