還是決定寫一下這個話題,Powershell 非我的強項,本文大部分内容也來自于其他部落格,就當是知識收藏。
大概10月份左右去做一個項目,看到以前同僚留下的資料中包含很多
PowerCLI
的腳本,有去批量做配置的,有批量收集資訊的,于是花時間整理了一套适合自己的,但總有一個問題不能很好地解決:Powershell
腳本是順序執行的,面對大批量的資料收集速度很慢,通常會耗費 1 小時以上才能收集完資訊,于是花了些時間嘗試性能調優,将優化經驗整理發出來。
0x00. 關于 PowerCLI 及 Powershell
Powercli
是 VMware 出品的一系列 Microsoft Powershell 子產品,通過這些子產品,用于可以非常快捷的利用 Powershell 對
vCenter、NSX、SRM 等資源進行批量操作,例如批量修改 ESXI 主機的配置、列舉虛拟機清單等等。國外的 vSphere 管理者用
PowerCLI 非常多,社群中積累了大量的 PowerCLI 腳本,隻要能想到的操作都能找到相關資源。
0x01. 優化原則
目前我使用過的優化方式有三種:
1、優化代碼,提升單行單碼的運作效率
2、異步執行指令
3、多線程
以下章節逐一講解每個部分
0x02. 優化代碼
檢視瓶頸在哪裡
Powershell 有個指令叫 Measure-Command ,通過此指令可以檢視一段代碼的運作時間,其使用方法如下:
Measure-Command{ 指令内容 }
例如,測試 Get-Process 的運作時間:

