65.9K
CodeProject 正在变化。 阅读更多。
Home

通过性能监控识别 NHibernate 相关瓶颈

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (12投票s)

2007 年 3 月 3 日

CPOL

9分钟阅读

viewsIcon

89030

downloadIcon

170

一种用于检测和隔离 NHibernate 相关性能和可伸缩性问题的提炼方法。

概述

我的工作需要我在DBA和软件工程师之间保持相对平衡的技能。通过这个职位,我学到了一些两方之间常见的误解。NHibernate对软件工程师来说非常棒,因为它抽象掉了数据层的大部分细节。然而,就像任何ORM框架一样,它附带的警告是,你必须注意它与数据库交互的方式。

这是两篇帮助您的基于NHibernate的应用程序满足性能和可伸缩性需求的文章的第一部分。本文重点在于有效收集数据并理解瓶颈。第二部分可以在这里找到。

首先,在考虑解决方案之前,正确识别您的性能问题至关重要,因为您需要能够量化更改所带来的性能差异。已经有许多书籍和应用程序可以帮助您进行应用程序负载测试。以下部分提出了一种使用基本工具识别数据层瓶颈的精炼方法。

您需要的工具

  • SQL Server(首选开发人员版):这与您的(企业/标准)生产服务器是相同的发行版,只不过它是为隔离的开发环境构建的。我们将使用的特定工具包括:

    • SQL Profiler - 独一无二!我们广泛使用它来查看NHibernate发送给数据库的每个数据库调用,以及每个调用产生的时间和CPU成本。对于SQL profiler的新手,请确保学习基础知识

    • 查询分析器 - 我们使用SQL profiler收集问题区域中的数据库调用集合。我们使用查询分析器通过查看其编译的执行计划来剖析和评估原子数据库调用的性能。优化特定查询本身就是一门艺术,超出了本文的范围。要学习如何通过查询分析器正确阅读和解释执行计划的基础知识,可以从这里开始入门

  • .NET分析器: 在更高的层面,我们需要一些程序能够从应用程序的.NET端报告性能,最好是能够以执行树路径的形式显示方法调用和执行时间的执行跟踪。根据我的经验,我可以推荐一些程序,例如JetBrains DotTraceAQTimeACT

  • 压力生成器: 随意选择您喜欢的第三方工具,但您需要的功能是能够以可变数量的并发请求多次“轰击”您的目标用例或方法。为了最大限度地控制和集中精力,如果您还没有负载测试模块,可以考虑编写一些自定义的模块。我附上了一个Visual Studio 2005中的示例项目,应该能帮助您指明正确的方向。这是一个骨架控制台应用程序,包含命令行、线程池和我们过去使用过的模拟设置。希望您能花更少的时间编写负载测试代码,花更多的时间进行实验!

  • Perfmon: 一个非常宝贵的性能指标工具,已内置在您的Windows系统中。各种优秀的计数器可以提供信息,但过多的信息可能会混淆视听。运行可伸缩性测试时请使用以下性能计数器:

    • SQLServer:锁::平均等待时间: 伸缩性差的标志,这显示了一个给定线程在完成其工作之前等待锁释放的时间。

    • 内存::每秒页错误: 数据库从磁盘读取而不是内存的频率是多少?这可以为改进索引提供线索。

进一步要求

  • 隔离您的测试环境: 性能测试是一项实验。就像任何适当的实验一样,我们需要建立一个受控环境。消除任何潜在的外部干扰非常重要。如果可能,尝试在本地机器上将应用程序作为独立程序运行。

    • 拔掉您的以太网电缆
    • 本地 SQL Server
    • 没有不必要的服务或程序,特别是任何可能在 SQL profiler 中产生“噪音”的东西。

瓶颈检测程序

  1. 一般流程

    当您知道需要调整的“用例”区域,但不确定具体哪些方法有问题时,请使用以下步骤收集更具体的数据。

    1. 启动您的程序,使其进入所讨论用例的初始状态。
    2. 启动 .NET 分析器,将其附加到您的应用程序。
    3. 运行用例。
    4. 用例完成后,停止 .NET 分析器,查看报告。

    .NET 分析器的数据将帮助我们了解哪些方法调用突出且有问题。一旦找到目标方法,您可以使用以下测试来深入挖掘真正的罪魁祸首。

  2. 方法特定(原子测试)

    给定一个目标方法,是时候找出该方法在单次调用中如何影响数据库了。

    1. 在该方法的开始和结束处设置断点,调试程序并到达方法调用。
    2. 启动 SQL profiler,并分析目标数据库。
    3. 运行调试器,一步一步地观察顺序数据库交互。看到NHibernate正在生成的命令了吗?记下哪些.NET命令转换为哪些SQL命令。
    4. 将分析器结果保存到数据库表中。

    是否有单个数据库调用耗时很长?数据库调用是否比您预期的多?记下来,并尝试找出原因,但要抵制过早得出结论的诱惑。

  3. 方法特定(可伸缩性测试)

    绘制一些图表,显示执行时间(以.NET配置文件或查询配置文件为单位)与线程数的关系。每个图表都有一定数量的方法调用(例如,1、10、100、1000等的方法调用图表)。

    如果可伸缩性是您的主要问题,那么您的图表看起来会有点像这样:

    良好的可伸缩性表现为随着线程的引入,执行时间会减少。当然,您可以添加的线程数量有一个阈值,超过这个阈值,“上下文切换”开销会阻碍您的性能(也称为“颠簸”),但这很自然,并取决于硬件和操作系统。以下是一个在最多5个线程下良好扩展的示例:

    您的应用程序在开始出现性能问题之前能管理多少个并发线程?这个阈值是否足以满足您的需求?

    此时,保存您的.NET跟踪、分析器跟踪和可伸缩性结果。请确保您可以在将来重新运行这些测试,以便在您为应用程序添加“涡轮增压”后,精确量化性能提升!

