java - tipos - transacciones base de datos pdf
Control de Concurrencia Distribuido (13)
He estado trabajando en esto durante unos días y he encontrado varias soluciones, pero ninguna de ellas increíblemente simple o ligera. El problema es básicamente esto: tenemos un clúster de 10 máquinas, cada una de las cuales ejecuta el mismo software en una plataforma ESB multiproceso. Puedo lidiar con problemas de concurrencia entre subprocesos en la misma máquina con bastante facilidad, pero ¿qué pasa con la concurrencia en los mismos datos en diferentes máquinas?
Esencialmente, el software recibe solicitudes para alimentar los datos de un cliente de una empresa a otra a través de servicios web. Sin embargo, el cliente puede o no existir aún en el otro sistema. Si no es así, lo creamos a través de un método de servicio web. Por lo tanto, requiere una especie de prueba y configuración, pero necesito un semáforo de algún tipo para evitar que las otras máquinas causen condiciones de carrera. He tenido situaciones en las que se creó un cliente remoto dos veces para un solo cliente local, lo que no es realmente deseable.
Las soluciones con las que he jugado conceptualmente son:
Usar nuestro sistema de archivos compartidos tolerante a fallas para crear archivos de "bloqueo" que cada máquina revisará según el cliente
Usando una tabla especial en nuestra base de datos, y bloqueando toda la tabla para hacer un "test-and-set" para un registro de bloqueo.
Uso de Terracotta, un software de servidor de código abierto que ayuda a escalar, pero utiliza un modelo de hub-and-spoke.
Usando EHCache para la replicación sincrónica de mis "bloqueos" en memoria.
No me puedo imaginar que soy la única persona que ha tenido este tipo de problema. ¿Cómo lo resolvió? ¿Has cocinado algo en la empresa o tienes un producto favorito de terceros?
De vuelta en el día, usábamos un "servidor de bloqueo" específico en la red para manejar esto. Bleh.
Su servidor de base de datos puede tener recursos específicamente para hacer este tipo de cosas. MS-SQL Server tiene bloqueos de aplicaciones utilizables a través de los procedimientos sp_getapplock / sp_releaseapplock .
He trabajado mucho con Coherence, lo que permitió varios enfoques para implementar un bloqueo distribuido. El enfoque ingenuo era solicitar bloquear el mismo objeto lógico en todos los nodos participantes. En términos de Coherence, esto estaba bloqueando una clave en un caché replicado. Este enfoque no se escala tan bien porque el tráfico de red aumenta linealmente a medida que agrega nodos. Una forma más inteligente era usar un caché distribuido, donde cada nodo en el clúster es naturalmente responsable de una parte del espacio clave, por lo que bloquear una clave en dicho caché siempre implicaba la comunicación con un nodo como máximo. Podrías desarrollar tu propio enfoque basado en esta idea, o mejor aún, obtener Coherencia. Realmente es el juego de herramientas de escalabilidad de tus sueños.
Yo agregaría que cualquier mecanismo de bloqueo basado en la red de multinodo medio decente tendría que ser razonablemente sofisticado para actuar correctamente en caso de falla de la red.
Hice un servicio RMI simple con dos métodos: bloquear y liberar. ambos métodos toman una clave (mi modelo de datos usaba UUID como pk, por lo que también era la tecla de bloqueo).
RMI es una buena solución para esto porque está centralizado. no se puede hacer esto con los EJB (especialmente en un clúster ya que no se sabe en qué máquina aterrizará su llamada). Además, es fácil.
funcionó para mí.
No estoy seguro si entiendo todo el contexto, pero parece que tiene 1 sola base de datos respaldando esto? ¿Por qué no utilizar el bloqueo de la base de datos? Si la creación del cliente es un único INSERT entonces esta declaración sola puede servir como un bloqueo ya que la base de datos rechazará un segundo INSERT que violaría una de sus restricciones (por ejemplo, el nombre del cliente único por ejemplo).
Si la operación de "inserción de un cliente" no es atómica y es un conjunto de declaraciones, entonces introduciría (o usaría) un INSERTO inicial que crearía un registro básico simple identificando a su cliente (con las restricciones necesarias de UNICIDAD) y luego haría todo el otras inserciones / actualizaciones en la misma transacción. De nuevo, la base de datos se ocupará de la coherencia y cualquier modificación concurrente hará que una de ellas falle.
Terracotta está más cerca de un modelo "por niveles": todas las aplicaciones de cliente hablan con una matriz de servidor de terracota (y lo que es más importante para la escala, no se comunican entre sí). Terracotta Server Array se puede agrupar tanto en escala como en disponibilidad (reflejado, disponibilidad y rayas, para escala).
En cualquier caso, como probablemente sepa, Terracotta le ofrece la capacidad de expresar la concurrencia en el clúster de la misma manera que en una JVM única utilizando POJO sincronizado / wait / notify o utilizando cualquiera de las primitivas java.util.concurrent como ReentrantReadWriteLock , CyclicBarrier, AtomicLong, FutureTask, etc.
Hay muchas recetas simples que demuestran el uso de estas primitivas en el Recetario de Terracota .
Como ejemplo, publicaré el ejemplo de ReentrantReadWriteLock (tenga en cuenta que no hay una versión del bloqueo "Terracotta"; solo usa Java ReentrantReadWriteLock normal)
import java.util.concurrent.locks.*;
public class Main
{
public static final Main instance = new Main();
private int counter = 0;
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true);
public void read()
{
while (true) {
rwl.readLock().lock();
try {
System.out.println("Counter is " + counter);
} finally {
rwl.readLock().unlock();
}
try { Thread.currentThread().sleep(1000); } catch (InterruptedException ie) { }
}
}
public void write()
{
while (true) {
rwl.writeLock().lock();
try {
counter++;
System.out.println("Incrementing counter. Counter is " + counter);
} finally {
rwl.writeLock().unlock();
}
try { Thread.currentThread().sleep(3000); } catch (InterruptedException ie) { }
}
}
public static void main(String[] args)
{
if (args.length > 0) {
// args --> Writer
instance.write();
} else {
// no args --> Reader
instance.read();
}
}
}
Iba a aconsejarme sobre el uso de memcached como un almacenamiento RAM distribuido muy rápido para mantener los registros; pero parece que EHCache es un proyecto similar pero más centrado en Java.
Cualquiera de los dos es el camino a seguir, siempre y cuando esté seguro de usar actualizaciones atómicas (memcached los admite, no sabe nada de EHCache). Es, de lejos, la solución más escalable.
Como un punto de datos relacionado, Google usa ''Chubby'', un almacenamiento de bloqueo distribuido rápido basado en RAM como la raíz de varios sistemas, entre ellos BigTable.
Usamos Terracota, entonces me gustaría votar por eso.
He estado siguiendo Hazelcast y parece ser otra tecnología prometedora, pero no puedo votar porque no la he usado, y sabiendo que usa un sistema basado en P2P, no confío en que sea grande. necesidades de escalado.
Pero también he oído hablar de Zookeeper, que salió de Yahoo, y se está moviendo bajo el paraguas de Hadoop. Si eres aventurero probando alguna tecnología nueva, esto realmente promete mucho, ya que es muy delgada y mala, y se centra en la coordinación. Me gusta la visión y la promesa, aunque podría ser demasiado verde.
Si puede configurar su balanceo de carga para que las solicitudes de un solo cliente siempre se asignen al mismo servidor, entonces puede manejar esto a través de la sincronización local. Por ejemplo, tome su ID de cliente mod 10 para encontrar cuál de los 10 nodos usará.
Incluso si no desea hacer esto en el caso general, sus nodos podrían proxy entre sí para este tipo específico de solicitud.
Suponiendo que sus usuarios son lo suficientemente uniformes (es decir, si tiene una tonelada de ellos) que no espera que aparezcan puntos calientes donde un nodo se sobrecarga, esto todavía debería escalar bastante bien.
Hemos estado desarrollando un marco de sincronización distribuida de código abierto, actualmente DistributedReentrantLock y DistributedReentrantReadWrite lock se han implementado, pero aún están en fase de prueba y refactorización. En nuestra arquitectura, las claves de bloqueo se dividen en segmentos y cada nodo es responsable de cierta cantidad de segmentos. De manera efectiva para solicitudes de bloqueo exitosas, solo hay una solicitud de red. También estamos utilizando la clase AbstractQueuedSynchronizer como estado de bloqueo local, por lo que todas las solicitudes de bloqueo fallidas se gestionan localmente, esto reduce drásticamente el tráfico de red. Estamos utilizando JGroups ( http://jgroups.org ) para comunicación grupal y Hessian para serialización.
para obtener más información, consulte http://code.google.com/p/vitrit/ .
Por favor envíeme sus valiosos comentarios.
Kamran
También puede considerar Cacheonix para bloqueos distribuidos. A diferencia de todo lo mencionado aquí, Cacheonix admite bloqueos ReadWrite con escalado de bloqueo de lectura a escritura cuando sea necesario:
ReadWriteLock rwLock = Cacheonix.getInstance().getCluster().getReadWriteLock();
Lock lock = rwLock.getWriteLock();
try {
...
} finally {
lock.unlock();
}
Descripción completa: soy un desarrollador de Cacheonix.
Es posible que desee considerar el uso de cerraduras distribuidas de Hazelcast . Super lite y fácil.
java.util.concurrent.locks.Lock lock = Hazelcast.getLock ("mymonitor");
lock.lock ();
try {
// do your stuff
}finally {
lock.unlock();
}
Hazelcast: cola distribuida, mapa, conjunto, lista, bloqueo
Como ya se está conectando a una base de datos, antes de agregar otra pieza de infraestructura, eche un vistazo a JdbcSemaphore , es simple de usar:
JdbcSemaphore semaphore = new JdbcSemaphore(ds, semName, maxReservations);
boolean acq = semaphore.acquire(acquire, 1, TimeUnit.MINUTES);
if (acq) {
// do stuff
semaphore.release();
} else {
throw new TimeoutException();
}
Es parte de la biblioteca spf4j .
Recomiendo usar Redisson . Implementa más de 30 estructuras de datos distribuidos y servicios, incluido java.util.Lock
. Ejemplo de uso:
Config config = new Config();
config.addAddress("some.server.com:8291");
Redisson redisson = Redisson.create(config);
Lock lock = redisson.getLock("anyLock");
lock.lock();
try {
...
} finally {
lock.unlock();
}
redisson.shutdown();