我有一台运行 Windows Server 2008 R2 x64 的服务器,配备 4GB RAM,托管大约 200 万到 300 万个文件,其中大多数是图像文件。
在一周的时间里,我注意到由于内存不足导致磁盘分页过多,服务器上的应用程序运行速度变得非常慢,这对当前在其上运行的所有服务产生了连锁反应,最终导致了严重的性能问题。
在任务管理器中调查后,我注意到几乎所有 4GB 都处于使用状态,但是当您查看“进程”选项卡时,那里的所有内存使用量总和并不相加,最多只有 1.5GB 应该处于使用状态。
使用 Google 查找解决方案,似乎大多数 RAM 都用于“元文件”,它是文件系统上文件的 NTFS 信息缓存,因此系统不必再次查询 MFT 以获取信息。此缓存从未被清除或在任务管理器中标记为“缓存”,或在 Sysinternal 的 RamMap 中标记为“待机”。
有一条建议安装 KB979149 修补程序,但在尝试安装时,系统显示“此更新不适用于您的计算机”。
到目前为止我发现的唯一临时修复方法是:
- 每 1-3 天使用 Sysinternals 的 RAMmap 来“清空系统工作集”,在任务管理器中将缓存标记为“待机”和“缓存”,以便其他应用程序可以使用 RAM。
- 重新启动机器,但这是不可取的,因为该服务器正在为公共网站提供服务。
目前我必须每隔几天执行一次 2. 修复,以防止它达到瓶颈水平。
之前:(使用 800 MB RAM - 其他应用程序无法使用此 RAM)
之后:(800 MB RAM 标记为缓存 - 可供其他应用程序使用)
所以我想问大家一个问题:是否存在什么方法可以限制这个图元文件的 RAM 使用量?
答案1
处理此问题的最佳方法是使用SetSystemFileCacheSize
API 作为MS KB976618 指示 用于指导。
不要定期清除缓存
使用该SetSystemFileCacheSize
功能而不是定期清除缓存可提高性能和稳定性。定期清除缓存会导致过多的图元文件和其他信息从内存中清除,Windows 将不得不从 HDD 重新将所需信息读回 RAM。每当您清除缓存时,这会导致性能突然严重下降几秒钟,随后性能会随着内存中填满图元文件数据而逐渐下降。
使用该SetSystemFileCacheSize
函数设置最小值和最大值,这将导致 Windows 将多余的旧图元文件数据标记为备用内存,正常缓存功能可以根据当前资源需求和正常缓存优先级使用或丢弃这些内存。如果 Windows 未将内存用于其他任何用途,这还允许比您设置的活动内存最大值更多的图元文件数据作为备用数据存储在内存中,同时保持足够的可用内存。这是始终保持系统性能特征良好的理想情况。
MS 不支持第三方程序
如果您和我一样,不想在生产服务器上运行来自某个未知第三方的二进制文件,那么您需要一个官方的 MS 工具或一些可以在这些服务器上运行之前进行检查的代码。如果不支付支持费用,几乎不可能从 M$ 获得 2008 R2 的 DynCache 工具,而且坦率地说,根据 2008 的代码,它似乎过于臃肿,无法完成这项任务,因为 Windows 已经具有动态调整缓存大小所需的内置逻辑 — 它只需要知道适合您系统的最大值。
以上所有问题的解决办法
我编写了一个可以在 64 位计算机上运行的 Powershell 脚本。您需要以管理员身份运行它,并具有提升的权限。您应该能够在任何 x64 Windows Vista / Server 2008 至 10 / Server 2012 R2 上运行它,并且具有任意数量的 RAM。您不需要安装任何其他软件,因此您的服务器/工作站将完全受到 MS 的支持。
您应该在每次启动时以提升的权限运行此脚本,以使设置永久生效。Windows 任务计划程序可以为您执行此操作。如果 Windows 安装在虚拟机内,并且您更改了分配给该虚拟机的 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 将根据系统需求动态调整活动内存中图元文件数据量的大小,因此您无需动态调整此最大值。
这不能解决映射文件缓存过大的任何问题。
我还编写了一个GetSystemFileCacheSize
Powershell 脚本,将其发布在 StackOverflow 上。
编辑:我还应该指出,您不应该从同一个 Powershell 实例多次运行这两个脚本,否则您将收到Add-Type
已进行调用的错误。
编辑:更新SetSystemFileCacheSize
脚本至 1.1 版本,可为您计算合适的最大缓存值,并具有更好的状态输出布局。
编辑:现在我已经升级了我的 Windows 7 笔记本电脑,我可以告诉你,该脚本在 Windows 10 中成功运行,尽管我还没有测试它是否仍然需要。但即使在移动虚拟机硬盘文件时,我的系统仍然很稳定。
答案2
我不敢说我是 Windows 操作系统内存或磁盘缓存内部工作原理方面的专家,但我有两点观察:
如果操作系统没有将数据缓存在内存中,它就必须从磁盘读取数据,而磁盘是一种比内存慢得多的存储介质,因此您现在看到的性能问题几乎肯定会更严重。
您试图通过治疗问题的症状而不是问题的根源来解决问题。问题的原因几乎肯定是缺乏足够的物理 RAM,我的建议是解决这个问题。
此外,虽然缓存可能使用了 1.5GB 的 RAM,但我想知道其他进程和服务的内存使用情况是多少,解决方案可能是调查该使用情况以查找潜在问题。
答案3
对于那些给出显而易见但无效的解决方案(仅添加更多 RAM)的人来说,你们显然没有亲自处理过这个问题。
正如之前的一位发帖者所说,无论你投入多少内存来解决这个问题……内存都会被填满。我正在我们的应用服务器上运行 Atlassian 工具集,该工具集已从 32 位 (2003) 迁移到 64 位 (2008)。很明显,性能有所下降。
查看任务管理器时,几乎所有内存都已用完;尽管正在运行的进程并未反映这一点。当我们将内存从 8 GB 增加到 16 GB 时,问题也随之而来,即消耗了额外的内存。
解决这个问题的唯一方法是重启服务器,这样内存使用量就会下降到与进程数量相当的水平(大约 3.5 GB)。大约一天后,内存使用量又开始上升。
我知道这是微软的一个新错误/功能,很高兴找到这篇文章。我喜欢微软把这个非常重要的细节留给用户去解决。我下载了 RamMap,你可能会认为它是一个原生实用程序,现在我可以看到 Metafile 的使用情况。我们将设置缓存每隔几天清除一次,希望这能解决这个问题。
有趣的是,我只在我们迁移的几台服务器中的一台上看到过这个问题,所以我想知道图元文件是否仅由某些类型的应用程序提供。
答案4
抱歉这么直接,但你升级服务器的内存容量,比现在工作站的内存容量稍微高一点怎么样?16GB 内存太便宜了。比你花半天时间还便宜。