在bash脚本中运行并行mongodump

时间:2017-05-22 13:34:15

标签: bash mongodb parallel-processing xargs gnu-parallel

编者注:这是关于running a specified number of commands in parallel的更一般的问题的后续问题。

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

LONGLONG SectorsPerPartition = PartitionEntry->PartitionLength.QuadPart / dg.BytesPerSector;

但是没有用。

也试过:

int __cdecl SortPartitions(PPARTITION_INFORMATION_EX PartitionEntry1, PPARTITION_INFORMATION_EX PartitionEntry2)
{
    if (!PartitionEntry1->PartitionNumber) return PartitionEntry2->PartitionNumber ? -1 : 0;
    if (!PartitionEntry2->PartitionNumber) return +1;
    if (PartitionEntry1->StartingOffset.QuadPart < PartitionEntry2->StartingOffset.QuadPart) return -1;
    if (PartitionEntry1->StartingOffset.QuadPart > PartitionEntry2->StartingOffset.QuadPart) return +1;
    return 0;
}

DWORD ExtendTest(HANDLE hDisk)
{
    STORAGE_DEVICE_NUMBER sdn;

    ULONG dwBytesRet;

    if (!DeviceIoControl(hDisk, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesRet, NULL))
    {
        return GetLastError();
    }

    if (sdn.DeviceType != FILE_DEVICE_DISK || sdn.PartitionNumber != 0)
    {
        return ERROR_GEN_FAILURE;
    }

    GET_LENGTH_INFORMATION gli;

    if (!DeviceIoControl(hDisk, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), &dwBytesRet, NULL))
    {
        return GetLastError();
    }

    DbgPrint("Disk Length %I64x (%I64u)\n", gli.Length.QuadPart, gli.Length.QuadPart);

    PVOID stack = alloca(guz);

    union {
        PVOID buf;
        PDRIVE_LAYOUT_INFORMATION_EX pdli;
    };

    ULONG cb = 0, rcb, PartitionCount = 4;

    for (;;)
    {
        if (cb < (rcb = FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[PartitionCount])))
        {
            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
        }

        if (DeviceIoControl(hDisk, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0, buf, cb, &dwBytesRet, NULL))
        {
            if (PartitionCount = pdli->PartitionCount)
            {
                PPARTITION_INFORMATION_EX PartitionEntry = pdli->PartitionEntry;

                qsort(PartitionEntry, PartitionCount, sizeof(PARTITION_INFORMATION_EX), 
                    (int (__cdecl *)(const void *, const void *))SortPartitions );

                do 
                {
                    if (!PartitionEntry->PartitionNumber)
                    {
                        continue;
                    }

                    LARGE_INTEGER EndOffset; 
                    LARGE_INTEGER MaximumOffset = PartitionCount != 1 ? (PartitionEntry + 1)->StartingOffset : gli.Length;

                    EndOffset.QuadPart = PartitionEntry->StartingOffset.QuadPart + PartitionEntry->PartitionLength.QuadPart;

                    if (EndOffset.QuadPart > MaximumOffset.QuadPart)
                    {
                        //??
                        __debugbreak();
                    }
                    else if (EndOffset.QuadPart < MaximumOffset.QuadPart)
                    {
                        DISK_GROW_PARTITION dgp;
                        dgp.PartitionNumber = PartitionEntry->PartitionNumber;
                        dgp.BytesToGrow.QuadPart = MaximumOffset.QuadPart - EndOffset.QuadPart;

                        WCHAR sz[128];

                        swprintf(sz, L"\\\\?\\GLOBALROOT\\Device\\Harddisk%d\\Partition%u", sdn.DeviceNumber, dgp.PartitionNumber);

                        HANDLE hPartition = CreateFile(sz, FILE_READ_ACCESS|FILE_WRITE_ACCESS, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

                        if (hPartition != INVALID_HANDLE_VALUE)
                        {  
                            // +++ begin extend
                            BOOL fOk = FALSE;

                            DISK_GEOMETRY dg;
                            if (DeviceIoControl(hPartition, IOCTL_DISK_GROW_PARTITION, &dgp, sizeof(dgp), 0, 0, &dwBytesRet, 0) &&
                                DeviceIoControl(hPartition, IOCTL_DISK_UPDATE_DRIVE_SIZE, 0, 0, &dg, sizeof(dg), &dwBytesRet, 0) &&
                                DeviceIoControl(hPartition, IOCTL_DISK_GET_PARTITION_INFO_EX, 0, 0, PartitionEntry, sizeof(*PartitionEntry), &dwBytesRet, 0)
                                )
                            {
                                LONGLONG SectorsPerPartition = PartitionEntry->PartitionLength.QuadPart / dg.BytesPerSector;

                                fOk = DeviceIoControl(hPartition, FSCTL_EXTEND_VOLUME, &SectorsPerPartition, 
                                    sizeof(SectorsPerPartition), 0, 0, &dwBytesRet, 0);

                            }

                            if (!fOk)
                            {
                                GetLastError();
                            }

                            //--- end extend
                            CloseHandle(hPartition);
                        }
                    }
                    // else EndOffset.QuadPart == MaximumOffset.QuadPart - partition can not be extended

                } while (PartitionEntry++, --PartitionCount);
            }

            return NOERROR;
        }

        switch (ULONG err = GetLastError())
        {
        case ERROR_MORE_DATA:
            PartitionCount = pdli->PartitionCount;
            continue;
        case ERROR_BAD_LENGTH:
        case ERROR_INSUFFICIENT_BUFFER:
            PartitionCount <<= 1;
            continue;
        default:
            return err;
        }
    }

}
DWORD ExtendTest()
{
    HANDLE hDisk = CreateFileW(L"\\\\?\\PhysicalDrive0", FILE_GENERIC_READ|FILE_GENERIC_WRITE, 
        FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

    if (hDisk != INVALID_HANDLE_VALUE)
    {
        DWORD err = ExtendTest(hDisk);
        CloseHandle(hDisk);

        return err;
    }

    return GetLastError();
}

但我明白了:

#!/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

3 个答案:

答案 0 :(得分:1)

尝试

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

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

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

  

bin / bash:-c:第0行:意外令牌附近的语法错误`(&#39;

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

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

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

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

答案 1 :(得分:0)

尝试以下 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仅适用于 GNU xargs,它将0解释为:“同时运行尽可能多的进程”(我是不清楚如何定义)。

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

    • 只有在(a)每个空格 - 空格分隔的单词应被视为自己的参数并且(b)这些单词不包含诸如*之类的通配字符时,它才能可靠地工作 - 见this Bash FAQ entry
  • 请注意所有变量引用(已知仅包含数字的引用除外)是双引号的健壮性。

  • 使用现代命令替换语法$(...)代替遗留语法`...`,即preferable

对于 GNU parallel 命令,请尝试以下变体,使用集合名称检索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确保使用双引号传递复杂命令会正确传递给shell。

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

疑难解答{1} / GNU -q调用:

xargs和GNU parallel都支持xargs(GNU parallel:alias parallel):

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

-t示例:

xargs

这会产生类似的结果:

xargs -t

注意:

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

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

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

  • 总体执行大约需要2.5秒,按以下方式分解:

    • 由于$ 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 -P 2命令并行运行,而echo 1; ...命令则作为 3rd 一个最初被阻止,因为一次允许运行的命令不超过2个。

    • 1秒后,echo 2; ...命令完成,将并行进程的运行次数降至1,触发执行剩余的echo 3; ...命令。

    • 因此,因为最后一个命令在1秒后启动并运行了1.5秒,所以最后一个命令在ca之后完成。 2.5秒(前两个命令分别在1秒和2秒后完成)。

GNU echo 1; ...示例:

echo 3; ...

注意:

  • 由于该命令使用引号来划分参数,并且必须将分界传递给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 "$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 - 引用可能看起来不寻常,但它是正确的shell引用,并且完全按照幕后调用的方式反映命令。

  • GNU -q期望参数默认为行分隔,因此shell命令使用ANSI C引用的字符串({{1})在各行上传递})\转义序列。

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

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

GNU \n还支持xargs打印命令 - 到 stdout - 而不实际运行它们。

parallel

答案 2 :(得分:0)

只需在

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