Freigeben über


ADO.NET-Grainpersistenz

Code für ein relationales Speicher-Back-End in Orleans basiert auf generischer ADO.NET-Funktionalität und ist daher vom Datenbankanbieter unabhängig. Das Orleans-Datenspeicherlayout wurde bereits im Rahmen von Laufzeittabellen erläutert. Die Verbindungszeichenfolgen wird wie im Orleans Konfigurationsleitfaden erläutert eingerichtet.

Damit Orleans-Code mit einem bestimmten relationalen Datenbank-Back-End funktioniert, ist Folgendes erforderlich:

  1. Die entsprechende ADO.NET-Bibliothek muss in den Prozess geladen werden. Dieser Vorgang sollte wie gewohnt definiert werden, z. B. über das DbProviderFactories-Element in der Anwendungskonfiguration.
  2. Konfigurieren Sie die ADO.NET-Invariante über die Invariant-Eigenschaft in den Optionen.
  3. Die Datenbank muss vorhanden und mit dem Code kompatibel sein. Hierzu wird ein anbieterspezifisches Datenbankerstellungsskript ausgeführt. Weitere Informationen finden Sie unter ADO.NET-Konfiguration.

Mit dem ADO.NET-Grainspeicheranbieter können Sie den Grainzustand in relationalen Datenbanken speichern. Derzeit werden die folgenden Datenbanken unterstützt:

  • SQL Server
  • MySQL/MariaDB
  • PostgreSQL
  • Oracle

Installieren Sie zunächst das Basispaket:

Install-Package Microsoft.Orleans.Persistence.AdoNet

Informationen zum Konfigurieren Ihrer Datenbank, einschließlich der entsprechenden ADO.NET-Invariante und der Setupskripts, finden Sie im Artikel zur ADO.NET-Konfiguration.

Im Folgenden finden Sie ein Beispiel für die Konfiguration eines ADO.NET-Speicheranbieters über ISiloHostBuilder:

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

Im Wesentlichen müssen Sie nur die datenbankanbieterspezifische Verbindungszeichenfolge und ein Invariant-Element (siehe ADO.NET-Konfiguration) zur Identifikation des Anbieters festlegen. Sie können auch das Format auswählen, in dem die Daten gespeichert werden: binär (Standard), JSON oder XML. Eine Binärdatei ist zwar die kompakteste Option, aber nicht transparent, und Sie können die Daten nicht lesen oder damit arbeiten. JSON ist die empfohlene Option.

Sie können über AdoNetGrainStorageOptions die folgenden Eigenschaften festlegen:

/// <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; }
}

Die ADO.NET-Persistenz verfügt über die Funktionalität, Daten zu versionieren und beliebige (De-)Serialisierer mit beliebigen Anwendungsregeln und Streaming zu definieren. Derzeit gibt es jedoch keine Methode, um sie für Anwendungscode verfügbar zu machen.

Gründe für ADO.NET-Persistenz

Folgende Prinzipien gelten für durch ADO.NET unterstützten Persistenzspeicher:

  1. Kontinuierliche Sicherheit und Zugänglichkeit für unternehmenskritische Daten, während sich Daten, das Format der Daten und der Code weiterentwickeln
  2. Nutzung anbieter- und speicherspezifischer Funktionen

In der Praxis bedeutet dies die Einhaltung der ADO.NET-Implementierungsziele sowie zusätzliche Implementierungslogik in ADO.NET-spezifischen Speicheranbietern, die eine Weiterentwicklung der Form der Daten im Speicher ermöglichen.

Neben den üblichen Speicheranbieterfunktionen verfügt der ADO.NET-Anbieter über integrierte Funktionen für folgende Aufgaben:

  1. Übertragen von Speicherdaten aus einem Format in ein anderes (z. B. von JSON in binär) beim Zustandsroundtrip.
  2. Formen des Typs, der gespeichert oder aus dem Speicher gelesen werden soll, nach Bedarf. Dadurch kann die Version des Zustands weiterentwickelt werden.
  3. Streamen von Daten aus der Datenbank.

Sowohl 1. als auch 2. können basierend auf beliebigen Entscheidungsparametern wie Grain-ID, Graintyp und Nutzdaten angewendet werden.

