Windows Server 2019 引入了一个选项,用于在存储空间直通中手动界定卷的分配。 这样做可以在某些情况下显著提高容错能力,但会增加一些管理注意事项和复杂性。 本主题介绍了其工作原理,并在 PowerShell 中提供了示例。
Important
此功能是 Windows Server 2019 中的新增功能。 该功能在 Windows Server 2016 中不可用。
Prerequisites
如果出现以下情况,请考虑使用此选项:
- 群集有六个或更多服务器;并且
- Your cluster uses only three-way mirror resiliency
如果出现以下情况,请勿使用此选项:
- 群集拥有少于六个服务器;或者
- Your cluster uses parity or mirror-accelerated parity resiliency
Understand
检查:定期分配
使用常规的三向镜像,卷被划分为多个小“板块”,这些“板块”将复制三次,并平均分布在群集中每个服务器的每个驱动器上。 有关更多详细信息,请阅读此“深入了解”博客。
此默认分配可将并行读取和写入数最大化,从而提高性能,并具有简洁化的优点:每个服务器繁忙程度相同,每个驱动器均充分利用,所有卷同时联机或脱机。 Every volume is guaranteed to survive up to two concurrent failures, as these examples illustrate.
但是,使用此分配,卷无法承受三次并发故障。 如果三台服务器同时发生故障,或者如果三台服务器中的驱动器同时发生故障,卷将变得不可访问,因为至少会有一些板块(非常有可能)分配给了故障的三个驱动器或服务器。
在下面的示例中,服务器 1、3 和 5 同时故障。 尽管许多板块有用于应对故障的副本,但有些没有:
卷会脱机、在服务器恢复之前不可访问。
新增:界定的分配
使用界定的分配,可以指定要使用的服务器子集(至少四个)。 该卷与以前一样被划分为复制三次的板块,但这些板块只分配给指定的服务器子集,而不是分配给所有服务器。
例如,如果有一个 8 节点群集(节点 1 到 8),则可以指定一个卷仅位于节点 1、2、3、4 中的磁盘上。
Advantages
使用示例分配时,卷很可能能够承受三次并发故障。 如果节点 1、2 和 6 关闭,则保存卷的 3 个数据副本的节点中只有 2 个关闭,卷将保持联机状态。
Survival probability depends on the number of servers and other factors – see Analysis for details.
Disadvantages
界定的分配增加了一些管理注意事项和复杂性:
The administrator is responsible for delimiting the allocation of each volume to balance storage utilization across servers and uphold high probability of survival, as described in the Best practices section.
通过划分分配,为每个服务器预留相当于一个容量驱动器的空间(没有最大容量)。 This is more than the published recommendation for regular allocation, which maxes out at four capacity drives total.
如果服务器发生故障且需要替换(如删除服务器及其驱动器中所述),管理员负责通过添加新服务器并删除失败的服务器来更新界定的受影响卷的信息 -示例如下。
PowerShell 中的用法
可以使用 New-Volume
cmdlet 在存储空间直通中创建卷。
例如,创建常规的三向镜像卷:
New-Volume -FriendlyName "MyRegularVolume" -Size 100GB
创建卷并界定其分配
创建三向镜像卷并界定其分配:
首先将群集中的服务器分配给变量
$Servers
:$Servers = Get-StorageFaultDomain -Type StorageScaleUnit | Sort FriendlyName
Tip
在存储空间直通中,术语“存储缩放单元”是指连接到一个服务器的所有原始存储,包括直接连接的驱动器和直接连接的带驱动器的外部机箱。 在此上下文中,它与“服务器”相同。
通过使用新
-StorageFaultDomainsToUse
参数并索引到$Servers
来指定要使用的服务器。 例如,若要将分配界定为第一、第二、第三和第四台服务器(索引 0、1、2 和 3):New-Volume -FriendlyName "MyVolume" -Size 100GB -StorageFaultDomainsToUse $Servers[0,1,2,3]
参见界定的分配
To see how MyVolume is allocated, use the Get-VirtualDiskFootprintBySSU.ps1
script in Appendix:
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
Note that only Server1, Server2, Server3, and Server4 contain slabs of MyVolume.
更改界定的分配
使用新的 Add-StorageFaultDomain
和 Remove-StorageFaultDomain
cmdlet 更改分配的界定方式。
For example, to move MyVolume over by one server:
Specify that the fifth server can store slabs of MyVolume:
Get-VirtualDisk MyVolume | Add-StorageFaultDomain -StorageFaultDomains $Servers[4]
Specify that the first server cannot store slabs of MyVolume:
Get-VirtualDisk MyVolume | Remove-StorageFaultDomain -StorageFaultDomains $Servers[0]
重新均衡存储池,使更改生效:
Get-StoragePool S2D* | Optimize-StoragePool
可以使用 Get-StorageJob
监视重新均衡的进度。
Once it is complete, verify that MyVolume has moved by running Get-VirtualDiskFootprintBySSU.ps1
again.
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
Note that Server1 does not contain slabs of MyVolume anymore – instead, Server5 does.
Best practices
下面是使用界定的卷分配时要遵循的最佳做法:
选择四个服务器
将每个三向镜像卷界定到四个服务器,而不是更多服务器。
Balance storage
根据卷大小,均衡分配给每个服务器的存储量。
错开界定的分配卷
To maximize fault tolerance, make each volume's allocation unique, meaning it does not share all its servers with another volume (some overlap is okay).
例如,在八节点系统上:卷 1:服务器 1、2、3、4 卷 2:服务器 5、6、7、8 卷 3:服务器 3、4、5、6 卷 4:服务器 1、2、7、8
Analysis
此部分得到一个卷保持联机和可访问状态的数学概率(或等效于保持联机和可访问状态的总存储的预期比例),是与故障数和群集大小的函数。
Note
本部分是选择阅读内容。 如果想了解其中的数学原理,请继续阅读! 如果不阅读也无碍:只需阅读 PowerShell 中的用法和最佳做法就能成功实现界定的分配。
故障不超过两次就无碍
无论怎样分配,每个三向镜像卷最多可以同时经受两次故障。 如果两个驱动器发生故障,或两个服务器发生故障,或驱动器和服务器各有一个发生故障,每个三向镜像卷仍能保持联机状态并可访问,即使是常规分配也是如此。
不能有超过一半的群集发生故障
相反,在极端情况下,群集中超过一半的服务器或驱动器同时故障,仲裁将失效,所有三向镜像卷都会脱机,无论使用的是哪种分配方式,都将无法访问。
如果介于两种情况之间呢?
如果一次发生三个或三个以上故障,但至少有一半的服务器和驱动器仍处于正常状态,则具有界定分配的卷可能会保持联机状态并可访问,具体取决于哪些服务器发生故障。
常见问题解答
是否可以界定某些卷,但不界定其他卷?
Yes. 可以选择是否按卷界定分配。
界定的分配是否会更改驱动器的替换方式?
不会,与常规分配相同。
Additional References
Appendix
此脚本可帮助你了解卷的分配方式。
如前所述,请复制/粘贴并保存为 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