导航
  • 主页
  • .NET Framework
  • ASP
  • Web Services
  • .NET互联网桌面应用
  • VB
  • 图表区
  • 分析与设计
  • 组件/控件开发
  • AppLauncher
  • 问答

(捐献)防止可淹没应用程序的池溢出

xiaojunyaoac 2007-04-30 10:53:30
William Vaughn

大多数 ADO.NET 数据提供程序使用连接池,以提高围绕 Microsoft 断开连接的 .NET 结构构建的应用程序的性能。应用程序首先打开一个连接(或从连接池获得一个连接句柄),接着运行一个或多个查询,然后处理行集,最后将连接释放回连接池。如果没有连接池,这些应用程序将花费许多额外时间来打开和关闭连接。

当您使用 ADO.NET 连接池来管理基于 Web 的应用程序和客户端/服务器 Web 服务应用程序的连接时,您的客户通常会获得更快的连接和更好的总体性能。但是,当您的应用程序或 Web 站点上突然涌入了同时希望进行连接的大量客户时,会发生什么事情呢?您的应用程序会“沉没”,还是会“游泳”?就像救生员一样,您需要仔细监视连接池,以维护它的良好性能,并防止连接池发生溢出。我们首先探讨连接池可能溢出的原因,然后讨论如何编写代码或使用 Windows 性能监视器来监视连接池。

正如我于 2003 年 5 月发表的 "Swimming in the .NET Connection Pool" (InstantDoc ID 38356) 一文中讨论的那样,当您使用连接池时,您需要知道许多有关可伸缩性和性能的详细信息。请记住,您需要监视和管理两个基本因素:每个池管理的连接数和连接池的数量。在一个有效的生产系统中,池的数量通常很少(1 到 10),而且,使用中的连接的总数也很少(少于 12 )有效的查询只用不到一秒钟的时间就可以完成,并断开连接。因此,即使有数百个客户同时访问您的 Web 站点,相对较少的几个连接常常足以处理整个负载。为了使您的应用程序有效地运行,您必须使连接资源处于自己的控制之下,并要监视池的状态,这样,在监视池发生溢出以及您的客户开始抱怨(或离开您的网站)之前您会收到某种警告。

为什么会发生连接池溢出?

参加电子邮件讨论组的人常常抱怨应用程序是如何在测试中是“龙”而在形成为产品时就变成了“虫”的。有时,他们会报告说,当连接了大约 100 个客户端时,应用程序会停止或挂起。请记住,一个池中的默认连接数是 100。如果您尝试从池中打开 100 个以上的连接,ADO.NET 会使应用程序的连接请求排队等候,直到有空闲的连接。应用程序(及其用户)将这种情况视为进入 Web 页的延迟或视为应用程序死锁。让我们首先讨论一下这个问题是如何产生的。

在 ADO.NET 中,SqlClient .NET 数据提供程序为您提供了两种打开和管理连接的方法。首先,当您需要手工管理连接时,可以使用 DataReader 对象。利用这种方法,您的代码将构造一个 SqlConnection 对象,设置 ConnectionString 属性,然后使用 Open 方法来打开连接。当代码完成 DataReader 后,您要在 SqlConnection 对象停止作用之前关闭 SqlConnection。要处理行集,您可以将 DataReader 传递到应用程序中的另一个例程,但仍然需要确保 DataReader 及其连接处于关闭状态。如果您不关闭 SqlConnection,代码会“泄漏”每个操作的连接,于是连接池对连接进行累积,最后便发生溢出。与 ADO 和 Visual Basic (VB) 6.0 中的情况不同,.NET 垃圾回收器不会为您关闭 SqlConnection 并进行清理。我稍后要讨论的 清单 1 显示了如何打开连接和生成 DataReader 以从一个简单的查询返回行集,来向连接池施加压力的。

您也可能在使用 DataAdapter 对象时遇到问题。DataAdapter Fill 和 Update 方法可自动打开 DataAdapter 对象的连接,并在数据 I/O 操作完成后关闭该连接。不过,如果该连接在执行 Fill 或 Update 方法时已经处于打开状态,那么,ADO.NET 在方法执行完以后不会关闭 SqlConnection。这是另一个发生连接“泄漏”的机会。

此外,您还可以使用基于 COM 的 ADO 从 .NET 应用程序创建连接。ADO 利用与 ADO.NET 相同的方式将这些连接组合成池,但不能像您使用 SqlClient ADO.NET 数据提供程序时那样,提供从应用程序监视连接池的方式。

指示 DataReader

