分布式服務(wù)治理zookeeper原理及使用大全

[TOC]

zookeeper動(dòng)物管理員全局把控。提供了配置管理、服務(wù)發(fā)現(xiàn)等服務(wù)。其本身也是可以集群化的。實(shí)現(xiàn)上是基于觀察者模式。不想eureka/consul等同類產(chǎn)品需要心跳機(jī)制。他本身支持觀察與主動(dòng)觸發(fā)機(jī)制;千里之行始于足下,我們已經(jīng)探索了eureka、consul兩個(gè)服務(wù)注冊(cè)的中間件了。今天我們繼續(xù)學(xué)習(xí)另外一個(gè)作為服務(wù)注冊(cè)的服務(wù)。

本文將從zookeeper單機(jī)到集群的安裝講解;在從集群leader選舉機(jī)制的講解及數(shù)據(jù)同步的梳理。到最終的基于zookeeper實(shí)現(xiàn)的配置管理及分布式鎖的應(yīng)用。從點(diǎn)到面在到應(yīng)用帶你體會(huì)一把過山車

簡(jiǎn)介

  • Zookeeper 大家都知道是動(dòng)物管理員的意思。在大數(shù)據(jù)全家桶中他的作用也是管理。下面我們分別從安裝到使用來看看zk的優(yōu)美

中心化

服務(wù) 特點(diǎn) 中心化 CAP
eureka peer to peer 每個(gè)eureka服務(wù)默認(rèn)都會(huì)向集群中其他server注冊(cè)及拉去信息 去中心化 AP
consul 通過其中節(jié)點(diǎn)病毒式蔓延至整個(gè)集群 多中心化 CP
zookeeper 一個(gè)leader多個(gè)followes 中心化 CP

事務(wù)

  • 上面提到zookeeper是一個(gè)leader多個(gè)followers。

  • 除了中心化思想外,zookeeper還有個(gè)重要的特性就是事務(wù)。zookeeper數(shù)據(jù)操作是具有原子性的。

  • 如何理解zookeeper的事務(wù)呢,其實(shí)內(nèi)部是通過版本管理數(shù)據(jù)實(shí)現(xiàn)事務(wù)性的。zookeeper每個(gè)客戶端初始化時(shí)都會(huì)初始化一個(gè)operation。每個(gè)client連接是都有個(gè)內(nèi)部的session管理。同一個(gè)session操作都會(huì)有對(duì)應(yīng)的版本記錄,zxid這樣能保證數(shù)據(jù)的一個(gè)一致性。

downlaod

zookeeper3.5.9

單機(jī)安裝

  • 在上面的地址下載后進(jìn)行解壓tar -zxvf apache-zookeeper-3.5.9-bin.tar.gz
  • 官方提供的相當(dāng)于是個(gè)模板,這個(gè)時(shí)候直接啟動(dòng)會(huì)報(bào)錯(cuò)的。
  • 根據(jù)報(bào)錯(cuò)信息我們知道缺失zoo.cfg默認(rèn)配置文件。在conf目錄下官方給我們提供了zoo_sample.cfg模板配置文件。我們只需要復(fù)制改文件為zoo.cfg在此基礎(chǔ)上進(jìn)行修該cp zoo_sample.cfg zoo.cfg
  • 我們修改下data路徑就可以啟動(dòng)了。
  • 啟動(dòng)之后通過'zkServer.sh status'查看zookeeper運(yùn)行狀態(tài)。我們可以看到此時(shí)是單機(jī)模式啟動(dòng)的。
  • 通過jps我們也能夠看到zookeeper啟動(dòng)成功了。

集群搭建

  • 這里的集群為了方便就演示偽集群版。即在一臺(tái)服務(wù)器上布置三臺(tái)zk服務(wù)。

  • mkdir {zk1,zk2,zk3} , 首先創(chuàng)建zk1,zk2,zk3三個(gè)文件夾存放zk

  • 將之前解壓好的zk文件夾分別復(fù)制到三個(gè)創(chuàng)建zk中

  • 在zookeeper進(jìn)群中,每臺(tái)服務(wù)都需要有一個(gè)編號(hào)。我們還需要寫入每臺(tái)機(jī)器的編號(hào)
  • 然后重復(fù)我們單機(jī)版的過程。在根目錄創(chuàng)建data文件夾,然后修改conf中對(duì)應(yīng)的data配置。只不過這里還需要我們修改一下端口號(hào)。因?yàn)樵谕慌_(tái)機(jī)器上所以需要不同的端口號(hào)才行。
  • 最后新增集群內(nèi)部通訊端口