Dies ist der Fall, damit Sie ein Serialisierungsformat wie Simple Binary Encoding (SBE) auswählen können, und implementiert IStorageDeserializer und IStorageSerializer. Die integrierten Serialisierer wurden mit der folgenden Methode erstellt:

Nachdem die Serialisierer implementiert wurden, müssen sie der StorageSerializationPicker-Eigenschaft in AdoNetGrainStorage hinzugefügt werden. Hier finden Sie eine Implementierung von IStorageSerializationPicker. Standardmäßig wird StorageSerializationPicker verwendet. Ein Beispiel für das Ändern des Datenspeicherformats oder die Verwendung von Serialisierern finden Sie unter RelationalStorageTests.

Derzeit gibt es keine Methode, um die Serialisierungsauswahl für die Orleans-Anwendung verfügbar zu machen, da es keine Methode für den Zugriff auf den vom Framework erstellten AdoNetGrainStorage gibt.

Ziele des Entwurfs

1. Zulassen der Verwendung eines Back-Ends mit einem ADO.NET-Anbieter

Dabei sollte die größtmögliche für .NET verfügbare Menge von Back-Ends abgedeckt werden, da dies einen Faktor bei lokalen Installationen darstellt. In der ADO.NET-Übersicht werden einige, aber nicht alle Anbieter aufgeführt, beispielsweise fehlt Teradata.

2. Beibehalten der Möglichkeit zum bedarfsgerechten Optimieren von Abfragen und der Datenbankstruktur, auch während einer Bereitstellung

In vielen Fällen werden die Server und Datenbanken von einem Drittanbieter in einem Vertragsverhältnis mit dem Client gehostet. Eine virtualisierte Hostumgebung, in der die Leistung aufgrund unvorhergesehener Faktoren wie Noisy Neighbors oder fehlerhafter Hardware schwankt, ist nicht ungewöhnlich. Es ist möglicherweise nicht möglich, Orleans-Binärdateien (aus vertraglichen Gründen) oder sogar Anwendungsbinärdateien zu ändern und erneut bereitzustellen, aber es ist in der Regel möglich, die Parameter für die Datenbankbereitstellung zu optimieren. Das Ändern von Standardkomponenten, z. B. Orleans-Binärdateien, erfordert ein langwierigeres Verfahren zur Optimierung in einer bestimmten Situation.

3. Gestatten der Nutzung hersteller- und versionsspezifischer Funktionen

Anbieter haben verschiedene Erweiterungen und Features in ihren Produkten implementiert. Es ist sinnvoll, diese Features zu nutzen, wenn sie verfügbar sind. Hierbei handelt es sich um Features wie natives UPSERT oder PipelineDB in PostgreSQL sowie PolyBase oder nativ kompilierte Tabellen und gespeicherte Prozeduren in SQL Server.

4. Ermöglichen der Optimierung von Hardwareressourcen

Beim Entwerfen einer Anwendung kann häufig vorhergesehen werden, welche Daten schneller eingefügt werden müssen als andere und welche Daten besser im kalten Speicher abgelegt werden könnten, um Kosten zu sparen (z. B. Aufteilen von Daten zwischen SSD und HDD). Weitere Überlegungen betreffen den physischen Speicherort der Daten (einige Daten könnten teurer, z. B. SSD RAID im Vergleich zu HDD RAID, oder besser geschützt sein) oder andere Entscheidungsgrundlagen. Im Zusammenhang mit Punkt 3 bieten einige Datenbanken spezielle Partitionierungsschemas, in SQL Server z. B. partitionierte Tabellen und Indizes.

Diese Prinzipien gelten während des gesamten Anwendungslebenszyklus. Im Hinblick darauf, dass eines der Prinzipien von Orleans Hochverfügbarkeit ist, sollte es möglich sein, das Speichersystem ohne Unterbrechung für die Orleans-Bereitstellung anzupassen, oder es sollte möglich sein, die Abfragen entsprechend den Daten und anderen Anwendungsparametern anzupassen. Ein Beispiel für dynamische Änderungen finden Sie im Blogbeitrag von Brian Harry:

