여러 컴퓨터에 대해 실행해야하거나 여러 개의 다른 인수로 실행해야하는 스크립트가있는 경우 새 PSJobStart-Job
을 생성하는 오버 헤드없이 ?
예를 들어 모든 도메인 구성원의 시간을 다시 동기화하고 싶습니다. 다음과 같이 싶습니다.
$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 }
}
그러나 각 PSSession이 명령을 연결하고 호출하기를 기다리지 않습니다. 잡스없이 어떻게 이것을 병렬로 할 수 있습니까?
답변
업데이트 -이 답변에서는 PowerShell 실행 영역의 프로세스 및 메커니즘과 이들이 멀티 스레드 비 순차 워크로드를 지원하는 방법에 대해 설명하지만 동료 PowerShell 애호가 인 Warren ‘Cookie Monster’F 는이 같은 개념을 하나의 도구로 통합했습니다. 호출 된 -아래에서 설명하는 작업을 수행 한 후 가져온 모듈, 실제로 멋진 물건을 포함하여 로깅 및 준비된 세션 상태를위한 옵션 스위치로 확장했습니다 . 광택 한 솔루션을 구축하기 전에 체크 아웃 하는 것이 좋습니다 !Invoke-Parallel
병렬 런 스페이스 실행
피할 수없는 대기 시간 단축
원래의 특정 경우, 호출 된 실행 파일 /nowait
에는 작업 (이 경우 시간 재 동기화)이 자체적으로 완료되는 동안 호출 스레드를 차단 하는 옵션이 있습니다.
이렇게하면 발급자 관점에서 전체 실행 시간이 크게 단축되지만 각 컴퓨터에 대한 연결은 여전히 순차적으로 수행됩니다. 시간 초과 대기 누적으로 인해 액세스 할 수없는 여러 가지 머신 수에 따라 수천 개의 클라이언트에 순서대로 연결하는 데 시간이 오래 걸릴 수 있습니다.
단일 또는 몇 번의 연속 시간 초과가 발생할 경우 모든 후속 연결을 대기열에 넣지 않기 위해 명령을 연결하고 호출하는 작업을 별도의 PowerShell Runspace에 병렬로 실행하여 전달할 수 있습니다.
런 스페이스 란 무엇입니까?
실행 영역은 가상 컨테이너에서 파워 쉘 코드가 실행하고, 대표 / PowerShell은 문 / 명령의 관점에서 환경을 보유하고 있습니다.
넓은 의미에서 1 Runspace = 1 실행 스레드이므로 PowerShell 스크립트를 “멀티 스레드”하는 데 필요한 모든 Runspace 컬렉션은 병렬로 실행될 수 있습니다.
원래 문제와 같이 여러 실행 영역 명령을 호출하는 작업은 다음과 같이 나눌 수 있습니다.
- RunspacePool 만들기
- PowerShell 스크립트 또는 이에 상응하는 실행 코드를 RunspacePool에 할당
- 코드를 비동기 적으로 호출하십시오 (예 : 코드가 리턴 될 때까지 기다리지 않아도 됨)
RunspacePool 템플릿
PowerShell에는 [RunspaceFactory]
Runspace 구성 요소를 만드는 데 도움 이되는 형식 가속기 가 있습니다.
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. APM을 사용하여 PowerShell 인스턴스를 비동기 적으로 호출합니다.
.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()
}
CPU에 8 개의 실행 영역을 한 번에 모두 실행할 수있는 용량이 있다고 가정하면 실행 시간이 크게 단축되지만 사용 된 “고급”방법으로 인해 스크립트의 가독성이 떨어짐을 알 수 있습니다.
최적의 병렬화 정도 결정 :
100 개의 실행 영역을 동시에 실행할 수있는 RunspacePool을 쉽게 만들 수 있습니다.
[runspacefactory]::CreateRunspacePool(1,100)
그러나 하루가 끝나면 로컬 CPU가 처리 할 수있는 실행 단위 수로 결정됩니다. 다시 말해, 코드가 실행되는 한 논리 프로세서가 코드 실행을 디스패치하는 것보다 더 많은 실행 영역을 허용하는 것은 의미가 없습니다.
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
}
답변
이 토론에 추가로, 누락 된 것은 실행 영역에서 작성된 데이터를 저장하는 콜렉터와 실행 영역의 상태를 확인하는 변수, 즉 완료 여부입니다.
#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
답변
PoshRSJob을 확인하십시오 . 기본 * -Job 함수와 동일 / 유사한 기능을 제공하지만 표준 Powershell 작업보다 훨씬 빠르고 반응이 빠른 Runspace를 사용합니다.
답변
@ mathias-r-jessen은 추가하고 싶은 세부 사항이 있지만 훌륭한 답변 이 있습니다.
최대 스레드
이론적으로 스레드는 시스템 프로세서의 수에 의해 제한되어야합니다. 그러나 AsyncTcpScan 을 테스트하는 동안에 대해 더 큰 값을 선택하여 성능이 훨씬 향상되었습니다 MaxThreads
. 따라서 해당 모듈에 -MaxThreads
입력 매개 변수 가있는 이유는 무엇입니까? 너무 많은 스레드를 할당하면 성능이 저하됩니다.
데이터 반환
데이터를 다시 가져 오는 ScriptBlock
것은 까다 롭습니다. OP 코드를 업데이트하고 AsyncTcpScan 에 사용 된 것과 통합했습니다 .
경고 : 다음 코드를 테스트 할 수 없습니다. Active Directory cmdlet을 사용한 경험을 바탕으로 OP 스크립트를 일부 변경했습니다.
# 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