你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

整体持久性反模式

将所有应用程序的数据放入单个数据存储可能会削弱性能,要么因为它会导致资源争用,要么因为数据存储不适合某些数据。

上下文和问题

从历史上看,无论应用程序可能需要存储的不同类型的数据,应用程序都使用了单个数据存储。 组织使用此方法简化应用程序设计或匹配开发团队的现有技能集。

现代基于云的系统通常具有额外的功能和非功能要求。 这些系统需要存储许多异类数据类型,例如文档、图像、缓存数据、排队消息、应用程序日志和遥测。 遵循传统方法并将所有这些信息放入同一数据存储可能会削弱性能,原因有两个主要原因:

  • 在同一数据存储中存储和检索大量不相关的数据可能会导致争用,从而导致响应时间变慢和连接失败。
  • 无论选择哪种数据存储,它可能不适合所有类型的数据。 或者,它可能未针对应用程序执行的操作进行优化。

以下示例演示了一个 ASP.NET Web API 控制器,该控制器向数据库添加新记录,并将结果记录到日志中。 日志存储在与业务数据相同的数据库中。

public class MonoController : ApiController
{
    private static readonly string ProductionDb = ...;

    public async Task<IHttpActionResult> PostAsync([FromBody]string value)
    {
        await DataAccess.InsertPurchaseOrderHeaderAsync(ProductionDb);
        await DataAccess.LogAsync(ProductionDb, LogTableName);
        return Ok();
    }
}

生成日志记录的速率可能会影响业务运营的性能。 如果另一个组件(例如应用程序进程监视器)定期读取和处理日志数据,这也会影响业务运营。

解决方案

根据数据使用情况分隔数据。 对于每个数据集,请选择最适合使用该数据集方式的数据存储。 在前面的示例中,应用程序应将日志记入一个独立于保存业务数据的数据库的存储区:

public class PolyController : ApiController
{
    private static readonly string ProductionDb = ...;
    private static readonly string LogDb = ...;

    public async Task<IHttpActionResult> PostAsync([FromBody]string value)
    {
        await DataAccess.InsertPurchaseOrderHeaderAsync(ProductionDb);
        // Log to a different data store.
        await DataAccess.LogAsync(LogDb, LogTableName);
        return Ok();
    }
}

问题和注意事项

  • 根据你如何使用和访问数据来分隔数据。 例如,不要在同一数据存储中存储日志信息和业务数据。 这些类型的数据具有不同的访问要求和模式。 日志记录本质上是有序的,而业务数据更有可能需要随机访问,并且通常是关系型的。

  • 请考虑每种数据类型的数据访问模式。 例如,将格式化的报表和文档存储在文档数据库中,例如 Azure Cosmos DB。 使用 Azure Redis 缓存 来缓存临时数据。

  • 请在遵循本指南但仍达到数据库限制时,考虑扩展数据库容量。 此外,请考虑水平缩放,并跨数据库服务器对负载进行分区。 但是,分区可能需要重新设计应用程序。 有关详细信息,请参阅 数据分区

检测问题

当系统耗尽数据库连接等资源时,系统可能会大幅降低速度,最终会失败。

可执行以下步骤来帮助确定原因:

  1. 配置系统以记录关键性能统计信息。 捕获每个操作的计时信息。 并捕获应用程序读取和写入数据的点。

  2. 在生产环境中监视系统几天,以获取系统使用方式的实际视图。 如果无法执行此过程,请使用执行典型操作系列的真实数量的虚拟用户运行脚本化负载测试。

  3. 使用遥测数据确定性能不佳的时间段。

  4. 确定在这些时间段内访问了哪些数据存储。

  5. 确定可能会面临争用的数据存储资源。

示例诊断

以下部分将这些步骤应用到前面所述的示例应用程序。

检测和监视系统

下图显示了前面所述的示例应用程序负载测试的结果。 测试使用最多 1,000 个并发用户的步骤负载。

显示基于 SQL 的控制器的负载测试性能结果的图形。

随着负载增加至 700 个用户,吞吐量也随之增加。 但此时,吞吐量会稳定,系统似乎以最大容量运行。 平均响应随着用户负载逐渐增加,表明系统无法跟上需求。

确定性能不佳的时间段

如果监视生产系统,你可能会注意到模式。 例如,响应时间可能会在每天的同一时间显著下降。 常规工作负荷或计划的批处理作业可能会导致此波动。 或者系统在某些时候可能有更多的用户。 应专注于这些事件的遥测数据。

查找响应时间增加与数据库活动增加或对共享资源的输入/输出(I/O)之间的相关性。 如果存在相关性,则表示数据库可能是瓶颈。

识别在这些时段访问了哪些数据存储

下一个图显示负载测试期间数据库吞吐量单位(DTU)的利用率。 DTU 是可用容量的度量值。 它是 CPU 利用率、内存分配和 I/O 速率的组合。 DTU 的使用率迅速达到 100%。 在上图中,吞吐量在此点达到峰值。 在测试完成之前,数据库利用率一直很高。 最后略有下降,这可能是由于限制、数据库连接竞争或其他因素造成的。

显示 Azure 经典门户中的数据库监视器的图形,其中显示了数据库的资源利用率。

检查数据存储的遥测数据

检测数据存储以捕获活动的低级详细信息。 在示例应用程序中,数据访问统计信息显示针对 PurchaseOrderHeader 表和 MonoLog 表执行的大量插入作。

显示示例应用程序的数据访问统计信息的图形。

识别资源争用

在这一点上,你可以查看源代码,重点关注应用程序访问竞争资源的地方。 找到如下所述的情况:

  • 被逻辑上分离的数据被写入同一个存储区。 日志、报告和排队消息等数据不应与业务信息保存在同一数据库中。
  • 所选数据存储与数据类型(例如关系型数据库中的大型 Blob 或 XML 文档)之间不匹配。
  • 数据具有不同的使用模式,但共享相同的存储。 示例包括将高写入低读取数据与低写入高读取数据一起存储。

实现解决方案并验证结果

此示例将应用程序更新为将日志写入单独的数据存储。 下图显示了负载测试结果。

显示使用 Polyglot 控制器的负载测试结果的图形。

吞吐量的模式与前面的图表类似,但性能峰值的点大约比之前高出每秒 500 个请求。 平均响应时间略低。 但是,这些统计信息不会讲述完整的故事。 业务数据库的遥测数据显示,DTU 使用率峰值约为 75%,而不是 100%。

显示 Azure 经典门户中的数据库监视器的图形,其中显示了 Polyglot 方案中数据库的资源利用率。

同样,日志数据库的最大 DTU 使用率仅达到约 70%。 数据库不再是系统性能的限制因素。

显示 Azure 经典门户中数据库监视器的图表,展示了多语言方案中日志数据库的资源利用率。