01-Inicio rápido de ZooKeeper

1 concepto de cuidador del zoológico

Zookeeper es un subproyecto del proyecto Apache Hadoop y es un servicio de directorio de árbol .

Zookeeper se traduce como cuidador del zoológico y es el administrador utilizado para administrar Hadoop (elefante), Hive (abeja) y Pig (cerdo). ZK para abreviar

Zookeeper es un proyecto Apache distribuido y de código abierto que coordina servicios para aplicaciones distribuidas .

Las principales funciones proporcionadas por Zookeeper incluyen:

  • ​ Gestión de configuración (como centro de configuración)
  • ​ Cerradura distribuida
  • ​ Gestión de clusters (como centro de registro)

Estructura de datos del cuidador del zoológico

La estructura del modelo de datos de Zookeeper es muy similar a la del sistema de archivos Unix: puede considerarse como un árbol en su conjunto y cada nodo se denomina ZNode. Cada ZNode puede almacenar 1 MB de datos de forma predeterminada y cada ZNode puede identificarse de forma única por su ruta.

1.1 escenarios de aplicación del cuidador del zoológico

Los servicios proporcionados incluyen: servicio de nombres unificado, gestión de configuración unificada, gestión de clústeres unificada, nodos de servidor dinámicos en línea y fuera de línea, equilibrio de carga suave, etc.

1) Servicio de nombres unificado

En un entorno distribuido, a menudo es necesario nombrar las aplicaciones/servicios de manera uniforme para una fácil identificación.

Por ejemplo: la IP no es fácil de recordar, pero el nombre de dominio es fácil de recordar

2) Gestión de configuración unificada

(1) En un entorno distribuido, la sincronización de archivos de configuración es muy común.

1. Generalmente se requiere que la información de configuración de todos los nodos en un clúster sea consistente, como un clúster Kafka.

2. Después de modificar el archivo de configuración, esperamos sincronizarlo rápidamente con cada nodo.

(2) El cuidador del zoológico puede implementar la gestión de la configuración

1. La información de configuración se puede escribir en un ZNode en zookeeper

2. Cada servidor cliente monitorea este ZNode

3. Una vez que se modifican los datos en ZNode, Zookeeper notificará a cada servidor cliente.

3) Gestión de clústeres unificada

(1) En un entorno distribuido, es necesario conocer el estado de cada nodo en tiempo real.

1. Se pueden realizar algunos ajustes según el estado en tiempo real del nodo.

(2) Zookeeper puede monitorear los cambios de estado del nodo

1. La información del nodo se puede escribir en un ZNode en Zookeeper

2. Supervise este ZNode para obtener sus cambios de estado en tiempo real.

4) Servidor dinámico en línea y fuera de línea

El cliente puede tener información en tiempo real sobre los cambios ascendentes y descendentes del servidor.

(1) Registrar información cuando se inicia el servidor (se crean todos los nodos temporales)

(2) Obtenga la lista actual de servidores en línea y regístrese para el monitoreo

(3) Si el nodo del servidor se desconecta

(4) Notificación del nodo del servidor conectado y desconectado

(5) Obtenga la lista de servidores nuevamente y regístrese para monitorear

5) Equilibrio de carga suave

Registre la cantidad de visitas a cada servidor en Zookeeper y deje que el servidor con la menor cantidad de visitas maneje las últimas solicitudes de los clientes.

1.2 Mecanismo de trabajo del cuidador del zoológico

Zookeeper se entiende desde la perspectiva del patrón de diseño: es un marco de gestión de servicios distribuidos diseñado en base al patrón de observador . Es responsable de almacenar y administrar los datos que interesan a todos y luego acepta el registro de los observadores . Una vez que se conoce el estado de estos cambios de datos, zookeeper Será responsable de notificar qué observadores se han registrado en zookeeper para responder en consecuencia.

1.3 Propósito del diseño de Zookeeper

El propósito del diseño se refleja principalmente en los siguientes aspectos:

1) Coherencia: no importa a qué servidor se conecte el cliente, verá la misma vista.

2) Tiempo real: los datos de Zookeeper se almacenan en la memoria, lo que puede lograr un alto rendimiento y una baja latencia.

3) Confiabilidad: Los servidores que conforman el servicio zookeeper deben conocer entre sí la existencia de otros servidores

4) Orden: por ejemplo, Zookeeper asigna un número de versión a cada operación de actualización, que es único y está ordenado.

5) Atomicidad: cuando el cliente zookeeper lee datos, solo puede tener dos estados: éxito o fracaso, y no habrá una situación en la que solo se lea una parte de los datos (es decir, la operación no se interrumpirá, ya sea exitosa o fallido)

1.4 Modelo del sistema Zookeeper

El modelo de sistema de Zookeeper incluye servidor y cliente.

1) El cliente puede conectarse a cualquier servidor del clúster Zookeeper. El cliente y el servidor establecen una conexión a través de TCP y son los principales responsables de enviar solicitudes y mensajes de latido, obtener respuestas y monitorear eventos.

2) Si la conexión TCP entre el cliente y el servidor se interrumpe, el cliente intenta conectarse automáticamente a otros servidores

3) Cuando un cliente se conecta a un servidor por primera vez, el servidor establecerá una sesión para el cliente. Cuando el cliente se conecta a otro servidor, el nuevo servidor restablecerá una sesión para el cliente

1.5 Rol del clúster Zookeeper

Hay tres roles en el servicio de clúster de cuidador del zoológico.

Por defecto, hay un líder (Líder) y un grupo compuesto por múltiples seguidores (Seguidor)

Líder Líder:

1. Procesar solicitudes de transacciones

2. Programador de cada servidor dentro del cluster

Seguidor Seguidor:

1. Procesar solicitudes de clientes que no sean transacciones y reenviar solicitudes de transacciones al servidor líder

2. Participar en la votación de la elección del líder (elección basada en el tamaño de ID del servidor)

Observador Observador:

1. Procesar solicitudes de clientes que no sean transacciones y emitir solicitudes de transacciones a no líderes

2. No participar en la votación de las elecciones de líderes.

El observador es un nuevo rol a partir de la versión zookeeper3.3.0. Cuando aumenta el número de nodos en un solo clúster de cuidador del zoológico, es necesario agregar más servidores para admitir más clientes, pero a medida que aumenta el número de servidores, la fase de votación lleva demasiado tiempo, lo que afecta el rendimiento del clúster. Para mejorar la escalabilidad del clúster y garantizar un alto rendimiento de datos, se presenta Observer

1.6 Funciones del cuidador del zoológico

  • Mientras sobrevivan más de la mitad de los nodos del clúster , el clúster Zookeeper podrá funcionar con normalidad. Por lo tanto, zookeeper es adecuado para instalar un número impar de servidores.

  • Coherencia global de los datos**: cada servidor guarda una copia idéntica de los datos**. No importa a qué servidor se conecte el cliente, los datos son consistentes.

  • Las solicitudes de actualización se ejecutan secuencialmente y las solicitudes de actualización del mismo Cliente se ejecutan secuencialmente en el orden en que ocurren.

  • Atomicidad de la actualización de datos : una actualización de datos tiene éxito o falla.

  • En tiempo real , dentro de un cierto rango de tiempo, el cliente puede leer los datos más recientes

2 Construcción del servicio Zookeeper

Página web oficial

https://zookeeper.apache.org

2.1 Modo independiente de Zookeeper

1) Preparación del entorno

El servidor Zookeeper se crea en Java, se ejecuta en JVM y debe instalarse en JDK7 o superior.

2) Cargar y descomprimir

Cargue el paquete comprimido de Zookeeper descargado en el directorio /opt/software** (todas las siguientes operaciones se basan en el usuario root**)

[root@kk01 software]# cd /opt/software/
# 上传 
[root@kk01 software]# rz
# 解压
[root@kk01 software]# tar -zxvf apache-zookeeper-3.6.1-bin.tar.gz
# 删除压缩包
[root@kk01 software]# rm -rf apache-zookeeper-3.6.1-bin.tar.gz 
# 将解压后的目录重命名
[root@kk01 software]# mv  apache-zookeeper-3.6.1-bin zookeeper-3.6.1

3) Configurar zoo.cfg

