PowerShell scriptlerimi Jobs kullanmadan paralel olarak nasıl çalıştırabilirim?


29

Birden fazla bilgisayara karşı veya birden fazla farklı argümanlarla çalıştırmam gereken bir komut dosyam varsa,Start-Job yeni bir PSJob eklemeye gerek kalmadan paralel olarak nasıl çalıştırabilirim ?

Örnek olarak, tüm etki alanı üyelerindeki zamanı yeniden senkronize etmek istiyorum , bunun gibi:

$computers = Get-ADComputer -filter * |Select-Object -ExpandProperty dnsHostName
$creds = Get-Credential domain\user
foreach($computer in $computers)
{
    $session = New-PSSession -ComputerName $computer -Credential $creds
    Invoke-Command -Session $session -ScriptBlock { w32tm /resync /nowait /rediscover }
}

Ancak her PSSession'ın bağlanıp komutu çağırmasını beklemek istemiyorum. İşler olmadan bu nasıl paralel olarak yapılabilir?

Yanıtlar:


51

Güncelleme - Bu cevap, PowerShell çalışma alanlarının sürecini ve mekaniğini ve bunların ardışık olmayan iş yüklerini size nasıl bağlamanıza yardımcı olabileceğini açıklasa da, PowerShell meraklısı Warren 'Cookie Monster' F daha fazla mil kullandı ve bu aynı kavramları tek bir araca dahil etti. çağırdı - aşağıda açıkladığım şeyi yapıyor ve o zamandan beri, giriş modülleri dahil olmak üzere oturum açma ve hazırlık oturumu için isteğe bağlı anahtarlarla genişleten, gerçekten harika şeyler - kendi parlak çözümünüzü oluşturmadan önce kontrol etmenizi şiddetle tavsiye ediyorum !Invoke-Parallel


Paralel Runspace yürütme ile:

Kaçınılmaz bekleme süresini azaltmak

Özgün özel durumda, çalıştırılabilir çalıştırılan dosya /nowait, iş (bu durumda, zaman senkronizasyonu) kendi başına bittiğinde, başlatılan iş parçacığının engellenmesini önleyen bir seçeneğe sahiptir.

Bu, genel yürütme süresini, ihraç edenin bakış açısıyla büyük ölçüde azaltır, ancak her bir makineye bağlantı hala sıralı olarak yapılır. Binlerce müşteriye sırayla bağlanmak, zaman aşımı beklemelerinin birikmesi nedeniyle bir nedenden veya erişilemeyen makinelerin sayısına bağlı olarak uzun sürebilir.

Bir veya birkaç ardışık zaman aşımı durumunda, sonraki tüm bağlantıları sıraya koymak zorunda kalmamak için, paralel olarak yürütülen PowerShell Çalışma Alanlarını ayırmak için komutları bağlama ve çağırma işini gönderebiliriz.

Runspace nedir?

Bir Runspace sanal kap içinde Powershell kod yürütür ve temsil / a PowerShell açıklamada / komuta perspektifinden Çevre tutar.

Genel olarak, 1 Runspace = 1 yürütme iş parçacığı, bu nedenle PowerShell betiğimizi "multi-thread" yapmamız gereken tek şey, daha sonra paralel olarak çalışabilen Runspaces koleksiyonudur.

Orijinal sorun gibi, komutları çalıştırma işi birden fazla çalışma alanı olarak ayrılabilir:

  1. RunspacePool oluşturma
  2. RunspacePool'a bir PowerShell betiği veya eşdeğer bir çalıştırılabilir kod parçası atama
  3. Kodu eşzamansız olarak çağırın (yani, kodun geri dönmesini beklemeniz gerekmez)

RunspacePool şablonu

PowerShell, [RunspaceFactory]çalışma alanı bileşenlerinin oluşturulmasında bize yardımcı olacak bir tür hızlandırıcıya sahiptir - hadi işe koyulalım

1. Bir RunspacePool oluşturun ve Open()bu:

$RunspacePool = [runspacefactory]::CreateRunspacePool(1,8)
$RunspacePool.Open()

İki argüman geçirilen CreateRunspacePool(), 1ve 8bize etkili vererek minimum ve herhangi bir zamanda çalıştırmak için izin runspaces maksimum sayıda olan maksimum paralellik derecesi 8.

