Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Los granos pueden tener varios objetos de datos persistentes con nombre asociados. Estos objetos de estado se cargan desde el almacenamiento durante la activación de los granos para que estén disponibles durante las solicitudes. La persistencia de Grain utiliza un modelo de complemento extensible, permitiéndole utilizar proveedores de almacenamiento para cualquier base de datos. Este modelo de persistencia está diseñado para simplificar y no está pensado para cubrir todos los patrones de acceso a datos. Los granos también pueden acceder a las bases de datos directamente sin usar el modelo de persistencia de granos.
En el diagrama anterior, UserGrain tiene un estado perfil y un estado Cart , cada uno almacenado en un sistema de almacenamiento independiente.
Objetivos
- Admite varios objetos de datos persistentes con nombre por grano.
- Permitir varios proveedores de almacenamiento configurados, cada uno de los cuales puede tener una configuración diferente y respaldada por un sistema de almacenamiento diferente.
- Habilite a la comunidad para desarrollar y publicar proveedores de almacenamiento.
- Proporcione a los proveedores de almacenamiento control total sobre cómo almacenan los datos de estado detallados en el almacén de respaldo persistente. Corollary: Orleans no proporciona una solución completa de almacenamiento ORM, pero permite a los proveedores de almacenamiento personalizados admitir requisitos ORM específicos según sea necesario.
Paquetes
Puede encontrar Orleans proveedores de almacenamiento de grano en NuGet. Los paquetes mantenidos oficialmente incluyen:
- Microsoft.Orleans. Persistence.AdoNet: para bases de datos SQL y otros sistemas de almacenamiento compatibles con ADO.NET. Para obtener más información, consulte ADO.NET persistencia de grano.
- Microsoft.Orleans.Persistence.AzureStorage: para Azure Storage, incluyendo Azure Blob Storage y Azure Table Storage (a través de la API de Azure Table Storage). Para más información, consulte Persistencia de granos en Azure Storage.
- Microsoft.Orleans. Persistence.Cosmos: el proveedor de Azure Cosmos DB. Para más información, consulte Persistencia de granos de Azure Cosmos DB.
- Microsoft.Orleans. Persistence.DynamoDB: para Amazon DynamoDB. Para obtener más información, consulte Persistencia de granos de Amazon DynamoDB.
Interfaz de Programación de Aplicaciones (API)
Los granos interactúan con su estado persistente mediante IPersistentState<TState>, donde TState
es el tipo de estado serializable:
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 inserta instancias de IPersistentState<TState>
en el grano como parámetros de constructor. Puede anotar estos parámetros con un PersistentStateAttribute atributo para identificar el nombre del estado que se inserta y el nombre del proveedor de almacenamiento que lo proporciona. En el ejemplo siguiente se muestra esto insertando dos estados con nombre en el UserGrain
constructor:
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;
}
}
Los distintos tipos de grano pueden usar proveedores de almacenamiento configurados diferentes, incluso si ambos son del mismo tipo (por ejemplo, dos instancias de proveedor de Azure Table Storage diferentes conectadas a diferentes cuentas de Azure Storage).
Estado de lectura
El estado del grano se lee automáticamente cuando se activa el grano, pero los granos son responsables de activar explícitamente la escritura de cualquier cambio en el estado del grano cuando sea necesario.
Si un grano desea volver a leer explícitamente su estado más reciente desde el almacén de respaldo, debe llamar al ReadStateAsync método . Esto vuelve a cargar el estado de grano del almacén persistente a través del proveedor de almacenamiento. La copia anterior en memoria del estado del grano se sobrescribe y se reemplaza cuando se completa el Task
de ReadStateAsync()
.
Acceda al valor del estado mediante la State
propiedad . Por ejemplo, el método siguiente accede al estado del perfil declarado en el código anterior:
public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);
No es necesario llamar ReadStateAsync()
durante el funcionamiento normal; Orleans carga el estado automáticamente durante la activación. Sin embargo, puede usar ReadStateAsync()
para actualizar el estado modificado externamente.
Consulte la sección Modos de error a continuación para obtener más información sobre los mecanismos de control de errores.
Estado de escritura
Puede modificar el estado a través de la State
propiedad . El estado modificado no se conserva automáticamente. En su lugar, decide cuándo conservar el estado llamando al WriteStateAsync método . Por ejemplo, el método siguiente actualiza una propiedad en State
y conserva el estado actualizado:
public async Task SetNameAsync(string name)
{
_profile.State.Name = name;
await _profile.WriteStateAsync();
}
Conceptualmente, el Orleans entorno de ejecución toma una copia profunda del objeto de datos del estado del 'grain' para su uso durante cualquier operación de escritura. En segundo plano, el entorno de ejecución podría usar reglas de optimización y heurística para evitar realizar alguna o toda la copia profunda en determinadas circunstancias, siempre que se conserve la semántica de aislamiento lógico esperada.
Consulte la sección Modos de error a continuación para obtener más información sobre los mecanismos de control de errores.
Borrado del estado
El ClearStateAsync método borra el estado del grano en el almacenamiento. Dependiendo del proveedor, esta operación podría eliminar opcionalmente el estado de grano por completo.
Comienza
Antes de que un grano pueda usar la persistencia, debe configurar un proveedor de almacenamiento en el silo.
En primer lugar, configure proveedores de almacenamiento, uno para el estado del perfil y otro para el estado del carro:
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();
Importante
Microsoft recomienda usar el flujo de autenticación más seguro disponible. Si se conecta a Azure SQL, el método de autenticación recomendado es Identidades administradas para recursos de Azure.
Ahora que ha configurado un proveedor de almacenamiento denominado "profileStore"
, puede acceder a este proveedor desde una unidad.
Puede agregar un estado persistente a un grano de dos maneras principales.
- Inyectando
IPersistentState<TState>
en el constructor del objeto 'grain'. - Al heredar de Grain<TGrainState>.
La manera recomendada de agregar almacenamiento a un grano es insertar IPersistentState<TState>
en el constructor del grano con un atributo asociado [PersistentState("stateName", "providerName")]
. Para obtener más información sobre Grain<TState>
, consulte Cómo usar Grain<TState>
para agregar almacenamiento a un grano a continuación. El uso de Grain<TState>
sigue siendo compatible, pero se considera un método obsoleto.
Declare una clase para mantener el estado de su grano.
[Serializable]
public class ProfileState
{
public string Name { get; set; }
public Date DateOfBirth { get; set; }
}
Inserte IPersistentState<ProfileState>
en el constructor del grano:
public class UserGrain : Grain, IUserGrain
{
private readonly IPersistentState<ProfileState> _profile;
public UserGrain(
[PersistentState("profile", "profileStore")]
IPersistentState<ProfileState> profile)
{
_profile = profile;
}
}
Importante
El estado del perfil no se cargará en el momento en que se inserta en el constructor, por lo que acceder a él no es válido en ese momento. El estado se cargará antes de que se llame a OnActivateAsync.
Ahora que el grano tiene un estado persistente, puede agregar métodos para leer y escribir el estado:
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();
}
}
Modos de falla para las operaciones de persistencia
Modos de fallo para las operaciones de lectura
Los errores devueltos por el proveedor de almacenamiento durante la lectura inicial de los datos de estado para un grano determinado producen un error en la operación de activación de ese grano. En tales casos, no habrá ninguna llamada al método de devolución de llamada de ciclo de vida de OnActivateAsync
ese grano. La solicitud original al grano que provocó los errores de activación de vuelta al autor de la llamada, al igual que cualquier otro error durante la activación específica. Los errores detectados por el proveedor de almacenamiento al leer los datos de estado de un grain en particular dan lugar a una excepción de ReadStateAsync
Task
. El grano puede elegir controlar o omitir la Task
excepción, al igual que cualquier otro Task
en Orleans.
Cualquier intento de enviar un mensaje a un grano que no se pudo cargar al iniciar el silo debido a una configuración de proveedor de almacenamiento que está faltante o incorrecta devuelve el error permanente BadProviderConfigException.
Modos de error para las operaciones de escritura
Los errores detectados por el proveedor de almacenamiento al escribir datos de estado para un grain específico dan lugar a una excepción lanzada por WriteStateAsync()
Task
. Normalmente, esto significa que la excepción de llamada de grain se devuelve al autor de la llamada del cliente, siempre que se WriteStateAsync()
Task
encadene correctamente en la devolución Task
final de este método de grain. Sin embargo, en determinados escenarios avanzados, puede escribir código específico para controlar específicamente estos errores de escritura, al igual que controlar cualquier otro error.Task
Los granos que ejecutan el control de errores o el código de recuperación deben detectar excepciones o fallas WriteStateAsync()
Task
y no volver a lanzarlas, lo que significa que han controlado correctamente el error de escritura.
Recomendaciones
Uso de la serialización JSON u otro formato de serialización tolerante a versiones
El código evoluciona y esto suele incluir tipos de almacenamiento. Para dar cabida a estos cambios, configure un serializador adecuado. Para la mayoría de los proveedores de almacenamiento, hay una UseJson
opción o similar disponible para usar JSON como formato de serialización. Asegúrese de que, al desarrollar contratos de datos, todavía se pueden cargar los datos almacenados.
Usar Grain<TState>
para agregar almacenamiento a un grano
Importante
El uso de Grain<T>
para agregar almacenamiento a un grano se considera funcionalidad heredada. Agregue almacenamiento de grano usando IPersistentState<T>
como se ha descrito anteriormente.
Las clases de grano que heredan de Grain<T>
(donde T
es un tipo de datos de estado específico de la aplicación que necesita persistencia) tienen su estado cargado automáticamente desde el almacenamiento especificado.
Marque estos granos con una StorageProviderAttribute especificación de una instancia con nombre de un proveedor de almacenamiento que se usará para leer y escribir los datos de estado para este grano.
[StorageProvider(ProviderName="store1")]
public class MyGrain : Grain<MyGrainState>, /*...*/
{
/*...*/
}
La Grain<T>
clase base define los métodos siguientes para que las subclases llamen a:
protected virtual Task ReadStateAsync() { /*...*/ }
protected virtual Task WriteStateAsync() { /*...*/ }
protected virtual Task ClearStateAsync() { /*...*/ }
El comportamiento de estos métodos corresponde a sus homólogos en IPersistentState<TState>
definidos anteriormente.
Creación de un proveedor de almacenamiento
Hay dos partes en las API de persistencia de estado: la API expuesta al grano a través de IPersistentState<T>
o Grain<T>
, y la API del proveedor de almacenamiento, centrada en IGrainStorage
: los proveedores de almacenamiento de interfaz deben implementar.
/// <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);
}
Cree un proveedor de almacenamiento personalizado implementando esta interfaz y registrando esa implementación. Para obtener un ejemplo de una implementación del proveedor de almacenamiento existente, consulte AzureBlobGrainStorage
.
Semántica del proveedor de almacenamiento
Un proveedor de almacenamiento Etag establecer un valor opaco específico del proveedor string
() como parte de los metadatos del estado de 'grain' rellenados cuando se leyó el estado. Algunos proveedores pueden optar por dejar esto como null
si no usaran Etag
.
Cualquier intento de realizar una operación de escritura cuando el proveedor de almacenamiento detecte una Etag
infracción de restricción debería resultar en un fallo de escritura con un error transitorio Task
y encapsular la excepción de almacenamiento subyacente.
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; }
}
Cualquier otra condición de error de una operación de almacenamiento debe provocar que el resultado Task
se interrumpa por una excepción que indique el problema de almacenamiento subyacente. En muchos casos, esta excepción se podría devolver al autor de la llamada que desencadenó la operación de almacenamiento al llamar a un método en el componente. Es importante tener en cuenta si el autor de la llamada puede deserializar esta excepción. Por ejemplo, es posible que el cliente no haya cargado la biblioteca de persistencia específica que contiene el tipo de excepción. Por este motivo, es aconsejable convertir excepciones en excepciones que se puedan propagar de nuevo al autor de la llamada.
Mapeo de datos
Los proveedores de almacenamiento individuales deben decidir cómo almacenar mejor el estado de grano: blob (varios formatos o formularios serializados) o las opciones obvias de columna por campo.
Registro de un proveedor de almacenamiento
El Orleans tiempo de ejecución resuelve un proveedor de almacenamiento del proveedor de servicios (IServiceProvider) cuando se crea un grano. El tiempo de ejecución resuelve una instancia de IGrainStorage. Si el proveedor de almacenamiento tiene un nombre (por ejemplo, mediante el atributo [PersistentState(stateName, storageName)]
), se resuelve una instancia nombrada de IGrainStorage
.
Para registrar una instancia nombrada de IGrainStorage
, use el método de extensión AddSingletonNamedService siguiendo aquí el ejemplo del proveedor AzureTableGrainStorage.