Cambie el nombre del archivo zoo_sample.cfg a zoo.cfg en el directorio /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/conf

[root@kk01 software]# cd zookeeper-3.6.1/conf/
# mv重命名 也可以使用拷贝更名(cp)
[root@kk01 conf]# mv zoo_sample.cfg zoo.cfg

# 在/opt/softeware/ zookeeper-3.6.1 目录下创建zookeeper存储目录zkData
[root@kk01 conf]# cd /opt/software/zookeeper-3.6.1
[root@kk01 zookeeper-3.6.1]# mkdir zkData

# 进入zoo.cfg文件进行修改存储目录
[root@kk01 zookeeper-3.6.1]# cd /opt/software/zookeeper-3.6.1/conf
[root@kk01 conf]# vim zoo.cfg

# 做出如下修改
dataDir=/opt/software/zookeeper-3.6.1/zkData

4) Configurar las variables de entorno de Zookeeper

[root@kk01 conf]# vim /etc/profile

# 在文件末尾添加以下内容

# zookeeper env
export ZOOKEEPER_HOME=/opt/software/zookeeper-3.6.1
export PATH=$PATH:$ZOOKEEPER_HOME/bin
     
# 使环境变量生效
[root@kk01 conf]# source /etc/profile

5) Inicie Zookeeper

Las variables de entorno de Zookeeper están configuradas, por lo que no es necesario ingresar al directorio bin de Zookeeper.

# 若未配置zookeeper环境变量,先切换目录
[root@kk01 conf]# cd /opt/software/zookeeper-3.6.1/bin/
# 启动
./zkServer.sh start
[root@kk01 bin]# ./zkServer.sh start


# 配置了环境变量直接使用以下命令
zkServer.sh start  #全局可用

# 见到如下信息则说明zookeeper启动成功
Using config: /opt/software/zookeeper-3.6.1/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

# 查看进程
[root@kk01 bin]# jps
2835 Jps
2764 QuorumPeerMain

Ver el inicio de Zookeeper

Hay dos formas de comprobar si Zookeeper se inició correctamente.

1. Verifique el estado de inicio de Zookeeper

[root@kk01 bin]# zkServer.sh status   # 看到如下信息说明启动成功
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper-3.6.1/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone

# 看到如下信息说明没有启动成功
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/zookeeper-3.6.1/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Error contacting service. It is probably not running.

2. Utilice el comando jps para ver el proceso del servicio Zookeeper QuorumPeerMain (este proceso es la entrada de inicio del clúster Zookeeper)

# 看到如下信息说明启动成功
[root@kk01 bin]# jps
2835 Jps
2764 QuorumPeerMain

2.1.1 Interpretación de los parámetros de configuración

El significado de los parámetros en el archivo de configuración zoo.cfg en Zookeeper es el siguiente

# 通信心跳时间,zookeeper服务器与客户端心跳时间,单位毫秒 
tickTime=2000	
# LF初始通信时限,即Leader和Follwer初始连接时能容忍的最多心跳数(tickTime的数量)
initLimit=10	
# LF同步通信时限,即Leader和Follwer之间通信时间如果超过 syncLimit*tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer
syncLimit=5
# 保存zookeeper中的数据(默认为tmp目录,容易被Linux系统定期删除,所以一把不使用默认的tmp目录)
dataDir=/opt/software/zookeeper-3.6.1/zkdata
# 客户端连接端口,通常不做修改
clientPort=2181

2.2 Zookeeper completamente distribuido

Introducción al grupo de cuidadores del zoológico

  • Elección de líder :

ID de servidor: ID del servidor

Por ejemplo, hay tres servidores, numerados 1, 2 y 3 respectivamente. Cuanto mayor sea el número, mayor será el peso en el algoritmo de selección.

  • Zxid: ID de datos

El ID de datos máximo almacenado en el servidor. Cuanto mayor sea el valor, más recientes serán los datos.

  • Durante el proceso de elección de Líder, si un Zookeeper obtiene más de la mitad de los votos, este Zookeeper puede convertirse en Líder.

Lo que necesitas saber sobre la construcción.

Los clústeres reales se implementan en diferentes servidores, aquí utilizamos máquinas virtuales para simular tres servidores para simular una distribución completa . Por supuesto, también puede utilizar una máquina virtual para crear un clúster pseudodistribuido . Luego diferencia según el puerto .

La diferencia entre completamente distribuido y pseudodistribuido es que: el completamente distribuido se construye en múltiples máquinas y se distingue según la IP. El pseudodistribuido se construye en una máquina y se diferencia según la IP y el puerto.

Planificación de clusters

Debido a que mientras más de la mitad de los nodos del clúster sobrevivan, el clúster Zookeeper puede funcionar normalmente, por lo que utilizamos tres servidores para la demostración aquí.

kk01	192.168.188.128
kk02	192.168.188.129
kk03    192.168.188.130

1) Trabajo de preparación

Instale jdk y zookeeper y cárguelos en el servidor (se han implementado en modo independiente y se omiten aquí)

2) Cree un archivo myid con el contenido 1 en /opt/software/zookeeper-3.6.1/zkData.

Este archivo registra la ID de cada servidor (se requiere que myid sea un número entero de 1 a 255, y el contenido de myid debe ser único en el clúster)

[root@kk01 zkData]# cd /opt/software/zookeeper-3.6.1/zkData
[root@kk01 zkData]# echo 1 > ./myid

3) Modifique el archivo zoo.cfg en el directorio /opt/software/zookeeper-3.6.1/conf

[root@kk01 zkData]# vi /opt/software/zookeeper-3.6.1/conf/zoo.cfg

# 在文件末尾添加如下内容
server.1=192.168.188.128:2881:3881
server.2=192.168.188.129:2881:3881
server.3=192.168.188.130:2881:3881

# 2881是Leader端口,负责和Follower进行通信。3881是Follower端口,进行推选Leader

# 配置参数解读
# 	server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口
# 	服务器ID,即配置在zkData目录下myid文件里的值
#		zk启动时读取此文件,拿到里面的数据与zoo.cfg里的配置信息对比,从而判断到底是哪个server
#	服务器IP地址
#	服务器之间通信端口,服务器Follwer与集群中的Leader服务器交换信息的端口
#	服务器之间投票选举端口,是万一集群中的Leader服务器挂掉了,需要一个端口来重新选举出新的Leader,这个端口就是用来执行选举时服务器相互通信的端口

4) Distribuir /opt/software/zookeeper-3.6.1/ a las máquinas virtuales kk02 y kk03

[root@kk01 zkData]# scp -r /opt/software/zookeeper-3.6.1/ root@kk03:/opt/software/zookeeper-3.6.1

[root@kk01 zkData]# scp -r /opt/software/zookeeper-3.6.1/ root@kk03:/opt/software/zookeeper-3.6.1

5) Modifique los archivos myid en el directorio /opt/software/zookeeper-3.6.1/zkData de las máquinas virtuales kk02 y kk03 respectivamente, los contenidos son 2 y 3 respectivamente.

# kk02
[root@kk02 ~]# cd /opt/software/zookeeper-3.6.1/zkData/
[root@kk02 zkData]# vi myid 

#kk03
[root@kk03 ~]# cd /opt/software/zookeeper-3.6.1/zkData/
[root@kk03 zkData]# vi myid 

6) Distribuya el archivo de configuración del entorno de la máquina virtual kk01 a kk02 y kk03.

[root@kk01 zkData]# scp -r /etc/profile root@kk02:/etc/profile
[root@kk01 zkData]# scp -r /etc/profile root@kk03:/etc/profile

# 分别在kk02、kk03下使用下面命令使环境变量生效
source /etc/profile

3) Utilice los siguientes comandos para iniciar el servidor Zookeeper en kk01, kk02 y kk03 respectivamente.

[root@kk01 zkData]# zkServer.sh  start
[root@kk02 zkData]# zkServer.sh  start
[root@kk03 zkData]# zkServer.sh  start

3) Para verificar el estado del servidor Zookeeper de tres máquinas virtuales , el comando es el siguiente

# 查看进程
[root@kk01 zkData]# jps
3440 QuorumPeerMain
3495 Jps