2. PowerShell örneği oluşturun, çalıştırılabilir bir kod ekleyin ve RunspacePool'umuza atayın:

PowerShell örneği, powershell.exeişlemle aynı değildir (bu gerçekten bir Host uygulamasıdır), ancak yürütülecek PowerShell kodunu temsil eden bir dahili çalışma zamanı nesnesidir. [powershell]PowerShell içinde yeni bir PowerShell örneği oluşturmak için tür hızlandırıcıyı kullanabiliriz :

$Code = {
    param($Credentials,$ComputerName)
    $session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
    Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}
$PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument("computer1.domain.tld")
$PSinstance.RunspacePool = $RunspacePool

3. PowerShell örneğini APM kullanarak eşzamansız olarak çağırın:

.NET geliştirme terminolojisinde Asenkron Programlama Modeli olarak bilinenleri kullanarak , komutun Beginçalıştırılmasına "yeşil ışık" verilmesi için bir yöntemin çağrılmasını Endve sonuçları toplama yöntemini bölebiliriz . Bu durumda herhangi bir geri bildirim ile gerçekten ilgilenmediğimizden (herhangi bir zamanda çıktının w32tmçıkmasını beklemiyoruz ), yalnızca ilk yöntemi çağırarak yapabiliriz.

$PSinstance.BeginInvoke()

Bir RunspacePool içinde sarma

Yukarıdaki tekniği kullanarak, yeni bağlantılar oluşturma ve uzak komutunu paralel yürütme akışına çağırarak art arda yinelemelerini sarabiliriz:

$ComputerNames = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName

$Code = {
    param($Credentials,$ComputerName)
    $session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
    Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}

$creds = Get-Credential domain\user

$rsPool = [runspacefactory]::CreateRunspacePool(1,8)
$rsPool.Open()

foreach($ComputerName in $ComputerNames)
{
    $PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument($ComputerName)
    $PSinstance.RunspacePool = $rsPool
    $PSinstance.BeginInvoke()
}

CPU'nun 8 çalışma alanının tümünü aynı anda yürütme kapasitesine sahip olduğunu varsayarak, yürütme süresinin büyük ölçüde azaldığını, ancak kullanılan "gelişmiş" yöntemlerden dolayı komut dosyasının okunabilirliği pahasına olduğunu görmemiz gerekir.


Optimum paralellik derecesinin belirlenmesi:

Aynı anda 100 çalışma alanının yürütülmesine izin veren bir RunspacePool oluşturabiliriz:

[runspacefactory]::CreateRunspacePool(1,100)

Ancak günün sonunda, yerel CPU'muzun kaç tane yürütme birimi işleyebileceğine bağlı. Başka bir deyişle, kodunuz yürütüldüğü sürece, kod yürütmesini gönderecek mantıksal işlemcilere sahip olduğunuzdan daha fazla çalışma alanına izin vermek mantıklı değildir.

WMI sayesinde, bu eşiğin belirlenmesi oldukça kolaydır:

