¿Alguien tiene un buen recurso para implementar una estrategia de agrupación de objetos compartidos para un recurso limitado en vena de la agrupación de conexiones SQL? (es decir, se implementaría completamente de forma segura para subprocesos).
Para realizar un seguimiento de la solicitud de aclaración de @Aaronaught, el uso del grupo sería para solicitudes de equilibrio de carga a un servicio externo. Para ponerlo en un escenario que probablemente sería más fácil de entender de inmediato en lugar de mi situación directa. Tengo un objeto de sesión que funciona de manera similar al ISession
objeto de NHibernate. Que cada sesión única gestione su conexión a la base de datos. Actualmente tengo 1 objeto de sesión de larga ejecución y encuentro problemas en los que mi proveedor de servicios limita la velocidad de mi uso de esta sesión individual.
Debido a su falta de expectativas de que una sola sesión sea tratada como una cuenta de servicio de larga duración, aparentemente la tratan como un cliente que está afectando su servicio. Lo que me lleva a mi pregunta aquí, en lugar de tener una sesión individual, crearía un grupo de sesiones diferentes y dividiría las solicitudes al servicio en esas sesiones múltiples en lugar de crear un único punto focal como lo estaba haciendo anteriormente.
Esperemos que ese fondo ofrezca algún valor, pero para responder directamente algunas de sus preguntas:
P: ¿Los objetos son caros de crear?
R: Ningún objeto es un grupo de recursos limitados.
P: ¿Se adquirirán / liberarán con mucha frecuencia?
R: Sí, una vez más se les puede pensar en NHibernate ISessions, donde generalmente se adquiere y libera 1 durante la duración de cada solicitud de página.
P: ¿Será suficiente un simple orden de llegada o necesita algo más inteligente, es decir, que evitaría la inanición?
R: Una distribución simple de tipo round robin sería suficiente, por inanición, supongo que quiere decir que si no hay sesiones disponibles, las personas que llaman se bloquean en espera de lanzamientos. Esto no es realmente aplicable ya que las sesiones pueden ser compartidas por diferentes personas que llaman. Mi objetivo es distribuir el uso en varias sesiones en lugar de una sola sesión.
Creo que esto es probablemente una divergencia del uso normal de un grupo de objetos, por lo que originalmente omití esta parte y planeé adaptar el patrón para permitir el intercambio de objetos en lugar de permitir que ocurra una situación de hambre.
P: ¿Qué pasa con cosas como prioridades, carga perezosa o ansiosa, etc.?
R: No hay una priorización involucrada, por simplicidad, solo asuma que crearía el grupo de objetos disponibles en la creación del grupo mismo.
fuente
Respuestas:
Agrupación de objetos en .NET Core
El núcleo dotnet tiene una implementación de agrupación de objetos añadida a la biblioteca de clases base (BCL). Puede leer el problema original de GitHub aquí y ver el código de System.Buffers . Actualmente,
ArrayPool
es el único tipo disponible y se utiliza para agrupar matrices. Hay una buena publicación de blog aquí .Un ejemplo de su uso se puede ver en ASP.NET Core. Debido a que está en el BCL de dotnet core, ASP.NET Core puede compartir su grupo de objetos con otros objetos como el serializador JSON de Newtonsoft.Json. Puede leer esta publicación de blog para obtener más información sobre cómo Newtonsoft.Json está haciendo esto.
Agrupación de objetos en el compilador de Microsoft Roslyn C #
El nuevo compilador de Microsoft Roslyn C # contiene el tipo ObjectPool , que se usa para agrupar objetos de uso frecuente que normalmente se actualizarían y la basura se recogería con mucha frecuencia. Esto reduce la cantidad y el tamaño de las operaciones de recolección de basura que tienen que suceder. Existen algunas subimplementaciones diferentes, todas utilizando ObjectPool (consulte: ¿Por qué hay tantas implementaciones de Object Pooling en Roslyn? ).
1 - SharedPools : almacena un grupo de 20 objetos o 100 si se usa BigDefault.
2 - ListPool y StringBuilderPool : no son implementaciones estrictamente separadas, sino envoltorios alrededor de la implementación SharedPools que se muestra arriba específicamente para List y StringBuilder's. Entonces, esto reutiliza el grupo de objetos almacenados en SharedPools.
3: PooledDictionary y PooledHashSet : utilizan ObjectPool directamente y tienen un grupo de objetos totalmente separado. Almacena un grupo de 128 objetos.
Microsoft.IO.RecyclableMemoryStream
Esta biblioteca proporciona agrupación de
MemoryStream
objetos. Es un reemplazo directo paraSystem.IO.MemoryStream
. Tiene exactamente la misma semántica. Fue diseñado por los ingenieros de Bing. Lea la publicación del blog aquí o vea el código en GitHub .Tenga en cuenta que
RecyclableMemoryStreamManager
debe declararse una vez y vivirá durante todo el proceso; este es el grupo. Está perfectamente bien usar varias piscinas si lo desea.fuente
RecyclableMemoryStream
eso es una adición increíble para optimizaciones de rendimiento ultra alto.Esta pregunta es un poco más complicada de lo que uno podría esperar debido a varias incógnitas: el comportamiento del recurso que se está agrupando, la vida útil esperada / requerida de los objetos, la razón real por la que se requiere el grupo, etc. Por lo general, los grupos son de propósito especial - hilo grupos, grupos de conexión, etc., porque es más fácil optimizar uno cuando se sabe exactamente qué hace el recurso y, lo que es más importante, tiene control sobre cómo se implementa ese recurso.
Como no es tan simple, lo que he intentado hacer es ofrecer un enfoque bastante flexible con el que pueda experimentar y ver qué funciona mejor. Disculpas de antemano por la larga publicación, pero hay mucho terreno por cubrir cuando se trata de implementar un grupo de recursos decente de propósito general. y realmente solo estoy rascando la superficie.
Un grupo de uso general tendría que tener algunas "configuraciones" principales, que incluyen:
Para el mecanismo de carga de recursos, .NET ya nos da una abstracción limpia: delegados.
Pase esto a través del constructor de la piscina y ya estamos listos para eso. El uso de un tipo genérico con una
new()
restricción también funciona, pero esto es más flexible.De los otros dos parámetros, la estrategia de acceso es la bestia más complicada, por lo que mi enfoque fue utilizar un enfoque basado en herencia (interfaz):
El concepto aquí es simple: dejaremos que la
Pool
clase pública maneje los problemas comunes como la seguridad de subprocesos, pero usaremos un "almacén de elementos" diferente para cada patrón de acceso. LIFO se representa fácilmente mediante una pila, FIFO es una cola y he utilizado una implementación de búfer circular no muy optimizada pero probablemente adecuada utilizando unList<T>
puntero e índice para aproximar un patrón de acceso de round-robin.Todas las clases a continuación son clases internas de
Pool<T>
: esta fue una elección de estilo, pero dado que realmente no están destinadas a usarse fuera delPool
, tiene más sentido.Estos son los obvios: apilar y hacer cola. No creo que realmente justifiquen mucha explicación. El búfer circular es un poco más complicado:
Podría haber elegido varios enfoques diferentes, pero la conclusión es que se debe acceder a los recursos en el mismo orden en que fueron creados, lo que significa que tenemos que mantener referencias a ellos pero marcarlos como "en uso" (o no ) En el peor de los casos, solo hay una ranura disponible, y se necesita una iteración completa del búfer para cada búsqueda. Esto es malo si tiene cientos de recursos agrupados y los adquiere y libera varias veces por segundo; no es realmente un problema para un grupo de 5-10 elementos, y en el caso típico , donde los recursos se usan ligeramente, solo tiene que avanzar uno o dos espacios.
Recuerde, estas clases son clases internas privadas, es por eso que no necesitan una gran cantidad de verificación de errores, el grupo en sí restringe el acceso a ellas.
Agregue una enumeración y un método de fábrica y hemos terminado con esta parte:
El siguiente problema a resolver es la estrategia de carga. He definido tres tipos:
Los dos primeros deben explicarse por sí mismos; el tercero es una especie de híbrido, carga los recursos de forma diferida pero en realidad no comienza a reutilizar ningún recurso hasta que el grupo esté lleno. Esto sería una buena compensación si desea que el grupo esté lleno (lo que parece que sí) pero desea diferir el gasto de crearlos hasta el primer acceso (es decir, para mejorar los tiempos de inicio).
Los métodos de carga realmente no son demasiado complicados, ahora que tenemos la abstracción de la tienda de artículos:
Los campos
size
ycount
arriba se refieren al tamaño máximo del grupo y al número total de recursos que posee el grupo (pero no necesariamente disponibles ), respectivamente.AcquireEager
es el más simple, supone que un artículo ya está en la tienda; estos artículos se cargarán previamente en la construcción, es decir, en elPreloadItems
método que se muestra al final.AcquireLazy
comprueba si hay elementos gratuitos en el grupo y, si no, crea uno nuevo.AcquireLazyExpanding
creará un nuevo recurso siempre que el grupo aún no haya alcanzado su tamaño objetivo. He intentado optimizar esto para minimizar el bloqueo, y espero no haber cometido ningún error (lo he probado en condiciones de subprocesos múltiples, pero obviamente no de forma exhaustiva).Tal vez se pregunte por qué ninguno de estos métodos se molesta en verificar si la tienda ha alcanzado el tamaño máximo. Llegaré a eso en un momento.
Ahora para la piscina en sí. Aquí está el conjunto completo de datos privados, algunos de los cuales ya se han mostrado:
Respondiendo a la pregunta que pasé por alto en el último párrafo: cómo asegurarnos de limitar el número total de recursos creados, resulta que .NET ya tiene una herramienta perfectamente buena para eso, se llama Semaphore y está diseñado específicamente para permitir un número de hilos de acceso a un recurso (en este caso, el "recurso" es el almacén de elementos interno). Como no estamos implementando una cola completa de productores / consumidores, esto es perfectamente adecuado para nuestras necesidades.
El constructor se ve así:
No debería haber sorpresas aquí. Lo único a tener en cuenta es la carcasa especial para la carga ansiosa, utilizando el
PreloadItems
método que ya se mostró anteriormente.Dado que casi todo se ha abstraído limpiamente por ahora, lo real
Acquire
y losRelease
métodos son realmente muy sencillos:Como se explicó anteriormente, estamos usando el
Semaphore
para controlar la concurrencia en lugar de verificar religiosamente el estado de la tienda de artículos. Mientras los artículos adquiridos se liberen correctamente, no hay nada de qué preocuparse.Por último, pero no menos importante, hay limpieza:
El propósito de esa
IsDisposed
propiedad quedará claro en un momento. Todo lo que elDispose
método principal realmente hace es deshacerse de los elementos agrupados reales si se implementanIDisposable
.Ahora, básicamente, puede usar esto tal cual, con un
try-finally
bloque, pero no me gusta esa sintaxis, porque si comienza a distribuir recursos agrupados entre clases y métodos, se volverá muy confuso. Es posible que la clase principal que utiliza un recurso ni siquiera tenga una referencia al grupo. Realmente se vuelve bastante desordenado, por lo que un mejor enfoque es crear un objeto agrupado "inteligente".Digamos que comenzamos con la siguiente interfaz / clase simple:
Aquí está nuestro
Foo
recurso desechable que se implementaIFoo
y tiene un código repetitivo para generar identidades únicas. Lo que hacemos es crear otro objeto agrupado especial:Esto simplemente representa todos los métodos "reales" en su interior
IFoo
(podríamos hacer esto con una biblioteca de proxy dinámico como Castle, pero no voy a entrar en eso). También mantiene una referencia a loPool
que lo crea, de modo que cuandoDispose
este objeto, se libera automáticamente al grupo. Excepto cuando el grupo ya se ha eliminado, esto significa que estamos en modo de "limpieza" y, en este caso, en realidad limpia el recurso interno .Usando el enfoque anterior, podemos escribir código como este:
Esto es algo muy bueno para poder hacer. Significa que el código que usa el
IFoo
(en oposición al código que lo crea) en realidad no necesita conocer el grupo. Incluso puede inyectarIFoo
objetos usando su biblioteca DI favorita yPool<T>
como proveedor / fábrica.Puse el código completo en PasteBin para que disfrutes al copiar y pegar. También hay un breve programa de prueba que puede usar para jugar con diferentes modos de carga / acceso y condiciones multiproceso, para asegurarse de que es seguro para subprocesos y no tiene errores.
Avíseme si tiene alguna pregunta o inquietud sobre esto.
fuente
Algo como esto podría satisfacer sus necesidades.
Ejemplo de uso
fuente
Put
método y lo dejaría fuera para simplificar algún tipo de verificación de si el objeto tiene fallas y para crear una nueva instancia para agregar al grupo en lugar de insertar la anterior?Muestra de MSDN: Cómo: crear un grupo de objetos mediante un ConcurrentBag
fuente
En el pasado, Microsoft proporcionó un marco a través de Microsoft Transaction Server (MTS) y más tarde COM + para hacer la agrupación de objetos para objetos COM. Esa funcionalidad se transfirió a System.EnterpriseServices en .NET Framework y ahora en Windows Communication Foundation.
Agrupación de objetos en WCF
Este artículo es de .NET 1.1 pero aún debe aplicarse en las versiones actuales de Framework (aunque WCF es el método preferido).
Agrupación de objetos .NET
fuente
IInstanceProvider
interfaz existe ya que implementaré esto para mi solución. Siempre soy fanático de apilar mi código detrás de una interfaz proporcionada por Microsoft cuando proporcionan una definición adecuada.Realmente me gusta la implementación de Aronaught, especialmente porque maneja la espera del recurso para estar disponible mediante el uso de un semáforo. Hay varias adiciones que me gustaría hacer:
sync.WaitOne()
async.WaitOne(timeout)
y exponer el tiempo de espera como parámetro deAcquire(int timeout)
método. Esto también necesitaría manejar la condición cuando el hilo agota el tiempo de espera de que un objeto esté disponible.Recycle(T item)
método para manejar situaciones cuando un objeto necesita ser reciclado cuando ocurre una falla, por ejemplo.fuente
Esta es otra implementación, con un número limitado de objetos en el grupo.
fuente
Orientado a Java, este artículo expone el patrón de agrupación de conexiónImpl y el patrón de agrupación de objetos abstractos y podría ser un buen primer acercamiento: http://www.developer.com/design/article.php/626171/Pattern-Summaries-Object-Pool. htm
Patrón de agrupación de objetos:
fuente
Una extensión de msdn es cómo crear un grupo de objetos usando un ConcurrentBag.
https://github.com/chivandikwa/ObjectPool
fuente
Puedes usar el paquete nuget
Microsoft.Extensions.ObjectPool
Documentaciones aquí:
https://docs.microsoft.com/en-us/aspnet/core/performance/objectpool?view=aspnetcore-3.1 https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.objectpool
fuente