[root@kk02 zkData]# jps
2898 Jps
2844 QuorumPeerMain

[root@kk03 zkData]# jps
2855 Jps
2794 QuorumPeerMain


# 查看状态
[root@kk01 zkData]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper-3.6.1/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

[root@kk02 zkData]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper-3.6.1/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

[root@kk03 zkData]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper-3.6.1/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

Verificar el estado de inicio arroja los resultados anteriores: Modo: seguidor Modo: aparece líder, lo que indica que el grupo de cuidadores del zoológico se estableció exitosamente.

2.2.1 Script de Shell para iniciar y detener el clúster de cuidador del zoológico con un clic

Iniciar y apagar el clúster de zookeeper requiere iniciar y apagar cada máquina virtual, lo cual no es eficiente. En el trabajo real, se utilizan muchos servidores. Para facilitar la administración del servidor Zookeeper, puede escribir el script xzk.sh para iniciar y detener el clúster del servidor Zookeeper con un solo clic.

En el directorio /usr/local/bin de la máquina virtual kk01, escriba el archivo de script xzk.sh , el contenido del archivo es el siguiente:

#!/bin/bash
cmd=$1
if [ $# -gt 1 ] ; then echo param must be 1 ; exit ; fi
for (( i=1 ; i <= 3; i++ )) ; do
        tput setaf 5
        echo ============ kk0$i $@ ============
        tput setaf 9
        ssh kk0$i "source /etc/profile ; zkServer.sh $cmd"
done

Agregue permisos de ejecución al propietario del script xzk.sh

chmod u+x xzk.sh
# 或
chmod 744 xzk.sh

Utilice los comandos de inicio y detención del script xzk.sh para apagar kk02 y kk03 en kk01 al mismo tiempo.

xzk.sh start
xzk.sh stop

Guiones más fáciles de entender

# 在虚拟机kk01的/usr/local/bin目录下 创建名为zk.sh的脚本

[root@kk01 ~]# cd /usr/local/bin/
[root@kk01 bin]# vim zk.sh

# 内容如下

#!/bin/bash

case $1 in
"start")
        for i in kk01 kk02 kk03
        do
                echo "----------------zookeeper $i start------------------------"
                ssh $i "/opt/software/zookeeper-3.6.1/bin/zkServer.sh $1"
        done
;;
"stop")
        for i in kk01 kk02 kk03
        do
                echo "----------------zookeeper $i stop------------------------"
                ssh $i "/opt/software/zookeeper-3.6.1/bin/zkServer.sh $1"
        done
;;
"status")
        for i in kk01 kk02 kk03
        do
                echo "----------------zookeeper $i status------------------------"
                ssh $i "/opt/software/zookeeper-3.6.1/bin/zkServer.sh $1"
        done
;;
*)
        echo "输入参数有误(请输入:start|stop|status)!"
esac



# 赋予文件可执行权限
[root@kk01 bin]# chmod u+x zk.sh 

El script anterior puede encontrar el siguiente error

[root@kk01 bin]# pwd
/usr/local/bin   # 因为我们脚本放在该目录下,可能会遇到如下错误

[root@kk01 bin]# zk.sh status
----------------zookeeper kk01 status------------------------
Error: JAVA_HOME is not set and java could not be found in PATH.
----------------zookeeper kk02 status------------------------
Error: JAVA_HOME is not set and java could not be found in PATH.
----------------zookeeper kk03 status------------------------
Error: JAVA_HOME is not set and java could not be found in PATH.


# 解决方法
# 方法一:将自定义脚本放在家目录的bin目录下(我们采用root用户,所以需要放在 /root/bin/目录下)
[root@kk01 ~]# mkdir -p /root/bin
[root@kk01 ~]# cp /usr/local/bin/zk.sh /root/bin/
[root@kk01 bin]# ll
total 4
-rwxr--r--. 1 root root 615 Apr 20 00:17 zk.sh
# /root/bin目录添加到环境变量
[root@kk01 ~]# vim /etc/profile
# 内容如下

# 将root的bin目录添加到环境
export PATH=$PATH:/root/bin

[root@kk01 ~]# source /etc/profile


# 尽力了九九八十一难,最终脚本如下(上面脚本,错误的原因:我们再/etc/profile中配置了zookeeper环境变量,因此启动zkServer.sh 不再需要加路径)

#!/bin/bash

case $1 in
"start")
        for i in kk01 kk02 kk03
        do
                echo "----------------zookeeper $i start------------------------"
                ssh $i " source /etc/profile;  zkServer.sh $1"
        done
;;
"stop")
        for i in kk01 kk02 kk03
        do
                echo "----------------zookeeper $i stop------------------------"
                ssh $i " source /etc/profile; zkServer.sh $1"
        done
;;
"status")
        for i in kk01 kk02 kk03
        do
                echo "----------------zookeeper $i status------------------------"
                ssh $i " source /etc/profile; zkServer.sh $1"
        done
;;
*)
                echo '输入参数有误(请输入:start|stop|status)!'
esac

2.3 Simulación de excepción de clúster (simulación de elecciones)

1) Primero pruebe qué pasará si el servidor cuelga

Detenga el servidor en kk03, observe kk01 y kk02 y descubra que no hay cambios.

# 在kk03上关闭zk
zkServer.sh stop

# 查看kk01   还是follower,无变化
[root@kk01 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

# 查看kk02   还是leader,无变化
[root@kk02 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

De esto podemos concluir que en un clúster de 3 nodos, si el servidor esclavo cuelga, el clúster será normal.

2) Después de que el servidor en kk01 también colgara, verifiqué kk02 (la ubicación del servidor principal) y descubrí que también dejó de funcionar.

# 在kk01上关闭zk
zkServer.sh stop

# 查看kk02   服务已停止
[root@kk02 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Error contacting service. It is probably not running.

De esto podemos concluir que en un clúster de 3 nodos, ambos servidores esclavos están inactivos y el servicio principal no puede ejecutarse . Porque la cantidad de máquinas ejecutables no excede la mitad de la cantidad de clústeres.

3) Iniciamos el servidor en kk01 nuevamente y descubrimos que el servidor en kk02 comenzó a funcionar normalmente nuevamente y seguía siendo el líder.

# 在kk01上开启zk
zkServer.sh start

# 查看kk02 	发现他又开始运行了,而且依然是领导者leader
[root@kk02 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

4) Iniciamos el servidor en kk03, apagamos el servidor en kk02 y verificamos el estado de kk01 y kk03.

# 在kk03上开启zk
zkServer.sh start
# 在kk02上关闭zk
zkServer.sh stop

# 查看kk01	依然是follower
[root@kk01 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

# 查看kk03   变为了leader
[root@kk03 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

Descubrió que ha surgido un nuevo líder.

De esto concluimos que cuando el servidor principal del clúster cuelga, otros servidores del clúster seleccionarán automáticamente el estado y luego generarán un nuevo líder.

5) ¿Qué pasará si inicias el servidor en kk02 nuevamente? ¿El servidor en kk02 volverá a convertirse en el nuevo líder?

# 在kk02上启动zk
zkServer.sh start

# 查看kk02
[root@kk02 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.

# 查看kk03  依然是leader
[root@kk03 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/apache-zookeeper-3.6.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

El servidor de kk02 se iniciará de nuevo y el servidor de kk03 seguirá siendo el líder.

De esto se puede concluir que el líder del clúster zk no cederá.

3 Mecanismo de elección del cuidador del zoológico

El mecanismo de elección de Zookeeper es un mecanismo de media mayoría: si se aprueba más de la mitad de los votos, se aprueba.

1) Iniciar las reglas de elección por primera vez:

  • Cuando se emiten más de la mitad de los votos, gana el que tenga el ID de servidor más grande.

2) Iniciar las reglas de elección por segunda vez:

  • El que tenga mayor ÉPOCA (período) gana directamente.
  • EPOCH es lo mismo, gana el que tenga el ID de transacción más grande.
  • Si los ID de transacción son los mismos, gana el que tenga el ID de servidor más grande.

detalles de la siguiente manera:

3.1 Mecanismo de elección de Zookeeper (primera puesta en marcha)

Cada operación de escritura del Cliente tendrá un ID de transacción (ZXID)

SID: ID del servidor . Se utiliza para identificar de forma única la máquina donde se encuentra un clúster de Zookeeper. Cada máquina no se puede repetir y es coherente con myid.

ZXID: ID de transacción. ZXID es un ID de transacción utilizado para identificar un cambio en el estado del servidor . En un momento determinado, es posible que el ZXID de cada máquina en el clúster no sea completamente consistente, esto está relacionado con la lógica de procesamiento del servidor Zookeeper para la "solicitud de actualización" del cliente.

Época: el nombre en clave de cada término líder . Cuando no hay líder, el valor del reloj lógico en la misma ronda de votación es el mismo. Estos datos aumentarán cada vez que se emita un voto.

假设zookeeper services有五台机器

Server1		Server2 	Server3 	Server4 	Server5
myid1		myid2		myid3		myid4		myid5

1) El servidor 1 inicia e inicia una elección. El servidor 1 emite su voto. En este momento, el servidor tiene 1 voto y un voto. Si no hay suficientes votos (3 votos), la elección no se puede completar y el estado del servidor 1 permanece MIRANDO.

2) El servidor 2 inicia e inicia otra elección. Los servidores 1 y 2 votan por sí mismos e intercambian información de voto**: en este momento, el servidor 1 descubre que el myid del servidor 2 es mayor que el que está votando actualmente (servidor 1) y cambia el voto para recomendar el servidor. 2**. En este momento, el servidor 1 tiene 0 votos y el servidor 2 tiene 2 votos, sin más de la mitad no se puede completar la elección y el estado de los servidores 1 y 2 permanece MIRANDO.