$NumberOfLogicalProcessor = (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
[runspacefactory]::CreateRunspacePool(1,$NumberOfLogicalProcessors)

Öte yandan, yürütmekte olduğunuz kod, ağ gecikmesi gibi dış etkenler nedeniyle çok fazla bekleme süresi içeriyorsa, mantıksal işlemcilere sahip olduğunuzdan daha fazla eşzamanlı çalışma alanı çalıştırmanın avantajlarından yararlanabilirsiniz. sonu bulmak için mümkün olan maksimum çalışma alanı aralığı :

foreach($n in ($NumberOfLogicalProcessors..($NumberOfLogicalProcessors*3)))
{
    Write-Host "$n: " -NoNewLine
    (Measure-Command {
        $Computers = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName -First 100
        ...
        [runspacefactory]::CreateRunspacePool(1,$n)
        ...
    }).TotalSeconds
}

4
İşler ağda bekletiliyorsa, örneğin uzak bilgisayarlarda PowerShell komutları kullanıyorsanız, herhangi bir CPU darboğazına basmadan önce mantıksal işlemcilerin sayısından kolayca uzaklaşabilirsiniz.
Michael Hampton

Bu doğru. Biraz değişti ve test için bir örnek verdi
Mathias R. Jessen

Tüm işin sonunda yapıldığından nasıl emin olunur? (Tüm script blokları bittikten sonra bir şeye ihtiyaç duyabilir)
sjzls

@NickW Harika bir soru. İşlerin takibi ve bugünün potansiyel çıktısının "hasadı" konusunda bir takip yapacağım, bizi izlemeye devam edin
Mathias R. Jessen

1
@ MathiasR.Jessen Çok iyi yazılmış bir cevap! Güncelleme için bekliyorum.
Sinyal 15

5

Bu tartışmaya ek olarak, eksik olan şey çalışma alanından oluşturulan verileri depolamak için bir toplayıcı ve çalışma alanının durumunu kontrol etmek için bir değişken, yani tamamlandı mı değil mi.

#Add an collector object that will store the data
$Object = New-Object 'System.Management.Automation.PSDataCollection[psobject]'

#Create a variable to check the status
$Handle = $PSinstance.BeginInvoke($Object,$Object)

#So if you want to check the status simply type:
$Handle

#If you want to see the data collected, type:
$Object

3

PoshRSJob göz atın . Yerel * -Job işlevleriyle aynı / benzer işlevler sağlar, ancak standart Powershell işlerinden daha hızlı ve daha duyarlı olma eğiliminde Runspaces kullanır.


1

@ mathias-r-jessen, eklemek istediğim ayrıntılar olsa da harika bir cevabı var.

Maksimum konu

Teoride, iplikler sistem işlemcileri sayısıyla sınırlandırılmalıdır. Ancak, AsyncTcpScan test ederken, daha büyük bir değer seçerek çok daha iyi performans elde ettim MaxThreads. Öyleyse neden bu modül bir -MaxThreadsgiriş parametresine sahip? Çok fazla iş parçacığı ayırmanın performansı engelleyeceğini unutmayın.

Veri döndürme

Verileri geri almak ScriptBlockzor. OP kodunu güncelledim ve AsyncTcpScan için kullanılana entegre ettim .

UYARI: Aşağıdaki kodu test edemedim. Active Directory cmdlet'leriyle çalışma deneyimime dayanarak OP komut dosyasında bazı değişiklikler yaptım.

# Script to run in each thread.
[System.Management.Automation.ScriptBlock]$ScriptBlock = {

    $result = New-Object PSObject -Property @{ 'Computer' = $args[0];
                                               'Success'  = $false; }

    try {
            $session = New-PSSession -ComputerName $args[0] -Credential $args[1]
            Invoke-Command -Session $session -ScriptBlock { w32tm /resync /nowait /rediscover }
            Disconnect-PSSession -Session $session
            $result.Success = $true
    } catch {

    }

    return $result

} # End Scriptblock

function Invoke-AsyncJob
{
    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true)]
        [System.Management.Automation.PSCredential]
        # Credential object to login to remote systems
        $Credentials
    )

    Import-Module ActiveDirectory

    $Results = @()

    $AllJobs = New-Object System.Collections.ArrayList

    $AllDomainComputers = Get-ADComputer -Filter * -Properties dnsHostName

    $HostRunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(2,10,$Host)

    $HostRunspacePool.Open()

    foreach($DomainComputer in $AllDomainComputers)
    {
        $asyncJob = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock).AddParameters($($($DomainComputer.dnsName),$Credentials))

        $asyncJob.RunspacePool = $HostRunspacePool

        $asyncJobObj = @{ JobHandle   = $asyncJob;
                          AsyncHandle = $asyncJob.BeginInvoke()    }

        $AllJobs.Add($asyncJobObj) | Out-Null
    }

    $ProcessingJobs = $true

    Do {

        $CompletedJobs = $AllJobs | Where-Object { $_.AsyncHandle.IsCompleted }

        if($null -ne $CompletedJobs)
        {
            foreach($job in $CompletedJobs)
            {
                $result = $job.JobHandle.EndInvoke($job.AsyncHandle)

                if($null -ne $result)
                {
                    $Results += $result
                }

                $job.JobHandle.Dispose()

                $AllJobs.Remove($job)
            } 

        } else {

            if($AllJobs.Count -eq 0)
            {
                $ProcessingJobs = $false

            } else {

                Start-Sleep -Milliseconds 500
            }
        }

    } While ($ProcessingJobs)

    $HostRunspacePool.Close()
    $HostRunspacePool.Dispose()

    return $Results

} # End function Invoke-AsyncJob
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.