01-ZooKeeper クイック スタート

1 動物園飼育員のコンセプト

Zookeeper は、Apache Hadoop プロジェクトの下のサブプロジェクトであり、ツリー ディレクトリ サービスです。

Zookeeper は動物園の飼育員と訳され、Hadoop (象)、Hive (蜂)、Pig (豚) を管理する管理者のことです。略してZK

Zookeeper は、分散アプリケーションのサービスを調整する分散オープン ソースApache プロジェクトです。

Zookeeper が提供する主な機能は次のとおりです。

  • 構成管理 (構成センターとして)
  • 分散ロック
  • クラスター管理(登録センターとして)

Zookeeper のデータ構造

Zookeeper データ モデルの構造は Unix ファイル システムに非常に似ており、全体としてツリーとみなすことができ、各ノードは ZNode と呼ばれます。ZNode はデフォルトで 1MB のデータを保存でき各 ZNode はパスによって一意に識別できます。

1.1 動物園飼育員のアプリケーション シナリオ

提供されるサービスには、統合ネーミング サービス、統合構成管理、統合クラスター管理、動的なオンラインおよびオフライン サーバー ノード、ソフト ロード バランシングなどが含まれます。

1) 統​​一ネーミングサービス

分散環境では、多くの場合、識別しやすいようにアプリケーション/サービスに統一した名前を付ける必要があります。

例: IP は覚えにくいですが、ドメイン名は覚えやすいです

2) 一元的な構成管理

(1) 分散環境では、構成ファイルの同期が非常に一般的です。

1. 一般に、kafka クラスターなど、クラスター内のすべてのノードの構成情報が一貫している必要があります。

2. 構成ファイルを変更した後、構成ファイルを各ノードに迅速に同期したいと考えています。

(2) 構成管理はzookeeperで実現可能

1. 構成情報をzookeeper上のZNodeに書き込むことができます

2. 各クライアントサーバーはこの ZNode を監視します

3. ZNode 内のデータが変更されると、Zookeeper は各クライアント サーバーに通知します。

3) クラスタの一元管理

(1) 分散環境では、各ノードの状態をリアルタイムに把握する必要があります。

1. ノードのリアルタイムのステータスに応じて、いくつかの調整を行うことができます。

(2) Zookeeper はノードのステータス変化を監視できます

1. Zookeeper 上の ZNode にノード情報を書き込むことができます

2. この ZNode を監視して、リアルタイムのステータス変化を取得します

4) サーバーはオンラインおよびオフラインで動的に動作します

クライアントは、サーバーの上流および下流の変更をリアルタイムで把握できます。

(1) サーバー起動時に情報を登録します(すべての一時ノードが作成されます)

(2) 現在のオンラインサーバーリストを取得し、監視登録を行う

(3) サーバーノードがオフラインになった場合

(4) サーバーノードのオンライン・オフライン通知

(5) 再度サーバー一覧を取得し、監視登録を行う

5) ソフトロードバランシング

Zookeeper の各サーバーへのアクセス数を記録し、アクセス数が最も少ないサーバーに最新のクライアント リクエストを処理させます。

1.2 飼​​育員の動作メカニズム

Zookeeper をデザイン パターンの観点から理解すると、オブザーバー パターンに基づいて設計された分散サービス管理フレームワークであり、誰もが関心を持つデータの保存と管理を担当し、オブザーバーの登録を受け付けます。データ変更、動物園飼育員どの観察者が動物園飼育員に登録されているかを通知し、それに応じて対応する責任があります。

1.3 Zookeeper の設計目的

設計の目的は主に次の側面に反映されています。

1) 一貫性: クライアントがどのサーバーに接続しても、同じビューが表示されます。

2) リアルタイム: Zookeeper データはメモリに保存されるため、高スループットと低遅延を実現できます。

3) 信頼性: Zookeeper サービスを構成するサーバーは、他のサーバーの存在について相互に認識している必要があります。

4) 順序性: たとえば、動物園の管理者は各更新操作にバージョン番号を割り当てますが、このバージョン番号は一意で順序付けられています。

