Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Getreide kann mehrere benannte persistente Datenobjekte zugeordnet sein. Diese Zustandsobjekte werden während der Kornaktivierung aus dem Speicher geladen, sodass sie während der Anforderungen verfügbar sind. Die Grain-Persistenz verwendet ein erweiterbares Plug-in-Modell, das die Nutzung von Speicher-Providern für jede Datenbank ermöglicht. Dieses Persistenzmodell ist auf Einfachheit ausgelegt und soll nicht alle Datenzugriffsmuster abdecken. Getreide kann auch direkt auf Datenbanken zugreifen, ohne das Kornpersistenzmodell zu verwenden.
Im vorherigen Diagramm verfügt UserGrain über einen Profilstatus und einen Cart-Zustand , die jeweils in einem separaten Speichersystem gespeichert sind.
Ziele
- Unterstützt mehrere benannte persistente Datenobjekte pro Korn.
- Lassen Sie mehrere konfigurierte Speicheranbieter zu, die potenziell eine andere Konfiguration haben und von einem anderen Speichersystem gesichert werden.
- Ermöglichen Sie der Community, Speicheranbieter zu entwickeln und zu veröffentlichen.
- Geben Sie Speicheranbietern vollständige Kontrolle darüber, wie sie Kornzustandsdaten im permanenten Sicherungsspeicher speichern. Corollary: Orleans bietet keine umfassende ORM-Speicherlösung, ermöglicht aber benutzerdefinierten Speicheranbietern, bestimmte ORM-Anforderungen nach Bedarf zu unterstützen.
Pakete
Sie finden Orleans Getreidespeicheranbieter auf NuGet. Zu den offiziell gepflegten Paketen gehören:
- Microsoft.Orleans. Persistence.AdoNet: Für SQL-Datenbanken und andere Von ADO.NET unterstützte Speichersysteme. Weitere Informationen finden Sie unter ADO.NET Kornpersistenz.
- Microsoft.Orleans.Persistence.AzureStorage: Für Azure Storage, einschließlich Azure Blob Storage und Azure Table Storage (über die Azure Table Storage API). Weitere Informationen finden Sie unter Azure Storage-Kornpersistenz.
- Microsoft.Orleans. Persistenz.Cosmos: Der Azure Cosmos DB-Anbieter. Weitere Informationen finden Sie unter Azure Cosmos DB Kornpersistenz.
- Microsoft.Orleans.Persistence.DynamoDB: für Amazon DynamoDB. Weitere Informationen finden Sie unter Amazon DynamoDB Grain Persistence.
Programmierschnittstelle (API)
Körner interagieren mit ihrem persistenten Zustand, wobei sie IPersistentState<TState>verwenden, bei dem TState
der serialisierbare Zustandstyp ist:
public interface IPersistentState<TState> : IStorage<TState>
{
}
public interface IStorage<TState> : IStorage
{
TState State { get; set; }
}
public interface IStorage
{
string Etag { get; }
bool RecordExists { get; }
Task ClearStateAsync();
Task WriteStateAsync();
Task ReadStateAsync();
}
public interface IPersistentState<TState> where TState : new()
{
TState State { get; set; }
string Etag { get; }
Task ClearStateAsync();
Task WriteStateAsync();
Task ReadStateAsync();
}
Orleans injiziert Instanzen von IPersistentState<TState>
als Konstruktorparameter in das Grain. Sie können diese Parameter mit einem PersistentStateAttribute Attribut kommentieren, um den Namen des injizierten Zustands und den Namen des Speicheranbieters zu identifizieren, der ihn angibt. Im folgenden Beispiel wird dies veranschaulicht, indem zwei benannte Zustände in den UserGrain
Konstruktor eingefügt werden:
public class UserGrain : Grain, IUserGrain
{
private readonly IPersistentState<ProfileState> _profile;
private readonly IPersistentState<CartState> _cart;
public UserGrain(
[PersistentState("profile", "profileStore")] IPersistentState<ProfileState> profile,
[PersistentState("cart", "cartStore")] IPersistentState<CartState> cart)
{
_profile = profile;
_cart = cart;
}
}
Verschiedene Getreidetypen können unterschiedliche konfigurierte Speicheranbieter verwenden, auch wenn beide denselben Typ haben (z. B. zwei verschiedene Azure Table Storage-Anbieterinstanzen, die mit verschiedenen Azure Storage-Konten verbunden sind).
Lesestatus
Der Status eines Körnchens wird automatisch erfasst, wenn es aktiviert wird. Aber die Körnchen sind dafür verantwortlich, den Schreibvorgang für jeden geänderten Status bei Bedarf explizit auszulösen.
Wenn ein Korn den aktuellen Zustand explizit aus dem Sicherungsspeicher erneut lesen möchte, sollte die ReadStateAsync Methode aufgerufen werden. Dadurch wird der Getreidezustand aus dem persistenten Speicher über den Speicheranbieter neu geladen. Die vorherige In-Memory-Kopie des Zustands des Grain wird überschrieben und ersetzt, wenn der Task
von ReadStateAsync()
abgeschlossen ist.
Greifen Sie mithilfe der State
Eigenschaft auf den Wert des Zustands zu. Die folgende Methode greift beispielsweise auf den im Code oben deklarierten Profilstatus zu:
public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);
Während des normalen Betriebs ist es nicht erforderlich, ReadStateAsync()
aufzurufen; Orleans lädt den Zustand während der Aktivierung automatisch. Sie können jedoch ReadStateAsync()
verwenden, um den extern geänderten Zustand zu aktualisieren.
Ausführliche Informationen zu Fehlerbehandlungsmechanismen finden Sie unten im Abschnitt " Fehlermodi ".
Schreibzustand
Sie können den Zustand über die State
Eigenschaft ändern. Der geänderte Zustand wird nicht automatisch beibehalten. Stattdessen entscheiden Sie, wann der Zustand beibehalten werden soll, indem Sie die WriteStateAsync Methode aufrufen. Mit der folgenden Methode wird beispielsweise eine Eigenschaft auf State
aktualisiert und der aktualisierte Zustand gespeichert.
public async Task SetNameAsync(string name)
{
_profile.State.Name = name;
await _profile.WriteStateAsync();
}
Konzeptionell verwendet die Orleans Runtime eine tiefe Kopie des Kornzustandsdatenobjekts für die Verwendung während aller Schreibvorgänge. Unter den Deckeln kann die Laufzeit Optimierungsregeln und Heuristiken verwenden, um die Ausführung einiger oder aller tiefen Kopien unter bestimmten Umständen zu vermeiden, vorausgesetzt, die erwartete logische Isolationsemantik wird beibehalten.
Weitere Informationen zu Fehlerbehandlungsmechanismen finden Sie im Abschnitt " Fehlermodi " weiter unten.
Status löschen
Die ClearStateAsync Methode löscht den Lagerzustand des Getreides. Je nach Anbieter kann dieser Vorgang optional den Kornzustand vollständig löschen.
Loslegen
Bevor ein Grain Datenpersistenz verwenden kann, müssen Sie einen Speicheranbieter im Silo konfigurieren.
Konfigurieren Sie zunächst Speicheranbieter, eine für den Profilstatus und eine für den Warenkorbstatus:
using IHost host = new HostBuilder()
.UseOrleans(siloBuilder =>
{
siloBuilder.AddAzureTableGrainStorage(
name: "profileStore",
configureOptions: options =>
{
// Configure the storage connection key
options.ConfigureTableServiceClient(
"DefaultEndpointsProtocol=https;AccountName=data1;AccountKey=SOMETHING1");
})
.AddAzureBlobGrainStorage(
name: "cartStore",
configureOptions: options =>
{
// Configure the storage connection key
options.ConfigureTableServiceClient(
"DefaultEndpointsProtocol=https;AccountName=data2;AccountKey=SOMETHING2");
});
})
.Build();
var host = new HostBuilder()
.UseOrleans(siloBuilder =>
{
siloBuilder.AddAzureTableGrainStorage(
name: "profileStore",
configureOptions: options =>
{
// Use JSON for serializing the state in storage
options.UseJson = true;
// Configure the storage connection key
options.ConnectionString =
"DefaultEndpointsProtocol=https;AccountName=data1;AccountKey=SOMETHING1";
})
.AddAzureBlobGrainStorage(
name: "cartStore",
configureOptions: options =>
{
// Use JSON for serializing the state in storage
options.UseJson = true;
// Configure the storage connection key
options.ConnectionString =
"DefaultEndpointsProtocol=https;AccountName=data2;AccountKey=SOMETHING2";
});
})
.Build();
Von Bedeutung
Microsoft empfiehlt, immer den sichersten Authentifizierungsflow zu verwenden. Wenn Sie eine Verbindung mit Azure SQL herstellen, ist Managed Identities for Azure Resources die empfohlene Authentifizierungsmethode.
Nachdem Sie nun einen Speicheranbieter mit dem Namen "profileStore"
konfiguriert haben, können Sie von einem Grain aus auf diesen Anbieter zugreifen.
Sie können einem Getreide auf zwei primäre Weise beständigen Zustand hinzufügen:
- Durch Einfügen
IPersistentState<TState>
in den Konstruktor des Korns. - Indem man von Grain<TGrainState> erbt
Die empfohlene Methode, einem Grain Speicher hinzuzufügen, besteht darin, IPersistentState<TState>
in den Konstruktor des Grain mit einem zugehörigen [PersistentState("stateName", "providerName")]
-Attribut einzuspritzen. Einzelheiten zu Grain<TState>
finden Sie unter Verwenden Grain<TState>
zum Hinzufügen von Speicher zu einem Korn unten. Die Verwendung Grain<TState>
wird weiterhin unterstützt, gilt jedoch als legacy-Ansatz.
Deklarieren Sie eine Klasse, die den Zustand Ihres Getreides speichert.
[Serializable]
public class ProfileState
{
public string Name { get; set; }
public Date DateOfBirth { get; set; }
}
IPersistentState<ProfileState>
in den Konstruktor des Korns einfügen.
public class UserGrain : Grain, IUserGrain
{
private readonly IPersistentState<ProfileState> _profile;
public UserGrain(
[PersistentState("profile", "profileStore")]
IPersistentState<ProfileState> profile)
{
_profile = profile;
}
}
Von Bedeutung
Der Profilstatus wird zu dem Zeitpunkt, zu dem er in den Konstruktor eingefügt wird, nicht geladen, sodass der Zugriff darauf zu diesem Zeitpunkt ungültig ist. Der Zustand wird geladen, bevor OnActivateAsync aufgerufen wird.
Nachdem das Korn nun einen beständigen Zustand aufweist, können Sie Methoden zum Lesen und Schreiben des Zustands hinzufügen:
public class UserGrain : Grain, IUserGrain
{
private readonly IPersistentState<ProfileState> _profile;
public UserGrain(
[PersistentState("profile", "profileStore")]
IPersistentState<ProfileState> profile)
{
_profile = profile;
}
public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);
public async Task SetNameAsync(string name)
{
_profile.State.Name = name;
await _profile.WriteStateAsync();
}
}
Fehlermodi für Persistenzvorgänge
Fehlermodi für Lesevorgänge
Fehler, die vom Speicheranbieter während des anfänglichen Lesens von Statusdaten für ein bestimmtes Korn zurückgegeben werden, schlagen beim Aktivierungsvorgang für dieses Korn fehl. In solchen Fällen gibt es keinen Aufruf der Lebenszyklusrückrufmethode dieses Korns OnActivateAsync
. Die ursprüngliche Anforderung an das Korn, das die Aktivierungsfehler zurück an den Aufrufer verursacht hat, genau wie jeder andere Fehler während der Kornaktivierung. Fehler, die vom Speicheranbieter beim Lesen von Zustandsdaten für ein bestimmtes Grain auftreten, führen zu einer Ausnahme durch ReadStateAsync
Task
. Der Grain kann wählen, ob er die Task
Ausnahme bearbeiten oder ignorieren soll, wie jedes andere Task
in Orleans.
Jeder Versuch, eine Nachricht an einen Grain zu senden, der aufgrund einer fehlenden oder fehlerhaften Speicheranbieterkonfiguration beim Silostart nicht geladen werden konnte, führt zu dem permanenten Fehler BadProviderConfigException.
Fehlermodi für Schreibvorgänge
Fehler des Speicheranbieters beim Schreiben von Zustandsdaten für einen spezifischen Grain führen zu einer Ausnahme, die von der WriteStateAsync()
Task
. Dies bedeutet in der Regel, dass die Kornaufruf-Ausnahme an den Clientanrufer zurückgeworfen wird, vorausgesetzt, dies WriteStateAsync()
Task
wird ordnungsgemäß in die endgültige Rückgabe Task
für diese Kornmethode verkettet. In bestimmten erweiterten Szenarien können Sie jedoch Graincode schreiben, um solche Schreibfehler speziell zu behandeln, genau wie bei der Behandlung anderer fehlerhafter Task
.
Grains, die fehlerbehandlungs- oder wiederherstellungscode ausführen , müssen Ausnahmen oder fehlerhafte WriteStateAsync()
Task
s abfangen und nicht erneut auslösen, was bedeutet, dass sie den Schreibfehler erfolgreich behandelt haben.
Empfehlungen
Verwenden der JSON-Serialisierung oder eines anderen versionstoleranten Serialisierungsformats
Code wird weiterentwickelt, und dies umfasst häufig Speichertypen. Um diese Änderungen zu berücksichtigen, konfigurieren Sie einen geeigneten Serialisierer. Für die meisten Speicheranbieter steht eine UseJson
Option oder ein ähnliches Format zur Verwendung von JSON als Serialisierungsformat zur Verfügung. Stellen Sie sicher, dass beim Entwickeln von Datenverträgen bereits gespeicherte Daten weiterhin geladen werden können.
Verwenden von Grain<TState>
zum Hinzufügen von Speicher zu einem Grain
Von Bedeutung
Die Verwendung von Grain<T>
zum Hinzufügen von Speicher zu einem Speichergranulat gilt als veraltete Funktionalität. Fügen Sie Getreidespeicher wie zuvor beschrieben unter Verwendung von IPersistentState<T>
hinzu.
Grain-Klassen, die von Grain<T>
erben (wobei T
ein anwendungsspezifischer Zustandsdatentyp ist, der Persistenz benötigt), laden ihren Zustand automatisch aus dem angegebenen Speicher.
Markieren Sie solche Körner mit einer Angabe einer StorageProviderAttribute benannten Instanz eines Speicheranbieters, die zum Lesen/Schreiben der Statusdaten für dieses Korn verwendet werden soll.
[StorageProvider(ProviderName="store1")]
public class MyGrain : Grain<MyGrainState>, /*...*/
{
/*...*/
}
Die Grain<T>
Basisklasse definiert die folgenden Methoden für Unterklassen, die aufgerufen werden sollen:
protected virtual Task ReadStateAsync() { /*...*/ }
protected virtual Task WriteStateAsync() { /*...*/ }
protected virtual Task ClearStateAsync() { /*...*/ }
Das Verhalten dieser Methoden entspricht ihren zuvor definierten Gegenstücken auf IPersistentState<TState>
.
Erstellen eines Speicheranbieters
Es gibt zwei Teile der Zustandspersistenz-APIs: die API, die dem Korn über IPersistentState<T>
oder Grain<T>
bereitgestellt wird, und die Speicheranbieter-API, die sich um IGrainStorage
zentriert—die Schnittstelle, die Speicheranbieter implementieren müssen.
/// <summary>
/// Interface to be implemented for a storage able to read and write Orleans grain state data.
/// </summary>
public interface IGrainStorage
{
/// <summary>Read data function for this storage instance.</summary>
/// <param name="stateName">Name of the state for this grain</param>
/// <param name="grainId">Grain ID</param>
/// <param name="grainState">State data object to be populated for this grain.</param>
/// <typeparam name="T">The grain state type.</typeparam>
/// <returns>Completion promise for the Read operation on the specified grain.</returns>
Task ReadStateAsync<T>(
string stateName, GrainId grainId, IGrainState<T> grainState);
/// <summary>Write data function for this storage instance.</summary>
/// <param name="stateName">Name of the state for this grain</param>
/// <param name="grainId">Grain ID</param>
/// <param name="grainState">State data object to be written for this grain.</param>
/// <typeparam name="T">The grain state type.</typeparam>
/// <returns>Completion promise for the Write operation on the specified grain.</returns>
Task WriteStateAsync<T>(
string stateName, GrainId grainId, IGrainState<T> grainState);
/// <summary>Delete / Clear data function for this storage instance.</summary>
/// <param name="stateName">Name of the state for this grain</param>
/// <param name="grainId">Grain ID</param>
/// <param name="grainState">Copy of last-known state data object for this grain.</param>
/// <typeparam name="T">The grain state type.</typeparam>
/// <returns>Completion promise for the Delete operation on the specified grain.</returns>
Task ClearStateAsync<T>(
string stateName, GrainId grainId, IGrainState<T> grainState);
}
/// <summary>
/// Interface to be implemented for a storage able to read and write Orleans grain state data.
/// </summary>
public interface IGrainStorage
{
/// <summary>Read data function for this storage instance.</summary>
/// <param name="grainType">Type of this grain [fully qualified class name]</param>
/// <param name="grainReference">Grain reference object for this grain.</param>
/// <param name="grainState">State data object to be populated for this grain.</param>
/// <returns>Completion promise for the Read operation on the specified grain.</returns>
Task ReadStateAsync(
string grainType, GrainReference grainReference, IGrainState grainState);
/// <summary>Write data function for this storage instance.</summary>
/// <param name="grainType">Type of this grain [fully qualified class name]</param>
/// <param name="grainReference">Grain reference object for this grain.</param>
/// <param name="grainState">State data object to be written for this grain.</param>
/// <returns>Completion promise for the Write operation on the specified grain.</returns>
Task WriteStateAsync(
string grainType, GrainReference grainReference, IGrainState grainState);
/// <summary>Delete / Clear data function for this storage instance.</summary>
/// <param name="grainType">Type of this grain [fully qualified class name]</param>
/// <param name="grainReference">Grain reference object for this grain.</param>
/// <param name="grainState">Copy of last-known state data object for this grain.</param>
/// <returns>Completion promise for the Delete operation on the specified grain.</returns>
Task ClearStateAsync(
string grainType, GrainReference grainReference, IGrainState grainState);
}
Erstellen Sie einen benutzerdefinierten Speicheranbieter, indem Sie diese Schnittstelle implementieren und diese Implementierung registrieren . Ein Beispiel für eine vorhandene Speicheranbieterimplementierung finden Sie unter AzureBlobGrainStorage
.
Speicheranbietersemantik
Ein undurchsichtiger anbieterspezifischer Etag Wert (string
) kann von einem Speicheranbieter als Teil der Kornstatusmetadaten festgelegt werden, die beim Lesen des Zustands aufgefüllt wurden. Einige Anbieter können dies als null
belassen, wenn sie Etag
nicht verwenden.
Jeder Versuch, eine Schreiboperation auszuführen, wenn der Speicheranbieter eine Etag
Constraint-Verletzung erkennt , sollte dazu führen, dass der Schreibvorgang Task
mit einem vorübergehenden Fehler InconsistentStateException fehlschlägt und die zugrunde liegende Speicherausnahme umschlossen wird.
public class InconsistentStateException : OrleansException
{
public InconsistentStateException(
string message,
string storedEtag,
string currentEtag,
Exception storageException)
: base(message, storageException)
{
StoredEtag = storedEtag;
CurrentEtag = currentEtag;
}
public InconsistentStateException(
string storedEtag,
string currentEtag,
Exception storageException)
: this(storageException.Message, storedEtag, currentEtag, storageException)
{
}
/// <summary>The Etag value currently held in persistent storage.</summary>
public string StoredEtag { get; }
/// <summary>The Etag value currently held in memory, and attempting to be updated.</summary>
public string CurrentEtag { get; }
}
Alle anderen Fehlerbedingungen eines Speichervorgangs müssen dazu führen, dass der zurückgegebene Task
durch eine Ausnahme aufgelöst wird, die das zugrunde liegende Speicherproblem angibt. In vielen Fällen kann diese Ausnahme an den Aufrufer zurückgegeben werden, der den Speichervorgang ausgelöst hat, indem eine Methode auf dem Grain aufgerufen wird. Es ist wichtig zu überlegen, ob der Aufrufer diese Ausnahme deserialisieren kann. Beispielsweise hat der Client möglicherweise nicht die spezifische Persistenzbibliothek geladen, die den Ausnahmetyp enthält. Aus diesem Grund ist es ratsam, Ausnahmen in Ausnahmen umzuwandeln, die an den Aufrufer zurückverbreitet werden können.
Datenabbildung
Einzelne Speicheranbieter sollten entscheiden, wie der Kornzustand am besten gespeichert werden soll – Blob (verschiedene Formate/ serialisierte Formulare) oder Spalten pro Feld sind offensichtliche Auswahlmöglichkeiten.
Registrieren eines Speicheranbieters
Die Orleans Laufzeit löst einen Speicheranbieter vom Dienstanbieter (IServiceProvider) auf, wenn ein Korn erstellt wird. Die Laufzeit löst eine Instanz von IGrainStorage. Wenn der Speicheranbieter benannt ist (z. B. über das [PersistentState(stateName, storageName)]
Attribut), wird eine benannte Instanz IGrainStorage
aufgelöst.
Um eine benannte Instanz von IGrainStorage
zu registrieren, verwenden Sie die AddSingletonNamedService Erweiterungsmethode nach dem Beispiel des AzureTableGrainStorage-Anbieters hier.