Windows-Server-2008-R2

Windows Server 2008 R2 元文件 RAM 使用情況

  • September 10, 2019

我有一台執行 Windows Server 2008 R2 x64 和 4GB RAM 的伺服器,它託管大約 2-3 百萬個文件,其中大部分是圖像文件。

在一周的時間裡,我注意到伺服器上的應用程序由於記憶體不足而對磁碟進行過多的分頁,從而導致伺服器上的應用程序緩慢爬行,這對目前執行在其上的所有服務產生了連鎖反應,導致性能問題。

在任務管理器中進行調查後,我注意到幾乎所有 4GB 都在使用中,但是當您查看“程序”選項卡時,那裡所有記憶體使用量的總和並沒有加起來,最多只有 1.5GB 應該在使用中。

使用Google查找解決方案,似乎大部分 RAM 都用於“元文件”中,該文件是文件系統上文件的 NTFS 資訊的記憶體,因此系統不必再次查詢 MFT 資訊。此記憶體永遠不會被清除或在任務管理器中標記為“記憶體”或在 Sysinternal 的 RamMap 中標記為“待機”。

有人建議安裝 KB979149 修補程序,但在嘗試安裝時,它顯示“此更新不適用於您的電腦”。

到目前為止,我發現的唯一臨時修復是:

  1. 每 1-3 天使用 Sysinternals 的 RAMmap 到“空系統工作集”,在任務管理器中將記憶體標記為“備用”和“記憶體”,以便其他應用程序可以使用 RAM。
  2. 重新啟動機器,這是不可取的,因為該伺服器正在為公共網站提供服務。

目前我必須每隔幾天執行 2. 修復以防止它達到瓶頸水平。

之前:(使用了 800 MB RAM - 其他應用程序無法使用此 RAM)

在此處輸入圖像描述

之後:(800 MB RAM 標記為記憶體 - 可用於其他應用程序)

所以我對你們所有人的問題是:是否存在任何方法來限制此元文件的 RAM 使用?

處理此問題的最佳方法是使用SetSystemFileCacheSizeAPI 作為MS KB976618 instructs 用於指示.

不要定期清除記憶體

使用該SetSystemFileCacheSize功能而不是定期清除記憶體可以提高性能和穩定性。定期清除記憶體將導致從記憶體中清除過多的元文件和其他資訊,Windows 將不得不將所需的資訊從 HDD 重新讀回 RAM。每當您清除記憶體時,這會導致性能突然嚴重下降幾秒鐘,然後隨著記憶體填充元文件數據而緩慢下降的良好性能。

使用該SetSystemFileCacheSize函式設置最小值和最大值,這將導致 Windows 將多餘的舊元文件數據標記為備用記憶體,正常記憶體函式可以根據目前資源需求和正常記憶體優先級使用或丟棄這些記憶體。這也允許比您設置的活動記憶體最大值更多的元文件數據在記憶體中作為備用數據(如果 Windows 沒有將記憶體用於其他任何事情),同時保持充足的可用記憶體。這是始終保持系統性能特性良好的理想情況。

MS 不支持第三方程序

如果您像我一樣不想在您的生產伺服器上執行來自未知第三方的二進製文件,您需要一個官方的 MS 工具或一些您可以在這些伺服器上執行之前檢查的程式碼。2008 R2 的 DynCache 工具實際上是不可能在不支付支持案例的情況下從 M$ 獲得的,坦率地說,根據 2008 年的程式碼,它似乎過於臃腫,因為 Windows 已經具有動態調整大小所需的內置邏輯記憶體——它只需要知道適合您系統的最大值。

解決以上所有問題

我編寫了一個可在 64 位機器上執行的 Powershell 腳本。您需要以具有提升權限的管理員身份執行它。您應該能夠按原樣在任何 x64 Windows Vista / Server 2008 (包括 10 / Server 2012 R2)和任意數量的 RAM 上執行它。您不需要安裝任何額外的軟體,因此您的伺服器/工作站完全得到 MS 的支持。

您應該在每次啟動時使用提升的權限執行此腳本,以使設置永久有效。Windows 任務計劃程序可以為您執行此操作。如果 Windows 安裝在虛擬機內,並且您更改了分配給該 VM 的 RAM 量,您還應該在更改後執行它。

即使在生產使用中,您也可以隨時在正在執行的系統上執行此腳本,而無需重新啟動系統或關閉任何服務。

# Filename: setfc.ps1
$version = 1.1

#########################
# Settings
#########################

# The percentage of physical ram that will be used for SetSystemFileCache Maximum
$MaxPercent = 12.5