Wenn eine Tabelle klein ist, spielt es fast keine Rolle, wie der Abfrageplan lautet. Wenn sie mittelgroß ist, muss der Abfrageplan einigermaßen funktionieren, aber bei sehr umfangreichen Tabellen (Millionen oder Milliarden von Zeilen) kann selbst eine geringfügige Variation im Abfrageplan verheerende Folgen haben. Aus diesem Grund versehen wir unsere sensiblen Abfragen mit zahlreichen Hinweisen.

5. Keine Annahmen darüber, welche Tools, Bibliotheken oder Bereitstellungsprozesse in Organisationen verwendet werden

Viele Organisationen sind mit einer bestimmten Reihe von Datenbanktools vertraut, z. B. Dacpac oder Red Gate. Es kann sein, dass für die Bereitstellung einer Datenbank entweder eine Berechtigung oder eine Person, z. B. eine Person in einer DBA-Rolle, erforderlich ist. In der Regel bedeutet dies auch, dass das Layout der Zieldatenbank und eine grober Entwurf der Abfragen, die die Anwendung zum Abschätzen der Last erstellen wird, vorliegen müssen. Möglicherweise gibt es Prozesse, die möglicherweise von Branchenstandards beeinflusst werden, die eine skriptbasierte Bereitstellung erfordern. Dies ist möglich, wenn die Abfragen und Datenbankstrukturen in einem externen Skript vorhanden sind.

6. Verwenden der Mindestmenge an Schnittstellenfunktionen, die zum Laden der ADO.NET-Bibliotheken und -Funktionen erforderlich sind

Dieses Vorgehen ist schnell und bietet weniger Oberfläche für Abweichungen der ADO.NET-Bibliotheksimplementierung.

7. Ermöglichen des Shardings beim Entwurf

Wenn dies sinnvoll erscheint, beispielsweise bei einem Anbieter von relationalem Speicher, können Sie das Sharding des Entwurfs ermöglichen. Dies bedeutet beispielsweise, dass keine datenbankabhängigen Daten (z. B. IDENTITY) verwendet werden. Informationen, die Zeilendaten unterscheiden, sollten nur auf Daten aus den tatsächlichen Parametern aufbauen.

8. Gewährleisten einer einfachen Testbarkeit des Entwurfs

Das Erstellen eines neuen Back-Ends sollte idealerweise so einfach sein wie das Übersetzen eines der vorhandenen Bereitstellungsskripts in den SQL-Dialekt des gewünschten Back-Ends, das Hinzufügen einer neuen Verbindungszeichenfolge zu den Tests (unter Annahme der Standardparameter), das Überprüfen, ob eine bestimmte Datenbank installiert ist, und das anschließende Ausführen der Tests für diese Datenbank.

9. Gewährleisten möglichst hoher Transparenz sowohl für das Portieren von Skripts für neue Back-Ends als auch für das Ändern bereits bereitgestellter Back-End-Skripts unter Berücksichtigung der vorherigen Punkte

Umsetzung der Ziele

