Windows Server 2008 R2图元文件RAM使用情况

我有一台运行Windows Server 2008 R2 x64的服务器,配有4GB的RAM,大约有2-3百万个文件,其中大部分是图像文件。

在一个星期的时间里,我注意到服务器上的应用程序由于内存不足而导致磁盘过度分页,从而导致缓慢的爬行,这对当前正在运行的所有服务产生了连锁效应,性能问题。

在任务pipe理器中进行调查后,我发现几乎所有的4GB都在使用中,但是在“进程”选项卡中查看时,所有内存使用情况的总和不会相加,最多只能使用1.5GB。

使用Google查找解决scheme,看起来大部分内存使用在“图元文件”中,这是文件系统上文件的NTFS信息caching,因此系统不必再次向MFT查询信息。 这个caching在任务pipe理器中永远不会被清除或标记为“caching”,或者在Sysinternal的RamMap中被标记为“备用”。

有一个build议安装KB979149修补程序,但尝试安装它时,它说“此更新不适用于您的计算机”。

到目前为止我发现的唯一临时修复是:

  1. 使用Sysinternals的RAMmap每隔1-3天“清空系统工作集”,在任务pipe理器中将caching标记为“待机”和“caching”,这样RAM可以被其他应用程序使用。
  2. 重新启动机器,这是不可取的,因为这台服务器正在服务于公共网站。

目前,我不得不每隔几天执行一次,以防止瓶颈达到瓶颈。

之前:(使用800MB RAM – 其他应用程序不能使用此RAM)

之后:(800MB RAM标记为caching – 可用于其他应用程序)

所以我的问题都是:是否有任何方法限制这个图元文件的内存使用量?

处理此问题的最佳方法是使用SetSystemFileCacheSize API作为MS KB976618指示。

不要定期清除caching

使用SetSystemFileCacheSize函数而不是定期清除caching提高了性能和稳定性。 定期清除caching将导致过多的元文件和其他信息从内存中清除,Windows将不得不从硬盘重新读取所需的信息。 每当您清除caching时,这会导致性能突然剧烈下降几秒钟,然后随着内存填充元文件数据而缓慢下降。

使用SetSystemFileCacheSize函数设置最小值和最大值,这会导致窗口将过时的旧元文件数据标记为备用内存,以便正常cachingfunction根据当前资源需求和正常caching优先级使用或丢弃。 这也允许更多的图元文件数据比你设置的最大活动内存大,如果在保持大量可用内存的情况下,窗口没有使用内存的话,它将作为备用数据存储在内存中。 这是始终保持系统性能特点的理想情况。

第三方程序不受MS支持

如果你像我一样,不想在你的生产服务器上运行一个不知名的第三方的二进制文件,你需要一个官方的MS工具或者一些你可以在这些服务器上运行的代码。 用于2008 R2的DynCache工具实际上不可能从M $获得,而不需要支付支持案例,坦率地说,根据2008年的代码,它似乎过于臃肿的任务,因为Windows已经有内置的逻辑需要dynamicresizecaching,它只需要知道一个适当的系统最大值。

解决所有上述问题

我写了一个适用于64位机器的powershell脚本。 您需要以具有提升特权的pipe理员身份运行它。 您应该可以在任何x64的Windows Vista / Server 2008上运行,直至包括10 / Server 2012 R2及任意数量的RAM。 您不需要安装任何额外的软件,因此您的服务器/工作站可以完全支持MS。

您应该在每次启动时使用提升的权限运行此脚本,以使该设置成为永久性的。 Windows任务计划程序可以为你做这个。 如果Windows安装在虚拟机内,并且更改分配给该虚拟机的RAM数量,则还应该在更改后运行它。

即使在生产环境中,您也可以在运行的系统上随时运行此脚本,而无需重新启动系统或closures任何服务。

# 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将根据系统需求dynamic调整活动内存中元文件数据的大小,因此您无需dynamic调整此最大值。

