Windows Server 2019 引進了手動分隔儲存空間直接存取中的磁碟區配置的選項。 這樣做可以大幅提高特定條件下的容錯能力,但也會增加一些額外的管理考慮和複雜度。 本主題說明其運作方式,並在 PowerShell 中提供範例。
重要
此功能是 Windows Server 2019 的新功能。 不適用於 Windows Server 2016。
必要條件
考慮在以下情況下使用此選項:
- 您的叢集有六部以上的伺服器;且
- 您的叢集僅使用 三向鏡像 容錯性
如果下列事項,請勿使用此選項:
了解
檢閱:一般配置
使用一般三向鏡像時,磁碟區會分成許多小型「Slab」,這些「Slab」會複製三次,並平均分散到叢集中每個伺服器中的每個磁碟機。 如需詳細資訊,請閱讀 此深入探討部落格。
此預設配置可將平行讀取和寫入次數最大化,進而提高效能,並具有簡潔化的優點:每個伺服器繁忙程度相同,每個磁碟機均充分利用,所有磁碟區同時連線或離線。 如 這些範例 所示,每個磁碟區都保證能承受兩個同時發生的故障。
不過,使用此配置時,磁碟區無法承受三次並行失敗。 如果三個伺服器同時失敗,或三個伺服器中的磁碟機同時失敗,磁碟區會變成無法存取,因為至少有一些 Slab (極有可能) 會配置給失敗的三個磁碟機或伺服器。
在下列範例中,伺服器 1、3 和 5 同時失敗。 雖然許多 Slab 有倖存的複本,但有些沒有:
磁碟區會離線並變成無法存取,直到伺服器復原為止。
新增:分隔配置
使用分隔配置時,您會指定 (至少四個) 要使用的伺服器子集。 這個磁碟區被劃分成板狀,像之前一樣,板狀被複製三次,但與之前不同的是,板狀不會配置到每部伺服器,而是僅配置給您指定的伺服器子集。
例如,如果您有 8 個節點叢集 (節點 1 到 8),則可以指定磁碟區只位於節點 1、2、3、4 中的磁碟。
優點
使用範例配置時,磁碟區很可能承受三個並行失敗。 如果節點 1、2 和 6 關閉,則儲存磁碟區的 3 個資料副本的節點中只有 2 個關閉,磁碟區將保持連線狀態。
生存機率取決於伺服器數目和其他因素 – 請參閱 分析 以取得詳細數據。
缺點
分隔配置會強加一些新增的管理考量和複雜性:
系統管理員負責分隔每個磁碟區的配置,以平衡伺服器之間的記憶體使用率,並維護高生存機率,如 最佳做法 一節所述。
使用分隔配置,保留相當於每部伺服器的一個容量磁碟驅動器(沒有最大值)。 這超過一般配置的 已發佈建議 ,最大值為四個容量磁碟驅動器總數。
如果伺服器失敗且需要取代,如 移除伺服器及其磁碟驅動器中所述,系統管理員會負責更新受影響磁碟區的分隔符,方法是新增伺服器並移除失敗的磁碟區 – 范例如下。
PowerShell 中的使用方式
您可以使用 New-Volume
Cmdlet 在儲存空間直接存取中建立磁碟區。
例如,若要建立一般三向鏡像磁碟區:
New-Volume -FriendlyName "MyRegularVolume" -Size 100GB
建立磁碟區並分隔其配置
若要建立三向鏡像磁碟區並分隔其配置:
首先,將叢集中的伺服器指派給變數
$Servers
:$Servers = Get-StorageFaultDomain -Type StorageScaleUnit | Sort FriendlyName
提示
在儲存空間直接存取中,「儲存體縮放單位」一詞是指連結至一個伺服器的所有原始儲存體,包括直接連結的磁碟機和具有磁碟機的直接連結外部機箱。 在此內容中,它與「伺服器」相同。
透過使用新的
-StorageFaultDomainsToUse
參數並編制索引到$Servers
來指定要使用的伺服器。 例如,若要將配置分隔至第一、第二、第三和第四伺服器(索引 0、1、2 和 3):New-Volume -FriendlyName "MyVolume" -Size 100GB -StorageFaultDomainsToUse $Servers[0,1,2,3]
請參閱分隔配置
若要查看 MyVolume 的設定方式,請使用Get-VirtualDiskFootprintBySSU.ps1
附錄中的腳稿:
PS C:\> .\Get-VirtualDiskFootprintBySSU.ps1
VirtualDiskFriendlyName TotalFootprint Server1 Server2 Server3 Server4 Server5 Server6
----------------------- -------------- ------- ------- ------- ------- ------- -------
MyVolume 300 GB 100 GB 100 GB 100 GB 100 GB 0 0
請注意,只有 Server1、Server2、Server3 和 Server4 包含 MyVolume 的分區。
變更分隔配置
使用新的 Add-StorageFaultDomain
和 Remove-StorageFaultDomain
Cmdlet 來變更配置分隔方式。
例如,若要將 MyVolume 移至一部伺服器:
指定第五台伺服器 可以 儲存 MyVolume 的資料塊:
Get-VirtualDisk MyVolume | Add-StorageFaultDomain -StorageFaultDomains $Servers[4]
指定第一部伺服器 無法 儲存 MyVolume 的區塊。
Get-VirtualDisk MyVolume | Remove-StorageFaultDomain -StorageFaultDomains $Servers[0]
重新平衡儲存集區,使變更生效:
Get-StoragePool S2D* | Optimize-StoragePool
您可以使用 Get-StorageJob
來監視重新平衡的進度。
完成後,請再次執行 以確認 Get-VirtualDiskFootprintBySSU.ps1
是否已經移動。
PS C:\> .\Get-VirtualDiskFootprintBySSU.ps1
VirtualDiskFriendlyName TotalFootprint Server1 Server2 Server3 Server4 Server5 Server6
----------------------- -------------- ------- ------- ------- ------- ------- -------
MyVolume 300 GB 0 100 GB 100 GB 100 GB 100 GB 0
請注意,Server1 不再包含 MyVolume 的分區,改由 Server5 包含。
最佳作法
以下是使用分隔磁碟區配置時遵循的最佳做法:
選擇四個伺服器
將每個三向鏡像磁碟區分隔為四個伺服器,而非更多伺服器。
平衡儲存體
平衡配置給每部伺服器的儲存體數量,並考慮磁碟區大小。
交錯分隔配置磁碟區
若要最大化容錯能力,請讓每個磁碟區的配置成為唯一的,這表示它不會與另一個磁碟區共用 其所有 伺服器(有些重疊沒問題)。
例如,在八個節點系統上:磁碟區 1:伺服器 1、2、3、4 磁碟區 2:伺服器 5、6、7、8 磁碟區 3:伺服器 3、4、5、6 磁碟區 4:伺服器 1、2、7、8
分析
本節得到一個磁碟區保持連線和可存取狀態的數學機率 (或同等於保持連線和可存取狀態的總儲存體的預期比例),是失敗數目和叢集大小的函式。
注意
本節是選擇性閱讀內容。 如果想了解其中的數學原理,請繼續閱讀! 但如果沒有,別擔心:PowerShell 中的使用和最佳做法是您需要成功實作定界分配。
只要不超過兩個失敗,就沒問題
不論配置為何,每個三向鏡像磁碟區最多可以同時承受兩次失敗。 如果兩個磁碟機失敗,或兩個伺服器失敗,或磁碟機和伺服器各有一個失敗,則每個三向鏡像磁碟機仍能保持連線狀態且可存取,即使是一般配置也是如此。
不能有超過一半的叢集失敗
相反地,在極端情況下,叢集中超過一半的伺服器或磁碟驅動器同時故障,仲裁會遺失,而且每一個三向鏡像磁碟區都會離線且無法存取,無論其配置如何。
如果是介於兩者之間呢?
如果一次發生三個或三個以上失敗,但至少有一半的伺服器和磁碟機仍在啟動中,則具有分隔配置的磁碟區可能會保持連線且可存取,視哪些伺服器失敗而定。
常見問題集
我可以分隔某些磁碟區,但不能分隔其他磁碟區嗎?
是。 您可以選擇個別磁碟區是否要分隔配置。
分隔配置會變更磁碟機更換的運作方式嗎?
否,這與一般配置相同。
其他參考
附錄
此指令碼可協助您查看磁碟區的配置方式。
若要如上所述使用,請複製/貼上並儲存為 Get-VirtualDiskFootprintBySSU.ps1
。
Function ConvertTo-PrettyCapacity {
Param (
[Parameter(
Mandatory = $True,
ValueFromPipeline = $True
)
]
[Int64]$Bytes,
[Int64]$RoundTo = 0
)
If ($Bytes -Gt 0) {
$Base = 1024
$Labels = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
$Order = [Math]::Floor( [Math]::Log($Bytes, $Base) )
$Rounded = [Math]::Round($Bytes/( [Math]::Pow($Base, $Order) ), $RoundTo)
[String]($Rounded) + " " + $Labels[$Order]
}
Else {
"0"
}
Return
}
Function Get-VirtualDiskFootprintByStorageFaultDomain {
################################################
### Step 1: Gather Configuration Information ###
################################################
Write-Progress -Activity "Get-VirtualDiskFootprintByStorageFaultDomain" -CurrentOperation "Gathering configuration information..." -Status "Step 1/4" -PercentComplete 00
$ErrorCannotGetCluster = "Cannot proceed because 'Get-Cluster' failed."
$ErrorNotS2DEnabled = "Cannot proceed because the cluster is not running Storage Spaces Direct."
$ErrorCannotGetClusterNode = "Cannot proceed because 'Get-ClusterNode' failed."
$ErrorClusterNodeDown = "Cannot proceed because one or more cluster nodes is not Up."
$ErrorCannotGetStoragePool = "Cannot proceed because 'Get-StoragePool' failed."
$ErrorPhysicalDiskFaultDomainAwareness = "Cannot proceed because the storage pool is set to 'PhysicalDisk' fault domain awareness. This cmdlet only supports 'StorageScaleUnit', 'StorageChassis', or 'StorageRack' fault domain awareness."
Try {
$GetCluster = Get-Cluster -ErrorAction Stop
}
Catch {
throw $ErrorCannotGetCluster
}
If ($GetCluster.S2DEnabled -Ne 1) {
throw $ErrorNotS2DEnabled
}
Try {
$GetClusterNode = Get-ClusterNode -ErrorAction Stop
}
Catch {
throw $ErrorCannotGetClusterNode
}
If ($GetClusterNode | Where State -Ne Up) {
throw $ErrorClusterNodeDown
}
Try {
$GetStoragePool = Get-StoragePool -IsPrimordial $False -ErrorAction Stop
}
Catch {
throw $ErrorCannotGetStoragePool
}
If ($GetStoragePool.FaultDomainAwarenessDefault -Eq "PhysicalDisk") {
throw $ErrorPhysicalDiskFaultDomainAwareness
}
###########################################################
### Step 2: Create SfdList[] and PhysicalDiskToSfdMap{} ###
###########################################################
Write-Progress -Activity "Get-VirtualDiskFootprintByStorageFaultDomain" -CurrentOperation "Analyzing physical disk information..." -Status "Step 2/4" -PercentComplete 25
$SfdList = Get-StorageFaultDomain -Type ($GetStoragePool.FaultDomainAwarenessDefault) | Sort FriendlyName # StorageScaleUnit, StorageChassis, or StorageRack
$PhysicalDiskToSfdMap = @{} # Map of PhysicalDisk.UniqueId -> StorageFaultDomain.FriendlyName
$SfdList | ForEach {
$StorageFaultDomain = $_
$_ | Get-StorageFaultDomain -Type PhysicalDisk | ForEach {
$PhysicalDiskToSfdMap[$_.UniqueId] = $StorageFaultDomain.FriendlyName
}
}
##################################################################################################
### Step 3: Create VirtualDisk.FriendlyName -> { StorageFaultDomain.FriendlyName -> Size } Map ###
##################################################################################################
Write-Progress -Activity "Get-VirtualDiskFootprintByStorageFaultDomain" -CurrentOperation "Analyzing virtual disk information..." -Status "Step 3/4" -PercentComplete 50
$GetVirtualDisk = Get-VirtualDisk | Sort FriendlyName
$VirtualDiskMap = @{}
$GetVirtualDisk | ForEach {
# Map of PhysicalDisk.UniqueId -> Size for THIS virtual disk
$PhysicalDiskToSizeMap = @{}
$_ | Get-PhysicalExtent | ForEach {
$PhysicalDiskToSizeMap[$_.PhysicalDiskUniqueId] += $_.Size
}
# Map of StorageFaultDomain.FriendlyName -> Size for THIS virtual disk
$SfdToSizeMap = @{}
$PhysicalDiskToSizeMap.keys | ForEach {
$SfdToSizeMap[$PhysicalDiskToSfdMap[$_]] += $PhysicalDiskToSizeMap[$_]
}
# Store
$VirtualDiskMap[$_.FriendlyName] = $SfdToSizeMap
}
#########################
### Step 4: Write-Out ###
#########################
Write-Progress -Activity "Get-VirtualDiskFootprintByStorageFaultDomain" -CurrentOperation "Formatting output..." -Status "Step 4/4" -PercentComplete 75
$Output = $GetVirtualDisk | ForEach {
$Row = [PsCustomObject]@{}
$VirtualDiskFriendlyName = $_.FriendlyName
$Row | Add-Member -MemberType NoteProperty "VirtualDiskFriendlyName" $VirtualDiskFriendlyName
$TotalFootprint = $_.FootprintOnPool | ConvertTo-PrettyCapacity
$Row | Add-Member -MemberType NoteProperty "TotalFootprint" $TotalFootprint
$SfdList | ForEach {
$Size = $VirtualDiskMap[$VirtualDiskFriendlyName][$_.FriendlyName] | ConvertTo-PrettyCapacity
$Row | Add-Member -MemberType NoteProperty $_.FriendlyName $Size
}
$Row
}
# Calculate width, in characters, required to Format-Table
$RequiredWindowWidth = ("TotalFootprint").length + 1 + ("VirtualDiskFriendlyName").length + 1
$SfdList | ForEach {
$RequiredWindowWidth += $_.FriendlyName.Length + 1
}
$ActualWindowWidth = (Get-Host).UI.RawUI.WindowSize.Width
If (!($ActualWindowWidth)) {
# Cannot get window width, probably ISE, Format-List
Write-Warning "Could not determine window width. For the best experience, use a Powershell window instead of ISE"
$Output | Format-Table
}
ElseIf ($ActualWindowWidth -Lt $RequiredWindowWidth) {
# Narrower window, Format-List
Write-Warning "For the best experience, try making your PowerShell window at least $RequiredWindowWidth characters wide. Current width is $ActualWindowWidth characters."
$Output | Format-List
}
Else {
# Wider window, Format-Table
$Output | Format-Table
}
}
Get-VirtualDiskFootprintByStorageFaultDomain