BigData~02:Hadoop基礎(chǔ)01 ~ ZooKeeper

...

一、相關(guān)概念

  1. 中間件:為分布式系統(tǒng)提供協(xié)調(diào)服務(wù)的組件,如專門用于計(jì)算服務(wù)的機(jī)器就是一個(gè)計(jì)算型中間件,還有專門用于存儲(chǔ)的機(jī)器就是存儲(chǔ)中間件等等;
  2. 分布式系統(tǒng):把一套系統(tǒng)按照業(yè)務(wù)進(jìn)行橫向拆分,不同業(yè)務(wù)階段的處理放在不同的計(jì)算機(jī)上,由這些計(jì)算機(jī)組成了一套業(yè)務(wù)處理鏈,那么這個(gè)業(yè)務(wù)處理鏈就是一個(gè)分布式系統(tǒng);
    • 內(nèi)部的每臺(tái)計(jì)算機(jī)都可以進(jìn)行通信(rpc/rest);
    • 可以這樣理解:加入我們的系統(tǒng)目前就在一個(gè)項(xiàng)目中,橫向拆分就是把MVC層拆出來作為一個(gè)模塊節(jié)點(diǎn)A,把Service層拆分出來當(dāng)做節(jié)點(diǎn)B,把DAO層拆分出來當(dāng)做節(jié)點(diǎn)C,那么節(jié)點(diǎn)A可以部署在服務(wù)器a上,節(jié)點(diǎn)B部署在服務(wù)器b上,節(jié)點(diǎn)C部署在節(jié)點(diǎn)c上;運(yùn)行的時(shí)候客戶端的請(qǐng)求發(fā)送到服務(wù)器a上,a處理的時(shí)候使用rpc/rest調(diào)用b,b處理的時(shí)候調(diào)用c,這就是所謂的分布式系統(tǒng);而且這個(gè)系統(tǒng)的特點(diǎn)很明顯,如果b的處理壓力過大,那么b節(jié)點(diǎn)可以單獨(dú)的擴(kuò)展為集群,這樣這個(gè)系統(tǒng)就成為了一個(gè)分布式集群系統(tǒng),大大增加了系統(tǒng)的可擴(kuò)展性,以及系統(tǒng)運(yùn)行時(shí)候的性能的保障;
  3. ZooKeeper:一個(gè)基于觀察者模式設(shè)計(jì)的,為用戶的分布式應(yīng)用程序提供分布式的協(xié)調(diào)服務(wù)的中間件框架;簡(jiǎn)稱ZK;主要用來解決分布式集群中應(yīng)用系統(tǒng)的一致性問題;支持Java,并且提供java和c的相關(guān)API;
  4. ZK的作用
    • Leader節(jié)點(diǎn)選舉:就是一個(gè)主備切換的過程,當(dāng)主節(jié)點(diǎn)掛了以后,從節(jié)點(diǎn)就可以接手主節(jié)點(diǎn)的工作,保證系統(tǒng)的高可用。
    • 統(tǒng)一配置管理:只需要配置部署一臺(tái)服務(wù)器,就可以把這個(gè)服務(wù)器上的配置資源更新到其他的服務(wù)器上。常用于云計(jì)算部署;
    • 發(fā)布訂閱: 類似于MQ、Dubbo,發(fā)布者把數(shù)據(jù)發(fā)布到znode上,訂閱者會(huì)去獲取這個(gè)信息;
    • 提供分布式鎖:在分布式環(huán)境中,各個(gè)服務(wù)器可能會(huì)搶奪同一個(gè)資源,分布式鎖就是解決這個(gè)搶奪過程的同步問題;
    • 集群管理:集群中各個(gè)服務(wù)器之間保持?jǐn)?shù)據(jù)的強(qiáng)一致性;
      • ZK中的節(jié)點(diǎn)角色分兩種:Leader和Follower/Observer,只有集群中有半數(shù)以上節(jié)點(diǎn)存活,集群就能提供服務(wù),一般情況下集群的節(jié)點(diǎn)數(shù)目不少于3個(gè)且為奇數(shù)個(gè);一個(gè)Leader,多個(gè)Follower組成一個(gè)ZK集群;

