Bloqueos en entornos distribuidos
En sistemas distribuidos, es fundamental garantizar la coherencia de datos al coordinar accesos concurrentes a recursos compartidos. Para ello, se utilizan mecanismos de sincronización como los bloqueos distribuidos, que permiten que solo un nodo acceda a una sección crítica a la vez.
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.CountDownLatch;
import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.recipes.locks.InterProcessLock; import org.apache.curator.framework.recipes.locks.InterProcessMutex; import org.apache.curator.retry.ExponentialBackoffRetry;
public class DistributedLockExample {
private static final String LOCK_PATH = "/distributed_lock";
private static final CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.64.60:2181")
.connectionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(3000, 3))
.build();
public static void main(String[] args) throws InterruptedException {
client.start();
final InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);
final CountDownLatch startSignal = new CountDownLatch(1);
for (int i = 0; i < 30; i++) {
new Thread(() -> {
try {
startSignal.await();
lock.acquire();
} catch (Exception e) {
e.printStackTrace();
}
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss|SSS");
String orderId = formatter.format(new Date());
System.out.println("Número de pedido generado: " + orderId);
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
startSignal.countDown();
}
}
</div>Este ejemplo utiliza `InterProcessMutex` desde Curator para gestionar el acceso exculsivo a una sección crítica mediante un nodo ZNode en ZooKeeper. La adquisición del bloqueo se realiza antes de generar un número de pedido, y se libera después, asegurando que no haya concurrencia en la generación de IDs.
### Herramietnas útiles en Curator
#### ZKPaths
Esta utilidad proporciona métodos para manipular rutas de nodos ZK, incluyendo la creación de rutas anidadas, extracción de nombres de nodo, y ordenamiento de hijos. Es especialmente útil cuando se necesita trabajar con estructuras jerárquicas en ZooKeeper.
<div>```
package utils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.ZKPaths;
import org.apache.curator.utils.ZKPaths.PathAndNode;
import org.apache.zookeeper.ZooKeeper;
public class ZKPathUtils {
private static final String BASE_PATH = "/utils/path";
private static final CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.64.60:2181")
.connectionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(3000, 3))
.build();
public static void main(String[] args) throws Exception {
client.start();
ZooKeeper zk = client.getZookeeperClient().getZooKeeper();
System.out.println(ZKPaths.fixForNamespace(BASE_PATH, "sub"));
System.out.println(ZKPaths.makePath(BASE_PATH, "sub"));
System.out.println(ZKPaths.getNodeFromPath("/utils/path/sub1"));
PathAndNode result = ZKPaths.getPathAndNode("/utils/path/sub1");
System.out.println("Ruta: " + result.getPath());
System.out.println("Nodo: " + result.getNode());
ZKPaths.mkdirs(zk, BASE_PATH + "/childA");
ZKPaths.mkdirs(zk, BASE_PATH + "/childB");
System.out.println("Hijos ordenados: " + ZKPaths.getSortedChildren(zk, BASE_PATH));
}
}
Esta clase permite asegurar que una ruta específica exista en ZooKeeper, creándola si no está presente. Es ideal para preparar el entorno de trabajo sin necesidad de comprobar manualmente la existencia de cada nodo.
TestingServer
Curator ofrece TestingServer para levantar un servidor ZooKeeper en memoria durante pruebas unitarias. Esto facilita el desarrollo local sin depender de infraestructuras externas.
TestingCluster
Para simular entornos de cluster, TestingCluster permite crear múltiples instancias de ZooKeeper localmente, emulando una arquitectura distribuida con varios nodos. Esto es esencial para probar comportamientos de consenso, fallas de red o recuperación de quórum.