5) 原子性: Zookeeper クライアントがデータを読み取るとき、成功または失敗の 2 つの状態のみがあり、データの一部だけが読み取られる状況はありません (つまり、成功または失敗のいずれであっても、操作は中断されません)。失敗した)

1.4 Zookeeper システムのモデル

Zookeeper のシステム モデルにはサーバーとクライアントが含まれます

1) クライアントは、Zookeeper クラスター内の任意のサーバーに接続できます。クライアントとサーバーは TCP 経由で接続を確立し、主にリクエストとハートビート メッセージの送信、応答の取得、イベントの監視を行います。

2) クライアントとサーバー間の TCP 接続が中断された場合、クライアントは自動的に他のサーバーへの接続を試みます。

3) クライアントが初めてサーバーに接続すると、サーバーはクライアントのセッションを確立します。クライアントが別のサーバーに接続すると、新しいサーバーはクライアントのセッションを再確立します。

1.5 Zookeeper クラスターの役割

Zookeeper クラスター サービスには 3 つの役割があります

デフォルトでは、リーダー (リーダー) と複数のフォロワーで構成されるクラスター (フォロワー) が存在します。

リーダー リーダー:

1. トランザクションリクエストの処理

2. クラスタ内の各サーバーのスケジューラ

フォロワー フォロワー:

1. クライアントの非トランザクション要求を処理し、トランザクション要求をリーダー サーバーに転送します。

2. リーダー選挙の投票に参加する (サーバー ID サイズに基づく選挙)

オブザーバー オブザーバー:

1. クライアントの非トランザクション要求を処理し、非リーダーにトランザクション要求を発行します。

2.リーダー選挙の投票に参加しない

オブザーバーは、zookeeper3.3.0 バージョンからの新しいロールです。単一の Zookeeper クラスター内のノードの数が増加すると、より多くのクライアントをサポートするために、より多くのサーバーを追加する必要がありますが、サーバーの数が増えると、投票フェーズに時間がかかりすぎるため、クラスターのパフォーマンスに影響します。クラスターのスケーラビリティを強化し、高いデータ スループットを確保するために、Observer が導入されています。

1.6 Zookeeper の機能

  • クラスター内のノードの半分以上が存続している限り、Zookeeper クラスターは正常に機能します。したがって、Zookeeper は奇数のサーバーをインストールするのに適しています。

  • グローバル データの一貫性**: 各サーバーはデータの同一のコピーを保存します**。クライアントがどのサーバーに接続しても、データは一貫しています。

  • 更新リクエストは順番に実行され、同じクライアントからの更新リクエストは発生した順序で順番に実行されます。

  • データ更新の原子性: データ更新は成功するか失敗します。

  • リアルタイム、特定の時間範囲内で、クライアントは最新のデータを読み取ることができます

2 飼育員サービス構築

公式ウェブサイト

https://zookeeper.apache.org

2.1 Zookeeper スタンドアロン モード

1) 環境の準備

Zookeeper サーバーは Java で作成され、JVM 上で実行され、JDK7 以降にインストールする必要があります。

2) アップロードと解凍

ダウンロードした Zookeeper 圧縮パッケージを /opt/software ディレクトリ**にアップロードします (以下の操作はすべて 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)zoo.cfgを設定する

/opt/software/zookeeper/apache-zookeeper-3.6.1-bin/conf ディレクトリ内のzoo_sample.cfg ファイルの名前をzoo.cfg に変更します。

[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) 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) Zookeeper を開始する

Zookeeper の環境変数が設定されているため、Zookeeper の bin ディレクトリに入る必要はありません。

# 若未配置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

Zookeeper の起動を表示する

Zookeeper が正常に起動したかどうかを確認するには 2 つの方法があります。

1. 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. jps コマンドを使用して、Zookeeper サービス プロセス QuorumPeerMain を表示します(このプロセスは、Zookeeper クラスターの起動入り口です)。

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

2.1.1 設定パラメータの解釈

Zookeeper の設定ファイルzoo.cfgのパラメータの意味は次のとおりです。

# 通信心跳时间,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 の完全分散

飼育員クラスターの概要

  • リーダー選挙:

Serverid: サーバーID

たとえば、3 つのサーバーがあり、それぞれ 1、2、3 という番号が付けられています。数値が大きいほど、選択アルゴリズムの重みが大きくなります。

  • Zxid: データID

