我使用logrotate
此copytruncate
选项。效果很好,可以创建一个稀疏文件,该文件以越来越多的“虚拟”空块开始,这些块不占用磁盘空间。
问题在于复制的文件:虽然它们占用的磁盘空间很小,但尝试使用它们检查它们less
却需要很长时间,因为“虚拟”空块会扩展为实际空块。我真的很想从复制文件的开头消除初始稀疏空块。
这是我目前所知道的: ls -ls
并且du
可以告诉我文件中有多少是“真实的”。我认为dd
可以用来制作没有前导空块的副本。但我无法将它们组合成可以放入文件postrotate
部分的内容logrotate.conf
。
我发现了一些使用tr
或sed
删除空值的方法,但这需要扩展文件(使虚拟空值变为物理空值),而且随着时间的推移,文件可能会增长到超过 1TB!我需要一种更“精准”的方法,无需扩展文件即可工作。它应该只需要处理 inode,因为稀疏块就在那里(而不是实际分配的区域)。
当然,“真正的”解决办法是让生成程序SIGHUP
重新打开其输出文件,但在这种情况下这是不可能的。
直接从稀疏文件中删除前导空块的最简单和最快的方法是什么?
附录:下面介绍如何制作您自己的稀疏文件:
$ dd if=/dev/zero of=sparse.txt bs=1 count=0 seek=8G
0+0 records in
0+0 records out
0 bytes (0 B) copied, 0.000226785 s, 0.0 kB/s
$ echo 'Hello, World!' >>sparse.txt
$ ls -ls sparse.txt
4 -rwxrwxrwx 1 me me 8589934606 Nov 6 10:20 sparse.txt
$ ls -lsh sparse.txt
4.0K -rwxrwxrwx 1 me me 8.1G Nov 6 10:20 sparse.txt
这个“巨大”的文件几乎不占用磁盘空间。现在试试less sparse.txt
。您必须浏览 8G 的空值才能找到末尾的字符。甚至tail -n 1 sparse.txt
需要很长时间。
答案1
首先我要感谢你们二位。
我确实使用以下 logrotate 配置测试了 @Michael 的解决方案,它按预期工作。但我们仍然有主日志文件和稀疏文件。
access_logs.conf: |
/var/log/access_v2.log {
daily
missingok
rotate 7
compress
delaycompress
dateformat -%Y%m%d_%H%M%S
create
su root root
firstaction
rm -rf /var/log/access_v2.log
sh /etc/logrotate-truncat/truncat.sh /var/log/access.log >> /var/log/access_v2.log
> /var/log/access.log
endscript
}
这是对此的另一种看法。如果我们删除有问题的日志文件(此处/var/log/access.log
)后可以重新创建它,那么我们可以在firstaction
部分中明确实现 copytrucate 所做的事情(复制文件然后截断它),但采用 copydelete 方式(复制文件然后删除它)。该文件/var/log/access.log
将自动创建,并且它不是稀疏文件。因此,无需清理稀疏文件。
样本
access_logs.conf: |
/var/log/access_v2.log {
daily
missingok
rotate 7
compress
delaycompress
dateformat -%Y%m%d_%H%M%S
create
su root root
firstaction
rm -rf /var/log/access_v2.log
cp /var/log/access.log /var/log/access_v2.log
rm -rf /var/log/access.log
endscript
}
答案2
这是我第一次似乎有效的尝试,使用stat
和dd
,效果很好仅有的对于前导稀疏文件:
#! /bin/bash
for f in $@; do
echo -n "$f : "
fields=( `stat -c "%o %B %b %s" $f` )
xfer_block_size=${fields[0]}
alloc_block_size=${fields[1]}
blocks_alloc=${fields[2]}
size_bytes=${fields[3]}
bytes_alloc=$(( $blocks_alloc * $alloc_block_size ))
alloc_in_xfer_blocks=$(( ($bytes_alloc + ($xfer_block_size - 1))/$xfer_block_size ))
size_in_xfer_blocks=$(( ($size_bytes + ($xfer_block_size - 1))/$xfer_block_size ))
null_xfer_blocks=$(( $size_in_xfer_blocks - $alloc_in_xfer_blocks ))
null_xfer_bytes=$(( $null_xfer_blocks * $xfer_block_size ))
non_null_bytes=$(( $size_bytes - $null_xfer_bytes ))
if [ "$non_null_bytes" -gt "0" -a "$non_null_bytes" -lt "$size_bytes" ]; then
cmd="dd if=$f of=$f.new bs=1 skip=$null_xfer_bytes count=$non_null_bytes"
echo $cmd
exec $cmd
else
echo "Nothing to do: File is not sparse."
fi
done
你怎么认为?
答案3
我在这里创建了一个帐户,以便感谢@BobC 的回答(和他的问题)。这是我解决长期存在的 Solr 日志问题所需要的催化剂。
我修改了Bobc的脚本,以将其优化为Logrotate用例(使用$xfer_block_size
foribs
和任意大(8m)obs
,然后是atr -d "\000"
以消除其余的空),然后在firstaction
我的logrotate
配置部分中使用它。
我猜我的解决方案有点不合常理,但它比在 80+ GB 的日志文件威胁到填满磁盘时必须弹出关键生产服务要好得多……
这就是我最终得到的结果:
#! /bin/bash
# truncat.sh
# Adapted from @BobC's script http://superuser.com/a/836950/539429
#
# Efficiently cat log files that have been previously truncated.
# They are sparse -- many null blocks before the interesting content.
# This script skips the null blocks in bulk (except for the last)
# and then uses tr to filter the remaining nulls.
#
for f in $@; do
fields=( `stat -c "%o %B %b %s" $f` )
xfer_block_size=${fields[0]}
alloc_block_size=${fields[1]}
blocks_alloc=${fields[2]}
size_bytes=${fields[3]}
bytes_alloc=$(( $blocks_alloc * $alloc_block_size ))
alloc_in_xfer_blocks=$(( ($bytes_alloc + ($xfer_block_size - 1))/$xfer_block_size ))
size_in_xfer_blocks=$(( ($size_bytes + ($xfer_block_size - 1))/$xfer_block_size ))
null_xfer_blocks=$(( $size_in_xfer_blocks - $alloc_in_xfer_blocks ))
null_xfer_bytes=$(( $null_xfer_blocks * $xfer_block_size ))
non_null_bytes=$(( $size_bytes - $null_xfer_bytes ))
if [ "$non_null_bytes" -gt "0" -a "$non_null_bytes" -lt "$size_bytes" ]; then
cmd="dd if=$f ibs=$xfer_block_size obs=8M skip=$null_xfer_blocks "
$cmd | tr -d "\000"
else
cat $f
fi
done
使用更大的块可以使dd
速度快几个数量级。 dd
首先进行切割,然后tr
修剪其余的空值。作为参考,对于 87 GiB 稀疏文件(包含 392 MiB 数据):
# ls -l 2015_10_12-025600113.start.log
-rw-r--r-- 1 solr solr 93153627360 Dec 31 10:34 2015_10_12-025600113.start.log
# du -shx 2015_10_12-025600113.start.log
392M 2015_10_12-025600113.start.log
#
# time truncat.sh 2015_10_12-025600113.start.log > test1
93275+1 records in
45+1 records out
382055799 bytes (382 MB) copied, 1.53881 seconds, 248 MB/s
real 0m1.545s
user 0m0.677s
sys 0m1.076s
# time cp --sparse=always 2015_10_12-025600113.start.log test2
real 1m37.057s
user 0m8.309s
sys 1m18.926s
# ls -l test1 test2
-rw-r--r-- 1 root root 381670701 Dec 31 10:07 test1
-rw-r--r-- 1 root root 93129872210 Dec 31 10:11 test2
# du -shx test1 test2
365M test1
369M test2
当我logrotate
使用来处理这个问题时copytruncate
,它花了一个多小时,并产生了一个完全实现的非稀疏文件 - 然后花了一个多小时来gzip
。
这是我的最终logrotate
解决方案:
/var/log/solr/rotated.start.log {
rotate 14
daily
missingok
dateext
compress
create
firstaction
# this actually does the rotation. At this point we expect
# an empty rotated.start.log file.
rm -f /var/log/solr/rotated.start.log
# Now, cat the contents of the log file (skipping leading nulls)
# onto the new rotated.start.log
for i in /var/log/solr/20[0-9][0-9]_*.start.log ; do
/usr/local/bin/truncat.sh $i >> /var/log/solr/rotated.start.log
> $i # truncate the real log
done
endscript
}
棘手的是,当您第一次设置它时,您必须创建一个空rotated.start.log
文件,否则logrotate
将永远不会选择并运行firstaction
脚本。
我确实看到了您的logrotate
错误单为此修复发布于logrotate 3.9.0
。不幸的是,如果我没有看错的话,实施的修复程序只能解决部分问题。它正确地复制了稀疏日志文件以创建另一个稀疏文件。但正如您所观察到的,这并不是我们真正想要的;我们希望副本排除所有不相关的空块并仅保留日志条目。之后copytruncate
,logrotate
仍然必须保留gzip
文件,并且gzip
不能有效地处理稀疏文件(它读取并处理每个空字节。)
copytruncate
我们的解决方案比修复更好,logrotate 3.9.x
因为它可以生成可以轻松压缩的干净日志。