#########################
# Init multipliers
#########################
$OSBits = ([System.IntPtr]::Size) * 8
switch ( $OSBits)
{
   32 { $KiB = [int]1024 }
   64 { $KiB = [long]1024 }
   default {
       # not 32 or 64 bit OS. what are you doing??
       $KiB = 1024 # and hope it works anyway
       write-output "You have a weird OS which is $OSBits bit. Having a go anyway."
   }
}
# These values "inherit" the data type from $KiB
$MiB = 1024 * $KiB
$GiB = 1024 * $MiB
$TiB = 1024 * $GiB
$PiB = 1024 * $TiB
$EiB = 1024 * $PiB


#########################
# Calculated Settings
#########################

# Note that because we are using signed integers instead of unsigned
# these values are "limited" to 2 GiB or 8 EiB for 32/64 bit OSes respectively

$PhysicalRam = 0
$PhysicalRam = [long](invoke-expression (((get-wmiobject -class "win32_physicalmemory").Capacity) -join '+'))
if ( -not $? ) {
   write-output "Trying another method of detecting amount of installed RAM."
}
if ($PhysicalRam -eq 0) {
   $PhysicalRam = [long]((Get-WmiObject -Class Win32_ComputerSystem).TotalPhysicalMemory) # gives value a bit less than actual
}
if ($PhysicalRam -eq 0) {
   write-error "Cannot Detect Physical Ram Installed. Assuming 4 GiB."
   $PhysicalRam = 4 * $GiB
}
$NewMax = [long]($PhysicalRam * 0.01 * $MaxPercent)
# The default value
# $NewMax = 1 * $TiB


#########################
# constants
#########################

# Flags bits
$FILE_CACHE_MAX_HARD_ENABLE     = 1
$FILE_CACHE_MAX_HARD_DISABLE    = 2
$FILE_CACHE_MIN_HARD_ENABLE     = 4
$FILE_CACHE_MIN_HARD_DISABLE    = 8


################################
# C# code
# for interface to kernel32.dll
################################
$source = @"
using System;
using System.Runtime.InteropServices;

