服务器上大约有 1.3 亿 (准确地说是 129923145 张find . -name "*.*" | wc -l
) 张图片,需要对它们进行去重,然后按照从小到大的顺序,将每百万张图片打包成 zip 压缩文件备用。 (假设有 50 万张唯一的 8kb 图片,60 万张唯一的 16kb 图片,95 万张唯一的 24kb 图片,我应该将 50 万张 8kb 图片和 50 万张 16kb 图片打包到第一个 zip 文件,然后将 10 万张 16kb 图片和 90 万张 24kb 图片打包到第二个 zip 文件,其余的打包到第三个 zip 文件),文件名必须保留,并且最好保留层次结构信息。
服务器配备 32 GB 内存、5.5 TB 硬盘空间(122 GB 可用),CPU 看起来像 E5-2680v4,我不知道它是物理机还是虚拟机。我可以让 IT 同事将内存增加到 512 GB,但这至少需要一周时间才能得到我们的技术总监、供应链人员、预算委员会和 IT 部门的批准,也许需要额外的时间来说服他们。
由于 raid 或其他原因(由 IT 人员完成),没有额外的磁盘空间可用。这是内部网,没有互联网,我宁愿不发送文件,也不愿提交申请。这是 Ubuntu 16.04,我确信有 vim、python(2 和 3)和 shell 可以使用。我只能 ssh 进入,不能sudo
。
我的解决方案是使用du -a
制作文件列表,使用md5sum
进行重复数据删除,用绝对路径重命名所有文件(替换/
为__DIVIDER__
),并将所有文件移动到SIZE/MD5/(fileNameWithAbsPath)
,然后为每个目录选择一个。在此期间,我遇到了“H 树索引错误”有没有更好的方法(更快,更简单等)来完成它,并且如果可能的话,避免H-tree索引错误?
顺便说一下,我是第六个担任这个职务的人。前五个人都离职了 :(
答案1
因此,解决您遇到的 bug 的一种方法是将大小和 md5 拆分为多个子字段。对于大小,您需要先将其填充为固定位数。
假设您要创建一个如下文件名:
#size / md5 / name
12345 / aabbccddeeffgghh / foo__DIVIDER__bar__DIVIDER__baz.jpg
改成:
# size /md5 /name
00/00/01/23/45/aa/bb/cc/dd/ee/ff/gg/hh/foo__DIVIDER__bar__DIVIDER__baz.jpg
...并且您已经限制了树中任何给定点的扇出级别,从而避免了所讨论的错误。
要真正生成这种格式的文件名树可能看起来像这样:
inDir=/path/to/tree/with/input/files
while IFS= read -r -d '' name; do
sp=$(stat --format=%010s -- "$name") # sp as short for "size padded"
size_dir=${sp:0:2}/${sp:2:2}/${sp:4:2}/${sp:6:2}/${sp:8:2}
{ read -r md5 _ < <(md5sum "$name") && [[ $md5 ]]; } || continue
md5_left=$md5
while [[ $md5_left ]]; do
md5_dir+="/${md5_left:0:2}"
md5_left=${md5_left:2}
done
sep=/
final_name="${size_dir}${md5_dir}/${name//$sep/__DIVIDER__}"
final_dir="${final_name%/*}"
if [[ -d "$final_dir" ]]; then
# Hardlink new file to existing ones (deduplication)
# Be sure to use an archiver that understands hardlinks (not zip)!
existing_files=( "$final_dir"/* )
if [[ -e "${existing_files[0]}" || -L "${existing_files[0]}" ]]; then
ln -- "${existing_files[0]}" "$final_name"
ln -f -- "$final_name" "$file" # and make our input file a hardlink as well
continue
fi
fi
# if we get here, the continue was not invoked
mkdir -p -- "${final_name%/*}"
ln -- "$name" "$final_name"
done < <(find "$inDir" -printf '%P\0')
当然,如果您的文件大于 9,999,999,999 字节,您将需要添加更多填充(可能使用%012s
而不是%010s
,并适当更改 的计算size_dir
)。