ADO.NET 粒度持久性

基于泛型 ADO.NET 功能构建的关系存储后端代码 Orleans ,因此与数据库供应商无关。 按照配置指南中所述Orleans设置连接字符串。

若要使 Orleans 代码函数与给定的关系数据库后端配合使用,需要满足以下条件:

  1. 将相应的 ADO.NET 库加载到进程中。 例如,通过应用程序配置中的 DbProviderFactories 元素来定义这一点。
  2. 通过选项中的 Invariant 属性配置 ADO.NET 不变式。
  3. 确保数据库存在并且与代码兼容。 为此,请运行特定于供应商的数据库创建脚本。 有关详细信息,请参阅 ADO.NET 配置

ADO.NET 粒度存储提供程序允许在关系数据库中存储粒度状态。 目前支持以下数据库:

  • SQL Server
  • MySQL/MariaDB
  • PostgreSQL
  • 甲骨文

首先,安装基本包:

Install-Package Microsoft.Orleans.Persistence.AdoNet

有关配置数据库的信息,请参阅 ADO.NET 配置 文章,包括相应的 ADO.NET 固定脚本和设置脚本。

以下示例演示如何通过 ISiloHostBuilder以下方法配置 ADO.NET 存储提供程序:

var siloHostBuilder = new HostBuilder()
    .UseOrleans(c =>
    {
        c.AddAdoNetGrainStorage("OrleansStorage", options =>
        {
            options.Invariant = "<Invariant>";
            options.ConnectionString = "<ConnectionString>";
            options.UseJsonFormat = true;
        });
    });

实质上,您只需设置数据库供应商专属的连接字符串和Invariant以确认供应商(请参阅ADO.NET 配置)。 还可以选择保存数据的格式:二进制(默认值)、JSON 或 XML。 虽然二进制文件是最紧凑的选项,但它不透明,你将无法直接读取或处理数据。 建议使用 JSON 选项。

可以通过以下方法 AdoNetGrainStorageOptions设置以下属性:

/// <summary>
/// Options for AdoNetGrainStorage
/// </summary>
public class AdoNetGrainStorageOptions
{
    /// <summary>
    /// Define the property of the connection string
    /// for AdoNet storage.
    /// </summary>
    [Redact]
    public string ConnectionString { get; set; }

    /// <summary>
    /// Set the stage of the silo lifecycle where storage should
    /// be initialized.  Storage must be initialized prior to use.
    /// </summary>
    public int InitStage { get; set; } = DEFAULT_INIT_STAGE;
    /// <summary>
    /// Default init stage in silo lifecycle.
    /// </summary>
    public const int DEFAULT_INIT_STAGE =
        ServiceLifecycleStage.ApplicationServices;

    /// <summary>
    /// The default ADO.NET invariant will be used for
    /// storage if none is given.
    /// </summary>
    public const string DEFAULT_ADONET_INVARIANT =
        AdoNetInvariants.InvariantNameSqlServer;

    /// <summary>
    /// Define the invariant name for storage.
    /// </summary>
    public string Invariant { get; set; } =
        DEFAULT_ADONET_INVARIANT;

    /// <summary>
    /// Determine whether the storage string payload should be formatted in JSON.
    /// <remarks>If neither <see cref="UseJsonFormat"/> nor <see cref="UseXmlFormat"/> is set to true, then BinaryFormatSerializer will be configured to format the storage string payload.</remarks>
    /// </summary>
    public bool UseJsonFormat { get; set; }
    public bool UseFullAssemblyNames { get; set; }
    public bool IndentJson { get; set; }
    public TypeNameHandling? TypeNameHandling { get; set; }

    public Action<JsonSerializerSettings> ConfigureJsonSerializerSettings { get; set; }

    /// <summary>
    /// Determine whether storage string payload should be formatted in Xml.
    /// <remarks>If neither <see cref="UseJsonFormat"/> nor <see cref="UseXmlFormat"/> is set to true, then BinaryFormatSerializer will be configured to format storage string payload.</remarks>
    /// </summary>
    public bool UseXmlFormat { get; set; }
}

ADO.NET 持久性提供程序可以对数据进行版本控制,并使用自定义应用程序规则和流式处理来定义任意的(反)序列化程序,但目前没有办法将此功能直接公开给应用程序代码。

