Kornpersistens
Korn kan ha flera namngivna beständiga dataobjekt som är associerade med dem. Dessa tillståndsobjekt läses in från lagringen under kornig aktivering så att de är tillgängliga under begäranden. Kornbeständighet använder en utökningsbar plugin-modell så att lagringsproviders för alla databaser kan användas. Den här beständighetsmodellen är utformad för enkelhetens skull och är inte avsedd att omfatta alla dataåtkomstmönster. Korn kan också komma åt databaser direkt, utan att använda modellen för kornbeständighet.
I diagrammet ovan har UserGrain ett profiltillstånd och ett kundvagnstillstånd , som var och en lagras i ett separat lagringssystem.
Mål
- Flera namngivna beständiga dataobjekt per kornighet.
- Flera konfigurerade lagringsproviders, som var och en kan ha olika konfigurationer och backas upp av ett annat lagringssystem.
- Storage leverantörer kan utvecklas och publiceras av communityn.
- Storage providrar har fullständig kontroll över hur de lagrar korntillståndsdata i det beständiga lagringsarkivet. Följd: Orleans tillhandahåller inte en omfattande ORM-lagringslösning, utan tillåter istället anpassade lagringsproviders att stödja specifika ORM-krav när det behövs.
Paket
Orleans grain storage providers finns på NuGet. Officiellt underhållna paket är:
- Microsoft.Orleans.Persistence.AdoNet är för SQL databaser och andra lagringssystem som stöds av ADO.NET. Mer information finns i ADO.NET Grain Persistence.
- Microsoft.Orleans.Persistence.AzureStorage är till för Azure Storage, inklusive Azure Blob Storage, Azure Table Storage och Azure CosmosDB via API:et för Azure Table Storage. Mer information finns i Azure Storage Grain Persistence.
- Microsoft.Orleans.Persistence.DynamoDB är för Amazon DynamoDB. Mer information finns i Amazon DynamoDB Grain Persistence.
API
Korn interagerar med sitt beständiga tillstånd med hjälp av IPersistentState<TState> var TState
är den serialiserbara tillståndstypen:
public interface IPersistentState<TState> where TState : new()
{
TState State { get; set; }
string Etag { get; }
Task ClearStateAsync();
Task WriteStateAsync();
Task ReadStateAsync();
}
Instanser av IPersistentState<TState>
matas in i kornigheten som konstruktorparametrar. Dessa parametrar kan kommenteras med ett PersistentStateAttribute attribut för att identifiera namnet på det tillstånd som matas in och namnet på lagringsprovidern som tillhandahåller det. Följande exempel visar detta genom att mata in två namngivna tillstånd i UserGrain
konstruktorn:
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;
}
}
Olika korntyper kan använda olika konfigurerade lagringsproviders, även om båda är av samma typ. Till exempel två olika Azure Table Storage-providerinstanser som är anslutna till olika Azure Storage konton.
Lästillstånd
Korntillståndet läses automatiskt när kornigheten aktiveras, men kornigheterna är ansvariga för att uttryckligen utlösa skrivning för alla ändrade kornighetstillstånd när det behövs.
Om ett korn uttryckligen vill läsa om det senaste tillståndet för det här kornet från lagringsplatsen bör kornigheten ReadStateAsync anropa metoden . Detta läser in kornighetstillståndet igen från det beständiga arkivet via lagringsprovidern, och den tidigare minnesintern kopian av kornighetstillståndet skrivs över och ersätts när Task
från ReadStateAsync()
slutförs.
Värdet för tillståndet används med hjälp av State
egenskapen . Följande metod använder till exempel profiltillståndet som deklareras i koden ovan:
public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);
Det finns inget behov av att anropa ReadStateAsync()
under normal drift. Tillståndet läses in automatiskt under aktiveringen. Kan dock ReadStateAsync()
användas för att uppdatera tillstånd som ändras externt.
Mer information om felhanteringsmekanismer finns i avsnittet Fellägen nedan.
Skrivtillstånd
Tillståndet kan ändras via egenskapen State
. Det ändrade tillståndet sparas inte automatiskt. I stället bestämmer utvecklaren när tillståndet ska bevaras genom att anropa WriteStateAsync metoden . Följande metod uppdaterar till exempel en egenskap på State
och bevarar det uppdaterade tillståndet:
public async Task SetNameAsync(string name)
{
_profile.State.Name = name;
await _profile.WriteStateAsync();
}
Konceptuellt tar Orleans Runtime en djup kopia av korntillståndsdataobjektet för dess användning under skrivåtgärder. Under förloppet kan körningen använda optimeringsregler och heuristik för att undvika att utföra en del eller hela den djupa kopian under vissa omständigheter, förutsatt att den förväntade logiska isoleringssemantiken bevaras.
Mer information om felhanteringsmekanismer finns i avsnittet Fellägen nedan.
Rensa tillstånd
Metoden ClearStateAsync rensar kornets tillstånd i lagringen. Beroende på providern kan den här åtgärden eventuellt ta bort kornighetstillståndet helt.
Kom igång
Innan ett korn kan använda beständighet måste en lagringsprovider konfigureras på silon.
Konfigurera först lagringsproviders, en för profiltillstånd och en för kundvagnstillstånd:
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 när en lagringsprovider har konfigurerats med namnet "profileStore"
kan vi komma åt den här providern från ett korn.
Det beständiga tillståndet kan läggas till i ett korn på två huvudsakliga sätt:
- Genom att
IPersistentState<TState>
injicera i kornets konstruktor. - Genom att ärva från Grain<TGrainState>.
Det rekommenderade sättet att lägga till lagring i ett korn är genom att IPersistentState<TState>
mata in i kornets konstruktor med ett associerat [PersistentState("stateName", "providerName")]
attribut. Mer information om Grain<TState>
finns nedan. Detta stöds fortfarande men anses vara en äldre metod.
Deklarera en klass som ska innehålla kornets tillstånd:
[Serializable]
public class ProfileState
{
public string Name { get; set; }
public Date DateOfBirth
}
Mata IPersistentState<ProfileState>
in i kornets konstruktor:
public class UserGrain : Grain, IUserGrain
{
private readonly IPersistentState<ProfileState> _profile;
public UserGrain(
[PersistentState("profile", "profileStore")]
IPersistentState<ProfileState> profile)
{
_profile = profile;
}
}
Anteckning
Profiltillståndet läses inte in när det matas in i konstruktorn, så åtkomsten är ogiltig vid den tidpunkten. Tillståndet läses in innan OnActivateAsync anropas.
Nu när kornigheten har ett beständigt tillstånd kan vi lägga till metoder för att läsa och skriva tillståndet:
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();
}
}
Fellägen för beständighetsåtgärder
Fellägen för läsåtgärder
Fel som returneras av lagringsprovidern under den första läsningen av tillståndsdata för det specifika kornet misslyckas aktiveringsåtgärden för det kornet. I sådana fall kommer det inte att finnas något anrop till det kornets OnActivateAsync()
livscykelåteranropsmetod. Den ursprungliga begäran till kornigheten som orsakade aktiveringen kommer att felas tillbaka till anroparen, på samma sätt som andra fel under grain-aktiveringen. Fel som påträffas av lagringsprovidern vid läsning av tillståndsdata för ett visst intervall resulterar i ett undantag från ReadStateAsync()
Task
. Kornigheten kan välja att hantera eller ignorera undantaget Task
, precis som andra Task
i Orleans.
Alla försök att skicka ett meddelande till ett korn som inte kunde läsas in vid silostart på grund av en saknad/felaktig lagringsproviderkonfiguration returnerar det permanenta felet BadProviderConfigException.
Fellägen för skrivåtgärder
Fel som påträffas av lagringsprovidern när tillståndsdata skrivs för ett visst intervall resulterar i ett undantag som genereras av WriteStateAsync()
Task
. Detta innebär vanligtvis att undantaget för kornanrop returneras till klientanroparen, förutsatt WriteStateAsync()
Task
att är korrekt länkad till den slutliga returen Task
för den här kornighetsmetoden. I vissa avancerade scenarier är det dock möjligt att skriva kornig kod för att specifikt hantera sådana skrivfel, precis som de kan hantera andra fel.Task
Korn som kör felhantering/återställningskod måste fånga upp undantag/fel WriteStateAsync()
Task
och inte utlösa dem igen, för att visa att de har hanterat skrivfelet.
Rekommendationer
Använda JSON-serialisering eller ett annat versionstolerant serialiseringsformat
Koden utvecklas och det omfattar ofta även lagringstyper. För att hantera dessa ändringar bör en lämplig serialiserare konfigureras. För de flesta lagringsproviders är ett UseJson
alternativ eller liknande tillgängligt för att använda JSON som serialiseringsformat. Se till att redan lagrade data fortfarande kan läsas in när du utvecklar datakontrakt.
Använda GrainTState<> för att lägga till lagring i ett kornigt intervall
Viktigt
Användning av Grain<T>
för att lägga till lagring i ett korn betraktas som äldre funktioner: kornlagring bör läggas till med hjälp av IPersistentState<T>
det som beskrivits tidigare.
Kornklasser som ärver från Grain<T>
(där T
är en programspecifik tillståndsdatatyp som måste bevaras) får sitt tillstånd inläst automatiskt från angiven lagring.
Sådana kornigheter är markerade med en StorageProviderAttribute som anger en namngiven instans av en lagringsprovider som ska användas för att läsa/skriva tillståndsdata för det här kornet.
[StorageProvider(ProviderName="store1")]
public class MyGrain : Grain<MyGrainState>, /*...*/
{
/*...*/
}
Basklassen Grain<T>
definierade följande metoder för underklasser som ska anropas:
protected virtual Task ReadStateAsync() { /*...*/ }
protected virtual Task WriteStateAsync() { /*...*/ }
protected virtual Task ClearStateAsync() { /*...*/ }
Beteendet för dessa metoder motsvarar deras motsvarigheter som IPersistentState<TState>
definierades tidigare.
Skapa en lagringsprovider
Det finns två delar i API:erna för tillståndspersistens: API:et som exponeras för kornet via IPersistentState<T>
eller Grain<T>
och API:et för lagringsprovidern, som är centrerad runt IGrainStorage
– gränssnittet som lagringsproviders måste implementera:
/// <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);
}
Skapa en anpassad lagringsprovider genom att implementera det här gränssnittet och registrera implementeringen . Ett exempel på en befintlig implementering av lagringsprovidern finns i AzureBlobGrainStorage
.
Storage providersemantik
Ett täckande providerspecifikt Etag värde (string
) kan anges av en lagringsprovider som en del av korntillståndsmetadata som fylldes i när tillståndet lästes. Vissa leverantörer kan välja att lämna detta som null
om de inte använder Etag
s.
Alla försök att utföra en skrivåtgärd när lagringsprovidern upptäcker en Etag
begränsningsöverträdelse bör orsaka att skrivning Task
får fel med tillfälliga fel InconsistentStateException och omsluta det underliggande lagringsfelet.
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; }
}
Andra feltillstånd från en lagringsåtgärd måste orsaka att den returnerade Task
bryts med ett undantag som anger det underliggande lagringsproblemet. I många fall kan det här undantaget returneras till anroparen som utlöste lagringsåtgärden genom att anropa en metod i kornigheten. Det är viktigt att överväga om anroparen kommer att kunna deserialisera det här undantaget. Klienten kanske till exempel inte har läst in det specifika beständighetsbiblioteket som innehåller undantagstypen. Därför rekommenderar vi att du konverterar undantag till undantag som kan spridas tillbaka till anroparen.
Datamappning
Enskilda lagringsproviders bör bestämma hur du bäst lagrar korntillstånd – blob (olika format/serialiserade formulär) eller kolumn-per-fält är uppenbara alternativ.
Registrera en lagringsprovider
Orleans-körningen löser en lagringsprovider från tjänstleverantören (IServiceProvider) när ett korn skapas. Körningen löser en instans av IGrainStorage. Om lagringsprovidern namnges, till exempel via attributet, löses en namngiven [PersistentState(stateName, storageName)]
instans av IGrainStorage
.
Om du vill registrera en namngiven instans av IGrainStorage
använder du tilläggsmetoden enligt exemplet med AzureTableGrainStorage-providern här.AddSingletonNamedService