3) El servidor 3 inicia e inicia una elección. En este momento, los servidores 1 y 2 cambiarán los votos al servidor 3. Los resultados de esta votación: el servidor 1 tiene 0 votos, el servidor 2 tiene 0 votos y el servidor 3 tiene 3 votos. En este momento, el servidor 3 tiene más de la mitad de los votos y el servidor es elegido Líder . El estado de los servidores 1 y 2 cambia a SIGUIENTE y el estado del servidor 3 cambia a LIDERANDO.

4) El servidor 4 inicia e inicia una elección. En este momento, los servidores 1, 2 y 3 han implementado la barra de estado MIRANDO y ya no cambiarán la información de votación. Los resultados del intercambio de información de votación: el servidor 3 tiene 3 votos y el servidor 4 tiene 1 voto. En este momento, el servidor 4 obedece, cambia el resultado de la votación al servidor 3 y cambia el estado a SIGUIENTE

5) El servidor 5 se inicia y actúa como el hermano menor como el 4.

3.2 Mecanismo de elección de Zookeeper (no es la primera puesta en marcha)

假设zookeeper services有五台机器

Server1		Server2 	Server3 	Server4 	Server5
myid1		myid2		myid3		myid4		myid5

follwer		follwer 	leadr		follwer		follwer

1) Cuando un servidor en el clúster de cuidador del zoológico encuentra las dos situaciones siguientes, comenzará a ingresar a la elección:

  • Inicio de inicialización del servidor
  • No se puede mantener la conexión con Leader mientras el servidor se está ejecutando

2) Cuando un servidor ingresa al proceso de elección de Líder, el grupo actual también puede estar en los dos estados siguientes:

  • Ya existe un Líder en el cluster

Para esta situación donde existe un líder, cuando la máquina intenta elegir un líder, se le informará la información del líder del servidor actual. Para la máquina, solo necesita establecer una conexión con la máquina líder y sincronizar el estado.

  • De hecho, no hay ningún líder en el grupo.
假设zookeeper services有五台服务器组成
SID分别为1、2、3、4、5
ZXID分别为8、8、8、7、7  并且此时SID为3的服务器是Leader
某一时刻,服务器3和5出现故障,因此需要重新进行Leader选举:
							(EPOCH,ZXID,SID)	(EPOCH,ZXID,SID)	(EPOCH,ZXID,SID)
SID为1、2、4的机器投票情况	 (1,8,7)			 (1,8,2)			 (1,7,4)

		选举Leader规则:
				1)EPOCH大的直接胜出
				2)EPOCH相同,事务ID(ZXID)大的胜出
				3)事务ID相同,服务器ID(SID)大的胜出
				
# 最终结果
服务器4当选Leader,服务器1、2为Follwer

4 El cliente escribe el proceso de datos en el servidor.

La solicitud de escritura del proceso de escritura se envía directamente al Líder


					1 write								 2 write
Client ------------------------------>  ZK Server1 Leader ---------->ZK Server2 Follwer 
					3 ack 
ZK Server2 Follwer ---------->ZK Server1 Leader  

(因此该集群中只有三台机器,数据写入了两台,超过半数了,zk Server Leader回应Client)
 					4 ack
ZK Server1 Leader  ------> Client 
 				   5 write
ZK Server1 Leader ------> ZK Server3 Follwer
 				   6 ack
ZK Server3 Follwer ------> ZK Server1 Leader

La solicitud de escritura del proceso de escritura se envía a Follwer

			1 write								2 write请求
Client -------------------> ZK Server2 Follwer ----------> ZK Server1 Leader
					3 write
ZK Server1 Leader  ----------> ZK Server2 Follwer 
					4 ack
ZK Server2 Follwer ----------> ZK Server1 Leader

(因此该集群中只有三台机器,数据写入了两台,超过半数了,zk Server Leader回应ZK Server2 Follwer )
				  5 ack
ZK Server1 Leader ----------> ZK Server2 Follwer 
(接着ZK Server2 Follwer 回应Client)
					6 ack
ZK Server2 Follwer ----------------> Client
					7 write 
ZK Server1 Leader ---------> ZK Server3 Follwer
					8 ack
ZK Server3 Follwer ----------> ZK Server1 Leader

5 operaciones de comando de Zookeeper

5.1 Modelo de datos de Zookeeper

Zookeeper es un servicio de directorio de árbol, su modelo de datos es muy similar al árbol de directorio del sistema de archivos Unix y tiene una estructura jerárquica.

Cada nodo aquí se llama ZNode , y cada nodo guardará sus propios datos e información del nodo. La información del nodo incluye: longitud de los datos, tiempo de creación, tiempo de modificación, número de nodos secundarios, etc.

Los nodos pueden tener nodos secundarios y se permite almacenar una pequeña cantidad (1 MB) de datos debajo del nodo.

5.2 tipo de nodo zk

Los nodos se pueden dividir en cuatro categorías principales:

  • ​ PERSISTENTE nodo persistente , nodo permanente
  • Nodo temporal EFÍMERO -e
  • ​ Nodo de secuencia de persistencia PERSISTENT_SEQUENTIAL: -s
  • ​ EPHEMERAL_SEQUENTIAL nodo de secuencia temporal: -es

Nodo de persistencia:

Los nodos no se eliminarán automáticamente después de que salga el cliente Zookeeper . El cliente Zookeeper crea nodos persistentes de forma predeterminada .

Nodo temporal:

Una vez que el cliente Zookeeper sale, el nodo se eliminará automáticamente . Los nodos temporales no pueden tener nodos secundarios. Los usuarios pueden juzgar la apertura o el cierre de servicios distribuidos a través de nodos temporales.

Nodo de secuencia:

Se agrega automáticamente un nodo de número de serie de 10 dígitos al final del nombre del nodo.

Nodos de secuencia de persistencia:

Después de que el cliente se desconecta de zk, el nodo todavía existe, pero zk lo numera secuencialmente.

Nodo de secuencia temporal:

Después de que el cliente se desconecta de ZooKeeper, el nodo se eliminará automáticamente, pero ZooKeeper numera el nodo secuencialmente.

Manifestación

[root@kk01 ~]# zkCli.sh -server 192.168.188.128:2181
# 1、创建 永久节点(不带序号) 默认
[zk: 192.168.188.128:2181(CONNECTED) 0] create /nhk "ninghongkang"   
Created /nhk
[zk: 192.168.188.128:2181(CONNECTED) 6] ls /    # 查看
[nhk, zookeeper]
[zk: 192.168.188.128:2181(CONNECTED) 7] get /nhk    # 查看节点值
ninghongkang