这不会解决您在映射文件caching过大时遇到的任何问题。

我也做了一个GetSystemFileCacheSize powershell脚本,并发布在https://stackoverflow.com/questions/5898843/c-sharp-get-system-file-cache-size/17875550#17875550


编辑:我也应该指出,你不应该从相同的PowerShell实例多次运行这两个脚本的任何一个,否则你会收到错误的Add-Type调用已经作出。

编辑:更新SetSystemFileCacheSize脚本到版本1.1,为您计算适当的最大caching值,并有一个更好的状态输出布局。

编辑:现在我已经升级了我的Windows 7笔记本电脑,我可以告诉你,脚本在Windows 10中成功运行,但我还没有testing,如果它仍然需要。 但是即使移动虚拟机硬盘文件,我的系统仍然是稳定的。

我不认为是Windows操作系统内存或磁盘caching内部工作的专家,但我有两个意见:

  1. 如果操作系统没有将数据caching在内存中,则必须从磁盘读取数据,这是存储介质和内存之间的指数级速度,所以现在看到的性能问题肯定会变得更糟。

  2. 你试图通过处理问题的症状而不是问题的原因来解决问题。 问题的原因几乎可以肯定是缺乏足够的物理RAM,我的build议是解决这个问题。

另外,虽然caching可能使用了1.5GB的RAM,但是我不知道其他进程和服务的内存使用情况,可能的解决scheme是调查这种使用情况以了解潜在的问题。

对于刚刚添加更多内存的人来说,显然没有办法解决这个问题。

正如前面的一个海报所说的那样,在这个问题上抛出多less内存并不重要,它将全部填满。 我在我们的应用程序服务器上运行了一个Atlassian工具,该工具从32位(2003)迁移到64位(2008)。 显而易见的是性能损失。

在看任务pipe理器时,几乎所有的内存都用完了; 即使正在运行的进程没有反映这一点。 当我们将内存从8 GB增加到16 GB时,问题也消耗了额外的内存。

处理问题的唯一方法是重新启动服务器,这会降低与进程相同的内存使用量(约3.5 GB)。 在一天左右内又开始攀升。

我知道这是一个新的Microsoft bug /function,很高兴find这篇文章。 我喜欢微软如何离开这个重要的细节,让用户了解。 我下载了RamMap,你会认为它是一个本地工具,现在我可以看到Metafile的用法。 我们将设置caching每隔几天清除一次,希望能够解决这个问题。

有趣的是,我只在我们的几个迁移的服务器上看过这个问题,所以我想知道是否只有特定types的应用程序提供图元文件。

这个问题可以使用SysInternals CacheSet工具快速解决。 只需将工作集的最大值设置为小于系统RAM的适当值,然后应用即可。

对不起,如此直接,但是你怎么把服务器升级到比现在工作站稍高一点的内存呢? 16GB的memroy吓坏了。 比你的时间半天便宜。

这里是一个链接,下载Microsoft DynCache工具 – 无需创build票或付费。 http://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=9258

(道歉 – 现在只注意到这不适用于R2版本)

有关持续高速caching增长的已知问题,请参阅 Microsoft博客: http : //blogs.msdn.com/b/ntdebugging/archive/2007/11/27/too-much-cache.aspx

Windows Server 2008 R2的[更新]工作修复程序。

我在Codeplex上find示例C#代码,使用Visual Studio快速创build了一个控制台C#项目并编译,工作。

https://asstoredprocedures.svn.codeplex.com/svn/ASSP/FileSystemCache.cs

请注意,您需要添加一个对Microsoft.AnalysisServices.AdomdClient的引用,可以在这里find它:

C:\ Program Files(x86)\ Microsoft.NET \ ADOMD.NET

并用(在我的情况下)对XMLaDiscover的不必要的引用注释掉ClearAllCaches()方法。 把这个放到TaskScheduler中。

您可以从MS获得DynCache工具,这将允许通过图元文件来限制RAM的使用。

点击这里从MS获取工具 。