サーバーに保存される最大のデータ ID。値が大きいほど、データはより新しいものになります。

  • リーダー選出プロセス中に、動物園の飼育員が過半数以上の票を獲得した場合、この動物園の飼育員がリーダーになることができます。

建築について知っておくべきこと

実際のクラスターはさまざまなサーバーにデプロイされますが、ここでは仮想マシンを使用して 3 台のサーバーをシミュレートし、完全な分散をシミュレートします。もちろん、仮想マシンを使用して擬似分散クラスターを構築することもできます。次に、ポートに基づいて区別します

完全分散と擬似分散の違いは、完全分散は複数のマシン上に構築され、IP に基づいて区別され、擬似分散は 1 台のマシン上に構築され、IP とポートに基づいて区別されることです。

クラスター計画

クラスター内のノードの半分以上が存続している限り、Zookeeper クラスターは正常に機能できるため、ここではデモンストレーションのために 3 台のサーバーを使用します。

kk01	192.168.188.128
kk02	192.168.188.129
kk03    192.168.188.130

1) 準備作業

jdkとzookeeperをインストールし、サーバーにアップロードします(これらはスタンドアロンモードで実装されているため、ここでは省略します)

2) /opt/software/zookeeper-3.6.1/zkData に内容 1 の myid ファイルを作成します。

このファイルには、各サーバーの ID が記録されます(myid は 1 ~ 255 の整数である必要があり、myid の内容はクラスター内で一意である必要があります)。

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

3) /opt/software/zookeeper-3.6.1/conf ディレクトリ内のzoo.cfg ファイルを変更します。

[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) /opt/software/zookeeper-3.6.1/ を仮想マシン kk02 および 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) 仮想マシン kk02 と kk03 の /opt/software/zookeeper-3.6.1/zkData ディレクトリにある myid ファイルをそれぞれ変更します (内容はそれぞれ 2 と 3)。

# 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) 仮想マシンkk01の環境設定ファイルをkk02、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) 次のコマンドを使用して、kk01、kk02、および kk03 で Zookeeper サーバーをそれぞれ起動します。

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

3) 3 つの仮想マシンの Zookeeper サーバーのステータスを確認するには、コマンドは次のとおりです。

# 查看进程
[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

起動ステータスを確認すると上記の結果が得られ、「モード: フォロワー」「モード: リーダー」と表示され、Zookeeper クラスターが正常に確立されたことがわかります。

2.2.1 ワンクリックでZookeeperクラスタを起動および停止するシェルスクリプト

Zookeeper クラスターの起動とシャットダウンには、各仮想マシンの起動とシャットダウンが必要であり、効率的ではありません。実際の作業では多くのサーバーが使用されますが、Zookeeper サーバーの管理を容易にするために、ワンクリックで Zookeeper サーバー クラスターを起動および停止する xzk.sh スクリプトを作成できます。

仮想マシン kk01 の /usr/local/bin ディレクトリに、次のような xzk.sh スクリプト ファイルを書き込みます。

#!/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

xzk.sh スクリプト所有者に実行権限を追加する

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

xzk.sh スクリプトの start コマンドと stop コマンドを使用して、kk01 上の kk02 と kk03 を同時にシャットダウンします。

xzk.sh start
xzk.sh stop

スクリプトを理解しやすくする

# 在虚拟机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 

上記のスクリプトでは次のエラーが発生する場合があります

[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 クラスタ例外シミュレーション(選出シミュレーション)

1) まず、サーバーがハングアップした場合に何が起こるかをテストします。

kk03 でサーバーを停止し、kk01 と kk02 を観察して、変化がないことを確認します。

# 在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

このことから、3 ノードのクラスターでは、スレーブ サーバーがハングアップしてもクラスターは正常であると結論付けることができます。

2) kk01 のサーバーもハングアップした後、kk02 (メイン サーバーの場所) を確認したところ、kk02 も動作を停止していることがわかりました。

# 在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.

このことから、3 ノード クラスタでは両方のスレーブ サーバーがダウンし、メイン サービスが実行できないと結論付けることができます。実行可能なマシンの数がクラスタ数の半分を超えないためです。