ADO.NET 持久性理由

ADO.NET 支持的持久性存储的原则是:

  1. 在数据、数据格式和代码不断发展时,保持业务关键型数据的安全和可访问性。
  2. 利用特定于供应商的功能和特定于存储的功能。

在实践中,这意味着遵守 ADO.NET 实现目标,并在 ADO.NET 特定的存储提供程序中包含一些附加的实现逻辑,以允许存储中的数据结构的发展。

除了常见的存储提供程序功能之外,ADO.NET 提供程序还具有内置功能:

  1. 在往返状态时,将存储数据从一种格式更改为另一种格式(例如,从 JSON 更改为二进制)。
  2. 可以以任意方式调整要保存或从存储中读取的类型。 这样,状态版本就可以演变。
  3. 从数据库中流式传输数据。

可以基于任意决策参数,例如1.2.有效负载数据,应用

这样,你可以选择序列化格式,例如简单二进制编码(SBE),并实现IStorageDeserializerIStorageSerializer。 内置序列化程序是使用此方法生成的:

实现序列化程序后,将其添加到 StorageSerializationPicker 中的 AdoNetGrainStorage 属性。 StorageSerializationPickerIStorageSerializationPicker 的默认实现。 可以在 RelationalStorageTests 中看到更改数据存储格式或使用序列化程序的示例。

目前,没有向应用程序公开序列化选取器 Orleans 的方法,因为无法直接访问框架创建的 AdoNetGrainStorage 实例。

设计目标

1.允许将任何后端与 ADO.NET 提供程序一起使用

这应涵盖适用于 .NET 的最广泛的后端集,这是本地安装中的一个因素。 某些提供程序在 ADO.NET 概述中列出,但并未列出所有提供程序,例如 Teradata

2.保持优化查询和数据库结构的潜力,即使在部署正在运行时也是如此

在许多情况下,第三方根据合同托管服务器和数据库。 在虚拟化托管环境中,性能因不可预见的因素如噪音干扰者或硬件故障而波动的情况并不少见。 可能不可能更改和重新部署 Orleans 二进制文件(由于合同原因),甚至应用程序二进制文件。 但是,通常可以调整数据库部署参数。 更改 标准组件(如 Orleans 二进制文件)需要给定情况的更长时间的优化过程。

3.允许使用特定于供应商的功能和特定于版本的功能

供应商在其产品中实现不同的扩展和功能。 当可用时,使用这些功能是明智的。 示例包括 PostgreSQL 中的 本地 UPSERTPipelineDB,以及 SQL Server 中的 PolyBase本地编译表和存储过程

4. 启用硬件资源的优化

设计应用程序时,通常可以预测哪些数据需要更快的插入,哪些数据更适合更便宜的 冷存储 (例如,在 SSD 和 HDD 之间拆分数据)。 其他注意事项包括数据的物理位置(某些存储可能更昂贵,例如 SSD RAID 与 HDD RAID 或更安全)或其他决策因素。 与 第 3 点相关,某些数据库提供特殊的分区方案,例如 SQL Server 分区表和索引

这些原则适用于整个应用程序生命周期。 考虑到 Orleans 的核心原则之一是高可用性,应该可以在不中断 Orleans 部署的情况下调整存储系统。 还可以根据数据和其他应用程序参数调整查询。 Brian Harry 的 博客文章 提供了动态更改的示例:

表较小时,查询计划几乎无关紧要。 当它是中等规模时,好的查询计划就可以,但如果规模巨大(数百万或数十亿行),即使查询计划稍有变化,也会导致严重问题。 因此,我们大量提示我们的敏感查询。

5.不假设工具、库或部署过程

许多组织都熟悉特定的数据库工具,例如 DacpacRedgate。 部署数据库可能需要权限或特定人员,例如 DBA 角色中的某人。 通常,这也意味着拥有目标数据库布局,以及应用程序生成的查询的粗略草图来估计负载。 流程(可能受行业标准影响)可能要求基于脚本的部署。 在外部脚本中具有查询和数据库结构可以实现此任务。

6. 使用加载 ADO.NET 库和功能所需的最低接口功能

此方法既快速又能减少暴露的表面积,从而减少 ADO.NET 库实现中潜在的不一致。

7. 使设计可分片