namespace MyTools
{
   public static class cache
   {
       [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
       public static extern bool GetSystemFileCacheSize(
           ref IntPtr lpMinimumFileCacheSize,
           ref IntPtr lpMaximumFileCacheSize,
           ref IntPtr lpFlags
           );

       [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
       public static extern bool SetSystemFileCacheSize(
         IntPtr MinimumFileCacheSize,
         IntPtr MaximumFileCacheSize,
         Int32 Flags
       );

       [DllImport("kernel32", CharSet = CharSet.Unicode)]
       public static extern int GetLastError();

       public static bool Get( ref IntPtr a, ref IntPtr c, ref IntPtr d )
       {
           IntPtr lpMinimumFileCacheSize = IntPtr.Zero;
           IntPtr lpMaximumFileCacheSize = IntPtr.Zero;
           IntPtr lpFlags = IntPtr.Zero;

           bool b = GetSystemFileCacheSize(ref lpMinimumFileCacheSize, ref lpMaximumFileCacheSize, ref lpFlags);

           a = lpMinimumFileCacheSize;
           c = lpMaximumFileCacheSize;
           d = lpFlags;
           return b;
       }

       
       public static bool Set( IntPtr MinimumFileCacheSize, IntPtr MaximumFileCacheSize, Int32 Flags )
       {
           bool b = SetSystemFileCacheSize( MinimumFileCacheSize, MaximumFileCacheSize, Flags );
           if ( !b ) {
               Console.Write("SetSystemFileCacheSize returned Error with GetLastError = ");
               Console.WriteLine( GetLastError() );
           }
           return b;
       }
   }

   public class AdjPriv
   {
       [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
       internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

       [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
       internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);

       [DllImport("advapi32.dll", SetLastError = true)]
       internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);

       [StructLayout(LayoutKind.Sequential, Pack = 1)]
       internal struct TokPriv1Luid
       {
           public int Count;
           public long Luid;
           public int Attr;
       }
       internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
       internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
       internal const int TOKEN_QUERY = 0x00000008;
       internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;

       public static bool EnablePrivilege(long processHandle, string privilege, bool disable)
       {
           bool retVal;
           TokPriv1Luid tp;
           IntPtr hproc = new IntPtr(processHandle);
           IntPtr htok = IntPtr.Zero;
           retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
           tp.Count = 1;
           tp.Luid = 0;
           if(disable)
           {
               tp.Attr = SE_PRIVILEGE_DISABLED;
           } else {
               tp.Attr = SE_PRIVILEGE_ENABLED;
           }
           retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
           retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
           return retVal;
       }
   }
}
"@
# Add the c# code to the powershell type definitions
Add-Type -TypeDefinition $source -Language CSharp

#########################
# Powershell Functions
#########################
function output-flags ($flags)
{
   Write-output ("FILE_CACHE_MAX_HARD_ENABLE  : " + (($flags -band $FILE_CACHE_MAX_HARD_ENABLE) -gt 0) )
   Write-output ("FILE_CACHE_MAX_HARD_DISABLE : " + (($flags -band $FILE_CACHE_MAX_HARD_DISABLE) -gt 0) )
   Write-output ("FILE_CACHE_MIN_HARD_ENABLE  : " + (($flags -band $FILE_CACHE_MIN_HARD_ENABLE) -gt 0) )
   Write-output ("FILE_CACHE_MIN_HARD_DISABLE : " + (($flags -band $FILE_CACHE_MIN_HARD_DISABLE) -gt 0) )
   write-output ""
}

#########################
# Main program
#########################

write-output ""

#########################
# Get and set privilege info
$ProcessId = $pid
$processHandle = (Get-Process -id $ProcessId).Handle
$Privilege = "SeIncreaseQuotaPrivilege"
$Disable = $false
Write-output ("Enabling SE_INCREASE_QUOTA_NAME status: " + [MyTools.AdjPriv]::EnablePrivilege($processHandle, $Privilege, $Disable) )

write-output ("Program has elevated privledges: " + ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") )
write-output ""
whoami /PRIV | findstr /I "SeIncreaseQuotaPrivilege" | findstr /I "Enabled"
if ( -not $? )  {
   write-error "user Security Token SE_INCREASE_QUOTA_NAME: Disabled`r`n"
}
write-output "`r`n"


#########################
# Get Current Settings
# Init variables
$SFCMin = 0
$SFCMax = 0
$SFCFlags = 0
#Get Current values from kernel
$status = [MyTools.cache]::Get( [ref]$SFCMin, [ref]$SFCMax, [ref]$SFCFlags )
#typecast values so we can do some math with them
$SFCMin = [long]$SFCMin
$SFCMax = [long]$SFCMax
$SFCFlags = [long]$SFCFlags
write-output "Return values from GetSystemFileCacheSize are: "
write-output "Function Result : $status"
write-output "            Min : $SFCMin"
write-output ("            Max : $SFCMax ( " + $SFCMax / 1024 / 1024 / 1024 + " GiB )")
write-output "          Flags : $SFCFlags"
output-flags $SFCFlags


#########################
# Output our intentions
write-output ("Physical Memory Detected : $PhysicalRam ( " + $PhysicalRam / $GiB + " GiB )")
write-output ("Setting Max to " + $MaxPercent + "% : $NewMax ( " + $NewMax / $MiB + " MiB )`r`n")

#########################
# Set new settings
$SFCFlags = $SFCFlags -bor $FILE_CACHE_MAX_HARD_ENABLE # set max enabled
$SFCFlags = $SFCFlags -band (-bnot $FILE_CACHE_MAX_HARD_DISABLE) # unset max dissabled if set
# or if you want to override this calculated value
# $SFCFlags = 0
$status = [MyTools.cache]::Set( $SFCMin, $NewMax, $SFCFlags ) # calls the c# routine that makes the kernel API call
write-output "Set function returned: $status`r`n"
# if it was successfull the new SystemFileCache maximum will be NewMax
if ( $status ) {
   $SFCMax = $NewMax
}


#########################
# After setting the new values, get them back from the system to confirm
# Re-Init variables
$SFCMin = 0
$SFCMax = 0
$SFCFlags = 0
#Get Current values from kernel
$status = [MyTools.cache]::Get( [ref]$SFCMin, [ref]$SFCMax, [ref]$SFCFlags )
#typecast values so we can do some math with them
$SFCMin = [long]$SFCMin
$SFCMax = [long]$SFCMax
$SFCFlags = [long]$SFCFlags
write-output "Return values from GetSystemFileCacheSize are: "
write-output "Function Result : $status"
write-output "            Min : $SFCMin"
write-output ("            Max : $SFCMax ( " + $SFCMax / 1024 / 1024 / 1024 + " GiB )")
write-output "          Flags : $SFCFlags"
output-flags $SFCFlags

頂部附近有一條線表示$MaxPercent = 12.5將新的最大工作集(活動記憶體)設置為總物理 RAM 的 12.5%。Windows 將根據系統需求動態調整活動記憶體中元文件數據的大小,因此您無需動態調整此最大值。

這不會解決您在映射文件記憶體變得太大時遇到的任何問題。

我還製作了一個GetSystemFileCacheSizePowershell 腳本並將其發佈在 StackOverflow 上


編輯:我還應該指出,您不應多次從同一個 Powershell 實例執行這兩個腳本中的任何一個,否則您將收到Add-Type已進行呼叫的錯誤。

編輯:將SetSystemFileCacheSize腳本更新到 1.1 版,為您計算適當的最大記憶體值並具有更好的狀態輸出佈局。

編輯:現在我已經升級了我的 Windows 7 筆記型電腦,我可以告訴你該腳本在 Windows 10 中成功執行,儘管我還沒有測試它是否仍然需要。但是即使移動虛擬機硬碟文件,我的系統仍然很穩定。

引用自:https://serverfault.com/questions/325277