孤立连接和溢出池是严重的问题,根据有关这些问题的新闻组讨论的数量来看,它们十分常见。这些问题最有可能是由 DataReader 引起的。为了测试 DataReader 的行为,我编写了一个 Windows 窗体 (WinForms) 示例应用程序,该示例突出了 CommandBehavior.CloseConnection 选项。(您可以在
现在您必须决定要采取的措施,我不建议您告诉用户您已经用完了所有连接。有些应用程序会通知用户系统正忙于帮助其他客户,并建议用户稍后进行访问。其他应用程序则播放一段动画,通知用户系统尚未死锁,而是正在忙于处理他们的请求。同时,您的代码重新尝试操作。在所有情况下,您应该记录这些故障,以便帮助诊断问题的症结所在,并记录您已经耗尽了资源。

监视连接池

您已经打开和关闭了一个连接,现在您希望知道该连接是否仍然处于打开状态。您可以使用几种方法来确定有多少连接仍然处于打开状态,以及它们正在执行何种操作:


运行 sp_who 或 sp_who2。这些系统存储过程从 sysprocess 系统表返回信息,该系统表显示所有工作进程的状态及其有关信息。通常,您会看到每个连接有一个服务器进程 ID (SPID)。如果您是通过在连接字符串中使用 Application Name 参数来命名您的连接的,那么,您将很容易找到工作的连接。


使用带有 SQLProfiler TSQL_Replay 模板的 SQL Server 事件探查器来跟踪打开的连接。如果您很熟悉事件探查器,此方法比通过使用 sp_who 进行轮询要更容易。


使用性能监视器来监视池和连接。我稍后再讨论此方法。


在代码中监视性能计数器。您可以通过使用例程来提取计数器或通过使用新的 .NET PerformanceCounter 控件来监视连接池的状况和已建立的连接的数量。这两种方法都包括在您可以从
您还可以通过使用 SQL Server 性能计数器 "User Connections" 来监视打开的连接的数量。该计数器被列在 Performance 对象下拉列表中的 SQL Server: General Statistics 下。我喜欢监视 "User Connections" 值和一些所选定的 .NET CLR Data SqlClient 计数器(我稍后将讨论此内容),因为我可以获得我需要的信息,而不必担心实例。

使用代码来监视性能计数器。当您需要以编程方式监视连接池时,您可以编写代码来监视由 SqlClient 管理的性能计数器 — 这些计数器与 MMC Windows NT 性能监视器管理单元所提供的计数器是相同的。编写执行监视的代码似乎是一件有些令人畏惧的事情。但我已经提供了从 SqlClient 提供程序的内部工作提取这些计数器的例程的快照(作为本文提供的可下载程序之一)。

您可以编写检查 表 2显示的五个计数器的代码。通过利用这五个计数器,您可以实时监视连接池。.NET 预期您会在性能监视器中提供一个类别 — 复制的 Performance Object — 并从那些注册到系统的计数器中选择适当的计数器。要访问 SqlClient 计数器,请将该类别设置为 ".NET CLR Data"。

使用 PerformanceCounter 控件。您可能会发现,在设计时向您的应用程序窗体添加 PerformanceCounter 要比手工编写代码来访问性能计数器更加容易。要使用 PerformanceCounter 控件,请从“Visual Studio .NET 工具箱组件”菜单中选择一个 PerformanceCounter,将它拖到您的应用程序窗体,然后设置属性,如 图 2 所示。这些控件工作在 Web 窗体和 WinForms 应用程序中。

因为 PerformanceCounter 控件提供了方便的下拉列表,所以,您可以在设计时看到任何一种性能计数器类别、计数器名称和特定实例 — 您将要运行的实例除外。这意味着您必须使用图 2 显示的方法来捕获应用程序正在使用的池的适当实例。为了回避这个问题,我选择 _global_ 实例。再次说明一下,此方法假设某个应用程序已经至少创建了一个池,因此您需要做好不存在计数器实例时 ADO.NET 引发异常的准备,就像它在不存在池连接时也会引发异常一样。

注意不准确的池计数。因为 SqlClient .NET 数据提供程序中存在 .NET 框架 1.1 尚未解决的错误,所以,性能计数器会在池实际上已经删除时错误地指示池“仍然存在”。我能通过结束 MMC 性能监视器管理单元、然后结束 Visual Studio .NET 来验证池已经不再存在。这些步骤说明,.NET 数据提供程序在创建连接池的进程结束时会正确地删除连接池。显然,这种不准确性降低了性能计数器在监视池方面的有效性,所以我希望 Microsoft 将来能解决这个问题。

计数器不显示的内容

您可能会面临的一个问题是无法从计数器或 SqlClient 属性看到每个池的配置。每个 SqlConnection 对象的 ConnectionString 保存着这些池设置的密钥。因为您不能依赖于默认设置,所以很难确定池几乎已满或很难使用。这会成为未来版本的 ADO.NET 的另一个方便功能。

不过,假设您知道各个连接池 ConnectionString 参数的值,则利用清单 1 中的代码,您可以很容易地设置一个计时器来检查您创建的特定池并报告使用百分比。然后,监视应用程序会向您发出警报,以便您可以解决问题并防止溢出。

最后,请记住,ADO.NET 采用的方法与基于 COM 的 ADO 有所不同。Visual Basic .NET 完全改变了放弃对象的方式,并且不再确保 Connection 对象在停止作用时被关闭。请确保 SqlConnection 对象(或任何 Connection 对象)在停止作用之前被关闭。

连接池是一种非常强大的功能,它可以提高应用程序的性能。但如果您不是一个出色的救生员,您的连接池会成为一个危害而不是一个优点。我希望本文讨论的方法有助于您有效地监视连接池并满足用户的需要。
...全文
110 点赞 收藏 2
写回复
2 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
civyliu 2007-04-30
bd
回复
smile9961 2007-04-30
sf
回复
发动态
发帖子
.NET技术社区
创建于2007-09-28

5.8w+

社区成员

.NET技术交流专区
申请成为版主
社区公告
暂无公告