Andreani.Arq.Orleans.Core¶
Esta librería contiene la implementación de Grains comunes.
HealthCheck¶
Implementación de Grain de HealthCheck.
Collections¶
En Orleans, es vital estar al tanto de los granos generados en nuestro sistema. Aunque a menudo tenemos la capacidad de asignar manualmente el ID para cada grano, hay situaciones en las que este ID es generado automáticamente por el sistema. En estos casos, se hace necesario tener una estrategia para agrupar estos granos según ciertos criterios.
Además, es importante señalar que Orleans no ofrece una funcionalidad nativa para buscar granos basada en campos del estado, similar a una cláusula "WHERE" en SQL. Por lo tanto, la responsabilidad recae en nosotros para crear nuestros propios mecanismos de agrupación de granos.
Esto es crucial por varias razones:
-
Gestión del Estado: Sin un mecanismo de agrupación, se vuelve complicado gestionar y acceder al estado de granos específicos de manera eficiente.
-
Monitoreo y Diagnóstico: Agrupar granos facilita el seguimiento y la identificación de problemas, lo cual es esencial para el mantenimiento y la optimización del sistema.
-
Optimización de Recursos: Al tener agrupaciones claras, podemos implementar estrategias más eficientes para la colocación de granos, lo que a su vez mejora el rendimiento del sistema.
-
Personalización de la Lógica de Negocio: Al crear nuestros propios agrupadores, tenemos más flexibilidad para implementar lógicas de negocio complejas que requieran la interacción entre múltiples granos.
Por lo tanto, la implementación de mecanismos propios para agrupar granos se convierte en una práctica esencial para la administración efectiva de aplicaciones distribuidas en Orleans.
Directory¶
El IDirectoryGrain<T>
es un grano diseñado para guardar referencias a un grano específico. Implementa IGrainWithStringKey
, lo que significa que es esencial asignarle un nombre programático para poder acceder a él más tarde. El tipo genérico T
está restringido a los tipos de ID que Orleans puede manejar.
// Implementaciones específicas
public interface IDirectoryGuidGrain : IDirectoryGrain<Guid>{}
public interface IDirectoryStringGrain : IDirectoryGrain<string>{}
public interface IDirectoryIntegerGrain : IDirectoryGrain<long>{}
public interface IDirectoryGuidCompoundGrain : IDirectoryGrain<(Guid, string)>{}
public interface IDirectoryIntegerCompoundGrain : IDirectoryGrain<(long, string)>{}
Dependiendo del tipo de ID que hayas seleccionado para tu grano, deberás optar por una de estas implementaciones específicas.
Ejemplo:
_grainFactory.GetGrain<IDirectoryGuidGrain>(nameof(IPersonGrains))
En este caso, IPersonGrains
tiene un ID de tipo Guid
, por lo que elegimos IDirectoryGuidGrain
.
Si deseas utilizar estos directorios, tu grano debe implementar la interfaz IGrainWithDirectory<T>
.
public interface IGrainWithDirectory<T>
{
Task<T> GetState();
}
Esta interfaz se usa para recuperar el valor del estado del grano cuando se invoca el método GetAll
.
Almacenamiento de Referencias¶
Los granos de tipo Directory
mantienen una lista de IDs en su estado, representada por IPersistentState<List<T>> _index
. Esta es una lista estándar de C#.
Para añadir o eliminar un elemento, el directorio primero verifica si el elemento ya existe.
Para comprobar la existencia de un elemento, se utiliza el método Exist
de Linq
.
Advertencia
Estos directorios están diseñados para almacenar un conjunto de referencias fijas. Dado que las listas en C# crecen con cada nuevo elemento, es vital controlar su tamaño para evitar un aumento en la carga de procesamiento del servidor.
Recuperación de Datos¶
Los directorios emplean una estrategia de paginación basada en marcadores.
El método GetAll
devuelve un objeto ResultFindAll
.
public record struct ResultFindAll<T>(string? Next, int Limit, T Content);
Donde: 1. Content: Contiene los datos de los granos almacenados. 2. Limit: Especifica el límite de elementos recuperados. 3. Next: Señala la siguiente posición para continuar iterando la lista.
Este método recorre la lista de IDs almacenados en el estado, rehidrata los granos correspondientes y luego invoca el método GetState()
de la interfaz IGrainWithDirectory
.
Ejemplo de uso
var offset = "";
var limit = 100;
var grains = await _client.GetGrain<IDirectoryStringGrain>(nameof(IUserGrains))
.GetAll<IUserGrains, UserDto>(offset, limit: limit);
BigDirectory¶
Los BigDirectory son un caso particular de los Directory. Poseen los mismos metodos que los Directory. Los BigDirectory a diferencia de los Directory son estructuras pensadas para almacenar una gran lista de elementos sin afectar la performance del cluster, ya que utiliza en vez de un lista enlazada de C# una lista enlazada desarrollada por Andreani para Orleans.
Almacenamiento de Referencias¶
Los BigDirectory Almacenan una referencia a un ICollectionGrain
y a IBloomFilterCollectionGrain
.
Cuando se almacena un elemento, se valida que no exista previamente utilizando BloomFilter y luego es agregado a ambas collecciones. Para remover un elemento, se valida que exista y luego solo se remueve de la CollectionGrain, esto es debido a que no es posible eliminar un elemento de BloomFilter.
Recuperación de Datos¶
Al igual que los Directory, los BigDirectory tienen el mismo input y output en el metodo GetAll
Su funcionamiento en vez de iterar una lista de Id, itera sobre la ICollectionGrain
obteniendo los valores almacenados con estrategia Latest
ICollectionGrain¶
El ICollectionGrain es un grain que tiene por estado la referencia el primer y último grano de la cadena.
El ICollectionGrain implementa el patron de comportamiento "Iterator" para recorrer la estructura de los IStorageGrain
Los IStorageGrain son los elementos de esta colección, estos almacenan el valor que queremos almacenar y la referencia al anterior y al posterior en la cadena.
El grain IStorageGrain
tiene la siguiente definición:
public interface IStorageGrain<T> : IGrainWithGuidKey
{
Task<Guid?> Previus();
Task<Guid?> Next();
Task<T> Value();
Task SetNext(Guid? next);
Task SetPrevius(Guid? previus);
Task SetValue(T value);
Task Save(T value, Guid? previus, Guid? next);
}
En resumen el StoregaGrain no es más que un grain que no tiene comportamiento, solo almacena información que es explotada por el ICollectionGrain
.
La defición de la interface de ICollectionGrain
es:
public interface ICollectionGrain<T> : IGrainWithGuidKey
{
Task<IStorageGrain<T>?> Current();
Task AddItem(T item);
Task RemoveItem(T item);
Task ChangeDirection(IteratorDirection direction);
Task<bool> MoveNext();
Task Skip(Guid id);
Task Reset();
Task<Guid?> HowIsNext();
}
Current
: Devuelve el Grain de la posición actual.AddItem
: Agrega un nuevo item a la lista. El proceso de agregado de item es: se crea el grain deStorageGrain
con el valor que queremos almacenar, se setea el next ennull
y el previus al id delLastGrain
de la cadena y este nuevo Grain pasa a ser el nuevoLastGrain
de la cadena. Por ultimo se apunta el valor denext
del viejoLastGrain
al nuevoLastGrain
RemoveItem
: Remueve un item de la cadena. Para el proceso de remove, se busca el elemento a eliminar en la cadena, y se modifican los grain que estaban antes y despues en la cadena, quitando la referencia del grain a eliminar y apuntandose entre estos respectivamente.ChangeDirection
: Cambia la dirección de recorrido de la lista. Por defecto esLatest
| Opciones:Earliest
,Latest
MoveNext
: Si existe una siguiente posición en la dirección de recorrido, mueve el puntero en la dirección necesaria y devuelvetrue
, en caso de no tener una siguiente posición devuelvefalse
Skip
: Posiciona el Current en la referencia solicitada.Reset
: Reinicia el Current.HowIsNext
: Devuelve la referencia al siguiente valor. Esto depende de la dirección en la que se este recorriendo.
Diagrama AddItem
Diagrama RemoveItem
Ejemplo de uso:
do
{
var element = await _collection.Current();
if (element == null)
break;
Console.WriteLine(await element.Value());
}
while (await _collection.MoveNext());
IBloomFilterCollectionGrain¶
El componente IBloomFilterCollectionGrain
se fundamenta en la aplicación del patrón de diseño BloomFilter
.
Este componente, IBloomFilterCollectionGrain
, mantiene una referencia a una ICollectionGrain
. En esta colección se guarda el arreglo de bytes que el BloomFilter utiliza.
La interfaz se define de la siguiente manera:
public interface IBloomFilterCollectionGrain<T> : IGrainWithGuidKey
{
Task AddAsync(T id);
Task<bool> Exist(T id);
}
Estrategia de Almacenamiento¶
Los parámetros estándar para el BloomFilter son los siguientes:
Número de elementos (n)
=100000
Algoritmo de Hash
= Murmur32BitsX86Cantidad de Funciones Hash (k)
=17
Tamaño del BitArray (m)
=2396272
(aproximadamente290 KB
)Tasa de Error (P)
=0.00001
Nota
El número de elementos se estableció en este valor para minimizar el uso excesivo de memoria. Siguiendo la misma lógica que con los BigDirectory, el arreglo de bits se guarda en una ICollectionGrain y se crea uno nuevo cada vez que se alcanza la meta de 100,000 registros.
Estrategia de Búsqueda¶
Para determinar si un elemento existe, se realiza una búsqueda en la lista de BloomFilters. Si el elemento no se encuentra en el lote actual, se carga el siguiente lote de la lista hasta llegar al final. Si el elemento no se encuentra en ninguno de los lotes, se devuelve false
.
Ejemplo de Uso
Guid guid = Guid.NewGuid();
_bloomFilter = _grainFactory.GetGrain<IBloomFilterCollectionGrain<long>>(guid);
// Add
var id = 123456;
await _bloomFilter.AddAsync(id);
// Exist
bool exist = await _bloomFilter.Exist(id)