# 2.创建带序号永久节点 (带序号的节点好处在与可以重复创建,他会在后面默认拼接10位的序列号)
[zk: 192.168.188.128:2181(CONNECTED) 9] create -s /nhk/app1 "test1"
Created /nhk/app10000000000
[zk: 192.168.188.128:2181(CONNECTED) 17] ls /nhk
[app10000000000]
[zk: 192.168.188.128:2181(CONNECTED) 18] get /nhk/app10000000000
test1
[zk: 192.168.188.128:2181(CONNECTED) 19] create -s /nhk/app1 "test1"
Created /nhk/app10000000001
[zk: 192.168.188.128:2181(CONNECTED) 20] ls /nhk
[app10000000000, app10000000001]

# 3.创建临时节点
[zk: 192.168.188.128:2181(CONNECTED) 21] create -e /nhk/app2    # 创建临时节点
Created /nhk/app2
[zk: 192.168.188.128:2181(CONNECTED) 22] create -e /nhk/app2    # 重复创建显示节点已存在
Node already exists: /nhk/app2
# 4.创建临时顺序节点(可重复创建)
[zk: 192.168.188.128:2181(CONNECTED) 23] create -e -s /nhk/app2
Created /nhk/app20000000003
[zk: 192.168.188.128:2181(CONNECTED) 24] create -e -s /nhk/app2
Created /nhk/app20000000004
[zk: 192.168.188.128:2181(CONNECTED) 27] ls /nhk
[app10000000000, app10000000001, app2, app20000000003, app20000000004]

# 重启zk客户端
[zk: 192.168.188.128:2181(CONNECTED) 28] quit

[root@kk01 ~]# zkCli.sh -server 192.168.188.128:2181
[zk: 192.168.188.128:2181(CONNECTED) 1] ls /nhk   # 查看发现临时节点已经被自动删除了
[app10000000000, app10000000001]

Zookeeper Server puede interactuar con el cliente Zookeeper y la API Java de Zookeeper

5.3 Comandos comunes en el servidor ZooKeeper

Atender Orden
Iniciar servicio zk ./zkServer.sh inicio
Verificar el estado del servicio zk ./zkServer.sh inicio
Detener el servicio zk ./zkServer.sh detener
Reiniciar el servicio zk ./zkServer.sh reiniciar

5.4 Comandos comunes del cliente Zookeeper

Control de permisos del nodo Zookeeper

En la producción real, varias aplicaciones suelen utilizar el mismo cuidador del zoológico, pero diferentes sistemas de aplicaciones rara vez utilizan datos comunes.

En vista de esta situación, Zookeeper utiliza la política ACL (Lista de control de acceso) para el control de permisos, que es similar al control de permisos del sistema de archivos de Linux. Los nodos de Zookeeper definen 5 permisos.

  • ​ crear permiso para crear nodos secundarios
  • ​ permiso de lectura para obtener datos de nodos secundarios y lista de nodos secundarios
  • permiso de escritura para actualizar los datos del nodo
  • ​eliminar permiso para eliminar nodos secundarios
  • ​ andin establece los permisos del nodo
Atender Orden
Conéctese al cliente local zk zkCli.sh (conectarse al cliente local)
Conéctese al servidor del cuidador del zoológico ./zkCli.sh -server [ip:puerto] Al conectarse a un servidor remoto, debe especificar la IP y el puerto.
Desconectar abandonar
Ver ayuda de comando ayuda
Mostrar nodos en el directorio especificado ls /ruta
Escuche los cambios en los nodos secundarios ls -w /ruta
Información secundaria adicional ls -s /ruta
Crear nodo crear/ruta de nodo [valor]
Crear nodos de secuencia crear -s /ruta del nodo [valor]
Crear nodo temporal crear -e /ruta del nodo [valor] (el cliente zk se eliminará automáticamente cuando se reinicie o se agote el tiempo de espera)
Obtener valor de nodo obtener la ruta del nodo
Monitorear cambios en el contenido del nodo (es decir, datos almacenados en el nodo) obtener -w /ruta
Información secundaria adicional obtener -s /ruta
Establecer valor de nodo establecer el valor de la ruta del nodo
Eliminar un solo nodo eliminar /ruta del nodo
Eliminar nodos con hijos (eliminación recursiva) eliminar todo/ruta del nodo
Ver el estado del nodo estadística/ruta
Ver detalles del nodo ls2 /ruta del nodo (no recomendado) ls -s /ruta del nodo

Nota: Los comandos para operar nodos en el cliente Zookeeper deben utilizar rutas absolutas.

Algunos detalles de parámetros:

  • czxid: ID de transacción del nodo creado
  • ctime: tiempo de creación (número de milisegundos desde que se creó znode, desde 1970)
  • mzxid: el ID de la última transacción actualizada (zxid de la última transacción actualizada de znode)
  • mtime: tiempo de modificación (número de milisegundos desde la última modificación de znode, desde 1970)
  • pzxid: el ID de transacción de la última lista de nodos secundarios actualizada (zxid del último nodo secundario actualizado de znode)
  • cversion: número de versión del nodo secundario (número de cambio del nodo secundario de znode, número de modificación del nodo secundario de zonde)
  • dataversion: número de versión de datos (número de cambio de datos de zona)
  • aclversion: número de versión del permiso (cambiar el número de la lista de control de acceso a zonde)
  • epheralOwner: utilizado para nodos temporales, representa el ID de transacción del nodo temporal, 0 si es un nodo persistente (si es un nodo temporal, el ID de sesión del propietario de esta zonde. Si no es un nodo temporal, es 0)
  • longitud de datos: la longitud de los datos almacenados en el nodo (longitud de datos de znode)
  • numChildren: el número de nodos secundarios del nodo actual (número de nodos secundarios de znode)

5.5 Principio del oyente

Explicación detallada del principio de seguimiento.

1) Primero debe haber un hilo principal()

2) Cree un cliente zookeeper en el hilo principal, en este momento se crearán dos hilos, uno responsable de la conexión de red (conectar) y otro responsable de escuchar (oyente)

3) Enviar eventos de escucha registrados al cuidador del zoológico a través del hilo de conexión

4) Agregue los eventos de escucha registrados a la lista de oyentes del cuidador del zoológico.

5) Cuando el cuidador del zoológico detecta cambios en los datos o en la ruta, enviará este mensaje al hilo de escucha.

6) El método Process() se llama internamente en el hilo de escucha.

		zk客户端											zk服务端
	1 Main()线程
	2 创建zkClient				5"/"路径数据发生变化      注册的监听器列表
			|-- Listener     <---------------------  	  4 Client:ip:port:/path
								3 getChildren("/",true)
			|--connect       --------------------->     
            
	6 listener线程调用process()

Monitoreo de escena

1)监听节点数据的变化
get path [watch]

2)监听子节点增减的变化
ls path [watch] 

Manifestación

# 1.节点的值变化监听
# 1)在kk01上注册监听/nhk节点 数据变化
[zk: localhost:2181(CONNECTED) 0] get -w /nhk
ninghongkang

# 2)在kk02主机上修改/nhk节点的数据
[zk: localhost:2181(CONNECTED) 2] set /nhk nhk666
[zk: localhost:2181(CONNECTED) 3] 

# 3)观察kk01主机收到的数据变化的监听
WATCHER::

WatchedEvent state:SyncConnected type:NodeDataChanged path:/nhk

# 注意:在kk02上多次修改/nhk节点的值,kk01不会再继续监听。因为注册一次,只能监听一次。如果想再次监听,需要再次注册


# 2.节点的子节点变化监听(路径变化)
# 1)在kk01上注册监听/nhk节点 子节点变化
[zk: localhost:2181(CONNECTED) 2] ls -w /nhk
[app10000000000, app10000000001]

# 2)在kk02主机上/nhk节点上创建新节点app2
[zk: localhost:2181(CONNECTED) 4] create /nhk/app2 "test2"
Created /nhk/app2

# 3)观察kk01主机收到的子节点变化的监听
WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/nhk

# 注意:节点的路径变化,也是注册一次,生效一次。想多次生效。就需要多次注册