适当时(例如,在关系存储提供程序中),使设计易于分片。 例如,这意味着避免数据库依赖的数据(如 IDENTITY 列)。 区分行数据的信息应仅依赖于实际参数中的数据。

8. 使设计易于测试

理想情况下,创建新后端应该与将现有部署脚本转换为目标后端的 SQL 方言一样简单,将新的连接字符串添加到测试(假设默认参数),检查数据库是否已安装,然后针对它运行测试。

9. 考虑前面的要点,使新后端的移植脚本和修改现有后端脚本尽可能透明

实现目标

该 Orleans 框架不知道特定于部署的硬件(在活动部署期间可能会更改)、部署生命周期中的数据更改,或者某些特定于供应商的功能仅在特定情况下可用。 因此,数据库与Orleans之间的接口应遵循最低限度的抽象和规则集,以实现这些目标,确保能够抵御滥用的稳健性,并促进测试。 请参阅 群集管理 和具体的 成员资格协议实现。 此外,SQL Server 实现还包含特定于 SQL Server 版本的优化。 数据库 Orleans 之间的接口协定定义如下:

  1. 一般的想法是,数据是通过与Orleans相关的特定查询进行读取和写入的。 Orleans 在读取时对列名称和类型进行操作,在写入时对参数名称和类型进行操作。
  2. 实现 必须 保留输入和输出名称和类型。 Orleans 使用这些参数按名称和类型读取查询结果。 允许供应商特定的优化和特定于部署的优化,只要维护接口协定,就鼓励贡献。
  3. 跨供应商特定脚本的实现 保留约束名称。 这简化了通过跨具体实现的统一命名进行故障排除。
  4. 版本 — 或在应用代码中称为ETag — 表示Orleans的唯一版本。 只要它表示唯一版本,它的实际实现的类型就并不重要。 在实现中, Orleans 代码需要有符号 32 位整数。
  5. 若要显式和删除歧义, Orleans 某些查询需要将 TRUE 返回为 > 0 值或 FALSE 为 = 0 值。 受影响的行或返回的行数并不重要。 如果引发错误或引发异常,查询 必须确保 整个事务回滚,并且可以返回 FALSE 或传播异常。
  6. 目前,除了一项查询之外,其他的都是单行插入或更新(注意:你可以将UPDATE查询替换为INSERT,前提是关联的SELECT查询执行了最后的写入)。

数据库引擎支持数据库内编程。 这类似于加载可执行脚本并调用它来执行数据库作。 在伪代码中,可以将其描述为:

const int Param1 = 1;
const DateTime Param2 = DateTime.UtcNow;
const string queryFromOrleansQueryTableWithSomeKey =
    "SELECT column1, column2 "+
    "FROM <some Orleans table> " +
    "WHERE column1 = @param1 " +
    "AND column2 = @param2;";
TExpected queryResult =
    SpecificQuery12InOrleans<TExpected>(query, Param1, Param2);

这些原则也 包含在数据库脚本中

应用自定义脚本的想法

  1. OrleansQuery中使用IF ELSE更改脚本,以便某些状态使用默认值INSERT保存,而其他粒度状态可能使用内存优化表。 相应地更改SELECT的查询。
  2. 利用1.的思路来利用其他特定于部署或供应商的方面,例如拆分数据到SSDHDD之间,将某些数据放入加密表中,或者可能通过 SQL Server 到 Hadoop 甚至链接服务器插入统计信息数据。

可以通过运行 Orleans 测试套件或在数据库中直接使用 SQL Server 单元测试项目来测试已更改的脚本。

新增 ADO.NET 提供程序的指南

  1. 根据上述 目标实现部分添加新的数据库设置脚本。
  2. 将供应商 ADO 固定名称添加到AdoNetInvariants,并将 ADO.NET 提供程序特定的数据添加到DbConstantsStore。 例如,在某些查询作中可能使用这些模式来选择正确的统计信息插入模式(例如, UNION ALL 无论是否使用 FROM DUAL)。
  3. Orleans 对所有系统存储进行了全面的测试:成员身份、提醒和统计信息。 通过复制粘贴现有测试类并更改 ADO 固定名称,为新数据库脚本添加测试。 此外,从RelationalStorageForTesting派生,以定义 ADO 不变性的测试功能。