1. 程式人生 > 其它 >zookeeper從入門到熟練使用

zookeeper從入門到熟練使用

zookeeper是一種分散式協調服務,用於管理大型主機。zk通過其架構和API解決了分散式環境中協調和管理服務中的問題。讓開發人員不再擔心應用程式的分散式特性,專注於應用的邏輯。

一、zookeeper的應用場景

1.分散式協調元件:通過nginx做負載均衡然後冗餘部署2個相同的服務,兩個服務中都有個flag標記,當A服務中的flag變成false的時候,兩個服務中的資料就不一致了,通過zookeeper監聽到A服務flag標記變了,通知服務b也把flag改為false,zookeeperwatch機制保證了資料的一致性。

2.無狀態化的實現:冗餘部署3個登入系統,登入資訊儲存在資料中心zookeeper中,對於一個登入系統不需要關心登入狀態了,登入狀態在zookeeper中維護了,這樣實現了這幾個系統的無狀態話。

3.分散式鎖:強一致性(順序一致性)。分散式鎖:放在zookeeper中,要去zookeeper中拿鎖才能執行業務。

二、搭建zookeeper伺服器

1.https://zookeeper.apache.org/ 進入到官網,選這個發行版

2.下載一個最新版的

然後在linux中隨便建立一個資料夾將剛才的壓縮包丟進去,tar -zvxf 檔名解壓,解壓完之後把壓縮檔案刪除了

3.進入conf目錄,將zoo_sample.cfg改名為zoo.cfg,再vim編輯zoo.cfg

修改一下zookeeper資料檔案和日誌檔案儲存的位置,第二項客戶端埠號不用修改,我是因為裝了tomcat才加了第三條(因為tomcat和zookeeper預設埠都是8080,安裝了tomcat會導致zookeeper啟動不了。)

注意!!!!:叢集搭建的時候不要第三句,想要tomcat的話可以去該tomcat的埠號,在阿里雲上面搭建的話要特別注意,ip地址不是本機網絡卡(每個配置檔案加上quorumListenOnAllIPs=true)

:wq儲存退出

cd ../bin進入bin目錄

./zkServer.sh start ../conf/zoo.cfg 指定配置檔案啟動

./zkServer.sh status ../conf/zoo.cfg檢視zk伺服器狀態

./zkServer.sh stop ../conf/zoo.cfg關閉zk伺服器

發現啟動不了,沒有開埠號

1、開啟防火牆
systemctl start firewalld

2、開放指定埠
firewall-cmd --zone=public --add-port=8543/tcp --permanent
命令含義:
--zone #作用域
--add-port=8543/tcp #新增埠,格式為:埠/通訊協議
--permanent #永久生效,沒有此引數重啟後失效

3、重啟防火牆

firewall-cmd --reload
4. 檢視埠號開放情況
firewall-cmd --list-ports

再次啟動搞定,./zkCli.sh進入客戶端 ls / 檢視根目錄下目錄列表,輸入help命令會給出提示可以操作的命令,安裝完成之後暫時先這樣了,因為zookeeper都是搭配其他元件使用的,之後會寫kafka中會使用。

ls -R /test遞迴查詢

三、zookeeper內部的資料模型

1.zk中的資料是儲存在節點上的,預設根節點是/,zk的資料模型類似樹結構,樹由節點組成,節點叫znode

建立節點:create /test

在節點中建立資料:create /test abc 拿資料:get /test 獲取節點詳細資訊(就是stat元資料):get -s /test

2.znode結構

  data:儲存資料

  acl:許可權

      c建立、w寫、r讀、d刪、a:admin許可權,允許對該節點進行acl許可權設定

  stat:當前znode的元資料

  child:當前節點的子節點

3.zk中節點znode的型別

通過zk客戶端在zk伺服器中建立節點

持久節點:create /test一直存在即使會話結束,用於儲存資料。zk伺服器會返回一個永久的sessionid給zk客戶端,

持久序號節點:create -s /test創建出的節點,會根據先後順序,在節點後帶上一個事務序號,越後執行數值越大,單調遞增,高併發場景下,誰先誰後,適用於分散式鎖的應用場景  

臨時節點:create -e /test在會話結束後,會被自動刪除。zk伺服器會返回一個有失效時間的sessionid給zk客戶端,持續會話會不斷的ping維持心跳,增加續約sessionid的時間,會話斷開就不會去ping訊息續約了,zk伺服器會週期性自動刪除沒有續約的sessionid對應的臨時節點。服務提供者通過建立臨時節點,實現服務的註冊於發現,提供者斷開連線,臨時連線被刪除,消費者就不能找到服務提供者呼叫服務了。