Aviso:

  • El monitoreo de cambios de datos del nodo, el registro una vez, solo se puede monitorear una vez. Si quieres volver a escuchar, debes registrarte nuevamente.

  • El cambio de ruta del nodo también se registra una vez y entra en vigor una vez. Quiere que surta efecto varias veces. Necesitas registrarte varias veces

6 operaciones de la API Java de ZooKeeper

6.1 API nativa de Zookeeper

1) Preparación de la creación

Asegúrese de que el servidor del clúster zookeeper en los servidores kk01, kk02 y kk03 esté iniciado.

Agregar dependencias relacionadas en pom

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.6.1</version>
        </dependency>
    </dependencies>

Cree un nuevo archivo log4j.properties en el directorio src/main/sources con el siguiente contenido

#############
# 输出到控制台
#############
# log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
# INFO:日志级别     CONSOLE:输出位置自己定义的一个名字     
log4j.rootLogger=INFO,CONSOLE
# 配置CONSOLE输出到控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 配置CONSOLE设置为自定义布局模式
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 配置CONSOLE日志的输出格式  [frame] 2019-08-22 22:52:12,000  %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n

2) Crear cliente de cuidador del zoológico

public class ZKClient {
    
    
    //  注意:逗号左右不能有空格,否则会连接不成功
    private static String connectString = "kk01:2181,kk02:2181,kk03:2181";
    private int sessionTimeout = 2000;
    private ZooKeeper zkClient = null;

