Buscando un patrón de bloqueo distribuido

10

Necesito un mecanismo de bloqueo de objetos recursivos personalizado \ patrón para un sistema distribuido en C #. Básicamente, tengo un sistema de múltiples nodos. Cada nodo tiene permisos de escritura exclusivos sobre n -número de piezas de estado. El mismo estado también está disponible en forma de solo lectura en al menos otro nodo. Algunas escrituras / actualizaciones deben ser atómicas en todos los nodos, mientras que otras actualizaciones eventualmente se volverán consistentes a través de procesos de replicación en segundo plano, colas, etc.

Para las actualizaciones atómicas, estoy buscando un patrón o muestras que me permitan marcar de manera eficiente un objeto como bloqueado para escrituras que luego pueda distribuir, confirmar, revertir, etc. Como el sistema tiene altos niveles de concurrencia, yo Supongo que tendré que ser capaz de acumular bloqueos que se agotarán o se desenrollarán una vez que se liberen los bloqueos.

La transacción o las piezas de mensajería no son el foco de esta pregunta, pero las proporcioné para un contexto adicional. Dicho esto, siéntase libre de articular qué mensajes cree que serían necesarios si lo desea.

Aquí hay una muestra vaga de lo que estaba imaginando, aunque estoy abierto a cualquier idea nueva además de implementar productos completamente nuevos.

thing.AquireLock(LockLevel.Write);

//Do work

thing.ReleaseLock();

Estaba pensando en usar métodos de extensión, que podrían verse más o menos así

public static void AquireLock(this IThing instance, TupleLockLevel lockLevel)
{ 
    //TODO: Add aquisition wait, retry, recursion count, timeout support, etc...  
    //TODO: Disallow read lock requests if the 'thing' is already write locked
    //TODO: Throw exception when aquisition fails
    instance.Lock = lockLevel;
}

public static void ReleaseLock(this IThing instance)
{
    instance.Lock = TupleLockLevel.None;
}

Para aclarar un par de detalles ...

  • Todas las comunicaciones son TCP / IP utilizando un protocolo binario de solicitud / respuesta
  • No existen tecnologías intermedias como colas o bases de datos.
  • No hay un nodo maestro central. En este caso, la disposición de bloqueo está definida por el iniciador de la cerradura y el socio que atenderá la solicitud con algún tipo de tiempo de espera para regular su comportamiento

¿Alguien tiene alguna sugerencia?

JoeGeeky
fuente
Las cerraduras son generalmente una característica estándar en la mayoría de los sistemas. Supongo que también está ahí para C #. (Un resultado de búsqueda de google: albahari.com/threading/part2.aspx ) ¿Estás tratando de lograr algo más allá de Mutex o semáforos básicos?
Dipan Mehta
2
@DipanMehta Lo siento, debería haber abordado esto más claramente. Los nodos que mencioné son máquinas en una red. Comprendo que Mutex y Semáforos son bloqueos en toda la máquina ( por ejemplo, procesos cruzados ) y no bloqueos que pueden extenderse entre las máquinas en una red.
JoeGeeky
@JoeGeeky Su pregunta es sobre el tema aquí y posiblemente sería demasiado teórica para Stack Overflow . Si desea volver a preguntar allí, puede hacerlo, pero querrá un fraseo más centrado en el código.
Adam Lear

Respuestas:

4

Gracias por las aclaraciones.

En ese caso, lo que recomendaría es usar un modelo de publicación / suscripción. Protocolo de bloqueo distribuido Chubby de Google (una implementación de Paxos )

Nunca he usado Paxos (o Chubby), pero parece que hay una implementación de código abierto aquí .

Si eso no funciona, puede implementar su propia versión de Paxos utilizando, por ejemplo, uno de los sospechosos habituales en términos de bibliotecas de mensajes: la biblioteca de cola de mensajes cero , RabbitMQ o ActiveMQ .


Respuesta anterior:

La mayoría de las sugerencias sobre SO ( [A] , [B] ) se utilizan para utilizar una cola de mensajes para lograr el bloqueo entre máquinas.

Su AcquireLockmétodo empujaría algo que identifica el objeto de bloqueo en la cola, verificando instancias anteriores de bloqueos antes del éxito. Su ReleaseLockmétodo eliminaría el objeto de bloqueo de la cola.

El usuario de SO atlantis sugiere, en esta publicación , la publicación de Jeff Key para algunos detalles.

