Многопоточность RunspaceFactory может оптимально подходить для этого типа работы - с одним ОГРОМНЫМ предупреждением. На net есть довольно много статей об этом. По сути, вы создаете блок сценариев, который принимает параметры для исходного файла, который нужно скопировать, и место назначения для записи и использует эти параметры для выполнения робокопии против него. Вы создаете отдельные экземпляры PowerShell для выполнения каждого варианта блока сценариев и добавляете его в RunspaceFactory. RunspaceFactory будет помещать в очередь задания и работать с числом X заданий, возможно, миллионами за раз, где X равно количеству потоков, выделенных для фабрики.
ПРЕДУПРЕЖДЕНИЕ. Прежде всего, чтобы поставить в очередь миллионы заданий относительно вероятных миллионов файлов в 6 ТБ, вам, вероятно, понадобятся огромные объемы памяти. Предполагая, что средняя длина пути для источника и места назначения составляет 40 символов (вероятно, очень великодушна) * WAG из 50 миллионов файлов занимает почти 4 ГБ в памяти, что не включает структурные издержки объекта, экземпляры PowerShell и т. Д. c. Вы можете преодолеть это либо разбив работу на более мелкие куски, либо использовать сервер с 128 ГБ ОЗУ или лучше. Кроме того, если вы не прервите задания после их обработки, вы также столкнетесь с утечкой памяти, но это будут только ваши задания, выдающие информацию, которую вы не закрываете после завершения.
Вот пример из недавнего проекта, в котором я перенес файлы со старого NAS домена на новый NAS домена - я использую Quest SecureCopy вместо RoboCopy, но вы сможете легко заменить эти биты:
## MaxThreads is an arbitrary number I use relative to the hardware I have available to run jobs I'm working on.
$FileRSpace_MaxThreads = 15
$FileRSpace = [runspacefactory]::CreateRunspacePool(1, $FileRSpace_MaxThreads, ([System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()), $Host)
$FileRSpace.ApartmentState = 'MTA'
$FileRSpace.Open()
## The scriptblock that does the actual work.
$sb = {
param(
$sp,
$dp
)
## This is my output object I'll emit through STDOUT so I can consume the status of the job in the main thread after each instance is completed.
$n = [pscustomobject]@{
'source' = $sp
'dest' = $dp
'status' = $null
'sdtm' = [datetime]::Now
'edtm' = $null
'elapsed' = $null
}
## Remove the Import-Module and SecureCopy cmdlet and replace it with the RoboCopy version
try {
Import-Module "C:\Program Files\Quest\Secure Copy 7\SCYPowerShellCore.dll" -ErrorAction Stop
Start-SecureCopyJob -Database "C:\Program Files\Quest\Secure Copy 7\SecureCopy.ssd" -JobName "Default" -Source $sp -Target $dp -CopySubFolders $true -Quiet $true -ErrorAction Stop | Out-Null
$n.status = $true
} catch {
$n.status = $_
}
$n.edtm = [datetime]::Now
$n.elapsed = ("{0:N2} minutes" -f (($n.edtm - $n.sdtm).TotalMinutes))
$n
}
## The array to hold the individual runspaces and ulitimately iterate over to watch for completion.
$FileWorkers = @()
$js = [datetime]::now
log "Job starting at $js"
## $peers is a [pscustomobject] I precreate that just contains every source (property 's') and the destination (property 'd') -- modify to suit your needs as necessary
foreach ($c in $peers) {
try {
log "Configuring migration job for '$($c.s)' and '$($c.d)'"
$runspace = [powershell]::Create()
[void]$runspace.AddScript($sb)
[void]$runspace.AddArgument($c.s)
[void]$runspace.AddArgument($c.d)
$runspace.RunspacePool = $FileRSpace
$FileWorkers += [pscustomobject]@{
'Pipe' = $runspace
'Async' = $runspace.BeginInvoke()
}
log "Successfully created a multi-threading job for '$($c.s)' and '$($c.d)'"
} catch {
log "An error occurred creating a multi-threading job for '$($c.s)' and '$($c.d)'"
}
}
while ($FileWorkers.Async.IsCompleted -contains $false) {
$Completed = $FileWorkers | ? { $_.Async.IsCompleted -eq $true }
[pscustomobject]@{
'Numbers' = ("{0}/{1}" -f $Completed.Count, $FileWorkers.Count)
'PercComplete' = ("{0:P2}" -f ($Completed.Count / $FileWorkers.Count))
'ElapsedMins' = ("{0:N2}" -f ([datetime]::Now - $js).TotalMinutes)
}
$Completed | % { $_.Pipe.EndInvoke($_.Async) } | Export-Csv -NoTypeInformation ".\$($DtmStamp)_SecureCopy_Results.csv"
Start-Sleep -Seconds 15
}
## This is to handle a race-condition where the final job(s) aren't completed before the sleep but do when the while is re-eval'd
$FileWorkers | % { $_.Pipe.EndInvoke($_.Async) } | Export-Csv -NoTypeInformation ".\$($DtmStamp)_SecureCopy_Results.csv"
Предлагаемые стратегии, если у вас нет мощного сервера для одновременной постановки всех заданий в очередь, - это либо пакетировать файлы в блоки статического размера (например, 100 000 или что-то еще, что может взять ваш hw), либо вы можете сгруппировать файлы вместе для отправки к каждому блоку сценария (например, 100 файлов в блоке сценария), что минимизирует количество заданий в очереди в фабрике пространства выполнения (но потребует некоторого изменения кода).
HTH
Редактировать 1: Для адресации при создании объекта ввода я использую
$destRoot = '\\destinationserver.com\share'
$peers = @()
$children = @()
$children += (get-childitem '\\sourceserver\share' -Force) | Select -ExpandProperty FullName
foreach ($c in $children) {
$peers += [pscustomobject]@{
's' = $c
'd' = "$($destRoot)\$($c.Split('\')[3])\$($c | Split-Path -Leaf)"
}
}
В моем случае я брал вещи из \ s erver1 \ share1 \ subfolder1 и переместить его в нечто вроде \ server2 \ share1 \ subfolder1 \ subfolder2. Таким образом, по сути, все, что делает массив $ peers, - это создание объекта, который принимает полное имя целевой цели и создает соответствующий путь назначения (поскольку имена серверов источника / назначения разные и, возможно, также имеют общее имя).
Вам не нужно этого делать, вы можете динамически построить место назначения и просто l oop через исходные папки. Я выполняю этот дополнительный шаг, потому что теперь у меня есть массив из двух свойств, который я могу проверить, точно ли он создан, а также выполнить тесты, чтобы убедиться, что вещи существуют и доступны.
В моем сценарии много раздувания из-за пользовательских объектов, предназначенных для выдачи мне вывода из каждого потока, помещенного в многопоточность, чтобы я мог видеть состояние каждой попытки копирования - отслеживать такие вещи, как папки, которые были успешны или нет, сколько времени потребовалось для выполнения этой отдельной копии, и т. д. Если вы используете robocopy и выводите результаты в текстовый файл, вам это может не понадобиться. Если вы хотите, чтобы я подключил скрипт к его базовым компонентам, просто чтобы получить многопоточность, я могу сделать это, если хотите.