server.1=192.168.44.130:28881:38881
server.2=192.168.44.130:28882:38882
server.3=192.168.44.130:28883:38883

  • 上述的端口只要保證可用就行了。
  • 上面是zk1的配置,其他讀者自行配置。

  • 在配置總我們多了server的配置。這個(gè)server是集群配置的重點(diǎn)

server.1=192.168.44.139:28881:38881

  • server是固定寫法
  • .1 : 1就是我們之前每臺(tái)zk服務(wù)寫入的myid里的數(shù)字
  • 192.168.44.130: 表示我們zk所在服務(wù)ip
  • 28881: 在zookeeper集群中l(wèi)eader和follewers數(shù)據(jù)備份通信端口
  • 38881: 選舉機(jī)制的端口
  • 上面我是通過zkServer啟動(dòng)不同配置文件。你們也可以在不同的zk包下分別啟動(dòng)。最終效果是一樣的。啟動(dòng)完成之后我們通過jps查看可以看到多了三個(gè)zk服務(wù)。在單機(jī)版的時(shí)候jps我們知道zk的主啟動(dòng)是QuorumPeerMain。
  • 上圖是其中一個(gè)zk服務(wù)的目錄結(jié)構(gòu)。我們可以看到data目錄有數(shù)據(jù)產(chǎn)生了。這是zk集群?jiǎn)?dòng)之后生成的文件。

  • 集群?jiǎn)?dòng)完成了。但是我們現(xiàn)在對(duì)zk集群好像還是沒有太大的感知。比如說我們不知道誰是leader。 可以通過如下命令查看每臺(tái)zk的角色

  • 我們的zk2是leader角色。其他是follower

Cli連接

  • 在zookeeper包中還有一個(gè)zkCli.sh這個(gè)是zk的客戶端。通過他我們可以連接zookeeper服務(wù)并進(jìn)行操作。

zkCli.sh -server 192.168.44.131:1181 可以連接zk服務(wù)

  • 下面我們測(cè)試下對(duì)zk的操作會(huì)不會(huì)是集群化的。
  • 我們zkCli連接了zk1服務(wù)1181,并且創(chuàng)建的一個(gè)zk節(jié)點(diǎn)名為node。我們?cè)谌k2服務(wù)上同樣可以看到這個(gè)node節(jié)點(diǎn)。

  • 至此,我們zookeeper集群搭建完成,并且測(cè)試也已經(jīng)通過了。

集群容錯(cuò)

Master選舉

  • 我們已上述集群?jiǎn)?dòng)是為例,簡(jiǎn)述下集群選舉流程。

①、zk1啟動(dòng)時(shí),這個(gè)時(shí)候集群中只有一臺(tái)服務(wù)就是zk1。此時(shí)zk1給集群投票自然被zk1自己獲取。 此時(shí)zk1有一票
②、zk2啟動(dòng)時(shí),zk1,zk2都會(huì)都一票給集群。因?yàn)檫M(jìn)群中zk1(myid)小于zk2(myid),所以這兩票被zk2獲取。這里為什么會(huì)是zk2獲取到呢。zookeeper節(jié)點(diǎn)都一份坐標(biāo)zk=(myid,zxid);myid是每個(gè)zk服務(wù)配置的唯一項(xiàng)。zxid是zk服務(wù)的一個(gè)64位內(nèi)容。高32沒master選舉一次遞增一次并同時(shí)清空低32位。低32位是每發(fā)生一次數(shù)據(jù)事務(wù)遞增一次。所以zxid最高說明此zk服務(wù)數(shù)據(jù)越新。
③、zk2獲得兩票后,此時(shí)已經(jīng)獲得了集群半數(shù)以上的票數(shù),少數(shù)服從多數(shù)此時(shí)zk2已經(jīng)是準(zhǔn)leader了同時(shí)zk1切換為following 。此時(shí)zk1已經(jīng)是zk2的跟班了
④、zk3啟動(dòng)時(shí),按道理zk3應(yīng)該會(huì)收到三票。但是因?yàn)閦k1已經(jīng)站隊(duì)到zk2了。zk2作為準(zhǔn)leader是不可能給zk3投票的。所以zk3最多只有自己一票,zk3明知zk2獲得半數(shù)以上,已經(jīng)是民意所歸了。所以zk3為了自己的前途也就將自己的一票投給了zk2.

  • zookeeper的投票選舉機(jī)制赤裸裸的就是一個(gè)官場(chǎng)。充滿的人心

leader宕機(jī)重新選舉

  • 其實(shí)在啟動(dòng)階段zk2獲取到兩張投票是有一個(gè)PK的邏輯在里面的。上述啟動(dòng)階段的投票是個(gè)人的一個(gè)抽象化理解。

  • 在上面說myid高的不會(huì)給myid低的投票實(shí)際上是一種片面的理解。實(shí)際上是會(huì)進(jìn)行投票的,投票之后會(huì)進(jìn)行兩張票PK,將權(quán)重高的一張票投出去選舉leader。有集群管理者進(jìn)行統(tǒng)計(jì)投票并計(jì)數(shù)。

  • 下面我們來看看重新選舉是的邏輯。也是真正的leader選舉的邏輯。

①、zk2服務(wù)掛了,這個(gè)時(shí)候zk1,zk3立馬切換為looking狀態(tài),并分別對(duì)集群內(nèi)其他服務(wù)進(jìn)行投票
②、zk1收到自己的和其他服務(wù)投過來的票(1,0)、(3,0) 。zk1會(huì)斟酌這兩張票,基于我們提到的算法num=10*zxid+myid ,所以zk1會(huì)將(3,0)這張票投入計(jì)數(shù)箱中
③、zk3收到兩種票(3,0)、(1,0),同樣會(huì)將(3,0)投入計(jì)數(shù)箱
④、最終統(tǒng)計(jì)zk3獲得兩票勝出。

  • 上面的選舉才是真正的選舉。啟動(dòng)時(shí)期我們只是加入了我們自己的理解在里面。選舉完之后服務(wù)會(huì)切換成leader、follower狀態(tài)進(jìn)行工作

數(shù)據(jù)同步

  • 同樣先上圖
  • client如果直接將數(shù)據(jù)變更請(qǐng)求發(fā)送到leader端,則直接從圖中第三步開始發(fā)送proposal請(qǐng)求等待過半機(jī)制后再發(fā)送commit proposal。follower則會(huì)開始更新本地zxid并同步數(shù)據(jù)。
  • 如果client發(fā)送的是follower,則需要follower先將請(qǐng)求轉(zhuǎn)發(fā)至leader然后在重復(fù)上面的步驟。
  • 在數(shù)據(jù)同步期間為了保障數(shù)據(jù)強(qiáng)一致性。leader發(fā)送的proposal都是有序的。follower執(zhí)行的數(shù)據(jù)變更也都是有順序的。這樣能保證數(shù)據(jù)最終一致性。
    在一段時(shí)間內(nèi)比如說需要對(duì)變量a=5和1=1操作。如果a=5和a=1是兩個(gè)事物。如果leader通知follower進(jìn)行同步,zk1先a=1在a=5。則zk1中的a為5.zk3反之來則zk3中a=1;這樣就會(huì)造成數(shù)不一致。但是zookeeper通過znode節(jié)點(diǎn)有序排列保證了follower數(shù)據(jù)消費(fèi)也是有序的。在莫一時(shí)刻zk1執(zhí)行了a=5,這時(shí)候client查了zk1的a是5,雖然表面上是臟數(shù)據(jù)實(shí)際上是zk1未執(zhí)行完。等待zk1執(zhí)行完a=1.這就叫數(shù)據(jù)<red>最終一致性</red>。
  • 下面一張圖可能更加的形象,來自于網(wǎng)絡(luò)圖片

特色功能

服務(wù)治理

  • 在我們eureka、consul章節(jié)已經(jīng)介紹了springcloud注冊(cè)的細(xì)節(jié)了。今天我們還是同樣的操作。已payment和order模塊來講服務(wù)注冊(cè)到zookeeper上??纯葱Ч?/li>

<!-- SpringBoot整合zookeeper客戶端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>

  • 配置文件配置添加zookeeper

spring:
  cloud:
    zookeeper:
      connect-string: 192.168.44.131:2181

  • 和consul一樣,這里的注冊(cè)會(huì)將spring.application.name值注冊(cè)過去。eureka是將spring.application.name的大寫名稱注冊(cè)過去。這個(gè)影響的就是order訂單中調(diào)用的地址區(qū)別。

點(diǎn)我看源碼

  • 還是一樣的操作。啟動(dòng)order、兩個(gè)payment之后我們調(diào)用http://localhost/order/getpayment/123 可以看到結(jié)果是負(fù)載均衡了。

配置管理

  • 熟悉springcloud的都知道,springcloud是有一個(gè)配置中心的。里面主要借助git實(shí)現(xiàn)配置的實(shí)時(shí)更新。具體細(xì)節(jié)我們后面章節(jié)會(huì)慢慢展開,本次我們展示通過zookeeper實(shí)現(xiàn)配置中心管理。
  • 首先我們?cè)谖覀兊膒ayment模塊繼續(xù)開發(fā),引入zookeeper-config模塊

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>

  • 然后我們需要有個(gè)儲(chǔ)備知識(shí),在spring加載配置文件的順序,會(huì)先加載bootstrap文件然后是application文件。這里bootstrap我們也用yml格式文件。

spring:
  application:
    name: cloud-payment-service
  profiles:
    active: dev
  cloud:
    zookeeper:
      connect-string: 192.168.44.131:2181
      config:
        enabled: true
        root: config
        profileSeparator: ','
      discovery:
        enabled: true
      enabled: true

  • 上面這部分需要解釋下
key 解釋
spring.application.name 服務(wù)名
spring.profiles 環(huán)境名
spring.cloud.enabled 用于激活config自動(dòng)配置
spring.cloud.zookeeper.config.root zookeeper根路徑
spring.cloud.zookeeper.profilSeparator key分隔符

@Component
@ConfigurationProperties(prefix = "spring.datasources")
@Data
@RefreshScope
public class Db {
    private String url;
}

  • 系統(tǒng)中會(huì)讀取配置文件中的spring.datasources.url這個(gè)屬性值。結(jié)合我們的bootstrap.yml文件中。此時(shí)會(huì)去zookeeper系統(tǒng)中查找/config/cloud-payment-service,dev/spring.datasources.url這個(gè)值。
    -關(guān)于那個(gè)zookeeper的key是如何來的。細(xì)心觀察下可以發(fā)現(xiàn)他的規(guī)律

  • /${spring.cloud.zookeeper.root}/${spring.application.name}${spring.cloud.zookeeper.profileSeparator}${spring.profiles}/${實(shí)際的key}

  • 唯一注意的是需要在類上添加@RefreshScope , 這個(gè)注解是cloud提供的。包括到后面的config配置中心都離不開這個(gè)注解

  • 此時(shí)通過前文提到的zkCli連接zk服務(wù),然后創(chuàng)建對(duì)應(yīng)節(jié)點(diǎn)就可以了。zookeeper服務(wù)需要逐層創(chuàng)建。比如上面提到的/config/cloud-payment-services/spring.datasources.url,我們需要create /config然后create /config/cloud-payment-services在創(chuàng)建最后的內(nèi)容。

  • 我們可以提前在zk中創(chuàng)建好內(nèi)容。然后localhost:8001/payment/getUrl獲取內(nèi)容。然后通過set /config/cloud-payment-services/spring.datasources.url helloworld 在刷新接口就可以看到最新的helloworld了。這里不做演示。

  • zookeeper原生的創(chuàng)建命令因?yàn)樾枰粚右粚觿?chuàng)建這還是很麻煩。還有我們有zkui這個(gè)插件。這個(gè)提供了zookeeper的可視化操作。還支持我們文件導(dǎo)入。上述的配置內(nèi)容我們只需要導(dǎo)入以下內(nèi)容的文件即可


/config/cloud-payment-services=spring.datasources.url=hello

zkui安裝使用

  • 上面我們提到了一個(gè)工具zkui,顧名思義他是zookeeper可視化工具。我們直接下載github源碼。
  • 官網(wǎng)安裝步驟也很簡(jiǎn)單,因?yàn)樗褪且粋€(gè)jar服務(wù)。
  • pom同級(jí)執(zhí)行maven clean install 打包jar 然后nohup java -jar zkui-2.0-SNAPSHOT-jar-with-dependencies.jar & 后臺(tái)啟動(dòng)就行了。默認(rèn)端口9090
  • 默認(rèn)用戶名密碼 官網(wǎng)都給了。 admin:manager

  • 在jar包同級(jí)官網(wǎng)提供了一份zookeeper配置模板。在里面我們可以配置我們的zookeeper。 如果是集群就配置多個(gè)就行了。
  • 關(guān)于這個(gè)zkui的使用這里不多介紹,就是一個(gè)可視化。程序員必備技能應(yīng)該都會(huì)使用的。

  • 我們項(xiàng)目里使用的都是單機(jī)的zookeeper,但是在上面安裝的時(shí)候我們也有集群zookeeper。端口分別是1181,1182,1183. 我們?cè)趜kui的配置文件config.cfg中配置集群即可。


分布式鎖

分布式鎖常用在共享資源的獲取上。在分布式系統(tǒng)的中我們需要協(xié)調(diào)每個(gè)服務(wù)的調(diào)度。如果不進(jìn)行控制的話很大程度會(huì)造成資源的浪費(fèi)甚至是資源溢出。常見的就是我們的庫(kù)存。

  • 之前我們springcloud中有一個(gè)order和兩個(gè)payment服務(wù)。payment主要用來做支付操作。如果訂單成功之后需要調(diào)用payment進(jìn)行扣款。這時(shí)候金額相當(dāng)于資源。這種資源對(duì)payment兩個(gè)服務(wù)來說是互斥操作。兩個(gè)payment過來操作金額時(shí)必須先后順序執(zhí)行。

mysql隔離控制

  • 在以前分布式還不是很普及的時(shí)候我們正常處理這些操作時(shí)都是結(jié)束數(shù)據(jù)庫(kù)的事務(wù)隔離級(jí)別。
隔離級(jí)別 隔離級(jí)別 現(xiàn)象
read uncommit 讀未提交 產(chǎn)生臟讀
read commited 讀已提交 幻讀(insert、delete)、不可重復(fù)讀(update)
repeatable read 可重復(fù)度 幻讀
serializable 串行化 無問題、效率變慢
  • 基于mysql隔離級(jí)別我們可以設(shè)置數(shù)據(jù)庫(kù)為串行模式。但是帶來的問題是效率慢,所有的sql執(zhí)行都會(huì)串行。

mysql鎖

  • 隔離級(jí)別雖然可以滿足但是帶來的問題確實(shí)不可接受。下面就會(huì)衍生出mysql鎖。我們可以單獨(dú)建一張表有數(shù)據(jù)代表上述成功。否則上鎖失敗。這樣我們?cè)诓僮鹘痤~扣減時(shí)先判斷下這張表有沒有數(shù)據(jù)進(jìn)行上鎖。但是如果上鎖之后會(huì)造成死鎖現(xiàn)象。因?yàn)槌绦虍惓_t遲沒有釋放鎖就會(huì)造成程序癱瘓。
  • 這個(gè)時(shí)候我們可以定時(shí)任務(wù)清除鎖。這樣至少保證其他線程可用。

redis鎖

  • 因?yàn)閞edis本身有失效屬性,我們不必?fù)?dān)心死鎖問題。且redis是內(nèi)存操作速度比mysql快很多。關(guān)于redis鎖的實(shí)現(xiàn)可以參考我的其他文章redis分布式鎖.

zookeeper鎖

  • 因?yàn)閦ookeeper基于觀察者模式,我們上鎖失敗后可以監(jiān)聽對(duì)應(yīng)的值直到他失效時(shí)我們?cè)谶M(jìn)行我們的操作這樣能夠保證我們有序處理業(yè)務(wù),從而實(shí)現(xiàn)鎖的功能。
  • 上圖是兩個(gè)線程上鎖的簡(jiǎn)易圖示。在zookeeper中實(shí)現(xiàn)分布式鎖主要依賴CuratorFramework、InterProcessMutex兩個(gè)類

CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.44.131:2181", new ExponentialBackoffRetry(1000, 3));
    client.start();
    InterProcessMutex mutex = new InterProcessMutex(client, "/config/test");
    long s = System.currentTimeMillis();
    boolean acquire = mutex.acquire(100, TimeUnit.SECONDS);
    if (acquire) {
        long e = System.currentTimeMillis();
        System.out.println(e - s+"@@@ms");
        System.out.println("lock success....");
    }

  • 最終InterProcessMutex實(shí)現(xiàn)加鎖。加鎖會(huì)在指定的key上添加一個(gè)新的key且?guī)в芯幪?hào)。此時(shí)線程中的編號(hào)和獲取的/config/test下集合編號(hào)最小值相同的話則上鎖成功。T2則上鎖失敗,此時(shí)會(huì)想前一個(gè)序號(hào)的key添加監(jiān)聽。即當(dāng)00001失效時(shí)則00002對(duì)應(yīng)的T2就會(huì)獲取到鎖。這樣可以保證隊(duì)列的有序進(jìn)行。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容