Peter K.
fuente
Gracias, pero estas soluciones no serían adecuadas ya que no tengo maestro central, base de datos o cola. He actualizado la pregunta con algunos detalles adicionales para aclarar algunos de estos detalles.
JoeGeeky
No podré usar estos productos directamente ya que ya existe un protocolo bien definido que debo usar para todas las comunicaciones entre nodos, pero Chubby y Paxos pueden tener patrones bien definidos de los que puedo aprender. Le daré un vistazo.
JoeGeeky
@JoeGeeky Sí, el enlace de Paxos tiene diagramas de secuencia que pueden permitirle implementarlo usando su enlace de comunicaciones preferido.
Peter
Aunque no es una respuesta directa, leer todas las cosas de Chubby y Paxos me ayudó a definir mi propia solución. No utilicé esas herramientas, pero pude definir un patrón razonable basado en algunos de sus conceptos. Gracias.
JoeGeeky
@JoeGeeky: Es bueno escuchar que fue de alguna ayuda, al menos. Gracias por el tic.
Peter K.
4

Me parece que tienes un par de tecnologías mixtas aquí:

  • comunicaciones (en las que confía esencialmente como 100% confiables ... que pueden ser fatales)

  • bloqueo / exclusión mutua

  • tiempos de espera (con qué propósito)?

Una advertencia: los tiempos de espera en los sistemas distribuidos pueden estar llenos de peligros y dificultades. Si se usan, deben configurarse y usarse con mucho cuidado porque el uso indiscriminado de los tiempos de espera no soluciona un problema, solo retrasa la catástrofe. (Si desea ver cómo se deben usar los tiempos de espera , lea y comprenda la documentación del protocolo de comunicación HDLC. Este es un buen ejemplo de uso adecuado e inteligente, en combinación con un sistema inteligente de codificación de bits para permitir la detección de cosas como la línea IDLE) .

Durante un tiempo trabajé en sistemas distribuidos multiprocesador conectados mediante enlaces de comunicación (no TCP, algo más). Una de las cosas que aprendí fue que, como una generalización aproximada, hay algunos lugares peligrosos de programación múltiple para ir:

  • depender de las colas generalmente termina en lágrimas (si la cola se llena, usted está en problemas. A MENOS QUE pueda calcular un tamaño de cola que nunca se llenará, en cuyo caso probablemente podría usar una solución sin cola)

  • la dependencia del bloqueo es dolorosa, intente y piense si hay otra forma (si debe usar el bloqueo, consulte la literatura, el bloqueo distribuido multiprocesador ha sido el tema de muchos documentos académicos de las últimas 2-3 décadas)

Tengo que proceder usando el bloqueo, luego:

ASUMIRÉ que usará los tiempos de espera solo como un medio de recuperación de último recurso, es decir, para la detección de una falla del sistema de comunicaciones subyacente. Asumiré además que su sistema de comunicación TCP / IP tiene un ancho de banda alto y puede considerarse como una latencia baja (idealmente cero, pero esto nunca sucede).

Lo que sugeriría es que cada nodo tiene una lista de conectividad de otros nodos a los que se puede conectar. (A los nodos no les importaría de dónde proviene una conexión). La población de las tablas a las que se puede conectar un nodo se deja como una cosa separada para resolver, no ha dicho si eso se establecería estáticamente o no. También se ignoran convenientemente cosas como la asignación de los números de puerto IP donde las conexiones entrarían en un nodo; puede haber buenas razones para aceptar solicitudes en un solo puerto o en múltiples puertos. Esto debe ser considerado cuidadosamente. Los factores incluirán colas implícitas, pedidos, uso de recursos, tipo de sistema operativo y capacidades.

Una vez que los nodos saben con quién se conectan, pueden enviar a ese nodo una solicitud de bloqueo y deben recibir una respuesta de bloqueo desde ese nodo remoto. Puede empaquetar esas dos operaciones en un contenedor para que parezca atómico. El efecto de esto es que los nodos que desean adquirir un bloqueo harán una llamada algo así como:

if (get_lock(remote_node) == timeout) then
  {
    take some failure action - the comms network is down
  }

/* Lock is now acquired - do work here */

if (release_lock(remote_node) == timeout) then
  {
    take some failure action - the comms network is down
  }

las llamadas get_lock y release_lock deberían ser algo así (en principio):

send_to_remote_node(lock_request)
get_from_remote_node_or_timeout(lock_reply, time)
if (result was timeout) then
  return timeout
else
  return ok

Deberá tener mucho cuidado con un sistema de bloqueo distribuido para que las unidades de trabajo realizadas mientras se mantiene un bloqueo sean pequeñas y rápidas porque tendrá muchos nodos remotos potencialmente retenidos esperando obtener un bloqueo. Este es efectivamente un sistema multiprocesador / comunicación de parada y espera que es robusto pero no tiene el rendimiento más alto posible.

Una sugerencia es adoptar un enfoque completamente diferente. ¿Puede usar una llamada a procedimiento remoto donde cada llamada RPC lleva un paquete de información que puede ser manejado por el destinatario y que elimina la necesidad de bloqueos?


