Оновлення - Хоча ця відповідь пояснює процес і механіку пробігу PowerShell і те, як вони можуть допомогти вам з багатопотоковими непослідовними навантаженнями, колег PowerShell любитель Warren 'Cookie Monster' F пройшов додаткову милю і включив ці самі поняття в єдиний інструмент зателефонував - він робить те, що я описую нижче, і з тих пір він розширив його додатковими перемикачами для реєстрації та підготовленим станом сеансу, включаючи імпортні модулі, дуже класні речі - настійно рекомендую перевірити це перед тим , як створити власне блискуче рішення!Invoke-Parallel
Із паралельним виконанням Runspace:
Скорочення неминучого часу очікування
У первинному конкретному випадку викликаний виконуваний файл має /nowait
опцію, яка запобігає блокуванню потоку виклику, поки завдання (у цьому випадку повторна синхронізація) закінчується самостійно.
Це значно скорочує загальний час виконання з точки зору емітентів, але підключення до кожної машини все ще відбувається в послідовному порядку. Підключення до тисяч клієнтів послідовно може зайняти багато часу, залежно від кількості машин, які з тих чи інших причин є недоступними, через накопичення часу очікування.
Щоб обійти необхідність встановлення черги на всі наступні з'єднання у випадку одного або декількох послідовних тайм-аутів, ми можемо відправити завдання з підключення та виклику команд для відділення PowerShell Runspaces, виконуючи паралельно.
Що таке пробірка?
Простір виконання являє собою віртуальний контейнер , в якому ваш код виконується PowerShell, і являє / тримає середовища з точки зору заяви / команди PowerShell.
У широкому розумінні, 1 Runspace = 1 потік виконання, тому все, що нам потрібно, щоб «багатопотоковий» наш скрипт PowerShell - це сукупність просторів Runspaces, які потім можуть у свою чергу виконуватись паралельно.
Як і вихідна проблема, завдання виклику команд декількох пробілів може бути розбито на:
- Створення RunspacePool
- Призначення сценарію PowerShell або еквівалентного фрагменту виконуваного коду RunspacePool
- Викликати код асинхронно (тобто не потрібно чекати повернення коду)
Шаблон RunspacePool
PowerShell має типовий прискорювач, який називається, [RunspaceFactory]
який допоможе нам у створенні компонентів пробігу - давайте працюємо
1. Створіть RunspacePool і Open()
це:
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,8)
$RunspacePool.Open()
Два аргументи передається CreateRunspacePool()
, 1
і 8
це мінімальна та максимальна кількість просторів виконання дозволило виконати в будь-який момент часу, що дає нам ефективну максимальну ступінь паралелізму 8.
2. Створіть екземпляр PowerShell, приєднайте до нього якийсь виконуваний код і призначте його нашому RunspacePool:
Екземпляр PowerShell - це не те саме, що powershell.exe
процес (який насправді є хост-додатком), а внутрішній об’єкт виконання, який представляє код PowerShell для виконання. Ми можемо використовувати [powershell]
прискорювач типу для створення нового екземпляра PowerShell в 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. Викликайте екземпляр PowerShell асинхронно за допомогою APM:
Використовуючи те, що відоме в термінології розробки .NET як модель асинхронного програмування , ми можемо розділити виклик команди на Begin
метод, який дає "зелене світло" для виконання коду та End
метод збору результатів. Оскільки ми в цьому випадку насправді не зацікавлені в будь-яких відгуках (ми не чекаємо результатів з w32tm
будь-якого випадку), ми можемо це зробити, просто викликавши перший метод
$PSinstance.BeginInvoke()
Згортання його в RunspacePool
Використовуючи вищевказану техніку, ми можемо обернути послідовні ітерації створення нових з'єднань та викликати віддалену команду в паралельний потік виконання:
$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()
}
Припускаючи, що ЦП має здатність виконувати всі 8 пробілів одночасно, ми повинні мати можливість бачити, що час виконання значно скорочується, але ціною читабельності сценарію завдяки досить "просунутим" методам, які використовуються.
Визначення оптимального ступеня паралізму:
Ми можемо легко створити RunspacePool, який дозволяє одночасно виконувати 100 пробілів:
[runspacefactory]::CreateRunspacePool(1,100)
Зрештою, все зводиться до того, скільки одиниць виконання наш локальний процесор може впоратися. Іншими словами, доки ваш код виконує, не має сенсу дозволити більше пробігу, ніж у вас є логічні процесори для відправки коду до.
Завдяки WMI цей поріг визначити досить просто:
$NumberOfLogicalProcessor = (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
[runspacefactory]::CreateRunspacePool(1,$NumberOfLogicalProcessors)
Якщо, з іншого боку, код, який ви виконуєте сам, вимагає багато часу очікування через зовнішніх факторів, таких як затримка мережі, ви все одно можете отримати вигоду від запуску більше одночасних пробігів, ніж у вас є логічні процесори, тому ви, ймовірно, хочете перевірити можливого максимального пробігу, щоб знайти беззбитковість :
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
}