Persistentie van graan
Korrels kunnen meerdere benoemde permanente gegevensobjecten hebben die eraan zijn gekoppeld. Deze statusobjecten worden tijdens het activeren van de korrel geladen vanuit de opslag, zodat ze beschikbaar zijn tijdens aanvragen. Grain persistence maakt gebruik van een uitbreidbaar invoegtoepassingsmodel, zodat opslagproviders voor elke database kunnen worden gebruikt. Dit persistentiemodel is ontworpen voor het gemak en is niet bedoeld om alle patronen voor gegevenstoegang te dekken. Korrels hebben ook rechtstreeks toegang tot databases, zonder het graanpersistentiemodel te gebruiken.
In het bovenstaande diagram heeft UserGrain een profielstatus en een winkelwagenstatus , die elk in een afzonderlijk opslagsysteem worden opgeslagen.
Doelstellingen
- Meerdere benoemde permanente gegevensobjecten per korrel.
- Meerdere geconfigureerde opslagproviders, die elk een andere configuratie kunnen hebben en kunnen worden ondersteund door een ander opslagsysteem.
- Storage providers kunnen worden ontwikkeld en gepubliceerd door de community.
- Storage providers volledige controle hebben over de wijze waarop ze gegevens over de status van de korrel opslaan in de permanente back-upopslag. Corollary: Orleans biedt geen uitgebreide ORM-opslagoplossing, maar stelt aangepaste opslagproviders in staat om specifieke ORM-vereisten te ondersteunen wanneer dat nodig is.
Pakketten
Orleans grain storage providers zijn te vinden op NuGet. Officieel onderhouden pakketten zijn:
- Microsoft.Orleans.Persistence.AdoNet is bedoeld voor SQL databases en andere opslagsystemen die worden ondersteund door ADO.NET. Zie ADO.NET Graanpersistentie voor meer informatie.
- Microsoft.Orleans.Persistence.AzureStorage is bedoeld voor Azure Storage, waaronder Azure Blob Storage, Azure Table Storage en Azure CosmosDB, via de Azure Table Storage-API. Zie Azure Storage Graanpersistentie voor meer informatie.
- Microsoft.Orleans.Persistence.DynamoDB is voor Amazon DynamoDB. Zie Amazon DynamoDB Grain Persistence voor meer informatie.
API
Korrels communiceren met hun permanente status met behulp van IPersistentState<TState> waar TState
het serialiseerbare statustype is:
public interface IPersistentState<TState> where TState : new()
{
TState State { get; set; }
string Etag { get; }
Task ClearStateAsync();
Task WriteStateAsync();
Task ReadStateAsync();
}
Exemplaren van IPersistentState<TState>
worden in het graan geïnjecteerd als constructorparameters. Deze parameters kunnen worden geannoteerd met een PersistentStateAttribute kenmerk om de naam te identificeren van de status die wordt geïnjecteerd en de naam van de opslagprovider die deze levert. In het volgende voorbeeld ziet u dit door twee benoemde statussen in de UserGrain
constructor te injecteren:
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;
}
}
Verschillende typen korrels kunnen verschillende geconfigureerde opslagproviders gebruiken, zelfs als beide hetzelfde type zijn; Bijvoorbeeld twee verschillende Azure Table Storage providerexemplaren die zijn verbonden met verschillende Azure Storage-accounts.
Leesstatus
De korrelstatus wordt automatisch gelezen wanneer het graan wordt geactiveerd, maar korrels zijn verantwoordelijk voor het expliciet activeren van de schrijfbewerking voor een gewijzigde korrelstatus wanneer dat nodig is.
Als een korrel expliciet de meest recente status voor dit graan uit de backingopslag wil lezen, moet het graan de ReadStateAsync methode aanroepen. Hiermee wordt de korrelstatus van het permanente archief opnieuw geladen via de opslagprovider en wordt de vorige in-memory kopie van de korrelstatus overschreven en vervangen wanneer de Task
status van ReadStateAsync()
voltooid is.
De waarde van de status wordt geopend met behulp van de State
eigenschap. De volgende methode heeft bijvoorbeeld toegang tot de profielstatus die is gedeclareerd in de bovenstaande code:
public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);
U hoeft tijdens de normale werking niet aan te roepen ReadStateAsync()
. De status wordt automatisch geladen tijdens de activering. ReadStateAsync()
Kan echter worden gebruikt voor het vernieuwen van de status die extern wordt gewijzigd.
Zie de sectie Foutmodi hieronder voor meer informatie over mechanismen voor foutafhandeling.
Schrijfstatus
De status kan worden gewijzigd via de State
eigenschap. De gewijzigde status wordt niet automatisch behouden. In plaats daarvan bepaalt de ontwikkelaar wanneer de status moet worden behouden door de WriteStateAsync methode aan te roepen. Met de volgende methode wordt bijvoorbeeld een eigenschap State
bijgewerkt en blijft de bijgewerkte status behouden:
public async Task SetNameAsync(string name)
{
_profile.State.Name = name;
await _profile.WriteStateAsync();
}
Conceptueel neemt de Orleans Runtime een grondige kopie van het gegevensobject voor de korrelstatus voor het gebruik ervan tijdens schrijfbewerkingen. Onder de dekking kan de runtime gebruikmaken van optimalisatieregels en heuristieken om te voorkomen dat sommige of alle diepe kopieën in sommige omstandigheden worden uitgevoerd, mits de verwachte semantiek voor logische isolatie behouden blijft.
Zie de sectie Foutmodi hieronder voor meer informatie over mechanismen voor foutafhandeling.
Status wissen
De ClearStateAsync methode wist de status van het graan in opslag. Afhankelijk van de provider kan deze bewerking eventueel de korrelstatus volledig verwijderen.
Aan de slag
Voordat een graan persistentie kan gebruiken, moet een opslagprovider worden geconfigureerd op de silo.
Configureer eerst opslagproviders, één voor profielstatus en één voor winkelwagenstatus:
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();
Nu een opslagprovider is geconfigureerd met de naam "profileStore"
, hebben we toegang tot deze provider vanaf een korrel.
De permanente status kan op twee primaire manieren aan een korrel worden toegevoegd:
- Door in de constructor van het graan te injecteren
IPersistentState<TState>
. - Door over te nemen van Grain<TGrainState>.
De aanbevolen manier om opslag toe te voegen aan een graan is door in de constructor van het graan te injecteren IPersistentState<TState>
met een gekoppeld [PersistentState("stateName", "providerName")]
kenmerk. Zie hieronder voor meer informatieGrain<TState>
. Dit wordt nog steeds ondersteund, maar wordt beschouwd als een verouderde benadering.
Declareer een klasse om de status van ons graan te bewaren:
[Serializable]
public class ProfileState
{
public string Name { get; set; }
public Date DateOfBirth
}
Injecteer IPersistentState<ProfileState>
in de constructor van het graan:
public class UserGrain : Grain, IUserGrain
{
private readonly IPersistentState<ProfileState> _profile;
public UserGrain(
[PersistentState("profile", "profileStore")]
IPersistentState<ProfileState> profile)
{
_profile = profile;
}
}
Notitie
De profielstatus wordt niet geladen op het moment dat deze wordt geïnjecteerd in de constructor, zodat toegang tot de status op dat moment ongeldig is. De status wordt geladen voordat OnActivateAsync deze wordt aangeroepen.
Nu het graan een permanente status heeft, kunnen we methoden toevoegen om de status te lezen en te schrijven:
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();
}
}
Foutmodi voor persistentiebewerkingen
Foutmodi voor leesbewerkingen
Fouten die door de opslagprovider worden geretourneerd tijdens de eerste leesbewerking van statusgegevens voor dat specifieke korrel, mislukken de activeringsbewerking voor dat korrel; In dat geval wordt er geen callback-methode voor de levenscyclus van dat graan OnActivateAsync()
aangeroepen. De oorspronkelijke aanvraag voor het korreltje waardoor de activering is veroorzaakt, wordt teruggezet naar de beller, op dezelfde manier als elke andere fout tijdens het activeren van de korrel. Fouten die door de opslagprovider zijn opgetreden bij het lezen van statusgegevens voor een bepaald graan, resulteren in een uitzondering van ReadStateAsync()
Task
. Het graan kan ervoor kiezen om de Task
uitzondering te verwerken of te negeren, net zoals elke andere Task
in Orleans.
Elke poging om een bericht te verzenden naar een korrel die niet kan worden geladen tijdens het opstarten van de silo vanwege een ontbrekende/slechte opslagproviderconfiguratie, retourneert de permanente fout BadProviderConfigException.
Foutmodi voor schrijfbewerkingen
Fouten die door de opslagprovider zijn opgetreden bij het schrijven van statusgegevens voor een bepaald korrel, resulteren in een uitzondering die wordt gegenereerd door WriteStateAsync()
Task
. Dit betekent meestal dat de korreloproep-uitzondering wordt teruggegooid naar de clientoproeper, mits de WriteStateAsync()
Task
fout correct is gekoppeld aan de uiteindelijke retour Task
voor deze korrelmethode. Het is echter mogelijk in bepaalde geavanceerde scenario's om graancode te schrijven om dergelijke schrijffouten specifiek af te handelen, net zoals ze andere fouten Task
kunnen verwerken.
Korrels die foutafhandeling/ herstelcode uitvoeren , moeten uitzonderingen/defecte WriteStateAsync()
Task
s vangen en niet opnieuw gooien, om aan te geven dat ze de schrijffout hebben verwerkt.
Aanbevelingen
JSON-serialisatie of een andere versietolerante serialisatie-indeling gebruiken
Code ontwikkelt zich en dit omvat vaak ook opslagtypen. Voor deze wijzigingen moet een geschikte serialisatiefunctie worden geconfigureerd. Voor de meeste opslagproviders is een UseJson
optie of vergelijkbaar beschikbaar om JSON te gebruiken als serialisatie-indeling. Zorg ervoor dat bij het ontwikkelen van gegevenscontracten die al opgeslagen gegevens nog steeds kunnen worden geladen.
GrainTState<> gebruiken om opslag toe te voegen aan een korrel
Belangrijk
Het gebruik Grain<T>
om opslag toe te voegen aan een korrel wordt beschouwd als verouderde functionaliteit: graanopslag moet worden toegevoegd met behulp van IPersistentState<T>
de eerder beschreven beschrijving.
Graanklassen die overnemen van Grain<T>
(waarbij T
een toepassingsspecifiek statusgegevenstype dat moet worden bewaard), worden automatisch geladen vanuit de opgegeven opslag.
Dergelijke korrels worden gemarkeerd met een StorageProviderAttribute die een benoemd exemplaar van een opslagprovider aangeeft dat moet worden gebruikt voor het lezen/schrijven van de statusgegevens voor dit graan.
[StorageProvider(ProviderName="store1")]
public class MyGrain : Grain<MyGrainState>, /*...*/
{
/*...*/
}
De Grain<T>
basisklasse heeft de volgende methoden gedefinieerd voor subklassen die moeten worden aangeroepen:
protected virtual Task ReadStateAsync() { /*...*/ }
protected virtual Task WriteStateAsync() { /*...*/ }
protected virtual Task ClearStateAsync() { /*...*/ }
Het gedrag van deze methoden komt overeen met de tegenhangers die IPersistentState<TState>
eerder zijn gedefinieerd.
Een opslagprovider maken
Er zijn twee delen voor de statuspersistentie-API's: de API die wordt blootgesteld aan het graan via IPersistentState<T>
of Grain<T>
, en de API van de opslagprovider, die is gecentreerd rond IGrainStorage
: de interface die opslagproviders moeten implementeren:
/// <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);
}
Maak een aangepaste opslagprovider door deze interface te implementeren en die implementatie te registreren . Zie AzureBlobGrainStorage
voor een voorbeeld van een bestaande implementatie van een opslagprovider.
semantiek van Storage provider
Een ondoorzichtige providerspecifieke Etag waarde (string
) kan worden ingesteld door een opslagprovider als onderdeel van de metagegevens van de korrelstatus die zijn ingevuld toen de status werd gelezen. Sommige providers kunnen ervoor kiezen dit te laten alsof null
ze geen s gebruiken Etag
.
Elke poging om een schrijfbewerking uit te voeren wanneer de opslagprovider een schending van een Etag
beperking detecteert , moet ertoe leiden dat de schrijfbewerking Task
wordt beschadigd met tijdelijke fouten InconsistentStateException en de onderliggende opslagonderzondering wordt verpakt.
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; }
}
Andere foutvoorwaarden van een opslagbewerking moeten ertoe leiden dat de geretourneerde Task
fout wordt verbroken met een uitzondering die het onderliggende opslagprobleem aangeeft. In veel gevallen kan deze uitzondering worden teruggestuurd naar de aanroeper die de opslagbewerking heeft geactiveerd door een methode op het graan aan te roepen. Het is belangrijk om te overwegen of de beller deze uitzondering al dan niet kan deserialiseren. De client heeft bijvoorbeeld mogelijk niet de specifieke persistentiebibliotheek geladen die het uitzonderingstype bevat. Daarom is het raadzaam om uitzonderingen te converteren naar uitzonderingen die kunnen worden doorgegeven aan de beller.
Gegevenstoewijzing
Afzonderlijke opslagproviders moeten bepalen hoe de korrelstatus het beste kan worden opgeslagen: blob (verschillende indelingen/ geserialiseerde formulieren) of kolom per veld zijn duidelijke keuzes.
Een opslagprovider registreren
De Orleans-runtime lost een opslagprovider op van de serviceprovider (IServiceProvider) wanneer er een korrel wordt gemaakt. Met de runtime wordt een exemplaar van IGrainStorage. Als de opslagprovider een naam heeft, bijvoorbeeld via het [PersistentState(stateName, storageName)]
kenmerk, wordt een benoemd exemplaar van IGrainStorage
deze provider omgezet.
Als u een benoemd exemplaar van IGrainStorage
wilt registreren, gebruikt u de AddSingletonNamedService extensiemethode volgens het voorbeeld van de AzureTableGrainStorage-provider hier.