Al volver a leer la pregunta, parece que realmente no desea preocuparse por el lado de la comunicación de las cosas, solo desea resolver su problema de bloqueo.

Por lo tanto, mi respuesta puede parecer un poco fuera de tema, sin embargo, creo que no puede resolver su problema de bloqueo sin tener las partes debajo también. Analogía: construir una casa sobre cimientos malos hace que se caiga ... Eventualmente.

rápidamente_ahora
fuente
1
La semántica de tiempo de espera está en gran parte allí para lidiar con nodos que desaparecen de la red, o para lidiar con grandes atrasos en las pilas de bloqueo ... Esto limitará el tiempo empleado bloqueado mientras espera adquirir un bloqueo y brindará una oportunidad a quienes soliciten el bloqueo. para iniciar otros procesos en medio de retrasos inesperados, fallas, etc. Además, esto evitaría que algo se bloquee para siempre en caso de que algo falle. Agradezco sus inquietudes, aunque en este punto, no veo ninguna alternativa dado que eventualmente algo fallará
JoeGeeky
Para hablar con algunos de sus otros comentarios, no estoy usando colas per se (en el sentido de comunicación asíncrona), aunque esperaría que los bloqueos se apilen y liberen según un patrón FIFO. No he conciliado del todo cómo funcionará esto en términos del patrón de solicitud / respuesta requerido, aparte de esto, tendrá que bloquearse de alguna manera y ser parte de un apretón de manos más grande. En este momento, estoy trabajando a través del mecanismo de bloqueo apilado dentro de un solo nodo y luego cómo funcionará a través del escenario distribuido. Leeré un poco más como me sugirió. Gracias
JoeGeeky
@JoeGeeky: un FIFO es una cola. Cuidado con las colas. Piensa en ese lado con mucho cuidado. Parece que no solo va a obtener algo "listo para usar", sino que tendrá que pensar detenidamente en su problema y solución.
rapid_now
Entiendo ... estaba tratando de aclarar la diferencia entre una cola FIFO utilizada en procesos asíncronos ( por ejemplo, un proceso se pone en cola y luego otro se pone en cola ). En este caso, las cosas deberán gestionarse en orden, pero el proceso que ingresa a la cola no se iría hasta que (a) obtengan el bloqueo, (b) se les niegue un bloqueo, o (c) se agote el tiempo y abandonen la línea. Más bien como hacer cola en el cajero automático. Esto se comporta como un patrón FIFO en el caso de éxito, pero los procesos pueden dejar de funcionar antes de llegar al frente de la línea. ¿En cuanto a las listas para usar? No, pero este no es un problema nuevo
JoeGeeky
0

Su pregunta puede implementarse fácilmente utilizando un caché distribuido como NCache. Lo que necesita es un mecanismo de bloqueo pesimista en el que pueda adquirir un bloqueo con un objeto. Luego realice sus tareas y operaciones y libere el bloqueo para que otras aplicaciones lo consuman más adelante.

Echa un vistazo al siguiente código;

Aquí adquiriría un bloqueo en una Clave específica y luego realizaría tareas (que van desde una o más operaciones) y finalmente liberaría el bloqueo cuando haya terminado.

// Instance of the object used to lock and unlock cache items in NCache
LockHandle lockHandle = new LockHandle();

// Specify time span of 10 sec for which the item remains locked
// NCache will auto release the lock after 10 seconds.
TimeSpan lockSpan = new TimeSpan(0, 0, 10); 

try
{
    // If item fetch is successful, lockHandle object will be populated
    // The lockHandle object will be used to unlock the cache item
    // acquireLock should be true if you want to acquire to the lock.
    // If item does not exists, account will be null
    BankAccount account = cache.Get(key, lockSpan, 
    ref lockHandle, acquireLock) as BankAccount;
    // Lock acquired otherwise it will throw LockingException exception

    if(account != null && account.IsActive)
    {
        // Withdraw money or Deposit
        account.Balance += withdrawAmount;
        // account.Balance -= depositAmount;

        // Insert the data in the cache and release the lock simultaneously 
        // LockHandle initially used to lock the item must be provided
        // releaseLock should be true to release the lock, otherwise false
        cache.Insert("Key", account, lockHandle, releaseLock); 
        //For your case you should use cache.Unlock("Key", lockHandle);
    }
    else
    {
        // Either does not exist or unable to cast
        // Explicitly release the lock in case of errors
        cache.Unlock("Key", lockHandle);
    } 
}
catch(LockingException lockException)
{
    // Lock couldn't be acquired
    // Wait and try again
}

Tomado del enlace: http://blogs.alachisoft.com/ncache/distributed-locking/

Basit Anwer
fuente