【发布时间】:2009-01-15 21:36:07
【问题描述】:
我有一个 Web 应用程序,它控制哪些 Web 应用程序从我们的负载平衡器获得流量。 Web 应用程序在每个单独的服务器上运行。
它在 ASP.NET 应用程序状态的对象中跟踪每个应用程序的“输入或输出”状态,并且只要状态更改,该对象就会序列化为磁盘上的文件。当 Web 应用程序启动时,状态会从文件中反序列化。
虽然网站本身只收到几个请求,而且它很少访问文件,但我发现由于某种原因在尝试读取或写入文件时很容易发生冲突。这种机制需要非常可靠,因为我们有一个自动化系统,定期对服务器进行滚动部署。
在任何人质疑上述任何一项的审慎性之前,请允许我简单地说,解释其背后的原因会使这篇文章比现在长得多,所以我想避免搬山。
也就是说,我用来控制对文件的访问的代码如下所示:
internal static Mutex _lock = null;
/// <summary>Executes the specified <see cref="Func{FileStream, Object}" /> delegate on
/// the filesystem copy of the <see cref="ServerState" />.
/// The work done on the file is wrapped in a lock statement to ensure there are no
/// locking collisions caused by attempting to save and load the file simultaneously
/// from separate requests.
/// </summary>
/// <param name="action">The logic to be executed on the
/// <see cref="ServerState" /> file.</param>
/// <returns>An object containing any result data returned by <param name="func" />.
///</returns>
private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
var l = new Logger();
if (ServerState._lock.WaitOne(1500, false))
{
l.LogInformation( "Got lock to read/write file-based server state."
, (Int32)VipEvent.GotStateLock);
var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate
, FileAccess.ReadWrite, FileShare.None);
result = func.Invoke(fileStream);
fileStream.Close();
fileStream.Dispose();
fileStream = null;
ServerState._lock.ReleaseMutex();
l.LogInformation( "Released state file lock."
, (Int32)VipEvent.ReleasedStateLock);
return true;
}
else
{
l.LogWarning( "Could not get a lock to access the file-based server state."
, (Int32)VipEvent.CouldNotGetStateLock);
result = null;
return false;
}
}
这通常有效,但有时我无法访问互斥锁(我在日志中看到“无法获得锁”事件)。我无法在本地重现这个 - 它只发生在我的生产服务器(Win Server 2k3/IIS 6)上。如果我删除超时,应用程序将无限期挂起(竞争条件??),包括后续请求。
当我确实收到错误时,查看事件日志会告诉我,在记录错误之前之前的请求已实现并释放了互斥锁。
互斥体在 Application_Start 事件中实例化。在声明中静态实例化它时,我得到相同的结果。
借口,借口:线程/锁定不是我的强项,因为我通常不必担心它。
关于为什么它随机无法获得信号的任何建议?
更新:
我添加了适当的错误处理(多么令人尴尬!),但我仍然遇到相同的错误 - 并且为了记录,未处理的异常从来都不是问题。
只有一个进程会访问该文件 - 我没有为此应用程序的网络池使用网络花园,也没有其他应用程序使用该文件。我能想到的唯一例外是当应用程序池回收时,旧的 WP 在创建新的 WP 时仍处于打开状态 - 但我可以从任务管理器中看出问题发生在只有一个工作进程时。
@mmr:使用 Monitor 与使用 Mutex 有何不同?根据 MSDN 文档,它看起来好像在有效地做同样的事情 - 如果我不能用我的 Mutex 获得锁,它确实通过返回 false 优雅地失败。
要注意的另一件事:我遇到的问题似乎完全是随机的 - 如果它在一个请求上失败,它可能会在下一个请求上正常工作。似乎也没有一种模式(至少肯定没有其他模式)。
更新 2:
此锁不用于任何其他调用。在 InvokeOnFile 方法之外引用 _lock 的唯一时间是在实例化时。
被调用的 Func 要么从文件中读取并反序列化为对象,要么将对象序列化并将其写入文件。这两个操作都不是在单独的线程上完成的。
ServerState.PATH 是一个静态只读字段,我不认为它会导致任何并发问题。
我还想重申我之前的观点,即我无法在本地(在 Cassini 中)重现这一点。
经验教训:
- 使用正确的错误处理(呵呵!)
- 为工作使用正确的工具(并对工具的作用/方式有基本的了解)。正如 sambo 指出的那样,使用 Mutex 显然有很多开销,这导致我的应用程序出现问题,而 Monitor 是专为 .NET 设计的。
【问题讨论】:
-
文件是两个服务器共享的单个文件,还是每个服务器共享一个文件?
-
你能描述一下调用 Invoke 需要多长时间吗?
-
您确定没有其他人在 ServerState._lock 上锁定...如果您将其替换为标准锁定模式(在静态对象上),它仍然挂起吗?
-
嗨丹尼尔,我更新了我的答案来回答你的问题。
-
@sambo99 - 只是用 DateTime 做一个简单的开始/结束比较。现在,它在 0ms 和 15ms 之间交替。是的,我敢肯定——我实际上是从标准锁定模式开始的,然后在挂起时改为互斥锁。