分享方式:


分隔儲存空間直接存取中的磁碟區配置

Windows Server 2019 引進了手動分隔儲存空間直接存取中的磁碟區配置的選項。 這樣做可以大幅提高特定條件下的容錯能力,但也會增加一些額外的管理考慮和複雜度。 本主題說明其運作方式,並在 PowerShell 中提供範例。

重要

此功能是 Windows Server 2019 的新功能。 不適用於 Windows Server 2016。

必要條件

綠色複選標記圖示。 如果出現以下情況,請考慮使用此選項:

  • 您的叢集有六部以上的伺服器;且
  • 您的叢集只使用三向鏡像復原

紅色 X 圖示。 如果出現以下情況,請勿使用此選項:

了解

檢閱:一般配置

使用一般三向鏡像時,磁碟區會分成許多小型「Slab」,這些「Slab」會複製三次,並平均分散到叢集中每個伺服器中的每個磁碟機。 如需詳細資訊,請參閱此深入探討部落格

此圖顯示磁碟區分成三個板堆疊,並平均分散到每部伺服器。

此預設配置可將平行讀取和寫入次數最大化,進而提高效能,並具有簡潔化的優點:每個伺服器繁忙程度相同,每個磁碟機均充分利用,所有磁碟區同時連線或離線。 每個磁碟區皆保證能承受最多兩次並行失敗,如這些範例所示。

不過,使用此配置時,磁碟區無法承受三次並行失敗。 如果三個伺服器同時失敗,或三個伺服器中的磁碟機同時失敗,磁碟區會變成無法存取,因為至少有一些 Slab (極有可能) 會配置給失敗的三個磁碟機或伺服器。

在下列範例中,伺服器 1、3 和 5 同時失敗。 雖然許多 Slab 有倖存的複本,但有些沒有:

此圖顯示以紅色醒目提示的六部伺服器中有三部,而整體磁碟區為紅色。

磁碟區會離線並變成無法存取,直到伺服器復原為止。

新增:分隔配置

使用分隔配置時,您會指定 (至少四個) 要使用的伺服器子集。 該磁碟區與以前一樣被劃分為複製三次的 Slab,但這些 Slab 只配置給指定的伺服器子集,而不是配置給所有伺服器。

例如,如果您有 8 個節點叢集 (節點 1 到 8),則可以指定磁碟區只位於節點 1、2、3、4 中的磁碟。

優點

使用範例配置時,磁碟區很可能承受三個並行失敗。 如果節點 1、2 和 6 關閉,則儲存磁碟區的 3 個資料副本的節點中只有 2 個關閉,磁碟區將保持連線狀態。

生存機率取決於伺服器數和其他因素,如需詳細資訊,請參閱分析

缺點

分隔配置會強加一些新增的管理考量和複雜性:

  1. 系統管理員負責分隔每個磁碟區的配置,以平衡伺服器之間的儲存體使用率,並維護高生存率,如最佳做法一節所述。

  2. 使用分隔配置時,為每個伺服器預留相當於一個容量磁碟機的空間 (沒有容量上限)。 這超出了針對一般配置的已發佈的建議中所述的量,其中建議最多配置不超過四個容量磁碟機。

  3. 如果伺服器失敗且需要替換 (如移除伺服器及其磁碟機中所述),系統管理員負責透過新增伺服器並移除失敗的伺服器來更新界定的受影響磁碟區的資訊,範例如下。

PowerShell 中的使用方式

您可以使用 New-Volume Cmdlet 在儲存空間直接存取中建立磁碟區。

例如,若要建立一般三向鏡像磁碟區:

New-Volume -FriendlyName "MyRegularVolume" -Size 100GB

建立磁碟區並分隔其配置

若要建立三向鏡像磁碟區並分隔其配置:

  1. 首先,將叢集中的伺服器指派給變數 $Servers

    $Servers = Get-StorageFaultDomain -Type StorageScaleUnit | Sort FriendlyName
    

    提示

    在儲存空間直接存取中,「儲存體縮放單位」一詞是指連結至一個伺服器的所有原始儲存體,包括直接連結的磁碟機和具有磁碟機的直接連結外部機箱。 在此內容中,它與「伺服器」相同。

  2. 透過使用新的 -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 的 Slab。

變更分隔配置

使用新的 Add-StorageFaultDomainRemove-StorageFaultDomain Cmdlet 來變更配置分隔方式。

例如,若要將 MyVolume 移動一個伺服器:

  1. 指定第五個伺服器可以儲存 MyVolume 的 Slab:

    Get-VirtualDisk MyVolume | Add-StorageFaultDomain -StorageFaultDomains $Servers[4]
    
  2. 指定第一個伺服器不可以儲存 MyVolume 的 Slab:

    Get-VirtualDisk MyVolume | Remove-StorageFaultDomain -StorageFaultDomains $Servers[0]
    
  3. 重新平衡儲存集區,使變更生效:

    Get-StoragePool S2D* | Optimize-StoragePool
    

您可以使用 Get-StorageJob 來監視重新平衡的進度。

完成後,請再次執行 Get-VirtualDiskFootprintBySSU.ps1,確認 MyVolume 已移動。

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 的 Slab,而是 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