筛选您的跟踪,找出问题的源头

以下 SQL 将帮助您找到最常用的查询

SELECT
DISTINCT cast(textdata as varchar(150)) as textdata,
avg(duration) as avg_duration,
count(duration) as Occurences 
FROM
[<yourTraceTableHere>] 
GROUP BY
Cast(textdata as VarChar(150))
ORDER BY
count(duration)desc

这将帮助您找到效率较低的查询

SELECT
DISTINCT cast(textdata as varchar(150)) as textdata,
avg(duration) as avg_duration,
count(duration) as Occurences
FROM
[<yourTraceTableHere>] 
GROUP BY
Cast(textdata as VarChar(150))
ORDER BY 
Avg(duration)desc 
  • 按CPU成本、执行时间对您的SQL Profile跟踪进行排序,以查找昂贵的查询。注意以下任何一点:

    • 是否有类似的查询过于频繁地发生?
    • 它们能否通过某种缓存绕过?
    • 您是否希望有一个存储过程来绕过一些昂贵且频繁调用的语句?
    • 单个表是否反复进行插入/更新?
    • 单个相对静态的表是否反复被查询,让您希望能够缓存它?

  • 在某些查询中,您是否加载了超出实际需要的数据?

  • 您是否比必要调用了更多的数据库?

列出您怀疑的问题,并按严重程度排序。此时,实施解决方案是很诱人的,但在开始之前,请确保您对问题所在以及它造成的总体时间成本有很好的把握。

常见的ORM相关性能问题

ORM会给您的系统带来哪些常见问题?

“喋喋不休”的应用程序服务器问题

很多时候,您的 .NET 层和数据库层之间存在过多的 I/O “对话”。您调用一个实体层对象,NHibernate 会自动从数据库中的值“填充”该对象。填充一个对象实际上需要哪些工作?

  • NHibernate 使用您选择的方言生成 SQL
  • NHibernate 为数据库分配一个连接
  • 数据库服务器解析、验证并将 SQL 编译成查询计划
  • 数据库服务器将交互广播给所有监听器
  • 数据库执行已编译的查询计划并将结果存储在数据库原生记录集中
  • 记录集转换为 DBLib 特定的数据结构(例如 ODBC 记录集)
  • 记录集被传输回 NHibernate 层
  • 记录集转换为 SQL Data Reader
  • NHibernate 将 SQL 数据读取器中的数据转换为请求的对象。

这可不是一丁点的 I/O 工作!任何优秀的 DBA 都会向您宣讲,加载超出实际需要的数据是一种糟糕的做法。下次您进行 DAO 调用时,请考虑这一点。

您加载的每个对象都会往返数据库吗?如果是这样,它会以数据库层通信的形式带来相当大的开销。结果呢?您的初步测试看起来不错,但稍后您会发现可伸缩性极差。解决方案?通过增加数据库调用的粒度来减少层之间的“对话”。下一章将解释如何管理此问题。

“贪婪”加载问题

有时,您可能想要一个特定的对象,但由于您的映射定义,您总是获得超出实际需要的数据。我见过“贪婪”加载问题以三种不同的方式出现:

  1. 缺乏使用“延迟加载”关系属性。在NHibernate 1.0.3.0中,默认情况下,子集合都不是延迟加载的。在NHibernate 1.2中,所有子集合默认都是延迟加载的。您是否在应该使用延迟属性的地方遗漏了它,或者您更适合采用立即加载方案?
  2. 当您只需要集合中的一个元素时,却加载了一个对象集合。您是否曾遍历一个巨大的“延迟”集合,只为了检索单个对象而将其余的留给垃圾回收器?考虑一下这个集合可能会变得多大,并考虑通过更有效的方式访问单个对象。
  3. 加载子对象(具有多对一关联的对象)会隐式强制加载父对象,以及伴随加载父对象的所有内容。这意味着,如果您加载一个具有长辈多对一关联的子对象,您会隐式加载所有父级、祖父级等,一直到树的顶部以及任何级别的相关数据。下一章将解释如何管理此问题。

我的下一部分将最终深入探讨如何控制NHibernate数据层访问,以实现更好的粒度、更高效的锁定、缓存方案、独立搜索,以及是的,存储过程/函数。

第二部分:性能增强

可以在此处找到。

© . All rights reserved.