1. Introducción a las cerraduras distribuidas
Los bloqueos distribuidos se utilizan principalmente para proteger los recursos compartidos entre los procesos, los hosts y las redes en un entorno distribuido para lograr un acceso mutuamente excluyente para garantizar la consistencia de los datos.
2. Introducción a la arquitectura
Antes de introducir el uso de Zookeeper para implementar bloqueos distribuidos, primero busque el diagrama actual de arquitectura del sistema
Explicación: Toda el área de la izquierda representa un clúster de Zookeeper. Locker es un nodo persistente de Zookeeper, y Node_1, Node_2 y Node_3 son nodos secuenciales temporales debajo del nodo persistente del casillero. Client_1, client_2, client_n significa múltiples clientes, y el servicio significa recursos compartidos que requieren acceso mutuamente excluyente.
Ideas para la adquisición de cerraduras distribuidas
1. Idea general de obtener cerraduras distribuidas
Al adquirir un bloqueo distribuido, cree un nodo secuencial temporal debajo del nodo del casillero y elimine el nodo temporal al liberar el bloqueo. El cliente llama al método CreateNode para crear nodos secuenciales temporales bajo el casillero, y luego llama a GetChildren ("Locker") para que todos los nodos infantiles bajo el casillero. Tenga en cuenta que no se requiere observador en este momento. Después de que el cliente obtiene todas las rutas de nodo infantil, si encuentra que el número de nodo infantil que creó antes es el más pequeño, se considera que el cliente ha obtenido el bloqueo. Si encuentra que el nodo que creó no es el más pequeño entre todos los niños del casillero, significa que no ha obtenido la cerradura. En este momento, el cliente necesita encontrar el nodo más pequeño que este, y luego llamar al método Exist () en él y registrar el oyente del evento en él. Después de eso, si se elimina el nodo en el que se trata, el observador del cliente recibirá la notificación correspondiente. En este momento, determinará nuevamente si el nodo que creó es el número de serie más pequeño entre los nodos de los casilleros. Rugao ha obtenido la cerradura. Si no, repita los pasos anteriores para continuar obteniendo un nodo más pequeño que usted y regístrese para escuchar. Todavía se requieren muchos juicios lógicos en el proceso actual.
2. El proceso de algoritmo central para adquirir bloqueos distribuidos
El siguiente es el mismo diagrama de flujo para analizar el algoritmo completo para adquirir bloqueos distribuidos, de la siguiente manera:
Explicación: Cuando el Cliente A desea adquirir un bloqueo distribuido, primero cree un nodo secuencial temporal (Node_N) debajo del casillero y luego obtenga inmediatamente todos los nodos infantiles (de primer nivel) debajo del casillero.
En este momento, debido a que varios clientes competirán por los bloqueos al mismo tiempo, el número de nodos infantiles bajo el casillero será mayor que 1. Para los nodos secuenciales, la característica es que hay un número numérico después del nombre del nodo. El número de número del nodo creado primero es más pequeño que el creado más adelante. Por lo tanto, los nodos infantiles se pueden clasificar de pequeños a grandes en el orden del sufijo del nombre del nodo. De esta manera, el primero es el nodo secuencial creado primero. ¡En este momento, representa al cliente que se esfuerza por la cerradura! En este momento, determine si el nodo más pequeño es el nodo_n creado por el cliente A antes. Si es así, significa que el Cliente A ha adquirido el bloqueo. Si no, significa que el bloqueo ha sido adquirido por otros clientes. Por lo tanto, el Cliente A tiene que esperar a que libere el bloqueo, es decir, el cliente B que ha adquirido el bloqueo elimina el nodo que creó.
En este momento, sabremos si el Cliente B ha lanzado el bloqueo escuchando el evento de eliminación de los nodos secuenciales más pequeños que Node_N Times. Si es así, el Cliente A adquiere a todos los niños bajo el Locker nuevamente y los compara con los nodos Node_N creados por sí mismo hasta que el Node_N creado por sí mismo es el número de secuencia más pequeño entre todos los niños de Locker, lo que significa que el Cliente A ha adquirido el bloqueo!
4. Implementación del código de bloqueos distribuidos basados en Zookeeper
1. Defina una interfaz de bloqueo distribuido
La interfaz de bloqueo distribuido definido es la siguiente:
Public Interface DistributedLock { / ** Adquirir el bloqueo, si no se obtiene, Wait* / public void adquirir () arroja excepción; / *** Adquirir el bloqueo hasta el tiempo de espera* @Param Tiempo de espera de tiempo* @param Unidad de la unidad Parámetro de parámetro Unidad* @return Si el bloqueo se obtiene* @throws Exception*/ public boolean adquirir (unidad de tiempo de tiempo de tiempo larga) arroja excepción; / *** Libere el bloqueo* @throws Exception*/ public void Release () arroja excepción;}2. Defina un simple mutex
Defina una clase de bloqueo mutex, implementa la interfaz de bloqueo definida anteriormente y herede un bloqueo de distribución basado en la clase base. Esta clase base se usa principalmente para interactuar con Zookeeper, incluido un método para tratar de adquirir el bloqueo y un bloqueo de liberación.
/** La implementación específica de la interfaz de bloqueo se logra principalmente por el bloqueo basado en la clase principal hereditaria. La clase principal se implementa en función de los detalles específicos de Zookeeper para implementar bloqueos distribuidos* /clase pública SimpledInsribedlockMutex extiende BasedScribedlock implementa el bloqueo distribuido { /*Se utiliza para guardar el nodo que implementa los bloqueos distribuidos en ZookeePer, como el nombre es un Locker: /Locker,*Este nodo debería ser un nodo persistente. Cree nodos secuenciales temporales en este nodo para implementar bloqueos distribuidos*/ String final privado BasePath; /*Nombre de bloqueo Prefijo. Por ejemplo, los nodos secuenciales creados en el casillero comienzan con bloqueo, que facilita el filtrado de nodos irrelevantes*Los nodos creados son similares a: Lock-00000001, Lock-00000002*/ String staticfinal privado Lock_Name = "Lock-"; /* Se utiliza para guardar los nodos secuenciales exitosos creados por un cliente bajo Locker, para operaciones relacionadas posteriores (como juicio)*/ String private ourlockPath; /*** Se utiliza para adquirir recursos de bloqueo y obtener bloqueos a través del método de adquisición de bloqueo de la clase principal* @param tiempo para adquirir el tiempo de tiempo de espera del bloqueo* @param tiempo de la unidad de tiempo tiempo* @return si el bloqueo se obtiene* @throws excepción*/privado boolean boolean internalkock (tiempo, tiempo, tiempo de tiempo de tiempo) arroja la excepción {// si nuestro bloqueo Para obtener más detalles, consulte la implementación de IntentLock. OurlockPath = intento (tiempo, unidad); devuelve ourlockpath! = nulo; } /*** Pase en el objeto de conexión del cliente ZOOKEEper, y BasePath* @param Cliente Objeto de conexión del cliente ZOOKEEPER* @param BasePath BasePath es un nodo persistente* /public SimpledIdistributedLockMutex (ZKClientExt Client, String BasePath) { /* llamando al constructor de la clase principal crea un basepath en ZoOKEEPer, y se establece un prejuicio de BasePfix, y se establece el constructor de un constructor de Basepath en ZoOKEEPER, y se establece el constructor de BasePhix, y se siente a un constructor de BasePath. Nodo Nodo Nodo *Guardar la referencia de BasePath al atributo de clase actual */ Super (Cliente, BasePath, Lock_Name); this.basepath = basepath; } /** Adquirir el bloqueo hasta el tiempo de espera, y se lanza una excepción después del tiempo de espera* /public void adquirir () arroja excepción {//-1 significa que el tiempo de espera no está configurado, y el tiempo de espera de TIEOUT está determinado por Zookeeper if (! Internallock (-1, null)) {¡Tire la nueva IoException ("La conexión no se puede obtener el bloqueo bajo el camino:" "" "¡" ""). }} / *** Adquirir el bloqueo con Tiempo de espera* / Public Boolean Adquire (Long Time, TimeUnit Unit) arroja excepción {return tutterallock (tiempo, unidad); } / ** Libere el bloqueo* / public void liberation () lanza la excepción {ReleasElock (ourlockPath); }}3. Detalles de la implementación de bloqueos distribuidos
La lógica clave para adquirir bloqueos distribuidos es BaseDistributedLock, que implementa los detalles de implementación de bloqueos distribuidos basados en Zookeeper.
Public Class BasedistRibuteDlock {Client de ZKClientext privado Final Private; ruta de cadena final privada; Cadena final privada BasePath; Nombre de bloqueo de cadena final privada; Integer final estático privado max_retry_count = 10; Public BasedSributedLock (ZKClientext Client, String Rath, String LockName) {this.client = client; this.basepath = ruta; this.path = path.concat ("/"). Concat (LockName); this.lockName = LockName; } private void deleteourpath (String ourPath) arroja excepción {client.delete (ourPath); } private String CreateLockNode (ZKClient Client, String Path) lanza la excepción {return client.createephemeralSequential (ruta, nulo); } / ** * Método central para obtener bloqueo * @param startmillis * @param milistowait * @param ourpath * @return * @throws excepción * / private boolean waittolock (long startmillis, long milistowait, string ourpath) lanza excepción {boolean havethelock = falso; booleano dodelete = falso; Pruebe {while (! haveTelock) {// Este método implementa la adquisición de todos los nodos secuenciales en el nodo del casillero, y se clasifica de una lista pequeña a grande <String> children = getSortedChildren (); Cadena secuencenodeName = ourpath.substring (basepath.length ()+1); // Calcule la posición de clasificación de los nodos de orden creados por el cliente en todos los nodos infantiles del casillero. Si el tipo es 0, significa que se ha obtenido el bloqueo. int ourIndex = children.IndexOf (secuencenodeName); /*Si el nodo de pedido [temporal] que creé antes no se encontró en GetSortedChildren, esto significa que el nodo que creamos puede eliminarse debido a una ruptura de flash de red. La excepción debe ser lanzada. Deje que el nivel anterior maneje el *El nivel anterior es detectar la excepción y ejecutar el número especificado de RETINES. Consulte el método de intento en el método de intento posterior*/ if (ourIndex <0) {lanzar nueva zknonoDeException ("no encontrado:" + secuencenodeName); } // Si el nodo creado por el cliente actual es mayor que 0 en la lista de nodos infantiles de bloqueo, significa que otros clientes han adquirido el bloqueo // en este momento, el cliente actual debe esperar a que otros clientes liberen el bloqueo, boolean isgetthelock = ourindex == 0; // ¿Cómo determinar si otros clientes han lanzado el bloqueo? Obtenga el nodo más pequeño que a sí mismo de la lista de nodos infantiles y configure una sesión de escucha para su cadena pathtowatch = isgetThelock? nulo: children.get (ourIdex - 1); if (isgetThelock) {havethelock = true; } else {// Si se elimina el nodo más pequeño, significa que el nodo del cliente actual debe ser el más pequeño, así que use CountDownLatch para realizar una cadena de espera anterior -SECHOENCEPATH = basepath .concat ("/") .concat (pathtowatch); Final CountdownLatch Latch = new CountdownLatch (1); final iZkDatalistener anteriorListener = new IzkDatalistener () {// Cuando ocurre un pequeño evento de deleción de nodo, deje que CountdownLatch termine y espere // en este momento, ¡debe dejar que el programa regrese de nuevo y haga un nuevo juicio! public void HandledAtadeleted (String DataPath) lanza la excepción {Latch.CountDown (); } public void HandledatAchange (String dataPath, datos del objeto) lanza la excepción {// ignorar}}; Pruebe {// Excepción si el nodo no existe Client.SubscriteDatAcHanges (AnteriorSequencePath, anteriorListener); if (Millistowait! = NULL) {Millistowait - = (System.CurrentTimemillis () - StartMillis); startmillis = System.CurrentTimemillis (); if (Millistowait <= 0) {dodelete = true; // cronometrado - Eliminar nuestra ruptura del nodo; } latch.await (Millistowait, TimeUnit.Microseconds); } else {latch.await (); }} Catch (ZknonodeeException e) {// ignorar} finalmente {client.unsubscritedAtACHANGES (AnteriorSequencePath, anteriorListener); }}}}} capt (excepción e) {// La excepción debe eliminarse dodelete = true; tirar E; } Finalmente {// si necesita eliminar el nodo if (dodelete) {deleteourpath (ourPath); }} return havethelock; } String private getLockNodeNumber (String Str, String LockName) {int index = str.lastIndexOf (LockName); if (index> = 0) {index += LockName.Length (); Índice de retorno <= str.length ()? str.substring (índice): ""; } return str; } Lista privada <String> getSortedChildren () lanza excepción {try {list <string> children = client.getChildren (basepath); Colección.sort (niños, nuevo comparador <tring> () {public int Compare (String LHS, String RHS) {return getLockNodeNumber (LHS, LockName) .compareto (getLockNodeNumber (rhs, lockname));}}); regresar hijos; } Catch (ZknonodeException e) {Client.CreatePersistent (BasePath, True); regreso getSortedChildren (); }} protegido void releaseLock (String LockPath) lanza la excepción {deleteoRpath (LockPath); } String Proteged String Intlock (Long Time, TimeUnit Unit) lanza la excepción {final de inicio largo = System.CurrentTimemillis (); final long milistowait = (unidad! = nulo)? unit.tomillis (tiempo): nulo; String ourPath = nulo; boolean hasthelock = false; boolean isdone = false; int retryCount = 0; // Net Flash Break requiere volver a intentar mientras (! ISDone) {isDone = true; Pruebe {// CreateLockNode se usa para crear el nodo de pedido [temporal] para que el cliente adquiera el bloqueo en Locker (nodo persistente de BasePath). OurPath = createLockNode (cliente, ruta); / *** Este método se utiliza para determinar si se ha obtenido el bloqueo, es decir, si los nodos de orden creados por usted son los más pequeños entre todos los nodos infantiles del casillero* Si no se adquiere el bloqueo, espere a que se libere la liberación del bloqueo, e intente nuevamente más tarde hasta que se adquiera el bloqueo o el tiempo*/ Hasthelock = Waittolock (StartMillis, MILLISTAWAIT, nuestro Path);;;;;;;;;;;;; } Catch (ZknonodeeException e) {if (retryCount ++ <max_retry_count) {isDone = false; } else {lanzar e; }}}} if (hasthelock) {return ourPath; } return null; }Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.