编者注:这是一个关于并行运行指定数量的命令的更一般问题的后续问题。
我正在尝试为 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 常见问题解答条目。
- 只有当 (a) 每个空格-空格分隔的单词都应被视为自己的参数并且 (b) 这些单词不包含通配字符(如
请注意,为了鲁棒性,所有变量引用(已知仅包含数字的引用除外)都用双引号引起来。
使用现代命令替换语法
$(...)
代替传统语法`...`
,后者更可取。
至于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 2
,echo 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 引用,并且完全反映了幕后调用的命令。
GNU
parallel
期望参数默认是行分隔的,因此 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 &