臨時序號節點 :create -e -s /test

Container節點:create -c /test當容器中沒有任何子節點,該容器節點會被zk定期刪除(60s)

TTL節點:直接節點到期時間,到期後被zk定時刪除,通過系統配置zookeeper.extendedTypesEnable=true開啟

4.zk的資料持久化

zk的資料是執行在記憶體中,zk提供兩種持久化機制:

  事務日誌:zk把執行的命令以日誌的形式儲存在dataLogDir指定的檔案中,當前沒有指定,則儲存在dataDir指定的路徑中。

  資料快照:zk會在一定時間內做一次記憶體資料的快照,把該時刻的記憶體資料儲存在快照檔案中

  

  zk在恢復時先恢復快照檔案中的資料到記憶體中,再用日誌檔案中的資料做增量恢復,恢復速度快。

四、zookeeper客戶端的使用

  1.多節點型別的建立略

  2.查詢節點 ls /ls -R ,獲取資料get,詳細查詢get -s ,往節點裡面存資料set

  3.刪除節點

    普通刪除:delete

    樂觀鎖刪除:delete -v 版本號 節點

  4.許可權設定:

    註冊當前會話的賬號和密碼:addauth digest mj:999

    建立節點並設定許可權: create /test 666 auth:mj:999:cdwra

    在另外一個會話中必須先使用賬號密碼(addauth digest mj:999),才能夠擁有操作該節點的許可權。

五、Curator客戶端的使用

  curator是Netflix公司開源的一套zookeeper客戶端框架,封裝了大部分zookeeper的功能,如選舉、分散式鎖等。

  1.引入依賴

  

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>2.14.1</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
</dependency>

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.配置檔案中配置curator的屬性集
curator.retryCount=5
curator.elapsedTimeMs=5000
curator.connectString=121.43.37.22:2181
curator.sessionTimeoutMs=6000
curator.connectionTimeoutMs=5000
3.搞個包裝類讀取屬性集
@Data
@Component
@ConfigurationProperties(prefix = "curator")
public class WrapperZK {
private int retryCount;
private int elapsedTimeMs;
private String connectString;
private int sessionTimeoutMs;
private int connectionTimeoutMs;

}

4.curator配置類

@Configuration
public class CuratorConfig {

@Autowired
WrapperZK wrapperZK;

@Bean(initMethod = "start")
public CuratorFramework curatorFramework(){
return CuratorFrameworkFactory.newClient(
wrapperZK.getConnectString(),
wrapperZK.getSessionTimeoutMs(),
wrapperZK.getConnectionTimeoutMs(),
new RetryNTimes(wrapperZK.getRetryCount(),wrapperZK.getElapsedTimeMs())
);
}

}
5.測試一些基本的增刪改查
@Slf4j
@SpringBootTest
class ZkcuratorApplicationTests {

@Autowired
CuratorFramework curatorFramework;
@Test
void createNode ()throws Exception {
//新增持久節點
String path = curatorFramework.create().forPath("/curator-node2");
//新增臨時序號節點
String path1 = curatorFramework.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/curator-node", "some-data".getBytes());
System.out.println(String.format("curator create node :%s successfully.",path));

System.in.read();
}
@Test
public void testGetData () throws Exception {
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
System.out.println(new String(bytes));
}

@Test
public void setData() throws Exception {
curatorFramework.setData().forPath("/curator-node2","大力3".getBytes());
byte[] bytes = curatorFramework.getData().forPath("/curator-node2");
System.out.println(new String(bytes));
}

@Test
public void testCreateWithParent() throws Exception {
String pathWithParent="/node-parent/sub-node-1";
String path=curatorFramework.create().creatingParentsIfNeeded().forPath(pathWithParent);
System.out.println(String.format("curator create node :%s successfully",path));
}
@Test
public void testDelete() throws Exception {
String parentWithParent="/node-parent";
curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath(parentWithParent);
}

}
六、zk實現分散式鎖
1.zk中鎖的種類:讀鎖(共享鎖):都可以讀,上讀鎖的前提是沒有上寫鎖。
         寫鎖:只有得到寫鎖的才能寫,上寫鎖的前提是沒有上任何鎖。(舉一個栗子:讀鎖:小王沒有結婚,大家都可以找她約會,寫鎖:小王要跟小明結婚了,就不能跟其他人約會了,只能跟小明約會了)
2.zk上讀鎖:
    建立一個臨時序號節點,節點的資料是read,表示讀鎖
    獲取當前zk中序號比自己小的所有節點
    判斷最小節點是否是讀鎖:如果是讀鎖,則上鎖成功。
                如果是寫鎖,則上鎖失敗,為最小節點設定監聽,阻塞等待,zk的watch機制會當最小節點發生變化時通知當前節點,再執行第二步的流程
3.zk上寫鎖:
    建立一個臨時序號節點,節點的資料是write,表示是寫鎖
    獲取zk中所有的子節點
    判斷自己是否是最小的節點:如果是最小節點,則上鎖成功
                 如果不是,說明前面還有鎖,則上鎖失敗,監聽最小的節點,如果最小節點有變化,則再執行第二步。
4.羊群效應:如果用上述的上鎖方式,只要有一個節點發生變化,就會觸發其他節點的監聽時間,這樣的話對zk的壓力非常大。可以調成鏈式監聽,併發是順位的,只需要監聽前一位即可。(curator原始碼幫我們解決掉了這個問題)
              
七、zk的watch機制:
1.watch機制
客戶端監聽某個節點的變化,也就是呼叫了create、delete、setdata方法的時候,就會觸發znode上註冊的對應時間,請求watch的客戶端會收到zk的非同步通知,
get -w /test 監聽節點——只能觸發一次,在拿資料時用get -w /test,就可以一直監聽。監聽父節點,子節點發生變化不會觸發監聽。
客戶端使用了NIO的通訊模式監聽服務端的呼叫
2.zkCli客戶端使用watch
get -w:一次性監聽節點內容變化
ls -w:監聽目錄,建立和刪除一級子節點會收到通知
ls -R -w:監聽所有層級子節點的目錄
3.curator客戶端使用watch
@Test
public void addNodeListener() throws Exception {
NodeCache nodeCache = new NodeCache(curatorFramework, "/curator-node");
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
log.info("{}path nodeChanged:","/curator-node");
printNodeData();
}
});
nodeCache.start();
System.in.read();
}

public void printNodeData() throws Exception {
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
log.info("data:{},",new String(bytes));
}
4.curator實現讀寫鎖
@SpringBootTest
@Slf4j
public class TestReadWriteLock {

@Autowired
private CuratorFramework client;
@Test
public void testGetReadLock() throws Exception {
//讀寫鎖
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(client, "/lock");
//獲取讀鎖物件
InterProcessMutex readLock = interProcessReadWriteLock.readLock();
System.out.println("等待獲取讀鎖物件");
//獲取鎖
readLock.acquire();//嘗試去拿鎖
System.out.println("拿到鎖咯");
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
System.out.println(i);
}
//釋放鎖
readLock.release();
System.out.println("釋放鎖殺果");
}
@Test
public void testGetReadLock2() throws Exception {
//讀寫鎖
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(client, "/lock");
//獲取讀鎖物件
InterProcessMutex readLock = interProcessReadWriteLock.readLock();
System.out.println("等待獲取讀鎖物件");
//獲取鎖
readLock.acquire();//嘗試去拿鎖
System.out.println("拿到鎖咯");
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
System.out.println(i);
}
//釋放鎖
readLock.release();
System.out.println("釋放鎖殺果");
}


@Test
public void testGetWriteLock() throws Exception {
//讀寫鎖
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(client, "/lock");
//獲取讀鎖物件
InterProcessMutex writeLock = interProcessReadWriteLock.writeLock();
System.out.println("等待獲取寫鎖物件");
//獲取鎖
writeLock.acquire();//嘗試去拿鎖
System.out.println("拿到鎖咯");
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
System.out.println(i);
}
//釋放鎖
writeLock.release();
System.out.println("釋放鎖殺果");
}


}
八、zookeeper叢集
1.zookeeper叢集中的節點有三種角色。
 Leader:處理叢集中所有事務請求(既能寫,又能讀),叢集中只有一個Leader。   
 Follower:只能處理讀請求,參與Leader選舉。
 Observer:只能處理讀請求,提升叢集讀的效能,不參與Leader選舉。
2.叢集搭建:搭建4個節點,其中一個節點為ObServer
 1)建立4個節點的myid,並設定值編輯myid按順序寫入1、2、3、4 

  2)去conf目錄複製zoo.cfg,進去編輯,注意如果是在雲伺服器上搭建,必須要加上第三行,因為雲伺服器的ip地址不是本機網絡卡,雲伺服器好像沒有網絡卡,這個可以去百度研究下。

  

 server的第一個埠號是叢集之間通訊的埠,第二列埠是選舉的埠。

3)開放埠

4)啟動./zkServer.sh start ../conf/zoox.cfg,按順序啟動所有的叢集節點,啟動後使用
./zkServer.sh status ../conf/zoox.cfg觀察,發現第二臺是Leader
5)zkCli連線叢集

九、ZAB協議

1.ZAB:zookeeper叢集部署中會以一主多從的方式進行,為了保證資料的一致性,採用了ZAB協議,這個協議解決了zookeeper的崩潰恢復和主從資料同步的問題。

2.ZAB協議定義的四種節點狀態:

  Looking:選舉狀態

  Following:Follower節點所處的狀態

  Leading:Leader節點所處的狀態

  Observing:觀察者節點所處的狀態

3.叢集上線的選舉過程:

  4臺叢集配置為例(一般出去observer奇術個數較好,容易滿足過半的要求):啟動第一臺它的狀態為Looking,當第二臺啟動的時候就開始進行選舉。

      選票的格式(myid,zxid),zxid為事務id,這個伺服器上發生的增刪改都會使zxid+1。選票大小:優先zxid大的,然後才是myid大的

      選舉:每個節點生成一張自己的選票,將選票投給其他節點,舉個列子,第一臺的選票為(1,0)第二臺的選票為(2,0),然後第一臺將選票投給第二臺,第二臺投給第一臺,

      第一臺裡就有(1,0)(2,0)第二臺(2,0)(1,0),將各自最大的選票投給自己的投票箱,這時叢集中有4臺機器,除開observer有3臺,而投票箱裡只有一票,不滿足半數以上,

      開始第二輪選票:將上一輪最大的選票更新為自己的選票,並投給其他節點,所以都是(2,0) (2,0),將(2,0)投到自己的投票箱,這時候選票箱就有2張(2,0)了,2號機票數過半,

      Leader為第二臺,選舉結束,所以說按順序啟動,始終都是第二臺是Leader。

      第三臺啟動發現叢集已經選舉出了Leader,於是把自己作為Follower。

4.崩潰恢復時的Leader選舉
    叢集建立連線後,Leader會發送ping格式的空資料維持心跳(也是BIO),叢集中Follower會週期性的去和Leader建立的socket連線裡面去讀取ping格式的空資料,讀取不到資料時Leader就掛掉了,就會從Follower狀態重新進入Looking狀態,
    其他節點也如此,然後重新進入選舉狀態。此時Leader還沒選舉出來,不能對外提供服務。
5.主從伺服器之間的資料同步
    客戶端向主節點寫資料的情況:1.主節點先把資料先到自己的資料檔案中,並給自己返回一個ACK
                  2.Leader把資料廣播給Follower,Follower將資料寫到本地的資料檔案中
                  3.從節點返回ACK給Leader
                  4.Leader收到超過叢集半數的ACK就廣播commit給Follower
                  5.從節點收到commit後將資料檔案中的資料寫到記憶體中(二階段提交,先到資料檔案再到記憶體中)  
6.zookeeper中的NIO和BIO的應用
NIO:
  用於被客戶端連線的2181埠,使用的是NIO模式與客戶端建立連線,多個客戶端的請求都放在一個佇列裡面,不會進行阻塞,zookeeper一個一個處理這些請求,實現多路複用。
  客戶端開啟Watch時,也使用NIO等待zookeeper伺服器的回撥。一個客戶端監聽多個znode,同理。
BIO:
  叢集選舉時,多個節點之間的投票通訊埠使用BIO通訊                   
                  

十、CAP理論:一個分散式系統最多隻能同時滿足CAP中三項中的2項,即CP或者AP。
P:分割槽容錯性(必須滿足)
C:一致性 A:可用性 C和A只能滿足其一。舉個列子,小王往銀行系統存了500塊,銀行系統是冗餘部署的2個,通過負載均衡訪問,小王往A系統存了500,資料要往B裡面同步,這個時候因為銀行系統網路波動,系統下線又上線,又要進行資料同步,
在同步的過程中,整個銀行系統是否允許對外提供服務,提供服務的話可能查不到這500快,滿足可用性不滿足一致性。等資料同步完成後再提供服務的話,滿足一致性不滿足可用性。              
      
  BASE理論:
      基本可用:電商大促時,為了應對訪問激增,部分使用者可能會被引導到降級頁面,服務層也可能只提供降級服務,這就是損失部分可用性的體現。
      軟狀態:允許系統存在中間狀態,而中間狀態不會影響系統整體可用性。分散式儲存中一般一份資料至少會有3個副本,允許不同節點間副本資料同步的延時就是軟狀態的體現。
      最終一致性:系統中的所有資料副本經過一定時間後,最終能夠達到一致的狀態。最終一致性是一種特殊的弱一致性,強一致性和弱一致性相反。
  zookeeper在資料同步時,追求的不是強一致性(半數以上的ACK就commit),追求的是順序一致性(事務id的單挑遞增實現),最終都會同步成功,同步後事務id會發生變化。