Pembaruan - Sementara jawaban ini menjelaskan proses dan mekanisme runspaces PowerShell dan bagaimana mereka dapat membantu Anda menangani banyak beban kerja non-sekuensial, sesama pecinta PowerShell Warren 'Cookie Monster' F telah bekerja lebih keras dan memasukkan konsep-konsep yang sama ini ke dalam satu alat. disebut - itu melakukan apa yang saya jelaskan di bawah ini, dan dia telah mengembangkannya dengan sakelar opsional untuk logging dan menyiapkan sesi status termasuk modul yang diimpor, hal-hal yang sangat keren - Saya sangat menyarankan Anda memeriksanya sebelum membangun Anda sendiri solusi mengkilap!Invoke-Parallel
Dengan eksekusi Paralel Runspace:
Mengurangi waktu tunggu yang tak terhindarkan
Dalam kasus spesifik asli, yang dieksekusi yang dijalankan memiliki /nowait
opsi yang mencegah pemblokiran utas pemanggilan sementara pekerjaan (dalam hal ini, sinkronisasi ulang waktu) selesai dengan sendirinya.
Ini sangat mengurangi waktu eksekusi keseluruhan dari perspektif emiten, tetapi menghubungkan ke setiap mesin masih dilakukan secara berurutan. Menghubungkan ke ribuan klien secara berurutan mungkin memakan waktu lama tergantung pada jumlah mesin yang karena satu dan lain hal tidak dapat diakses, karena akumulasi waktu tunggu habis.
Untuk menyiasati harus mengantri semua koneksi berikutnya dalam kasus satu atau beberapa timeout berturut-turut, kita dapat mengirim pekerjaan menghubungkan dan menjalankan perintah untuk memisahkan PowerShell Runspaces, mengeksekusi secara paralel.
Apa itu Runspace?
Sebuah runspace adalah wadah virtual di mana mengeksekusi kode PowerShell Anda, dan mewakili / memegang Lingkungan dari perspektif PowerShell pernyataan / perintah.
Secara umum, 1 Runspace = 1 utas eksekusi, jadi yang kita butuhkan untuk "multi-utas" skrip PowerShell kami adalah kumpulan Runspace yang kemudian dapat dieksekusi secara paralel.
Seperti masalah aslinya, tugas menjalankan perintah beberapa runspace dapat dipecah menjadi:
- Membuat RunspacePool
- Menetapkan skrip PowerShell atau bagian yang setara dari kode yang dapat dieksekusi ke RunspacePool
- Meminta kode secara tidak sinkron (mis. Tidak harus menunggu kode kembali)
Templat RunspacePool
PowerShell memiliki akselerator tipe yang disebut [RunspaceFactory]
yang akan membantu kita dalam pembuatan komponen runspace - mari kita mulai bekerja
1. Buat RunspacePool dan Open()
itu:
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,8)
$RunspacePool.Open()
Dua argumen yang diteruskan ke CreateRunspacePool()
, 1
dan 8
adalah jumlah minimum dan maksimum runspace diizinkan untuk dieksekusi pada waktu tertentu, memberi kita tingkat paralelisme maksimum 8 yang efektif .
2. Buat sebuah instance dari PowerShell, lampirkan beberapa kode yang dapat dieksekusi untuk itu dan tetapkan ke RunspacePool kami:
Contoh PowerShell tidak sama dengan powershell.exe
proses (yang sebenarnya adalah aplikasi Host), tetapi objek runtime internal yang mewakili kode PowerShell untuk dieksekusi. Kita bisa menggunakan [powershell]
akselerator tipe untuk membuat contoh PowerShell baru di dalam PowerShell:
$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. Aktifkan instance PowerShell secara asinkron menggunakan APM:
Menggunakan apa yang dikenal dalam terminologi pengembangan .NET sebagai Model Pemrograman Asynchronous , kita dapat membagi permohonan perintah menjadi Begin
metode, untuk memberikan "lampu hijau" untuk mengeksekusi kode, dan End
metode untuk mengumpulkan hasil. Karena kami dalam hal ini tidak benar-benar tertarik pada umpan balik (kami tidak menunggu output dari w32tm
anyways), kami dapat membuat karena dengan hanya memanggil metode pertama
$PSinstance.BeginInvoke()
Membungkusnya dalam RunspacePool
Menggunakan teknik di atas, kita dapat membungkus iterasi berurutan untuk membuat koneksi baru dan menjalankan perintah jarak jauh dalam aliran eksekusi paralel:
$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()
}
Dengan asumsi bahwa CPU memiliki kapasitas untuk mengeksekusi semua 8 runspace sekaligus, kita harus dapat melihat bahwa waktu eksekusi sangat berkurang, tetapi dengan biaya keterbacaan skrip karena metode yang agak "canggih" yang digunakan.
Menentukan tingkat parallism yang optimal:
Kita dapat dengan mudah membuat RunspacePool yang memungkinkan untuk mengeksekusi 100 runspace secara bersamaan:
[runspacefactory]::CreateRunspacePool(1,100)
Tetapi pada akhirnya, semuanya tergantung pada berapa banyak unit eksekusi yang dapat ditangani oleh CPU lokal kami. Dengan kata lain, selama kode Anda dieksekusi, tidak masuk akal untuk mengizinkan lebih banyak runspaces daripada yang Anda miliki dengan prosesor logis untuk mengirimkan eksekusi kode.
Berkat WMI, ambang ini cukup mudah untuk ditentukan:
$NumberOfLogicalProcessor = (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
[runspacefactory]::CreateRunspacePool(1,$NumberOfLogicalProcessors)
Jika, di sisi lain, kode yang Anda jalankan sendiri memerlukan banyak waktu tunggu karena faktor eksternal seperti latensi jaringan, Anda masih dapat memperoleh manfaat dari menjalankan runspace yang lebih simultan daripada yang Anda miliki dengan prosesor logis, jadi Anda mungkin ingin menguji dari kisaran kemungkinan runspaces maksimum untuk menemukan titik impas :
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
}