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

...

一长窄、相關(guān)概念

  1. 中間件:為分布式系統(tǒng)提供協(xié)調(diào)服務(wù)的組件按厘,如專門用于計(jì)算服務(wù)的機(jī)器就是一個(gè)計(jì)算型中間件,還有專門用于存儲的機(jī)器就是存儲中間件等等碳默;
  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)部的每臺計(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í)候客戶端的請求發(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ù)的中間件框架;簡稱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)一配置管理:只需要配置部署一臺服務(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. 下載安裝
    • 建議在官方歸檔頁面下載;
    • 解壓下載到的安裝包匈子,配置環(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)的最長能忍受的心跳間隔數(shù)访锻,按照tickTime倍數(shù)形式表示,集群配置闹获;
      • syncLimit:Follower節(jié)點(diǎn)和Leader節(jié)點(diǎn)之間發(fā)送請求與應(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)都存儲自身的數(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è)版本號职祷,該版本號會(huì)隨著該節(jié)點(diǎn)的信息更新而更新(類似數(shù)據(jù)庫的樂觀鎖)氏涩,這個(gè)版本信息可以通過命令來展示;
    • 每個(gè)節(jié)點(diǎn)存儲的數(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包請求搜吧;
  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ī)制簡介
    • 我們對每個(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使用場景
    • 集群統(tǒng)一資源配置

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

  1. ACL簡介
    • 權(quán)限控制列表:ACL(Access Control List)
    • 為了保障數(shù)據(jù)的安全性,針對節(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:超級管理員權(quán)限烘嘱;
          • 編輯zkServer.sh中昆禽,找到行nohup行的${ZOO_LOG4J_PROP}",在其后添加"-Dzookeeper.DigestAuthenticationProvider.superDigest=<username>:<BASE64(SHA1(<password>))>"
          • 重啟zkServer蝇庭;
          • 登陸認(rèn)證:addauth digest <username>:<BASE64(SHA1(<password>))>醉鳖,這樣就可以使用超級管理員賬號了;
      • 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使用場景
    • 分離開發(fā)測試環(huán)境:即不同的節(jié)點(diǎn)設(shè)置不同的訪問權(quán)限北发;

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

  1. 簡介
    • 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編號即可笙以;
    • 編輯zoo.cfg:
      • 配置dataDirdataLogDir,不要放置在tmp下冻辩,個(gè)人喜歡放在安裝目錄或者獨(dú)立的硬盤的一個(gè)目錄下猖腕,并且確保程序有訪問權(quán)限;
      • 在每個(gè)節(jié)點(diǎn)上配置所有節(jié)點(diǎn)的端口映射關(guān)系:①對外訪問端口:2181恨闪;②Leader和Follower之間的端口:2888倘感;③組件之間投票通訊端口:3888;對應(yīng)的配置如下:
        <server-name-prefix>.<myid>=<host>:<leader-listener-port>:<vote-port>
        
        1. <leader-listener-port>:是該服務(wù)器一旦成為Leader之后需要監(jiān)聽的端口咙咽,用于接收來自follower的請求老玛;
        2. <vote-port>:集群中的每一個(gè)節(jié)點(diǎn)在最開始選舉Leader時(shí)監(jiān)聽的端口,用于服務(wù)器互相之間通信選舉Leader钧敞;
    • 建議關(guān)閉防火墻蜡豹,因?yàn)榧褐辉趦?nèi)部訪問,不會(huì)暴露到對外的環(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. 分布式讀寫凤巨,更新請求轉(zhuǎn)發(fā)视乐,由leader實(shí)施;更新請求順序進(jìn)行敢茁,來自同一個(gè)client的更新請求按其發(fā)送順序依次執(zhí)行佑淀;
  3. 操作原子性:一損俱損,全榮為榮彰檬;
  4. 數(shù)據(jù)實(shí)時(shí)性:在一定時(shí)間范圍內(nèi)伸刃,client能讀到最新數(shù)據(jù),ZK節(jié)點(diǎn)越多實(shí)時(shí)性越差逢倍;
  5. 數(shù)據(jù)可靠性:每次對ZK的操作都記錄在服務(wù)端捧颅;

十、ZK選舉Leader

  1. 問題的提出:
    • 在配置集群的時(shí)候我么是不配置哪個(gè)節(jié)點(diǎn)是Leader節(jié)點(diǎn)较雕,哪些事Follower節(jié)點(diǎn)的碉哑,當(dāng)我們在每個(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ù):得票者越大薪前,越容易成為Leader;
    • 選舉中節(jié)點(diǎn)的狀態(tài):
      • LOOKING:競選狀態(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參考下面的測試類秉沼;
  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)聽器之后再次注冊監(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的使用場景(參考自這里)

  1. 統(tǒng)一命名服務(wù)(Name Service)
    • 統(tǒng)一命名服務(wù)是ZK的內(nèi)置功能棘捣,類似于JNDI,我們在調(diào)用其API的時(shí)候使用了這一功能休建,在這里不再贅述乍恐;
  2. 配置管理(Configuration Management)
    • 問題:配置的管理在分布式應(yīng)用環(huán)境中很常見,例如同一個(gè)應(yīng)用系統(tǒng)需要多臺服務(wù)器配合運(yùn)行测砂,但是它們運(yùn)行的應(yīng)用系統(tǒng)的某些配置項(xiàng)是相同的茵烈,如果要修改這些相同的配置項(xiàng),那么就必須同時(shí)修改每臺運(yùn)行這個(gè)應(yīng)用系統(tǒng)的服務(wù)器砌些,這樣非常麻煩而且容易出錯(cuò)呜投。
    • 解決:ZK就能很好的幫我們完成這項(xiàng)配置工作,可以把配置存儲在ZK的一個(gè)節(jié)點(diǎn)上,設(shè)置監(jiān)聽宙彪,當(dāng)我們修改了ZK中這個(gè)節(jié)點(diǎn)的數(shù)據(jù)的時(shí)候矩动,監(jiān)聽器就能偵測到這個(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)該注冊監(jiān)聽一個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn),客戶端在服務(wù)器上注冊臨時(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)是列表中最小編號的目錄節(jié)點(diǎ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)的思路也非常簡單,就是在特定的目錄下創(chuàng)建SEQUENTIAL類型的子目錄 /queue_i浮创,這樣就能保證所有成員加入隊(duì)列時(shí)都是有編號的忧吟,出隊(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ù)器列表雏掠,并且注冊子節(jié)點(diǎn)監(jiān)聽
         */
        private void updateServersAndListenChildrenNodes() throws Exception {
            // 獲取服務(wù)器子節(jié)點(diǎn)信息斩祭,并且對父節(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)建客戶端對象
            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集群注冊服務(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("請?zhí)砑訁?shù)hostname");
                return;
            }
    
            // 利用zk連接注冊服務(wù)器信息
            server.registerServer(args[0]);
    
            // 啟動(dòng)業(yè)務(wù)功能
            server.handleBussiness(args[0]);
        }
    
    }
    

十四乡话、寫在最后

  1. 在本篇文章中提到了很多知識點(diǎn),但是很多知識點(diǎn)只是提了一下耳奕,由于篇幅有限绑青,只能寫到這里了,如果需要再補(bǔ)充吧
  2. 補(bǔ)充一個(gè)原理講解比較深刻的博客屋群,大家可以去看看闸婴;
  3. 文章中也引用了一些其他博主的內(nèi)容,已標(biāo)記出處芍躏,如果轉(zhuǎn)載請標(biāo)明這些博主的出處邪乍,我的就無所謂了,謝謝
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末对竣,一起剝皮案震驚了整個(gè)濱河市庇楞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌否纬,老刑警劉巖吕晌,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異临燃,居然都是意外死亡睛驳,警方通過查閱死者的電腦和手機(jī)烙心,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乏沸,“玉大人淫茵,你說我怎么就攤上這事〉旁荆” “怎么了痘昌?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炬转。 經(jīng)常有香客問我辆苔,道長,這世上最難降的妖魔是什么扼劈? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任驻啤,我火速辦了婚禮,結(jié)果婚禮上荐吵,老公的妹妹穿的比我還像新娘骑冗。我一直安慰自己,他們只是感情好先煎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布贼涩。 她就那樣靜靜地躺著,像睡著了一般薯蝎。 火紅的嫁衣襯著肌膚如雪遥倦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天占锯,我揣著相機(jī)與錄音袒哥,去河邊找鬼。 笑死消略,一個(gè)胖子當(dāng)著我的面吹牛堡称,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播艺演,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼却紧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胎撤?” 一聲冷哼從身側(cè)響起晓殊,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哩照,沒想到半個(gè)月后挺物,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡飘弧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年识藤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砚著。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痴昧,死狀恐怖稽穆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赶撰,我是刑警寧澤舌镶,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站豪娜,受9級特大地震影響餐胀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘤载,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一否灾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鸣奔,春花似錦墨技、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锨匆,卻和暖如春崭别,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背统刮。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工紊遵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侥蒙。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像匀奏,于是被迫代替她去往敵國和親鞭衩。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

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