二、ZooKeeper單機(jī)安裝配置

  1. 下載安裝
    • 建議在官方歸檔頁(yè)面下載;
    • 解壓下載到的安裝包,配置環(huán)境變量ZOOKEEPER_HOMEPATH
  2. ZooKeeper的配置
    • 安裝完成后,在conf文件夾下面有一個(gè)文件:zoo_sample.cfg文件,拷貝這個(gè)文件為zoo.cfg;
    • 在這個(gè)配置文件中有很多的常用的變量需要我們配置:
      • tickTime:這個(gè)時(shí)間是作為 Zookeeper 服務(wù)器之間或客戶端與服務(wù)器之間維持心跳的時(shí)間間隔,也就是每個(gè) tickTime 時(shí)間就會(huì)發(fā)送一個(gè)心跳,單位:毫秒,單機(jī)集群通用配置;
      • initLimit:初始化時(shí),F(xiàn)ollower節(jié)點(diǎn)連接到Leader節(jié)點(diǎn)的最長(zhǎng)能忍受的心跳間隔數(shù),按照tickTime倍數(shù)形式表示,集群配置;
      • syncLimit:Follower節(jié)點(diǎn)和Leader節(jié)點(diǎn)之間發(fā)送請(qǐng)求與應(yīng)答最多能忍受的心跳間隔,集群配置;
      • dataDir:常用數(shù)據(jù)所在目錄,必須配置,單機(jī)集群通用配置;
      • dataLogDir:日志目錄,如果沒有配置,則使用dataDir,單機(jī)集群通用配置;
      • clientPort:連接服務(wù)器的端口,默認(rèn)2181,單機(jī)集群通用配置;
  3. ZooKeeper的使用
    • 啟動(dòng)ZooKeeper服務(wù):
      ./zkServer.sh start
      ./zkServer.sh start-foreground
      
    • 關(guān)閉ZooKeeper服務(wù):
      ./zkServer.sh stop
      
    • 重啟ZooKeeper服務(wù):
      ./zkServer.sh restart
      
    • 查看ZooKeeper服務(wù)狀態(tài):
      ./zkServer.sh status
      

三、ZooKeeper的數(shù)據(jù)模型

  1. ZK的數(shù)據(jù)模型
    • 是一個(gè)樹形模型;
    • 每一個(gè)節(jié)點(diǎn)成為znode,每個(gè)節(jié)點(diǎn)都存儲(chǔ)自身的數(shù)據(jù),可以有子節(jié)點(diǎn);
    • 每個(gè)節(jié)點(diǎn)都有兩種類型:臨時(shí)和永久;臨時(shí)節(jié)點(diǎn)在與客戶端斷開連接之后消失,子節(jié)點(diǎn)和自身數(shù)據(jù)都會(huì)丟失;
    • 每個(gè)節(jié)點(diǎn)都有一個(gè)版本號(hào),該版本號(hào)會(huì)隨著該節(jié)點(diǎn)的信息更新而更新(類似數(shù)據(jù)庫(kù)的樂觀鎖),這個(gè)版本信息可以通過命令來展示;
    • 每個(gè)節(jié)點(diǎn)存儲(chǔ)的數(shù)據(jù)大小不宜過大(幾K);
    • 每個(gè)節(jié)點(diǎn)都可以設(shè)置ACL權(quán)限;
  2. ZK的數(shù)據(jù)模型的相關(guān)操作
    • 連接客戶端,必須先啟動(dòng)Server,再去使用Client連接Server
      ./zkCli.sh
      
    • zkCli相關(guān)命令
      • 查看zkCli的所有命令
        help
        
      • 列舉當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)
        ls <node-path> [watch]
        
      • 查看當(dāng)前節(jié)點(diǎn)的狀態(tài)
        stat <node-path>
        
        • cZxid:節(jié)點(diǎn)id;
        • cTime:create time;
        • mZXid:修改節(jié)點(diǎn)后的節(jié)點(diǎn)id;
        • mTime:modify time;
        • pZxid:子節(jié)點(diǎn)的id???
        • cversion:子節(jié)點(diǎn)的version;
        • dataVersion:當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)版本;
        • aclVersion:acl權(quán)限版本;
        • ephemeralOwner:臨時(shí)Owner內(nèi)存地址,如果值是0x0,那么這個(gè)節(jié)點(diǎn)就是持久性節(jié)點(diǎn),否則這個(gè)節(jié)點(diǎn)就是臨時(shí)節(jié)點(diǎn);臨時(shí)節(jié)點(diǎn)不是一斷開就立即刪除(心跳機(jī)制,斷開連接之后,沒有了心跳,就會(huì)去刪除臨時(shí)節(jié)點(diǎn)),而是有一個(gè)實(shí)效時(shí)間,在這個(gè)時(shí)間過后才會(huì)刪除;
        • dataLength:數(shù)據(jù)字節(jié)數(shù);
        • numChildren:子節(jié)點(diǎn)個(gè)數(shù);
      • 同事查看當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)以及當(dāng)前節(jié)點(diǎn)的狀態(tài)
        ls2 <node-path> [watch] # 本質(zhì)上就是ls和stat組合命令
        
      • 查看當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)
        get <node-path>
        
    • 關(guān)閉客戶端連接
      • Ctrl + C

