运行 bash 脚本中的并行 mongodump

running parallel mongodump in bash script

编者注:这是关于 running a specified number of commands in parallel.

的更笼统问题的后续问题

我正在尝试 运行 这个 mongodb 20 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 &

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

但是,我建议您总是像这样 "$var" 将您的变量括在双引号中,否则会发生许多异常情况并干扰您的命令的执行。 比如这个错误:

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

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

因此,它的安全版本是:

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

您可以查看 here 为什么以及何时使用双引号以了解更多详细信息。

尝试以下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.

    • 请注意,这假定集合名称既没有嵌入白色 space 也没有嵌入 \ 个字符。
  • -P 0 仅适用于 GNU xargs,它将 0 解释为:"run as many processes as possible simultaneously" (我不清楚它是如何定义的。

  • 使用 for 循环命令输出通常 脆弱 .

    • 只有在 (a) 每个 whitespace-space 分隔的单词都应该被视为它自己的参数并且 (b) 这些单词不包含诸如 * - 见 this Bash FAQ entry.
  • 请注意所有变量引用(已知仅包含 数字 的变量引用除外)如何 双引号 以确保稳健性.

  • 使用现代命令替换语法 $(...) 而不是旧语法 `...`,即 preferable.


至于 GNU parallel 命令,尝试以下变体,with stdin input from the collection name-retrieving mongo 命令, 如上:

... | 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}[= 控制 read-from-stdin 参数在命令行中的放置位置63=]

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

  • Shell 变量引用被双引号以确保它们按原样使用。


故障排除 xargs / GNU parallel 调用:

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 ""' -

这会产生如下内容:

sh -c eval "" - echo 1; sleep 1
sh -c eval "" - echo 2; sleep 2
2
1
sh -c eval "" - 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; ...命令,作为3rd 一个最初被阻止了,因为一次允许 运行 的命令不超过 2 个。

    • 1 秒后,echo 1; ... 命令完成,运行ning 并行进程的计数下降到 1,触发执行剩余的 echo 3; ... 命令.

    • 因此,因为最后一个命令在 1 秒后开始,运行 持续 1.5 秒,所以最后一个命令在 ca 之后完成。 2.5 秒(而前 2 个命令分别在 1 秒和 2 秒后完成)。

GNU parallel -t 示例:

$ time echo $'echo 1; sleep 1\necho 2; sleep 2\necho 3; sleep 1.5' | 
    parallel -P 2 -q -t sh -c 'eval ""' -

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

  • \-quoting 可能看起来不寻常,但它是正确的 shell 引用并准确反映在幕后调用的命令。

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

  • 总体处理时间比 xargs 长,这是您为 GNU parallel 的附加功能和技术基础(Perl 脚本)支付的 - 可能微不足道的 - 价格.

  • 这些附加功能之一是前面提到的输出序列化(分组):第一个命令的输出可预测首先,即使它和第二个命令同时启动;直到 it 完成后才打印第二个命令的输出(这就是第 3 个命令行的诊断打印首先显示的原因)。

GNU parallel 还支持 --dry-run,其中 打印命令 - 到 stdout - 实际上没有 运行ning 它们。

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

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 &