在 bash 脚本中运行并行 mongodump



编者注:这是一个关于并行运行指定数量的命令的更一般问题的后续问题。

我正在尝试为 20 台 mongodb 服务器运行此 mongodb 备份脚本。

#!/bin/bash
#daily backup for mongo db's
BACKUP_HOSTS=(example.com staging.example.com prod.example.com example1.com)
#d=$(date +%Y-%m-%dT%H:%M:%S --date "-65 days")
d=$(date +%Y-%m-%dT%H:%M:%S --date "-5 days")
oid=$(mongo --quiet --eval "ObjectId.fromDate(ISODate('$d'))")
cd /data/daily/
rm -r /data/daily/*
TODAY=$(date +"%y-%m-%d")
mkdir "$TODAY"
cd $TODAY
#create subfolders
for HOST in ${BACKUP_HOSTS[@]}
do
mkdir $HOST
done
#extract mongo dumps
echo "$(date '+%Y-%m-%d %H:%M:%S') start retrieving Mongodb backups"
for h in ${BACKUP_HOSTS[@]};do
dbs=`mongo --eval "db.getMongo().getDBNames()" --host $h | grep '"' | tr -d '",' `
for db in $dbs; do
col=`mongo  $db --host $h --quiet --eval "db.getCollectionNames()" | tr -d ',"[]' `
for collection in $col; do
xargs -P 0 -n 1 mongodump --host $h -q "{_id:{$gt:$oid}}" -d $db -c $collection --out /data/daily/$TODAY/$h
done
done
done

但是不起作用。

也尝试过:

parallel -P 0 -n 1 mongodump --host $h "{_id:{$gt:$oid}}" -d $db -c $collection --out /data/daily/$TODAY/$h

但我得到:

bin/bash: -c: line 0: syntax error near unexpected token `('

尝试

mongodump --host $h -q "{_id:{$gt:$oid}}" -d $db -c $collection > /data/daily/$TODAY/$h &

最后&使命令在后台运行,因此循环的每个命令都将与前一个命令并行运行。也看看这个。

但是,我建议您始终将变量括在双引号中,例如"$var",否则可能会出现许多异常并干扰命令的执行。 比如这个错误:

bin/bash:-c:第 0 行:意外标记"("附近出现语法错误

似乎是由变量$collection具有的某些特殊字符引起的。

因此,它的安全版本将是:

mongodump --host "$h" -q "{_id:{$gt:$oid}}" -d "$db" -c "$collection" > /data/daily/"$TODAY"/"$h" &

您可以在此处查看为什么以及何时使用双引号以获取更多详细信息。

请尝试以下xargs -P解决方案:

for h in "${BACKUP_HOSTS[@]}";do
dbs=$(mongo --eval "db.getMongo().getDBNames()" --host "$h" | grep '"' | tr -d '",')
for db in $dbs; do
mongo "$db" --host "$h" --quiet --eval "db.getCollectionNames()" | tr -d ',"[]' |
xargs -P 0 -n 1 mongodump --host "$h" -q "{_id:{$gt:$oid}}" -d "$db" --out "/data/daily/$TODAY/$h" -c 
done
done
  • xargs仅对stdin输入进行操作,而您的解决方案尝试不提供任何 stdin 输入;上面的解决方案将集合名称检索mongo的结果直接传送到xargs

    • 请注意,这假定集合名称既没有嵌入空格,也没有嵌入字符。
  • -P 0仅适用于GNUxargs,它将0解释为:"同时运行尽可能多的进程"(我不清楚它是如何定义的)。

  • 使用for循环命令输出通常是脆弱的。

    • 只有当 (a) 每个空格-空格分隔的单词都应被视为自己的参数并且 (b) 这些单词不包含通配字符(如*)时,它才能可靠地工作 - 请参阅此 Bash 常见问题解答条目。
  • 请注意,为了鲁棒性,所有变量引用(已知仅包含数字的引用除外)都用双引号引起来

  • 使用现代命令替换语法$(...)代替传统语法`...`,后者更可取。


至于GNUparallel命令,请尝试以下变体,使用集合名称检索mongo命令中的 stdin 输入,如上所示

... | parallel -P 0 -N 1 -q mongodump --host "$h" -q "{_id:{$gt:$oid}}" -d "$db" -c {1} --out "/data/daily/$TODAY/$h"
  • -N 1而不是-n 1允许您使用占位符{1}来控制从 stdin 读取参数在命令行上的位置

  • -q可确保将带有双引号的复杂命令正确传递到 shell。

  • 外壳变量引用用双引号引起来,以确保按原样使用。


xargs/GNUparallel调用故障排除:

xargs和 GNUparallel都支持-t(GNU 并行:别名--verbose):

  • -t启动之前将每个命令行打印到stderr
  • 警告:使用xargs时,输入引用将不会反映在打印的命令中,因此您将无法验证指定的参数边界。

xargs -t示例:

$ time echo '"echo 1; sleep 1" "echo 2; sleep 2" "echo 3; sleep 1.5"' |
xargs -t -P 2 -n 1 sh -c 'eval "$1"' -

这会产生类似的东西:

sh -c eval "$1" - echo 1; sleep 1
sh -c eval "$1" - echo 2; sleep 2
2
1
sh -c eval "$1" - echo 3; sleep 1.5
3
real    0m2.535s
user    0m0.013s
sys 0m0.013s

注意:

  • 命令行缺少参数周围的原始引号,但调用仍按最初指定的方式执行。

  • 它们在命令启动之前立即打印(到 stderr)。

  • 如前所述,命令的输出可能会无序到达,并且不可预测地交错。

  • 整体执行大约需要 2.5 秒,细分如下:

    • 由于-P 2echo 1; ...echo 2; ...命令并行运行,而echo 3; ...命令,作为第 3 个命令最初被保留,因为一次最多只能运行 2 个命令。

    • 1 秒后,echo 1; ...命令完成,将运行并行进程的计数减少到 1,触发执行剩余的echo 3; ...命令。

    • 因此,由于最后一个命令在 1 秒后
    • 启动并运行了 1.5 秒,因此最后一个命令在大约 2.5 秒后完成(而前 2 个命令分别在 1 秒和 2 秒后完成)。

GNUparallel -t的例子:

$ time echo $'echo 1; sleep 1necho 2; sleep 2necho 3; sleep 1.5' | 
parallel -P 2 -q -t sh -c 'eval "$1"' -
sh -c eval "$1" - echo 1; sleep 1
sh -c eval "$1" - echo 2; sleep 2
1
sh -c eval "$1" - echo 3; sleep 1.5
2
3
real    0m2.768s
user    0m0.165s
sys 0m0.094s

注意:

  • 因为该命令使用引号来划分参数,并且该分隔必须传递到sh,因此还必须指定-q

  • 引用可能看起来不寻常,但它是正确的 shell 引用,并且完全反映了幕后调用的命令。

  • GNUparallel期望参数默认是行分隔的,因此 shell 命令使用带有n转义序列的 ANSI C 引号字符串 ($'...') 在各个行上传递。

  • 整体处理比xargs需要更长的时间,这是你为GNUparallel的附加功能和技术基础(Perl脚本)付出的代价,可能可以忽略不计。

  • 其中一个附加功能是前面提到的输出序列化(分组):第一个命令的输出是可以预见的,即使它和第二个命令同时启动;第二个命令的输出直到完成后才打印出来(这就是为什么首先显示第三个命令行的诊断打印)。

GNUparallel还支持--dry-run,它打印命令- 到stdout- 而不实际运行它们。

$ echo $'echo 1; sleep 1necho 2; sleep 2necho 3; sleep 1.5' |
parallel -P 2 -q --dry-run sh -c 'eval "$1"' -
sh -c eval "$1" - echo 1; sleep 1
sh -c eval "$1" - echo 2; sleep 2
sh -c eval "$1" - echo 3; sleep 1.5

只需要在末尾添加一个&

for collection in $cols; do
mongodump --host "$h" -q "{_id:{$gt:$oid}}" -d "$db" -c 
"$collection" --out /data/daily/$TODAY/$h
done &

相关内容

  • 没有找到相关文章

最新更新