Dem Orleans-Framework sind keine bereitstellungsspezifischen Hardwarekomponenten (welche Hardware sich während der aktiven Bereitstellung ändern kann), keine Änderungen von Daten während des Bereitstellungslebenszyklus und keine bestimmten herstellerspezifischen Features, die nur in bestimmten Situationen verwendet werden können, bekannt. Aus diesem Grund sollte die Schnittstelle zwischen der Datenbank und Orleans die Mindestmenge an Abstraktionen und Regeln einhalten, um diese Ziele zu erreichen, die Lösung vor Missbrauch zu schützen und sie bei Bedarf einfach testen zu können. Laufzeittabellen, Clusterverwaltung und die konkrete Implementierung des Mitgliedschaftsprotokolls. Außerdem enthält die SQL Server-Implementierung eine spezifische Optimierung für die jeweilige SQL Server-Edition. Der Schnittstellenvertrag zwischen der Datenbank und Orleans ist wie folgt definiert:

  1. Die allgemeine Idee ist, dass Daten über Orleans-spezifische Abfragen gelesen und geschrieben werden. Orleans arbeitet beim Lesen mit Spaltennamen und -typen und beim Schreiben mit Parameternamen und -typen.
  2. Bei den Implementierungen müssen Eingabe- und Ausgabenamen und -typen beibehalten werden. Orleans verwendet diese Parameter, um Abfrageergebnisse nach Name und Typ zu lesen. Die anbieter- und bereitstellungsspezifische Optimierung ist zulässig, und Beiträge werden empfohlen, solange der Schnittstellenvertrag eingehalten wird.
  3. Bei der anbieterspezifische Skripts übergreifenden Implementierung sollten die Einschränkungsnamen beibehalten werden. Dies vereinfacht die Problembehandlung durch die einheitliche, konkrete Implementierungen übergreifende Benennung.
  4. Version – oder ETag im Anwendungscode – steht in Orleans für eine eindeutige Version. Der Typ der tatsächlichen Implementierung ist nicht wichtig, solange sie einer eindeutigen Version entspricht. In der Implementierung erwartet Orleans-Code eine 32-Bit-Ganzzahl mit Vorzeichen.
  5. Um Mehrdeutigkeiten auszuräumen, erwartet Orleans, dass einige Abfragen entweder den Wert TRUE as > 0 oder den Wert FALSE as = 0 zurückgeben. Demnach spielt die Anzahl der betroffenen oder zurückgegebenen Zeilen keine Rolle. Wenn ein Fehler oder eine Ausnahme ausgelöst wird, muss die Abfrage sicherstellen, dass für die gesamte Transaktion ein Rollback ausgeführt wird, und kann entweder FALSE zurückgeben oder die Ausnahme weitergeben.
  6. Derzeit sind bis auf eine Abfrage alle Abfragen einzeilige Einfügungen oder Aktualisierungen. (Beachten Sie, dass UPDATE-Abfragen durch INSERT ersetzt werden können, sofern die zugeordneten SELECT-Abfragen den letzten Schreibvorgang ausgeführt haben.)

Datenbank-Engines unterstützen die datenbankinterne Programmierung. Dies ähnelt dem Konzept, ein ausführbares Skript zu laden und aufzurufen, um Datenbankvorgänge auszuführen. In Pseudocode könnte es wie folgt dargestellt werden:

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);

Diese Prinzipien sind auch in den Datenbankskripts enthalten.

Einige Ideen zum Anwenden angepasster Skripts

  1. Ändern Sie Skripts in OrleansQuery für die Grainpersistenz mit IF ELSE, sodass ein Zustand mithilfe der INSERT-Standardanweisung gespeichert wird, während einige Grainzustände arbeitsspeicheroptimierte Tabellen verwenden können. Die SELECT-Abfragen müssen entsprechend geändert werden.
  2. Die Idee in 1. kann verwendet werden, um andere bereitstellungs- oder anbieterspezifische Aspekte zu nutzen, z. B. das Aufteilen von Daten zwischen SSD und HDD, das Einfügen einiger Daten in verschlüsselte Tabellen oder das Einfügen von Statistikdaten über SQL Server-to-Hadoop oder sogar Verbindungsserver.

Die geänderten Skripts können durch Ausführen der Orleans-Testsammlung oder direkt in der Datenbank, z. B. über das SQL Server-Komponententestprojekt, getestet werden.

Richtlinien zum Hinzufügen neuer ADO.NET-Anbieter

  1. Fügen Sie gemäß dem obigen Abschnitt Umsetzung der Ziele ein neues Datenbanksetupskript hinzu.
  2. Fügen Sie den ADO-Invariantennamen des Anbieters zu AdoNetInvariants und ADO.NET-anbieterspezifische Daten zu DbConstantsStore hinzu. Diese werden (möglicherweise) in einigen Abfragevorgängen verwendet, beispielsweise, um den richtigen Statistikeinfügemodus (d. h. UNION ALL mit oder ohne FROM DUAL) auszuwählen.
  3. Orleans verfügt über umfassende Tests für alle Systemspeicher: Mitgliedschaft, Erinnerungen und Statistiken. Das Hinzufügen von Tests für das neue Datenbankskript erfolgt durch Kopieren und Einfügen vorhandener Testklassen und Ändern des ADO-Invariantennamens. Leiten Sie außerdem von RelationalStorageForTesting ab, um die Testfunktionalität für die ADO-Invariante zu definieren.