四、ZK的常用操作

  1. Session基本原理
    • 服務(wù)端和客戶端之間的連接稱之為Session;
    • 每個(gè)Session都可以設(shè)置一個(gè)超市時(shí)間;
    • 心跳結(jié)束,Session過期,臨時(shí)節(jié)點(diǎn)就會(huì)被丟棄;
    • 心跳機(jī)制:客戶端向服務(wù)端的ping包請(qǐng)求;
  2. 常用命令
    • 創(chuàng)建節(jié)點(diǎn)
      create [-s] [-e] <node-path> <node-data> <acl>
      
      • -s:sequence,創(chuàng)建順序節(jié)點(diǎn)
      • -e:ephemeral,創(chuàng)建臨時(shí)節(jié)點(diǎn);臨時(shí)節(jié)點(diǎn)上不可以創(chuàng)建永久節(jié)點(diǎn);
    • 修改節(jié)點(diǎn)
      set <node-path> <node-data> [<version>]
      
    • 刪除節(jié)點(diǎn)
      delete <node-path> [<version>]
      

五、watch機(jī)制

  1. 機(jī)制簡(jiǎn)介
    • 我們對(duì)每個(gè)節(jié)點(diǎn)的操作都會(huì)有一個(gè)監(jiān)聽者watch,可以理解為一個(gè)觸發(fā)器;當(dāng)有人操作了節(jié)點(diǎn),那么就會(huì)觸發(fā)這個(gè)節(jié)點(diǎn)的watch事件,來更新這個(gè)節(jié)點(diǎn)以及父節(jié)點(diǎn)的屬性值;
    • watch是一次性的,觸發(fā)后事件完成后就立即銷毀;可以通過Apache實(shí)現(xiàn)永久性的watch;
    • 不同的類型的watch觸發(fā)的事件也是不同的:
      • 節(jié)點(diǎn)創(chuàng)建
      • 節(jié)點(diǎn)刪除
      • 節(jié)點(diǎn)數(shù)據(jù)更新
    • 常見的watch事件
      • 當(dāng)前節(jié)點(diǎn)創(chuàng)建節(jié)點(diǎn)觸發(fā)事件:NodeCreated
        • 如果當(dāng)前節(jié)點(diǎn)不存在也可以設(shè)置,報(bào)錯(cuò)可忽略
      • 當(dāng)前節(jié)點(diǎn)數(shù)據(jù)更新事件:NodeDataChanged
      • 當(dāng)前節(jié)點(diǎn)刪除事件:NodeDeleted
      • 子節(jié)點(diǎn)創(chuàng)建、刪除事件:NodeChildrenChanged
      • 修改子節(jié)點(diǎn),不觸發(fā)父節(jié)點(diǎn)事件
  2. watch相關(guān)的命令
    • 通過查詢數(shù)據(jù)設(shè)置watch
      get <node-path> [watch]
      
    • 通過查詢節(jié)點(diǎn)狀態(tài)設(shè)置watch
      stat <node-path> [watch]
      
    • 通過列舉子節(jié)點(diǎn)設(shè)置watch
      ls[2] <node-path> [watch]
      
  3. Watch使用場(chǎng)景
    • 集群統(tǒng)一資源配置

六、權(quán)限控制列表

  1. ACL簡(jiǎn)介
    • 權(quán)限控制列表:ACL(Access Control List)
    • 為了保障數(shù)據(jù)的安全性,針對(duì)節(jié)點(diǎn)設(shè)置相關(guān)的讀寫權(quán)限;這個(gè)權(quán)限可以設(shè)置不同的權(quán)限范圍和角色;
    • ACL的結(jié)構(gòu)單位:[<scheme>:<id>:<permissions>]
      • scheme:采用的權(quán)限機(jī)制
        1. world:這個(gè)scheme下只有一個(gè)id,即anyone;組合world:anyone:<permissions>
        2. auth:認(rèn)證登錄;組合:auth:<username>:<password>:<permissions>
        3. digest:加密訪問;組合digest:<username>:BASE64(SHA1(<password>)):<persissions>
        4. ip:限制ip訪問;組合ip:<ip-addr>:<permissions>
        5. super:超級(jí)管理員權(quán)限;
          • 編輯zkServer.sh中,找到行nohup行的${ZOO_LOG4J_PROP}",在其后添加"-Dzookeeper.DigestAuthenticationProvider.superDigest=<username>:<BASE64(SHA1(<password>))>"
          • 重啟zkServer;
          • 登陸認(rèn)證:addauth digest <username>:<BASE64(SHA1(<password>))>,這樣就可以使用超級(jí)管理員賬號(hào)了;
      • id:允許訪問的用戶id
      • permissions:權(quán)限字符串:CRDWA,這5個(gè)字母可以任意組合
        1. Create:創(chuàng)建權(quán)限
        2. Read:讀當(dāng)前節(jié)點(diǎn)和子節(jié)點(diǎn)的權(quán)限
        3. Delete:刪除權(quán)限
        4. Write:寫權(quán)限
        5. Admin:分配權(quán)限的權(quán)限
  2. ACL使用
    • addauth:添加認(rèn)證授權(quán)信息:
      addauth <scheme> <acl>
      # 添加一個(gè)用戶名為zhangsan,密碼為123456的用戶:認(rèn)證過程
      addauth digest zhangsan:123456
      
    • setAcl:設(shè)置具體節(jié)點(diǎn)的權(quán)限信息:
      setAcl <node-path> <acl>
      # 給一個(gè)用戶zhangsan設(shè)置權(quán)限,注意:只是第一次設(shè)置有效
      setAcl /level1/level2 auth:zhangsan:123456:cdrwa
      
    • getAcl:獲取具體節(jié)點(diǎn)的權(quán)限信息:
      getAcl <node-path>
      # 默認(rèn)權(quán)限:
      'world,' anyone
      : cdrwa
      
  3. ACL使用場(chǎng)景
    • 分離開發(fā)測(cè)試環(huán)境:即不同的節(jié)點(diǎn)設(shè)置不同的訪問權(quán)限;

