RunspaceFactory 多线程可能最适合这种类型的工作——有一个巨大的警告。网上有不少关于它的文章。本质上,您创建一个脚本块,该脚本块为要复制的源文件和要写入的目标文件提供参数,并使用这些参数对其执行 robocopy。您创建单独的 PowerShell 实例来执行脚本块的每个变体并将其附加到 RunspaceFactory。 RunspaceFactory 会将作业排队并一次处理可能数百万个作业 X 编号,其中 X 等于您为工厂分配的线程数。
警告:首先,相对于 6TB 中可能拥有的数百万个文件,要排队数百万个作业,您可能需要大量内存。假设源和目标的平均路径长度为 40 个字符(可能非常大)* 5000 万个文件的 WAG 本身在内存中接近 4GB,这不包括对象结构开销、PowerShell 实例等。您可以克服这要么将工作分解成更小的块,要么使用具有 128GB RAM 或更好的服务器。此外,如果您在处理完作业后不终止它们,您还会遇到看似内存泄漏的情况,但只是您的作业产生的信息在完成时不会关闭。
这是我最近将文件从旧域 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 或您的硬件可以采用的任何内容),或者您可以将文件组合在一起发送到每个脚本块(例如,每个脚本块 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)"
}
}
在我的例子中,我从 \server1\share1\subfolder1 中取出一些东西并将其移动到 \server2\share1\subfolder1\subfolder2 之类的地方。所以本质上,'$peers' 数组所做的就是构造一个对象,该对象采用源目标的全名并构造相应的目标路径(因为源/目标服务器名称不同,并且可能也共享名称)。
您不必这样做,您可以动态构建目标并循环访问源文件夹。我执行了这个额外的步骤,因为现在我有一个两个属性数组,我可以验证它是准确预先构建的,并执行测试以确保事物存在并且可以访问。
我的脚本中有很多额外的膨胀,因为自定义对象意味着我可以从放入多线程的每个线程中获得输出,因此我可以看到每次复制尝试的状态——跟踪诸如文件夹之类的东西成功与否,执行该单独复制需要多长时间等。如果您使用 robocopy 并将结果转储到文本文件,您可能不需要这个。如果您希望我将脚本与其准系统组件配对以实现多线程,如果您愿意,我可以这样做。