3) kk01 でサーバーを再度起動すると、kk02 のサーバーが再び正常に動作し始め、依然としてリーダーであることがわかりました。

# 在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) kk03 でサーバーを起動し、kk02 でサーバーをシャットダウンし、kk01 と 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

新しいリーダーが現れたことがわかりました

このことから、クラスター内のメイン サーバーがハングアップすると、クラスター内の他のサーバーが自動的に状態を選択し、新しいリーダーを生成すると結論付けられます。

5) kk02 でサーバーを再度起動するとどうなりますか。kk02 のサーバーが再び新しいリーダーになりますか?

# 在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

kk02 のサーバーが再起動され、kk03 のサーバーが引き続きリーダーになります。

このことから、zk クラスターのリーダーは譲歩しないと結論付けることができます。

3 動物園飼育員の選出メカニズム

動物園飼育員の選出メカニズムは半数のメカニズムであり、投票の過半数が可決された場合に可決されます。

1) 選挙ルールを初めて開始します。

  • 投票の半分以上が投じられた場合、より大きなサーバー ID を持つサーバーが勝ちます。

2) 2 回目の選挙ルールを開始します。

  • EPOCH (ピリオド) が大きい方が直接勝ちます。
  • EPOCH は同じで、トランザクション ID が大きい方が勝ちです。
  • トランザクション ID が同じ場合、サーバー ID が大きい方が優先されます。

詳細は次のとおりです。

3.1 Zookeeper 選出メカニズム (初回起動)

クライアントの各書き込み操作にはトランザクション ID (ZXID) があります。

SID:サーバー IDZookeeper クラスターが配置されているマシンを一意に識別するために使用されます。各マシンは重複できず、myid と一貫性があります。

ZXID: トランザクション ID。ZXID は、サーバーのステータスの変化を識別するために使用されるトランザクション ID ですある時点で、クラスター内の各マシンの ZXID が完全に一致しないことがありますが、これは、クライアントの「更新リクエスト」に対する Zookeeper サーバーの処理ロジックに関連しています。

エポック:各リーダー用語のコード名リーダーが存在しない場合、同じ投票ラウンドの論理クロック値は同じになります。このデータは投票が行われるたびに増加します。

假设zookeeper services有五台机器

Server1		Server2 	Server3 	Server4 	Server5
myid1		myid2		myid3		myid4		myid5

1) サーバー 1 が起動し、選挙が開始されます。サーバー 1 が投票を行います。この時点で、サーバーは 1 票と 1 票を持っています。十分な投票 (3 票) がない場合、選挙は完了できず、サーバー 1 のステータスは LOOKING のままになります。

2) サーバー 2 が起動し、別の選挙が開始されます。サーバー 1 と 2 はそれぞれ自分に投票し、投票情報を交換します**: このとき、サーバー 1 は、サーバー 2 の myid が現在投票しているサーバー (サーバー 1) よりも大きいことを発見し、投票を推奨サーバーに変更します。 2**。このとき、サーバー1の投票数は0、サーバー2の投票数は2であり、半分以上がないと選挙は完了せず、サーバー1、2のステータスはLOOKINGのままとなります。

3) サーバー 3 が起動し、選挙が開始されます。この時点で、サーバー 1 と 2 は投票をサーバー 3 に変更します。この投票の結果: サーバー 1 は 0 票、サーバー 2 は 0 票、サーバー 3 は 3 票です。この時点では、サーバー 3 が半数以上の票を獲得しており、そのサーバーがリーダーに選出されますサーバー 1 と 2 のステータスは FOLLWERING に変更され、サーバー 3 のステータスは LEADERING に変更されます。

4) サーバー 4 が起動し、選挙が開始されます。この時点で、サーバー 1、2、および 3 は LOOKING ステータス バーを展開しており、投票情報は変更されません。投票情報を交換した結果:サーバー 3 が 3 票、サーバー 4 が 1 票です。このとき、サーバー 4 は従い、投票結果をサーバー 3 に変更し、ステータスを FOLLWERING に変更します。

5) サーバー 5 が起動し、サーバー 4 と同様に弟として機能します。

3.2 Zookeeper の選出メカニズム (最初の起動ではない)

假设zookeeper services有五台机器

Server1		Server2 	Server3 	Server4 	Server5
myid1		myid2		myid3		myid4		myid5

follwer		follwer 	leadr		follwer		follwer

1) Zookeeper クラスター内のサーバーが次の 2 つの状況に遭遇すると、選出が開始されます。

  • サーバー初期化起動
  • サーバーの実行中はリーダーとの接続を維持できません

2) サーバーがリーダー選出プロセスに入ると、現在のクラスターも次の 2 つの状態になる可能性があります。

  • クラスター内にはすでにリーダーが存在します

このようなリーダーが存在する場合、マシンがリーダーを選出する際には、現在のサーバーのリーダー情報が通知されるため、マシンはリーダーマシンとの接続を確立し、ステータスを同期するだけで済みます。

  • 確かにクラスター内にリーダーは存在しません
假设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 クライアントはデータ処理をサーバーに書き込みます

書き込みプロセスの書き込みリクエストはリーダーに直接送信されます。


					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

書き込み処理の書き込みリクエストはフォロワーに送信されます

			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 Zookeeper コマンドの操作

5.1 Zookeeper データモデル

Zookeeper はツリー ディレクトリ サービスであり、そのデータ モデルは Unix ファイル システムのディレクトリ ツリーに非常に似ており、階層構造を持っています。

ここでの各ノードはZNodeと呼ばれ、各ノードはデータ長、作成時刻、変更時刻、子ノードの数などの独自のデータとノード情報を保存します。

ノードは子ノードを持つことができ、ノードの下に少量 (1MB) のデータを保存できます。

5.2 zkノードタイプ

ノードは 4 つの主要なカテゴリに分類できます。

  • PERSISTENT永続ノード、永続ノード
  • EPHEMERAL 一時ノード-e
  • PERSISTENT_SEQUENTIAL 永続シーケンス ノード: -s
  • EPHEMERAL_SEQUENTIAL 一時シーケンス ノード: -es

永続ノード:

Zookeeper クライアントが終了した後、ノードは自動的に削除されませんZookeeper クライアントはデフォルトで永続ノードを作成します。

一時ノード:

Zookeeperクライアントが終了すると、ノードは自動的に削除されます一時ノードは子ノードを持つことができません。ユーザーは一時ノードを通じて分散サービスの開始または終了を判断できます。

シーケンスノード:

10 桁のシリアル番号ノードがノード名の末尾に自動的に追加されます。

永続シーケンス ノード:

クライアントが zk から切断した後もノードはまだ存在しますが、zk はノードに連続した番号を付けます。

一時シーケンスノード:

クライアントが ZooKeeper から切断されると、ノードは自動的に削除されますが、ZooKeeper はノードに連続した番号を付けます。

デモ

[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 サーバーは、Zookeeper クライアントおよび Zookeeper Java API と対話できます。

5.3 ZooKeeper サーバーの一般的なコマンド

仕える 注文
zkサービスを開始する ./zkServer.sh 開始
zkサービスステータスを確認する ./zkServer.sh 開始
zkサービスを停止する ./zkServer.sh 停止
zkサービスを再起動します ./zkServer.sh の再起動

5.4 Zookeeperクライアントの共通コマンド

Zookeeper ノードの権限制御

実際の運用では、複数のアプリケーションが同じ Zookeeper を使用することがよくありますが、異なるアプリケーション システムが共通のデータを使用することはほとんどありません。

このような状況を考慮して、Zookeeper では、Linux ファイル システムの権限制御と同様の、ACL (Access Control List) ポリシーを使用して権限制御を行います。Zookeeper ノードは 5 つの権限を定義します。

  • 子ノードを作成するための create 権限
  • 子ノードデータと子ノードリストを取得するための読み取り権限
  • ノードデータを更新するための書き込み権限
  • 子ノードを削除する削除権限
  • andin はノードの権限を設定します
仕える 注文
zkローカルクライアントに接続します zkCli.sh (ローカルクライアントに接続)
Zookeeper サーバーに接続する ./zkCli.sh -server [ip:port] リモートサーバーに接続する場合は、IP とポートを指定する必要があります。
切断する やめる
コマンドのヘルプを表示する ヘルプ
指定したディレクトリ内のノードを表示します ls /パス
子ノードの変更をリッスンする ls -w /パス
追加の二次情報 ls -s /パス
ノードの作成 作成/ノードパス [値]
シーケンスノードの作成 create -s /node path [値]
一時ノードの作成 create -e /node path [値] (zk クライアントは再起動またはタイムアウト時に自動的に削除されます)
ノード値を取得する /node パスを取得する
ノードのコンテンツ (つまり、ノードに保存されているデータ) の変更を監視します。 get -w /パス
追加の二次情報 get -s /パス
ノード値を設定する /node パス値を設定します
単一ノードを削除する /ノードパスを削除します
子を持つノードの削除 (再帰的削除) deleteall/ノードパス
ノードのステータスを表示する 統計/パス
ノードの詳細を表示する ls2 /node パス (非推奨) ls -s /node パス

注: Zookeeper クライアントでノードを操作するコマンドは絶対パスを使用する必要があります。

パラメータの詳細:

  • czxid: 作成したノードのトランザクションID
  • ctime: 作成時間 (1970 年以降、znode が作成されてからのミリ秒数)
  • mzxid: 最終更新トランザクションID (znodeの最終更新トランザクションのzxid)
  • mtime: 変更時間 (1970 年以降、znode が最後に変更されてからのミリ秒数)
  • pzxid: 最後に更新された子ノードリストのトランザクションID (znodeの最後に更新された子ノードのzxid)
  • cversion: 子ノードのバージョン番号 (znode 子ノード変更番号、zonde 子ノード変更番号)
  • dataversion: データのバージョン番号 (ゾンデデータ変更番号)
  • aclversion: 権限バージョン番号 (zonde アクセス制御リストの変更番号)
  • epheralOwner: 一時ノードに使用され、一時ノードのトランザクション ID を表します。永続ノードの場合は 0 (一時ノードの場合は、このゾンデの所有者のセッション ID。一時ノードでない場合は、は0です)
  • datalength: ノードに格納されているデータの長さ(znodeのデータ長)
  • numChildren: 現在のノードの子ノードの数 (znode の子ノードの数)

5.5 リスナーの原則

監視原理の詳細説明

1) まず main() スレッドが必要です

2) メインスレッドでzookeeperクライアントを作成します この時点で、ネットワーク接続を担当するスレッド(connect)とリッスンを担当するスレッド(listener)の2つのスレッドが作成されます。

3) 登録されたリスニング イベントを接続スレッド経由で飼育員に送信します。

4) 登録されたリスニング イベントを飼育員のリスナー リストに追加します。

5) Zookeeper がデータ変更またはパス変更を検出すると、このメッセージをリスナー スレッドに送信します。

6) process() メソッドがリスナー スレッドの内部で呼び出されます。

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

シーンモニタリング

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

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

デモ

# 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

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

知らせ:

  • ノードデータ変更監視は、一度登録すると一度だけ監視できます。もう一度聴きたい場合は再度登録が必要です

  • ノードのパス変更も一度登録され、一度だけ有効になります。複数回有効にしたい。複数回登録する必要がある

6 ZooKeeper Java API操作

6.1 Zookeeper ネイティブ API

1) 作成準備

kk01、kk02、および kk03 サーバー上の Zookeeper クラスター サーバーが起動していることを確認します。

関連する依存関係を 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>

次の内容を含む新しい log4j.properties ファイルを src/main/sources ディレクトリに作成します。

#############
# 输出到控制台
#############
# 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) Zookeeper クライアントの作成

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) 子ノードの作成

    @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();
        }
    }

テスト: kk01 の zk クライアントでのノードの作成を確認します。

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

4) 子ノードを取得し、子ノードの変更をリッスンします。

@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);
    }

テスト: kk01 の zk クライアントの下にサブノード app1 および app2 を作成して、変更/テスト ノードを表示します。

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

アイデア コンソールは次の情報を出力します。

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

5) 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

Zookeeper は、開発者がクライアント プログラミングを実行し、ニーズに応じてサーバー上のデータを操作できるようにする Java API を提供します。

開発環境を構成する

IDEA の pom.xml ファイルの内容を次のように変更します。

<!-- 导入以下依赖坐标 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 キュレーターの紹介

Curator は、Apache Zookeeper の Java クライアント ライブラリです。

共通の Zookeeper Java API:

  • ネイティブJava API
  • ZKCクライアント
  • キュレーター

Curator プロジェクトの目標は、Zookeeper クライアントの使用を簡素化することです。

Curator は元々 Netflix によって開発され、後に Apache Foundation に寄贈され、現在は Apache のトップレベル プロジェクトとなっています。

6.2.2 Curator APIの共通操作

準備

関連する依存関係を 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>

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

接続を確立する

2 番目の接続方法を使用することをお勧めします

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();
        }
    }
}

ノードの追加

基本的な作成

 /**
     * 创建节点: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();
        }
 }
    

テスト

idea でテストを実行すると、zkclient に次の情報が表示されます。

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

データを含むノードを作成する

 /**
     * 创建节点: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();
        }
    }

テスト

idea でテストを実行すると、zkclient に次の情報が表示されます。

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

ノードを作成してタイプを設定する

  /**
     * 创建节点: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){
            // 因为是测试,所以弄个死循环保持客户端不断开连接
        }
}

テスト

idea でテストを実行すると、zkclient に次の情報が表示されます。

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

次に、アイデアを中断して、zkclient に次の情報が表示され、app3 が消えます。

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

マルチレベルノードの作成

/**
     * 创建节点: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){
    
    
            // 因为是测试,所以弄个死循环保持客户端不断开连接
        }
    }

テスト

idea でテストを実行すると、zkclient に次の情報が表示され、マルチレベル ディレクトリが正常に作成されたことがわかります。

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

要約する

  • ノードの作成: create().forPath(" ")
  • データを含むノードを作成します: create().forPath("",data)
  • ノードのタイプを設定します: create().withMode().forPath("",data)
  • マルチレベル ノードの作成: create().creatingParentContainersIfNeeded().forPath("",data)

ノードの削除

単一ノードを削除する

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

テスト

idea でテストを実行した後、対応するノード app1 が zkclient でクエリできない場合は、削除が成功したことを意味します。

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

子ノードを含むノードを削除する

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

テスト

idea でテストを実行した後、対応するノード app4 が zkclient でクエリできない場合は、削除が成功したことを意味します。

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

正常に削除される必要があります

ネットワークのジッターを防ぐため。重要なのは、削除が成功しなかった場合に再試行することです。

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

テスト

idea でテストを実行した後、対応するノード app2 が zkclient でクエリできない場合は、削除が成功したことを意味します。

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

折り返し電話

/**
     * 删除节点
     */
    @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();
        }
    }

テスト

idea でテストを実行すると、関連情報がコンソールに表示されます。対応するノード app1 が zkclient でクエリできない場合は、削除が成功したことを意味します。名前空間にノードがない場合は、名前空間も削除されます。

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

要約する

  • ノードの削除: deletete deleteall
  • 1. 単一ノードを削除します: delete().forPath("")
  • 2. 子ノードを持つノードを削除します: delete().deletingChildrenIfNeeded().forPath("")
  • 3. ネットワークのジッターを防ぐために、ノードは正常に削除される必要があります。本質は再試行することです: delete().guaranteed().forPath("");
  • 4.コールバック: バックグラウンド

ノードの変更

データを変更する

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

テスト

idea でテストを実行すると、変更が成功したことを示す次の情報が zkclient に表示されます。

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

バージョンに応じて変更する

    /**
     * 修改数据
     * 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();
        }
    }

テスト

idea でテストを実行すると、変更が成功したことを示す次の情報が zkclient に表示されます。

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

要約する

データを変更する

  • データを変更します setData().forPath("", data);

  • setData().withVersion(バージョン情報).forPath("", data)をバージョンに応じて変更します。

クエリノード

ノードデータのクエリ

   /**
     * 查询节点:
     * 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();
        }
    }

子ノードのクエリ

 /**
     * 查询节点:
     * 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);
        }
    }

ノードステータス情報のクエリ

  /**
     * 查询节点:
     * 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);  // 仅用于测试,真实项目中不会是这个用发
    }

要約する

  • 1. データのクエリ: getData().forPath("") を取得します。
  • 2. 子ノードをクエリします: ls getChildren().forPath("")
  • 3. ノードのステータス情報をクエリします。 ls -s getData().storingStatIn(status object).forPath("");

7 監視イベント監視

  • Zookeeper を使用すると、ユーザーは指定されたノードにいくつかのウォッチャーを登録でき、特定のイベントがトリガーされると、Zookeeper サーバーは関心のあるクライアントにイベントを通知します。このメカニズムは、Zookeeper トレーニング分散調整サービスの重要な機能です。

  • Zookeeper では、パブリッシュ/サブスクライブ機能を実装するために Watcher メカニズムが導入されており、複数のサブスクライバーが同時にオブジェクトを監視できるようになり、オブジェクト自体のステータスが変化すると、すべてのサブスクライバーに通知されます。

  • Zookeeper はウォッチャーを登録することでイベント監視をネイティブにサポートしていますが、その使用は特に便利ではなく、開発者はウォッチャーを繰り返し登録する必要があり、面倒です。

  • Curator が Zookeeper のサーバー側イベントを監視するための Cache を導入

  • Zookeeper は 3 種類の Watcher を提供します。

1) NodeCache: 特定のノードを監視するだけ

2) PathChildrenCache: ZNode の子ノードを監視します。

3) TreeCache: PathChildrenCache と PathChildrenCache の組み合わせと同様に、ツリー全体のすべてのノードを監視できます。

7.1 NodeCache のデモ

接続の確立と終了の操作は上記と同じなので、ここでは繰り返しません。

/**
 * 演示 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) {
    
    
        // 因为是测试,所以弄个死循环保持客户端不断开连接
    }
}

テスト

Zookeeper クライアントで /clear/app1 ノードに変更を加えると (ノードの追加、削除、変更)、IEDA コンソールにノードの変更が出力されるのがわかります~

7.2 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 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 分散ロック

  • スタンドアロン アプリケーションを開発して同期を行う場合、マルチスレッド間のコード同期の問題を解決するために synchronized メソッドまたは Lock メソッドを使用しますが、このとき、マルチスレッドは同じ JVM 上で問題なく実行されます。
  • しかし、アプリケーションが分散クラスターで動作する場合、アプリケーションは複数の JVM 下の作業環境に属し、JVM 間のマルチスレッド ロックでは同期の問題を解決できません。
  • したがって、マシン間のプロセス間のデータ同期の問題を処理するには、より高度なロック メカニズム、つまり分散ロックが必要です。

8.1 Zookeeper の分散ロック原理

中心的なアイデア: クライアントがロックを取得したい場合、ノードを作成し、ロックを使用した後、ノードを削除します。

ルートノード / の下に /lock ノードがあると仮定すると、

1) クライアントがロックを取得すると、ロック ノードの下に一時シーケンスノードが作成されます。

2) 次に、ロックの下にあるすべての子ノードを取得します。クライアントがすべての子ノードを取得した後、作成した子ノードのシーケンス番号が最小であることが判明した場合、クライアントはロックを取得したと見なされます。(つまり、小さい優先度が必要です) ロックが使用された後、ノードは削除されます。

3) 作成したノードがロックのすべての子ノードの中で最小でないことがわかった場合、それはロックを取得できていないことを意味します。このとき、クライアントは自分より小さいノードを見つける必要があります。削除イベントをリッスンするためにイベント リスナーを登録します。

4) 自分より小さいノードが削除されたことが判明した場合、クライアントの Watcher はその通知を受け取りますが、このとき、自身が作成したノードがロック子ノードの中で最も小さいシーケンス番号を持つかどうかを再度判断します。存在する場合はロックが取得され、そうでない場合はロックが取得されます。その後、上記の手順を繰り返して、引き続き自分より小さいノードを取得し、監視に登録します。

8.2 Curator は分散ロック API を実装します

Curator には 5 つのロック スキームがあります。

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

8.3 分散ロックの場合

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();
    }
}

おすすめ

転載: blog.csdn.net/weixin_56058578/article/details/132717176