七、四字命令(The Four Letter Words)

  1. 簡(jiǎn)介
    • ZK可以通過四字命令與服務(wù)器進(jìn)行交互;比較適合監(jiān)控信息;
  2. 使用
    • 安裝nc:yum install nc
    • nc命令使用規(guī)則:echo [command] | nc [ip] [port]
  3. 常用命令
    • stat:查看ZK的狀態(tài)信息;
      echo stat | nc localhost 2181
      # 執(zhí)行結(jié)果:
      Zookeeper version: 3.4.9-1757313, built on 08/23/2016 06:50 GMT
      Clients:
      /localhost:46026[0](queued=0,recved=1,sent=0)
      Latency min/avg/max: 0/1/8
      Received: 10
      Sent: 9
      Connections: 1
      Outstanding: 0
      Zxid: 0xa
      Mode: standalone
      Node count: 4
      
    • ruok:查看ZK的啟動(dòng)狀態(tài);
      echo ruok | nc localhost 2181
      # 執(zhí)行結(jié)果:
      imok
      
    • dump:查看ZK的啟動(dòng)狀態(tài);
      echo dump | nc localhost 2181
      # 執(zhí)行結(jié)果:
      SessionTracker dump:
      Session Sets (0):
      ephemeral nodes dump:
      Sessions with Ephemerals (0):
      
    • conf:查看ZK服務(wù)器的相關(guān)配置參數(shù);
      echo conf | nc localhost 2181
      # 執(zhí)行結(jié)果:
      clientPort=2181
      dataDir=/usr/local/bin/zookeeper-3.4.9/data/version-2
      dataLogDir=/usr/local/bin/zookeeper-3.4.9/log/version-2
      tickTime=2000
      maxClientCnxns=60
      minSessionTimeout=4000
      maxSessionTimeout=40000
      serverId=0
      
    • cons:查看連接到ZK服務(wù)器的信息;
      echo cons | nc localhost 2181
      # 執(zhí)行結(jié)果:
       /0:0:0:0:0:0:0:1:49998[0](queued=0,recved=1,sent=0)
       /0:0:0:0:0:0:0:1:49996[1](queued=0,recved=1,sent=1,sid=0x1645e34b63f0001,lop=SESS,est=1530597540388,to=30000,lcxid=0x0,lzxid=0xb,lresp=1530597540403,llat=14,minlat=0,avglat=14,maxlat=14)
      
    • envi:查看ZK服務(wù)器的環(huán)境變量;
      echo envi | nc localhost 2181
      # 執(zhí)行結(jié)果:
      Environment:
      zookeeper.version=3.4.9-1757313, built on 08/23/2016 06:50 GMT
      host.name=localhost
      java.version=1.8.0_171
      java.vendor=Oracle Corporation
      java.home=/usr/local/bin/jdk1.8.0_171/jre
      java.class.path=/usr/local/bin/zookeeper-3.4.9/bin/../build/classes:/usr/local/bin/zookeeper-3.4.9/bin/../build/lib/*.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/slf4j-log4j12-1.6.1.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/slf4j-api-1.6.1.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/netty-3.10.5.Final.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/log4j-1.2.16.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/jline-0.9.94.jar:/usr/local/bin/zookeeper-3.4.9/bin/../zookeeper-3.4.9.jar:/usr/local/bin/zookeeper-3.4.9/bin/../src/java/lib/*.jar:/usr/local/bin/zookeeper-3.4.9/bin/../conf:
      java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
      java.io.tmpdir=/tmp
      java.compiler=<NA>
      os.name=Linux
      os.arch=amd64
      os.version=3.10.0-862.3.2.el7.x86_64
      user.name=root
      user.home=/root
      user.dir=/usr/local/bin/zookeeper-3.4.9/bin
      
    • mntr:查看ZK服務(wù)器的健康信息;
      echo mntr | nc localhost 2181
      # 執(zhí)行結(jié)果:
      zk_version      3.4.9-1757313, built on 08/23/2016 06:50 GMT
      zk_avg_latency  1
      zk_max_latency  14
      zk_min_latency  0
      zk_packets_received     59
      zk_packets_sent 58
      zk_num_alive_connections        2
      zk_outstanding_requests 0
      zk_server_state standalone
      zk_znode_count  4
      zk_watch_count  0
      zk_ephemerals_count     0
      zk_approximate_data_size        27
      zk_open_file_descriptor_count   27
      zk_max_file_descriptor_count    4096
      
    • wchs:查看ZK服務(wù)器的watch信息;
      echo wchs | nc localhost 2181
      # 執(zhí)行結(jié)果:
      0 connections watching 0 paths
      Total watches:0
      
    • wchc:查看ZK服務(wù)器中session與帶有watch的節(jié)點(diǎn)的關(guān)系信息(3.4.10后需要開啟白名單才能執(zhí)行);
      echo wchc | nc localhost 2181
      
    • wchp:查看ZK服務(wù)器中帶有watch的節(jié)點(diǎn)在哪個(gè)session中(3.4.10后需要開啟白名單才能執(zhí)行);
      echo wchp | nc localhost 2181
      
    • 開啟白名單:在zoo.cfg文件中添加如下一行命令,重啟即可:
      4lw.commands.whitelist=*
      4lw.commands.whitelist=<cmd1>,<cmd2>...
      

八、ZK搭建集群

  1. 偽分布式環(huán)境搭建(為學(xué)習(xí))

    • 下載ZooKeeper安裝包;
    • 解壓安裝包;
    • 復(fù)制conf文件夾下的zoo_sample.cfg為zoo.cfg;
    • 配置各個(gè)節(jié)點(diǎn)的myid:向每個(gè)節(jié)點(diǎn)的ZK的數(shù)據(jù)文件夾中寫入一個(gè)文件myid,這個(gè)文件中寫入當(dāng)前機(jī)器的id編號(hào)即可;
    • 編輯zoo.cfg:
      • 配置dataDirdataLogDir,不要放置在tmp下,個(gè)人喜歡放在安裝目錄或者獨(dú)立的硬盤的一個(gè)目錄下,并且確保程序有訪問權(quán)限;
      • 在每個(gè)節(jié)點(diǎn)上配置所有節(jié)點(diǎn)的端口映射關(guān)系:①對(duì)外訪問端口:2181;②Leader和Follower之間的端口:2888;③組件之間投票通訊端口:3888;對(duì)應(yīng)的配置如下:
        <server-name-prefix>.<myid>=<host>:<leader-listener-port>:<vote-port>
        
        1. <leader-listener-port>:是該服務(wù)器一旦成為L(zhǎng)eader之后需要監(jiān)聽的端口,用于接收來自follower的請(qǐng)求;
        2. <vote-port>:集群中的每一個(gè)節(jié)點(diǎn)在最開始選舉Leader時(shí)監(jiān)聽的端口,用于服務(wù)器互相之間通信選舉Leader;
    • 建議關(guān)閉防火墻,因?yàn)榧褐辉趦?nèi)部訪問,不會(huì)暴露到對(duì)外的環(huán)境中;
  2. 真分布式環(huán)境搭建(為應(yīng)用)

九、ZK集群的特點(diǎn)

  1. 數(shù)據(jù)一致性:每個(gè)ZK節(jié)點(diǎn)保存一份相同的數(shù)據(jù)副本,client無論連接到哪個(gè)節(jié)點(diǎn),數(shù)據(jù)都是一致的,即單一視圖
  2. 分布式讀寫,更新請(qǐng)求轉(zhuǎn)發(fā),由leader實(shí)施;更新請(qǐng)求順序進(jìn)行,來自同一個(gè)client的更新請(qǐng)求按其發(fā)送順序依次執(zhí)行;
  3. 操作原子性:一損俱損,全榮為榮;
  4. 數(shù)據(jù)實(shí)時(shí)性:在一定時(shí)間范圍內(nèi),client能讀到最新數(shù)據(jù),ZK節(jié)點(diǎn)越多實(shí)時(shí)性越差;
  5. 數(shù)據(jù)可靠性:每次對(duì)ZK的操作都記錄在服務(wù)端;

十、ZK選舉Leader

  1. 問題的提出:
    • 在配置集群的時(shí)候我么是不配置哪個(gè)節(jié)點(diǎn)是Leader節(jié)點(diǎn),哪些事Follower節(jié)點(diǎn)的,當(dāng)我們?cè)诿總€(gè)節(jié)點(diǎn)上配置了這個(gè)集群的所有節(jié)點(diǎn)的映射關(guān)系之后,當(dāng)ZK節(jié)點(diǎn)一個(gè)接著一個(gè)的啟動(dòng),系統(tǒng)會(huì)自動(dòng)的選舉出Leader,那這個(gè)Leader的節(jié)點(diǎn)選舉時(shí)怎樣進(jìn)行的呢?
  2. Leader選舉機(jī)制
    • Leader選舉機(jī)制的考慮因素:
      • 節(jié)點(diǎn)id:id即權(quán)重,節(jié)點(diǎn)id越大選舉的權(quán)重越大;
      • 數(shù)據(jù)id:數(shù)據(jù)越新,數(shù)據(jù)id越大,選舉的權(quán)重越大;
      • 投票次數(shù):得票者越大,越容易成為L(zhǎng)eader;
    • 選舉中節(jié)點(diǎn)的狀態(tài):
      • LOOKING:競(jìng)選狀態(tài);
      • FOLLOWING:隨從狀態(tài),同步leader狀態(tài),參與投票;
      • OBSERVING:觀察狀態(tài),同步leader狀態(tài),不參與投票;
      • LEADING:領(lǐng)導(dǎo)者狀態(tài);

十一、ZK的JavaAPI的使用

  1. 添加ZooKeeper的pom:
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.5</version>
    </dependency>
    
    • org.apache.zookeeper.Zookeeper是客戶端入口主類,負(fù)責(zé)建立與ZK服務(wù)器節(jié)點(diǎn)的會(huì)話;主要的API參考下面的測(cè)試類;
  2. ZKClientTest:
    import lombok.extern.slf4j.Slf4j;
    import org.apache.zookeeper.*;
    import org.apache.zookeeper.data.Stat;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.util.List;
    
    /**
     * @Author: ShrekerNil
     * @Date: 2018/7/5 10:28
     * @Description: ZooKeeper的API的使用
     */
    @Slf4j
    public class ZKClientTest {
    
        private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181";
        private static final int sessionTimeout = 2000;
    
        private ZooKeeper zkClient = null;
    
        @Before
        public void init() throws Exception {
            zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                // Watcher接口是當(dāng)zkClient監(jiān)聽到watch事件觸發(fā)的時(shí)候就會(huì)去調(diào)用該接口process方法的一個(gè)接口
                public void process(WatchedEvent event) {
                    // 收到事件通知后的回調(diào)函數(shù)(應(yīng)該是我們自己的事件處理邏輯)
                    log.info("接收到事件,類型:{},事件發(fā)生節(jié)點(diǎn):{}", event.getType(), event.getPath());
                    // 因?yàn)楸O(jiān)聽器只會(huì)生效一次,所以在消費(fèi)監(jiān)聽器之后再次注冊(cè)監(jiān)聽器
                    try {
                        zkClient.getChildren("/", true);
                    } catch (Exception e) {
                        log.error("添加監(jiān)聽器失敗", e);
                    }
                }
            });
    
        }
    
        // 創(chuàng)建數(shù)據(jù)節(jié)點(diǎn)到ZK中
        @Test
        public void testCreate() throws Exception {
            // 參數(shù)1:要?jiǎng)?chuàng)建的節(jié)點(diǎn)的路徑 參數(shù)2:節(jié)點(diǎn)大數(shù)據(jù) 參數(shù)3:節(jié)點(diǎn)的權(quán)限 參數(shù)4:節(jié)點(diǎn)的類型
            String nodePathCreated = zkClient.create("/node110", "hellozk".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            //上傳的數(shù)據(jù)可以是任何類型,但都要轉(zhuǎn)成byte[]
            log.info(nodePathCreated);
        }
    
        //判斷znode是否存在
        @Test
        public void testExist() throws Exception {
            Stat stat = zkClient.exists("/node110", false);
            log.info(stat == null ? "Not exist!" : "Exists");
        }
    
        // 獲取子節(jié)點(diǎn)
        @Test
        public void getChildren() throws Exception {
            List<String> children = zkClient.getChildren("/", true);
            for (String child : children) {
                log.info(child);
            }
            Thread.sleep(20000);
        }
    
        //獲取znode的數(shù)據(jù)
        @Test
        public void getNodeData() throws Exception {
            byte[] data = zkClient.getData("/node110", false, null);
            log.info(new String(data));
        }
    
        //設(shè)置znode數(shù)據(jù)
        @Test
        public void setNodeData() throws Exception {
            zkClient.setData("/node110", "something in my heart is ...".getBytes(), -1);
            byte[] data = zkClient.getData("/node110", false, null);
            log.info(new String(data));
        }
    
        //刪除znode
        @Test
        public void deleteZnode() throws Exception {
            //第二個(gè)參數(shù):指定要?jiǎng)h除的版本,-1表示刪除所有版本
            zkClient.delete("/node110", -1);
        }
    
    }
    

十二、ZK的使用場(chǎng)景(參考自這里)

  1. 統(tǒng)一命名服務(wù)(Name Service)
    • 統(tǒng)一命名服務(wù)是ZK的內(nèi)置功能,類似于JNDI,我們?cè)谡{(diào)用其API的時(shí)候使用了這一功能,在這里不再贅述;
  2. 配置管理(Configuration Management)
    • 問題:配置的管理在分布式應(yīng)用環(huán)境中很常見,例如同一個(gè)應(yīng)用系統(tǒng)需要多臺(tái)服務(wù)器配合運(yùn)行,但是它們運(yùn)行的應(yīng)用系統(tǒng)的某些配置項(xiàng)是相同的,如果要修改這些相同的配置項(xiàng),那么就必須同時(shí)修改每臺(tái)運(yùn)行這個(gè)應(yīng)用系統(tǒng)的服務(wù)器,這樣非常麻煩而且容易出錯(cuò)。
    • 解決:ZK就能很好的幫我們完成這項(xiàng)配置工作,可以把配置存儲(chǔ)在ZK的一個(gè)節(jié)點(diǎn)上,設(shè)置監(jiān)聽,當(dāng)我們修改了ZK中這個(gè)節(jié)點(diǎn)的數(shù)據(jù)的時(shí)候,監(jiān)聽器就能偵測(cè)到這個(gè)事件,進(jìn)而把配置應(yīng)用到各自的服務(wù)器中。
  3. 集群管理(Group Membership)
    • 問題:在做集群的時(shí)候,一般我們都使用Master、Slave模式,但是如果Master掛了呢?這個(gè)時(shí)候就會(huì)出現(xiàn)問題,整個(gè)服務(wù)就掛掉了,HA化為泡影;
    • 解決:這個(gè)問題的解決方式有很多,很多的框架都提供了集群管理的功能,當(dāng)然我們今天的主角ZK也不例外。首先我們應(yīng)該注冊(cè)監(jiān)聽一個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn),客戶端在服務(wù)器上注冊(cè)臨時(shí)(EPHEMERAL)的節(jié)點(diǎn),當(dāng)出現(xiàn)如上的問題的時(shí)候,就觸發(fā)ZK的事件,我們就知道哪個(gè)節(jié)點(diǎn)掛了,根據(jù)一定的算法選舉出新的Leader,讓集群接著向外界提供服務(wù);
  4. 共享鎖(Locks)
    • 共享鎖在同一個(gè)進(jìn)程中很容易實(shí)現(xiàn),但是在跨進(jìn)程或者在不同 Server 之間就不好實(shí)現(xiàn)了。Zookeeper 卻很容易實(shí)現(xiàn)這個(gè)功能,實(shí)現(xiàn)方式也是需要獲得鎖的 Server 創(chuàng)建一個(gè) EPHEMERAL_SEQUENTIAL 目錄節(jié)點(diǎn),然后調(diào)用 getChildren方法獲取當(dāng)前的目錄節(jié)點(diǎn)列表中最小的目錄節(jié)點(diǎn)是不是就是自己創(chuàng)建的目錄節(jié)點(diǎn),如果正是自己創(chuàng)建的,那么它就獲得了這個(gè)鎖,如果不是那么它就調(diào)用 exists(String path, boolean watch) 方法并監(jiān)控 Zookeeper 上目錄節(jié)點(diǎn)列表的變化,一直到自己創(chuàng)建的節(jié)點(diǎn)是列表中最小編號(hào)的目錄節(jié)點(diǎn),從而獲得鎖,釋放鎖很簡(jiǎn)單,只要?jiǎng)h除前面它自己所創(chuàng)建的目錄節(jié)點(diǎn)就行了。
  5. 隊(duì)列管理
    • ZK可以處理兩種類型的隊(duì)列:同步隊(duì)列和FIFO隊(duì)列(完整代碼參看這里);
    • 同步隊(duì)列實(shí)現(xiàn):當(dāng)一個(gè)隊(duì)列的成員都聚齊時(shí),這個(gè)隊(duì)列才可用,否則一直等待所有成員到達(dá);
      • 實(shí)現(xiàn)思路:創(chuàng)建一個(gè)父目錄/synchronizing,每個(gè)成員都監(jiān)控標(biāo)志(Set Watch)位目錄 /synchronizing/start是否存在,然后每個(gè)成員都加入這個(gè)隊(duì)列,加入隊(duì)列的方式就是創(chuàng)建 /synchronizing/member_i的臨時(shí)目錄節(jié)點(diǎn),然后每個(gè)成員獲取/ synchronizing目錄的所有目錄節(jié)點(diǎn),也就是member_i。判斷i的值是否已經(jīng)是成員的個(gè)數(shù),如果小于成員個(gè)數(shù)等待/synchronizing/start的出現(xiàn),如果已經(jīng)相等就創(chuàng)建/synchronizing/start
    • FIFO隊(duì)列實(shí)現(xiàn):隊(duì)列按照 FIFO 方式進(jìn)行入隊(duì)和出隊(duì)操作,例如實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者模型;
      • 實(shí)現(xiàn)思路:實(shí)現(xiàn)的思路也非常簡(jiǎn)單,就是在特定的目錄下創(chuàng)建SEQUENTIAL類型的子目錄 /queue_i,這樣就能保證所有成員加入隊(duì)列時(shí)都是有編號(hào)的,出隊(duì)列時(shí)通過getChildren()方法可以返回當(dāng)前所有的隊(duì)列中的元素,然后消費(fèi)其中最小的一個(gè),這樣就能保證 FIFO;

十三、集群節(jié)點(diǎn)上下線感知系統(tǒng)實(shí)現(xiàn)

  1. 客戶端實(shí)現(xiàn)

    import lombok.extern.slf4j.Slf4j;
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooKeeper;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author: ShrekerNil
     * @Date: 2018/7/5 14:21
     * @Description: DistributedClient用來描述ZK在客戶端的用法
     */
    @Slf4j
    public class DistributedClient {
    
        private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181";
        private static final int sessionTimeout = 2000;
        private static final String baseNode = "/servers";
        private ZooKeeper zkClient = null;
        private volatile List<String> servers;
    
        /**
         * 創(chuàng)建到zk的客戶端連接
         */
        private void connectZoooKepper() throws Exception {
            zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                public void process(WatchedEvent event) {
                    try {
                        updateServersAndListenChildrenNodes();
                    } catch (Exception e) {
                        log.error("更新服務(wù)器列表失敗", e);
                    }
                }
            });
        }
    
        /**
         * 更新服務(wù)器列表,并且注冊(cè)子節(jié)點(diǎn)監(jiān)聽
         */
        private void updateServersAndListenChildrenNodes() throws Exception {
            // 獲取服務(wù)器子節(jié)點(diǎn)信息,并且對(duì)父節(jié)點(diǎn)進(jìn)行監(jiān)聽
            List<String> nodes = zkClient.getChildren(baseNode, true);
    
            // 先創(chuàng)建一個(gè)局部的list來存服務(wù)器信息
            List<String> temp = new ArrayList<String>();
            for (String node : nodes) {
                // child只是子節(jié)點(diǎn)的節(jié)點(diǎn)名
                byte[] data = zkClient.getData(baseNode + "/" + node, false, null);
                temp.add(new String(data));
            }
            servers = temp;
    
            //打印服務(wù)器列表
            log.debug("servers:{}", servers.toString());
        }
    
        /**
         * 業(yè)務(wù)功能
         */
        private void handleBussiness() throws InterruptedException {
            log.debug("Client start working.....");
            log.debug("Using servers:{}", servers);
            Thread.sleep(Long.MAX_VALUE);
        }
    
        public static void main(String[] args) throws Exception {
            // 創(chuàng)建客戶端對(duì)象
            DistributedClient client = new DistributedClient();
            // 連接ZK
            client.connectZoooKepper();
            // 更新servers的子節(jié)點(diǎn)信息(并監(jiān)聽),從中獲取服務(wù)器信息列表
            client.updateServersAndListenChildrenNodes();
            // 業(yè)務(wù)線程啟動(dòng)
            client.handleBussiness();
        }
    
    }
    
  2. 集群端實(shí)現(xiàn):

    import lombok.extern.slf4j.Slf4j;
    import org.apache.zookeeper.*;
    
    /**
     * @Author: ShrekerNil
     * @Date: 2018/7/5 14:19
     * @Description: DistributedServer用來描述ZK在服務(wù)器端的用法
     */
    @Slf4j
    public class DistributedServer {
    
        private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181";
        private static final int sessionTimeout = 2000;
        private static final String parentNode = "/servers";
    
        private ZooKeeper zkClient = null;
    
        /**
         * 創(chuàng)建到zk的客戶端連接
         */
        private void connectZoooKepper() throws Exception {
            zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                public void process(WatchedEvent event) {
                    // 收到事件通知后的回調(diào)函數(shù)(應(yīng)該是我們自己的事件處理邏輯)
                    log.debug(event.getType() + "---" + event.getPath());
                    try {
                        zkClient.getChildren("/", true);
                    } catch (Exception e) {
                        log.error("添加監(jiān)聽器失敗", e);
                    }
                }
            });
        }
    
        /**
         * 向zk集群注冊(cè)服務(wù)器信息
         */
        private void registerServer(String hostname) throws Exception {
            String newNode = zkClient.create(parentNode + "/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            log.debug("Created new node {} in {}", newNode, hostname);
        }
    
        /**
         * 業(yè)務(wù)功能
         */
        private void handleBussiness(String hostname) throws InterruptedException {
            log.debug(hostname + " start working.....");
            Thread.sleep(Long.MAX_VALUE);
        }
    
        public static void main(String[] args) throws Exception {
            // 創(chuàng)建Server
            DistributedServer server = new DistributedServer();
    
            // 連接ZK
            server.connectZoooKepper();
    
            int length = args.length;
            if (length < 1) {
                log.error("請(qǐng)?zhí)砑訁?shù)hostname");
                return;
            }
    
            // 利用zk連接注冊(cè)服務(wù)器信息
            server.registerServer(args[0]);
    
            // 啟動(dòng)業(yè)務(wù)功能
            server.handleBussiness(args[0]);
        }
    
    }
    

十四、寫在最后

  1. 在本篇文章中提到了很多知識(shí)點(diǎn),但是很多知識(shí)點(diǎn)只是提了一下,由于篇幅有限,只能寫到這里了,如果需要再補(bǔ)充吧
  2. 補(bǔ)充一個(gè)原理講解比較深刻的博客,大家可以去看看;
  3. 文章中也引用了一些其他博主的內(nèi)容,已標(biāo)記出處,如果轉(zhuǎn)載請(qǐng)標(biāo)明這些博主的出處,我的就無所謂了,謝謝
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容