    @Before
    public void init() throws IOException, InterruptedException {
    
    
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
    
    
            @Override
            public void process(WatchedEvent event) {
    
    

            }
        });
    }

    @After
    public void close(){
    
    
        try {
    
    
            zkClient.close();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

3) Crear nodos secundarios

    @Test
    public void create() {
    
    
        try {
    
    
            //   final String path, 要创建的节点的路径
            //   byte[] data, 节点数据
            //   List<ACL> acl,  节点权限
            //   CreateMode createMode  节点的类型
            zkClient.create("/test", "666".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        } catch (KeeperException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

Prueba: Verifique la creación de nodos en el cliente zk de kk01

[zk: localhost:2181(CONNECTED) 3] ls /
[nhk, test, zookeeper]

4) Obtenga nodos secundarios y escuche los cambios en los nodos secundarios

@Before
    public void init() throws IOException, InterruptedException {
    
    
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
    
    
            @Override
            public void process(WatchedEvent event) {
    
    
                List<String> children = null;
                try {
    
    
                    children = zkClient.getChildren("/test", true);
                } catch (KeeperException e) {
    
    
                    e.printStackTrace();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println("------------------");
                for (String child : children) {
    
    
                    System.out.println(child);
                }
            }
        });
    }

@Test
    public void getChildren() throws InterruptedException {
    
    
        List<String> children = null;
        try {
    
    
            children = zkClient.getChildren("/test", true);
        } catch (KeeperException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("------------------");
        for (String child:children) {
    
    
            System.out.println(child);
        }

        // 为了让程序不那么快结束,我们让程序睡起来
        Thread.sleep(Integer.MAX_VALUE);
    }

Prueba: cree los subnodos app1 y app2 en el cliente zk de kk01 para ver las modificaciones/el nodo de prueba

[zk: localhost:2181(CONNECTED) 7] create /test/app1 "t1"
Created /test/app1
[zk: localhost:2181(CONNECTED) 8] create /test/app2 "t2"
Created /test/app2

La consola de ideas imprime la siguiente información.

------------------
------------------
------------------
app1
------------------
app2
app1

5) Determinar si existe ZNode

   @Test
    public void exist(){
    
    
        Stat status = null;
        try {
    
    
            status = zkClient.exists("/test", false);
        } catch (KeeperException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(status == null? "节点不存在":"节点存在");
        
    }

6.2 API de curador

Zookeeper proporciona una API de Java para facilitar a los desarrolladores realizar la programación del cliente y operar datos en el servidor según las necesidades.

Configurar el entorno de desarrollo

Modifique el contenido del archivo pom.xml en IDEA de la siguiente manera:

<!-- 导入以下依赖坐标 zk依赖记得要与zk版本一致-->
<dependency>
	<groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.1</version> 
</dependency>
 <!-- 单元测试 -->
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.10</version>
	<scope>test</scope>
 </dependency>

6.2.1 Introducción al curador

Curator es una biblioteca cliente Java para Apache Zookeeper

API Java común de Zookeeper:

  • API nativa de Java
  • ​ ZKCliente
  • ​ Curador

El objetivo del proyecto Curator es simplificar el uso de los clientes de Zookeeper.

Curator fue desarrollado originalmente por Netflix y luego donado a la Fundación Apache, actualmente es un proyecto de alto nivel de Apache.

6.2.2 Operaciones comunes de Curator API

Preparación

Importar dependencias relacionadas en pom

    <!-- curator-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.0.0</version>
        </dependency>

        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>

        <!--log4j相关jar-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        
   <!-- 中文乱码问题 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>

archivo log.properties

log4j.rootLogger=DEBUG,console
#----------------输出为控制台-------------------#
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%c]%m%n

establecer conexión

Se recomienda utilizar el segundo método de conexión.

public class CuratorTest {
    
    
    private static CuratorFramework client;

    /**
     * 建立连接
     */
    @Before
    public void testConnect() {
    
    
        /**
         * String connectString,  连接字符串
         * int sessionTimeoutMs,  会话超时时间 单位ms
         * int connectionTimeoutMs,  连接超时时间 单位ms
         * RetryPolicy retryPolicy  重试策略
         */
        // 重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);

        // 第一种连接方式
        //CuratorFramework curator = CuratorFrameworkFactory.newClient("192.168.188.128:2181", 60 * 1000, 15 * 1000, retryPolicy);
        // 开启连接
        //curator.start();

        // 第二种连接
        client = CuratorFrameworkFactory.builder().connectString("192.168.188.128:2181").
                sessionTimeoutMs(60 * 1000).
                connectionTimeoutMs(15 * 1000).
                retryPolicy(retryPolicy).namespace("clear").build(); // 在根节点指定命名空间
        		// 注:使用该方式创建的命名空间,当其下无节点时,断开链接会自动将命名空间删除

        // 开启连接
        client.start();
    }

    /**
     * 关闭连接
     */
    @After
    public void close() {
    
    
        if (client != null) {
    
    
            client.close();
        }
    }
}

Agregar nodo

Creación básica

 /**
     * 创建节点:create 持久 临时 顺序 数据
     * 1.基本创建
     * 2.创建节点 带有数据
     * 3.设置节点的类型
     * 4.创建多级节点 /app1/p1
     */
    @Test
    public void testCreate(){
    
    
        // 1.基本创建
        // 如果创建节点,没有指明数据,则默认将当前客户端的ip作为数据存储
        try {
    
    
            String path = client.create().forPath("/app1");
            System.out.println(path);
        } catch (Exception e) {
    
    
            System.out.println("创建失败~~~");
            e.printStackTrace();
        }
 }
    

prueba

Después de ejecutar la prueba en idea, verá la siguiente información en zkclient

[zk: localhost:2181(CONNECTED) 11] get /clear/app1
192.168.56.1

Crear un nodo con datos

 /**
     * 创建节点:create 持久 临时 顺序 数据
     * 1.基本创建
     * 2.创建节点 带有数据
     * 3.设置节点的类型
     * 4.创建多级节点 /app1/p1
     */
    @Test
    public void testCreate2(){
    
    
        // 2.创建节点 带有数据
        try {
    
    
            // 如果创建节点,没有指明数据,则默认将当前客户端的ip作为数据存储
            String path = client.create().forPath("/app2","zjh".getBytes());
            System.out.println(path);
        } catch (Exception e) {
    
    
            System.out.println("创建失败~~~");
            e.printStackTrace();
        }
    }

prueba

Después de ejecutar la prueba en idea, verá la siguiente información en zkclient

[zk: localhost:2181(CONNECTED) 12] get /clear/app2
zjh

Crear nodo y establecer tipo

  /**
     * 创建节点:create 持久 临时 顺序 数据
     * 1.基本创建
     * 2.创建节点 带有数据
     * 3.设置节点的类型
     * 4.创建多级节点 /app1/p1
     */
    @Test
    public void testCreate3(){
        // 3.设置节点的类型
        // 默认类型:持久化
        try {
         // 创建临时节点
            String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3","zjh".getBytes());
            System.out.println(path);
            
        } catch (Exception e) {
            System.out.println("创建失败~~~");
            e.printStackTrace();
        }
        
        while (true){
            // 因为是测试,所以弄个死循环保持客户端不断开连接
        }
}

prueba

Después de ejecutar la prueba en idea, verá la siguiente información en zkclient

[zk: localhost:2181(CONNECTED) 29] ls /clear
[app1, app2, app3]

Luego, interrumpimos la idea y vemos la siguiente información en zkclient, app3 desaparece

[zk: localhost:2181(CONNECTED) 29] ls /clear
[app1, app2, app3]
[zk: localhost:2181(CONNECTED) 30] ls /clear
[app1, app2]

Crear nodos multinivel

/**
     * 创建节点:create 持久 临时 顺序 数据
     * 1.基本创建
     * 2.创建节点 带有数据
     * 3.设置节点的类型
     * 4.创建多级节点 /app1/p1
     */
    @Test
    public void testCreate4(){
    
    
        // 4.创建多级节点 /app4/p1
        try {
    
    
            // 创建临时节点
            String path = client.create().creatingParentContainersIfNeeded().forPath("/app4/p1","zjh".getBytes());
            System.out.println(path);
        } catch (Exception e) {
    
    
            System.out.println("创建失败~~~");
            e.printStackTrace();
        }
        while (true){
    
    
            // 因为是测试,所以弄个死循环保持客户端不断开连接
        }
    }

prueba

Después de ejecutar la prueba en idea, verá la siguiente información en zkclient, que indica que el directorio multinivel se creó correctamente.

[zk: localhost:2181(CONNECTED) 17] ls /clear/app4
[p1]

Resumir

  • Crear nodo: create().forPath(" ")
  • Cree un nodo con datos: create().forPath("",data)
  • Establezca el tipo de nodo: create().withMode().forPath("",data)
  • Cree nodos de varios niveles: create().creatingParentContainersIfNeeded().forPath("",data)

Eliminar nodo

Eliminar un solo nodo

 /**
     * 删除节点
     */
    @Test
    public void testDelete(){
        try {
             // 删除单个节点
            client.delete().forPath("/app1");
        } catch (Exception e) {
            System.out.println("删除失败~~~");
            e.printStackTrace();
        }
    }

prueba

Después de ejecutar la prueba en idea, si el nodo correspondiente app1 no se puede consultar en zkclient, significa que la eliminación se realizó correctamente.

[zk: localhost:2181(CONNECTED) 5] ls /clear
[app2, app4]

Eliminar nodo con nodos secundarios

/**
     * 删除节点
     */
    @Test
    public void testDelete2(){
    
    
        // 删除带子节点的节点
        try {
    
    
            client.delete().deletingChildrenIfNeeded().forPath("/app4");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

prueba

Después de ejecutar la prueba en idea, si el nodo correspondiente app4 no se puede consultar en zkclient, significa que la eliminación se realizó correctamente.

[zk: localhost:2181(CONNECTED) 14] ls /clear
[app2]

Debe eliminarse correctamente

Para evitar la inestabilidad de la red. La esencia es volver a intentarlo si la eliminación no se realiza correctamente.

    /**
     * 删除节点
     */
    @Test
    public void testDelete3(){
        // 必须成功删除节点
        try {
            client.delete().guaranteed().forPath("/app2");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

prueba

Después de ejecutar la prueba en idea, si el nodo correspondiente app2 no se puede consultar en zkclient, significa que la eliminación se realizó correctamente.

[zk: localhost:2181(CONNECTED) 17] ls /clear
[]

llamar de vuelta

/**
     * 删除节点
     */
    @Test
    public void testDelete4(){
    
    
        try{
    
    
            // 回调
            client.delete().guaranteed().inBackground(new BackgroundCallback() {
    
    
                @Override
                public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
    
    
                    System.out.println("我被删除了~");
                    System.out.println(event);
                }
            }).forPath("/app1");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

prueba

Después de ejecutar la prueba en idea, la información relevante se imprime en la consola. Si el nodo correspondiente app1 no se puede consultar en zkclient, significa que la eliminación se realizó correctamente. Cuando no hay ningún nodo en el espacio de nombres, el espacio de nombres también se eliminará.

[zk: localhost:2181(CONNECTED) 28] ls /
[hbase, zookeeper]

Resumir

  • Eliminar nodo: eliminar eliminar todo
  • 1. Eliminar un solo nodo: eliminar().forPath("")
  • 2. Eliminar nodos con nodos secundarios: eliminar().deletingChildrenIfNeeded().forPath("")
  • 3. El nodo debe eliminarse correctamente: para evitar la fluctuación de la red. La esencia es volver a intentarlo: eliminar().guaranteed().forPath("");
  • 4.Devolución de llamada: en segundo plano

Modificar nodo

cambiar los datos

   /**
     * 修改数据
     * 1.修改数据
     * 2.根据版本修改
     */
    @Test
    public void testSet(){
    
    
        try {
    
    
            // 修改数据
            client.setData().forPath("/app1","miss you".getBytes());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

prueba

Después de ejecutar la prueba en idea, verá la siguiente información en zkclient, que indica que la modificación se realizó correctamente.

[zk: localhost:2181(CONNECTED) 25] get /clear/app1
miss you

Modificar según versión

    /**
     * 修改数据
     * 1.修改数据
     * 2.根据版本修改
     */
    @Test
    public void testSeForVersion() throws Exception {
        Stat status = new Stat();
        // 查询节点状态信息
        client.getData().storingStatIn(status).forPath("/app1");

        int version = status.getVersion(); // 查询出来的结果
        System.out.println(version);
        // 根据版本修改
        try {
            client.setData().withVersion(version).forPath("/app1","dont".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

prueba

Después de ejecutar la prueba en idea, verá la siguiente información en zkclient, que indica que la modificación se realizó correctamente.

[zk: localhost:2181(CONNECTED) 29] get /clear/app1
dont

Resumir

cambiar los datos

  • Modificar datos setData().forPath("", datos);

  • Modifique setData().withVersion(version information).forPath("", data) según la versión;

Nodo de consulta

Consultar datos del nodo

   /**
     * 查询节点:
     * 1.查询数据:get
     * 2.查询子节点:ls
     * 3.查询节点状态信息:ls -s
     */
    @Test
    public void testGet1() {
    
    
        try {
    
    
            // 查询节点数据
            byte[] data = client.getData().forPath("/app1");  // 路径前必须加/ 否则报错 Path must start with / character
            System.out.println(new String(data));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

Consultar nodos secundarios

 /**
     * 查询节点:
     * 1.查询数据:get
     * 2.查询子节点:ls
     * 3.查询节点状态信息:ls -s
     */
    @Test
    public void testGet2() {
    
    
        List<String> path = null;
        try {
    
    
            // 查询子节点 ls
            path = client.getChildren().forPath("/app4");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        for (String p :path) {
    
    
            System.out.println(p);
        }
    }

Consultar información de estado del nodo

  /**
     * 查询节点:
     * 1.查询数据:get
     * 2.查询子节点:ls
     * 3.查询节点状态信息:ls -s
     */
    @Test
    public void testGet3() {
    
    
        Stat status = new Stat();
        /**
         * 如下是各种状态信息,需要时直接调用 get set方法即可
         * public class Stat implements Record {
         *     private long czxid;
         *     private long mzxid;
         *     private long ctime;
         *     private long mtime;
         *     private int version;
         *     private int cversion;
         *     private int aversion;
         *     private long ephemeralOwner;
         *     private int dataLength;
         *     private int numChildren;
         *     private long pzxid;
         */
        System.out.println(status);
        // 查询节点状态信息:ls -s
        try {
    
    
            client.getData().storingStatIn(status).forPath("/app1");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println(status);  // 仅用于测试,真实项目中不会是这个用发
    }

Resumir

  • 1. Consultar datos: obtener getData().forPath("")
  • 2. Consultar nodos secundarios: ls getChildren().forPath("")
  • 3. Consultar información sobre el estado del nodo: ls -s getData().storingStatIn(status object).forPath("");

7 Ver monitoreo de eventos

  • Zookeeper permite a los usuarios registrar algunos observadores en nodos designados y, cuando se activan algunos eventos específicos, el servidor Zookeeper notificará a los clientes interesados ​​sobre el evento. Este mecanismo es una característica importante del servicio de coordinación distribuida de capacitación de Zookeeper.

  • El mecanismo Watcher se introduce en Zookeeper para implementar la función de publicación / suscripción, que permite que varios suscriptores monitoreen un objeto al mismo tiempo. Cuando el estado de un objeto cambia, se notificará a todos los suscriptores.

  • Zookeeper admite de forma nativa el monitoreo de eventos mediante el registro de Watchers, pero su uso no es particularmente conveniente y requiere que los desarrolladores registren Watchers repetidamente, lo cual es engorroso.

  • Curator presenta Cache para monitorear los eventos del lado del servidor de Zookeeper

  • Zookeeper ofrece tres tipos de Vigilantes:

1) NodeCache: simplemente monitoree un nodo específico

2) PathChildrenCache: monitorear los nodos secundarios de un ZNode

3) TreeCache: puede monitorear todos los nodos en todo el árbol, similar a la combinación de PathChildrenCache y PathChildrenCache

7.1 Demostración de NodeCache

Las operaciones de establecimiento y cierre de conexiones son las mismas que las anteriores y no se repetirán aquí.

/**
 * 演示 NodeCache:给指定的节点注册监听器
 */
@Test
public void testNodeCache() {
    
    
    // 1.创建NodeCache对象
    NodeCache nodeCache = new NodeCache(client, "/app1");
    // 2.注册监听
    /*    nodeCache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                System.out.println("节点变化了~");
            }
        });*/
    nodeCache.getListenable().addListener(() -> System.out.print("节点变化了~"));
    // 3.开启监听 如果设置为true,则开启监听,加载缓冲数据
    try {
    
    
        nodeCache.start(true);
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }

    while (true) {
    
    
        // 因为是测试,所以弄个死循环保持客户端不断开连接
    }
}

prueba

Cuando realizamos cambios en el nodo /clear/app1 (agregar, eliminar, modificar nodos) en el cliente zookeeper, veremos la consola IEDA imprimiendo los cambios del nodo ~

7.2 Demostración de PathChildrenCache

/**
 * 演示 PathChildrenCache 监听某个节点的所有孩子节点们
 */
@Test
public void testPathChildrenCache() throws Exception {
    
    
    // 1.创建监听对象
    PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/app2",true);
    // 2.判断监听器
    pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
    
    
        @Override  // 可用lambda表达式简化
        public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
    
    
            System.out.println("子节点变化了~");
            System.out.println(event);
            // 监听子节点的数据变更,并且拿到变更后的数据
            // 1)获取类型
            PathChildrenCacheEvent.Type type = event.getType();
            // 2)判断类型是否是update
            if(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
    
    
                System.out.println("子节点的数据变更了~");
                // 第一个getData()应该是获取子节点
                // 第二格getData()应该是获取子节点的数据
                byte[] data = event.getData().getData();
                System.out.println(new String(data));  // 字节数组以ASCII码形式输出

            }
        }
    });
    // 3.开启监听
    pathChildrenCache.start();
    while (true) {
    
    
        // 因为是测试,所以弄个死循环保持客户端不断开连接
    }
}

7.3 Demostración de TreeCache

 /**
     * 演示 TreeCache 监听某个节点自己和其子节点们
     */
    @Test
    public void testTreeCache() {
    
    
        // 1.创建监听器
        TreeCache treeCache = new TreeCache(client, "/app2");
        // 2.注册监听
        treeCache.getListenable().addListener(new TreeCacheListener() {
    
    
            @Override
            public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
    
    
                System.out.println("节点变化了~~");
                System.out.println(event);
            }
        });
        // 3.开启监听
        try {
    
    
            treeCache.start();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        while (true) {
    
    
            // 因为是测试,所以弄个死循环保持客户端不断开连接
        }
    }

8 cerradura distribuida

  • Cuando desarrollamos aplicaciones independientes e implicamos sincronización, utilizamos métodos sincronizados o de bloqueo para resolver el problema de la sincronización de código entre subprocesos múltiples. En este momento, los subprocesos múltiples se ejecutan bajo la misma JVM sin ningún problema.
  • Pero cuando nuestra aplicación funciona en un clúster distribuido, que es un entorno de trabajo bajo múltiples JVM, los problemas de sincronización no se pueden resolver mediante bloqueos de subprocesos múltiples entre JVM.
  • Por lo tanto, se necesita un mecanismo de bloqueo más avanzado para manejar el problema de sincronización de datos entre procesos entre máquinas , es decir, bloqueo distribuido.

8.1 Principio de bloqueo distribuido de Zookeeper

Idea central : cuando el cliente quiere adquirir un candado, crea un nodo y, después de usar el candado, elimina el nodo.

Cuando asumimos que hay un nodo /lock debajo del nodo raíz /

1) Cuando el cliente adquiere el bloqueo, se crea un nodo de secuencia temporal debajo del nodo de bloqueo .

2) Luego obtenga todos los nodos secundarios bajo el bloqueo. Después de que el cliente obtenga todos los nodos secundarios, si descubre que el nodo secundario que creó tiene el número de secuencia más pequeño, se considera que el cliente ha obtenido el bloqueo. (Es decir, se requiere una prioridad pequeña). Después de usar el bloqueo, el nodo se eliminará.

3) Si descubre que el nodo que creó no es el más pequeño entre todos los nodos secundarios del bloqueo, significa que no ha obtenido el bloqueo. En este momento, el cliente necesita encontrar el nodo que sea más pequeño que él y registre un detector de eventos para que escuche los eventos de eliminación.

4) Si se descubre que el nodo más pequeño que él ha sido eliminado, el Vigilante del cliente recibirá la notificación correspondiente y en este momento juzgará nuevamente si el nodo que creó tiene el número de secuencia más pequeño entre los nodos secundarios bloqueados. Si es así, se obtendrá el bloqueo. Si no, se obtendrá el bloqueo. Luego repita los pasos anteriores para continuar obteniendo un nodo más pequeño que usted y registrarse para el monitoreo.

8.2 Curator implementa API de bloqueo distribuido

Hay cinco esquemas de bloqueo en Curator:

InterProcessMultiLock   分布式排它锁(非可重入锁)
InterProcessMutex       分布式可重入锁排它锁
InterProcessReadWriteLock   分布式读写锁
InterProcessMultiLock		将多个锁作为单个实体管理的容器
InterProcessSemaphoreV2		共享信号量

8.3 Caja de cerradura distribuida

Simular el sistema de captura de boletos 12306

public class SaleTickets12306 implements Runnable {
    
    
    private int tickets = 10;  // 模拟的票数
    private InterProcessMutex lock;

    public SaleTickets12306(){
    
    
        // 重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("192.168.188.128:2181")
                .sessionTimeoutMs(60*1000)
                .connectionTimeoutMs(15*1000)
                .retryPolicy(retryPolicy)
                .namespace("nhk")  // 程序临时创建的命名空间
                .build();
        // 开启连接
        client.start();

        lock = new InterProcessMutex(client,"/lock");

    }

    @Override
    public void run() {
    
    
        while (true) {
    
    
            // 获得锁
            try {
    
    
                lock.acquire(3, TimeUnit.SECONDS);
                if (tickets > 0) {
    
    
                    System.out.println(Thread.currentThread().getName() + ":" + tickets--);
                    Thread.sleep(100);
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                // 释放锁
                try {
    
    
                    lock.release();
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }

        }
    }
}

public class LookTest {
    
    
    public static void main(String[] args) {
    
    
        SaleTickets12306 saleTickets12306 = new SaleTickets12306();

        // 创建售票平台
        Thread t1 = new Thread(saleTickets12306,"携程");
        Thread t2 = new Thread(saleTickets12306,"铁友");

        t1.start();
        t2.start();
    }
}

Supongo que te gusta

Origin blog.csdn.net/weixin_56058578/article/details/132717176
Recomendado
Clasificación