将過濾左移
一個通用的優化方式如下:假如你要先收集大量的資料,應當盡早對資料進行過濾,以縮減這部分資料的大小,再進行其他二次過濾。或者按照原作者的話,将過濾往代碼的左邊移動。
例如:
想要通過 get-process 擷取 svchost 這個程序的 CPU 資訊,可以通過以下兩組代碼實作:
Get-Process -Name 'Dock' | Where-Object {$_.CPU -ne $null} | Select-Object CPU
Get-Process | Where-Object {$_.ProcessName -eq 'Dock'} | Select-Object CPU | Where-Object {$_.CPU -ne $null}
性能對比如下:
可以看到前者運作速度比後者要快一倍多,如果是執行大量的任務,這一點點差距累計起來的差距是很大的。
PowerCLI 代碼性能優化
筆者對 PowerCLI 的腳本進行了類似測試發現随着寫法不同,代碼執行速度差異很大,如下例:擷取主機所在叢集資訊:
Get-VMHost $vmHost | Get-cluster
Get-Cluster -vmhost $vmHost
又如對 ESXi 本地盤數量的統計:
雖然 esxcli 看似步驟更多,代碼更多,但使用 esxcli 的效率明顯高于 Get-scsilun 的效率:
$ESXCLI = Get-EsxCli -VMHost $VMHost $luncount=$ESXCLI.storage.core.device.list() | select DeviceType | where DeviceType -eq "Direct-Access"write-host "Count is" $luncount.Count -ForegroundColor Green
$luncount=get-vmhost $VMhost| Get-ScsiLun -LunType disk write-host "Count is" $luncount.Count -ForegroundColor Green
遇到大量代碼時,需要反複測試不同寫法帶來的速度差異,選擇最優的。
指派的效率遠遠高于執行一個查詢
另外測試發現,所有指派的操作速度都低于 1ms,是以這類操作并不影響性能。具體到代碼優化上,應該盡可能減少與 vCenter 的互動。例如擷取主機所在叢集:
Get-VMHost $vmHost|Get-cluster
Get-Cluster -vmhost $vmHost
$info.cluster=$vmhost.Parent.name
直接的指派遠比執行一條查詢快。三條指令執行速度分别如下:
0x03. 異步任務
PowerCLI 有相當一部分操作可以使用
-RunAsync
參數,添加此參數後,此指令在執行後隻通過 vCenter 建立任務,并不監控任務執行情況。
在執行某些批量任務的時候,例如虛拟機開關機、虛拟機建立會非常有用。
0x04. 使用多線程
Powershell 有多種方式來建立多線程任務,時間原因我隻測試使用了 PSJob ,除此之外還有 Runspace 和 RSJob ,有興趣的可以自行研究。
PSJob 使用條件
如果要使用 PSJob 建立多線程任務,前提條件有:
- Powershell 3.0 以上版本
- 主控端的記憶體和CPU足夠多
- 確定程式可以多線程運作
注意:在進行多線程改造前,需要確定多線程運作時,多個線程間不會沖突。例如多個程序同時寫入檔案便會沖突,但是同時讀取一個檔案就沒問題。
PSJobs 的 cmdlet 内置于 Microsoft.PowerShell.Core 中,可以使用下列指令檢視此 cmdlet 下的指令:
Get-Command *-Job
PSJobs 共有 8 種狀态,最常用的如下:
- Completed :Job 已經完成,可以擷取 job 輸出的資訊,或者 job 可以被安全移除。
- Running:Job 正在運作,無法停止(除非強制停止),無法擷取輸出資訊。
- Blocked:Job 正在運作,但是系統提示要求輸入資訊才能繼續。
- Failed:執行 Job 時出錯。
start-job 開始任務
使用
start-job
可以建立一個新的任務,如果使用 measure-command 檢視任務執行時間,可以看到遠遠小于指令真實的執行時間。
get-job 擷取 job 狀态
通過
get-job
指令可以檢視 Job 的運作情況。
PS51> Start-Job -Scriptblock {Start-Sleep 5}
PS51> Get-Job
上例中,如果任務還在運作,則 State 顯示 Running,如果運作完畢,State 為 Completed。
另外在運作完後 HasMoreData 值為 False,表示此 Job 沒有輸出。
下圖是其他狀态的截圖,使用的指令見 Command 列。
receive-job 擷取任務輸出
通過
receive-job
指令可以檢視 Job 的輸出資訊(使用
write-output
輸出),注意一旦輸出後 Job 中就不會再有這些資訊了,是以建議等 Job 完成後再完整收集資訊。
$Job = Start-Job -ScriptBlock {Write-Output 'Hello World'}
Receive-Job $Job
參數傳遞
預設 start-job 生産的任務無法使用全局定義的變量,需要 ArgumentList 進行變量的傳遞。
Start-Job -Scriptblock {param ($Text) Write-Output $Text} -ArgumentList "Hello world!"
任務執行完畢後的操作
在做一些批量收集的時候,我們通過
start-job
來建立了大量的 Job,但這隻是下發了任務,我們還需要在任務執行完畢後讀取并處理資料,在此我使用 for 循環定期檢查 Job 是不是已經完成,隻有等待完成後才批量讀取資料,具體操作如下:
$tasklist=@(
"task1"
"task2"
"task3"
)
$jobs=@()
Foreach ($task in $tasklist) {
write-host "Starting Job..."
$jobs += start-job -ScriptBlock {
param ($taskname)
sleep 5
Write-Output "Task Finished : "$taskname
} -ArgumentList $task}while($jobs.state -contains "Running")
{write-host "still running!" -ForegroundColor Yellow
sleep 1
}
write-host "Finished!" -ForegroundColor Greenreceive-job $jobs
輸出優化
上面提到可以在 job 内使用
Write-Output
輸出資訊 ,job 外使用
receive-job
來接受資訊,
在實際測試時發現
Write-Output
對于一個比較大的變量效率非常低(例如我嘗試輸出 Get-vmhost 的結果,幾十分鐘沒有反應),低到懷疑人生,是以最佳方法是在 Job 内完成該有的過濾。
例如:
Measure-Command{
write-host "Starting Job..."
$job = start-job -ScriptBlock { $debuginfo=Connect-VIServer -Server "192.168.1.1" -User '[email protected]' -Password 'VMware1!' -WarningAction Ignore $vmHost =get-vmhost "esx06.vsphere.local" Write-Output $vmHost Disconnect-VIServer -Server "192.168.1.1" -Confirm:$false}while($job.state -contains "Running") { write-host "still running!" -ForegroundColor Yellow sleep 1
}
write-host "Finished!" -ForegroundColor Greenreceive-job $job}
與
Measure-Command{ $job = start-job -ScriptBlock { $debuginfo=Connect-VIServer -Server "192.168.1.1" -User '[email protected]' -Password 'VMware1!' -WarningAction Ignore $vmHost =get-vmhost "esx06.vsphere.local"| select name,Version Write-Output $vmHost Disconnect-VIServer -Server "192.168.1.1" -Confirm:$false } while($job.state -contains "Running") { write-host "still running!" -ForegroundColor Yellow sleep 1 } write-host "Finished!" -ForegroundColor Greenreceive-job $job}
後面的指令隻是在輸出時增加了
select name,Version
,執行時間隻有 7 秒,而前面的程式幾十分鐘了還沒反應。
PSJob 應用于 PowerCLI
在應用于 PowerCLI 時,發現每個 job 都必須單獨連接配接 vCenter,好在這個操作可以将密碼寫入代碼,無需互動:
$debuginfo=Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false
$debuginfo=Connect-VIServer -Server "192.168.1.1" -User '[email protected]' -Password 'VMware1!' -WarningAction Ignore
在通過以上各種手段處理後,原本收集100台主機的資訊需要十幾分鐘,現在縮減到了兩分多鐘:
不過對應的,電腦風扇開始狂響。。
參考資料: