A. zookeeper集群為什麼是3個以上節點
Apache Zookeeper是我最近遇到的最酷的技術,我是在研究Solr Cloud功能的時候發現的。Solr的分布式計算讓我印象深刻。你只要開啟一個新的實例就能自動在Solr Cloud中找到。它會將自己分派到某個分片中,並確定出自己是一個Leader(源)還是一個副本。不一會兒,你就可以在你的那些伺服器上查詢到了。即便某些伺服器宕機了也可以繼續工作。非常動態、聰明、酷。
將運行多個應用程序作為一個邏輯程序並不是什麼新玩意。事實上,我在幾年前就已寫過類似的軟體。這種架構比較讓人迷惑,使用起來也費勁。為此Apache Zookeeper提供了一套工具用於管理這種軟體。
為什麼叫Zoo?「因為要協調的分布式系統是一個動物園」。
在本篇文章中,我將說明如何使用PHP安裝和集成Apache ZooKeeper。我們將通過service來協調各個獨立的PHP腳本,並讓它們同意某個成為Leader(所以稱作Leader選舉)。當Leader退出(或崩潰)時,worker可檢測到並再選出新的leader。
ZooKeeper是一個中性化的Service,用於管理配置信息、命名、提供分布式同步,還能組合Service。所有這些種類的Service都會在分布式應用程序中使用到。每次編寫這些Service都會涉及大量的修bug和競爭情況。正因為這種編寫這些Service有一定難度,所以通常都會忽視它們,這就使得在應用程序有變化時變得難以管理應用程序。即使處理得當,實現這些服務的不同方法也會使得部署應用程序變得難以管理。
雖然ZooKeeper是一個Java應用程序,但C也可以使用。這里就有個PHP的擴展,由Andrei Zmievski在2009創建並維護。你可以從PECL中下載,或從GitHub中直接獲取PHP-ZooKeeper。
要使用該擴展你首先要安裝ZooKeeper。可以從官方網站下載。
$ tar zxfv zookeeper-3.4.5.tar.gz
$ cd zookeeper-3.4.5/src/c
$ ./configure --prefix=/usr/
$ make
$ sudo make install
這樣就會安裝ZooKeeper的庫和頭文件。現在准備編譯PHP擴展。
$ cd$ git clone https://github.com/andreiz/php-zookeeper.git
$ cd php-zookeeper
$ phpize
$ ./configure
$ make
$ sudo make install
將「zookeeper.so」添加到PHP配置中。
$ vim /etc/php5/cli/conf.d/20-zookeeper.ini
因為我不需要運行在web服務環境下,所以這里我只編輯了CLI的配置。將下面的行復制到ini文件中。
extension=zookeeper.so
使用如下命令來確定擴展是否已起作用。
$ php -m | grep zookeeper
zookeeper
現在是時候運行ZooKeeper了。目前唯一還沒有做的是配置。創建一個用於存放所有service數據的目錄。
$ mkdir /home/you-account/zoo
$ cd$ cd zookeeper-3.4.5/
$ cp conf/zoo_sample.cfg conf/zoo.cfg
$ vim conf/zoo.cfg
找到名為「dataDir」的屬性,將其指向「/home/you-account/zoo」目錄。
$ bin/zkServer.sh start
$ bin/zkCli.sh -server 127.0.0.1:2181[zk: 127.0.0.1:2181(CONNECTED) 14] create /test 1
Created /test[zk: 127.0.0.1:2181(CONNECTED) 19] ls /[test, zookeeper]
此時,你已成功連到了ZooKeeper,並創建了一個名為「/test」的znode(稍後我們會用到)。ZooKeeper以樹形結構保存數據。這很類似於文件系統,但「文件夾」(譯者註:這里指非最底層的節點)又和文件很像。znode是ZooKeeper保存的實體。Node(節點)的說法很容易被混淆,所以為了避免混淆這里使用了znode。
因為我們稍後還會使用,所以這里我們讓客戶端保持連接狀態。開啟一個新窗口,並創建一個zookeeperdemo1.php文件。
<?php
class ZookeeperDemo extends Zookeeper {
public function watcher( $i, $type, $key ) {
echo "Insider Watcher\n";
// Watcher gets consumed so we need to set a new one
$this->get( '/test', array($this, 'watcher' ) );
}
}
$zoo = new ZookeeperDemo('127.0.0.1:2181');$zoo->get( '/test', array($zoo, 'watcher' ) );
while( true ) {
echo '.';
sleep(2);}
現在運行該腳本。
$ php zookeeperdemo1.php
此處應該會每隔2秒產生一個點。現在切換到ZooKeeper客戶端,並更新「/test」值。
[zk: 127.0.0.1:2181(CONNECTED) 20] set /test foo
這樣就會靜默觸發PHP腳本中的「Insider Watcher」消息。怎麼會這樣的?
ZooKeeper提供了可以綁定在znode的監視器。如果監視器發現znode發生變化,該service會立即通知所有相關的客戶端。這就是PHP腳本如何知道變化的。Zookeeper::get方法的第二個參數是回調函數。當觸發事件時,監視器會被消費掉,所以我們需要在回調函數中再次設置監視器。
現在你可以准備創建分布式應用程序了。其中的挑戰是讓這些獨立的程序決定哪個(是leader)協調它們的工作,以及哪些(是worker)需要執行。這個處理過程叫做leader選舉,在ZooKeeper Recipes and Solutions你能看到相關的實現方法。
這里簡單來說就是,每個處理(或伺服器)緊盯著相鄰的那個處理(或伺服器)。如果一個已被監視的處理(也即Leader)退出或者崩潰了,監視程序就會查找其相鄰(此時最老)的那個處理作為Leader。
在真實的應用程序中,leader會給worker分配任務、監控進程和保存結果。這里為了簡化,我跳過了這些部分。
創建一個新的PHP文件,命名為worker.php。
<?php
class Worker extends Zookeeper {
const CONTAINER = '/cluster';
protected $acl = array(
array(
'perms' => Zookeeper::PERM_ALL,
'scheme' => 'world',
'id' => 'anyone' ) );
private $isLeader = false;
private $znode;
public function __construct( $host = '', $watcher_cb = null, $recv_timeout = 10000 ) {
parent::__construct( $host, $watcher_cb, $recv_timeout );
}
public function register() {
if( ! $this->exists( self::CONTAINER ) ) {
$this->create( self::CONTAINER, null, $this->acl );
}
$this->znode = $this->create( self::CONTAINER . '/w-',
null,
$this->acl,
Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE );
$this->znode = str_replace( self::CONTAINER .'/', '', $this->znode );
printf( "I'm registred as: %s\n", $this->znode );
$watching = $this->watchPrevious();
if( $watching == $this->znode ) {
printf( "Nobody here, I'm the leader\n" );
$this->setLeader( true ); }
else {
printf( "I'm watching %s\n", $watching );
}
}
public function watchPrevious() {
$workers = $this->getChildren( self::CONTAINER );
sort( $workers );
$size = sizeof( $workers );
for( $i = 0 ; $i < $size ; $i++ ) {
if( $this->znode == $workers[ $i ] ) {
if( $i > 0 ) {
$this->get( self::CONTAINER . '/' . $workers[ $i - 1 ], array( $this, 'watchNode' ) );
return $workers[ $i - 1 ];
}
return $workers[ $i ];
}
}
throw new Exception( sprintf( "Something went very wrong! I can't find myself: %s/%s",
self::CONTAINER,
$this->znode ) );
}
public function watchNode( $i, $type, $name ) {
$watching = $this->watchPrevious();
if( $watching == $this->znode ) {
printf( "I'm the new leader!\n" );
$this->setLeader( true );
}
else {
printf( "Now I'm watching %s\n", $watching ); }
}
public function isLeader() {
return $this->isLeader;
}
public function setLeader($flag) {
$this->isLeader = $flag;
}
public function run() {
$this->register();
while( true ) {
if( $this->isLeader() ) {
$this->doLeaderJob();
}
else {
$this->doWorkerJob();
}
sleep( 2 );
}
}
public function doLeaderJob() {
echo "Leading\n";
}
public function doWorkerJob() {
echo "Working\n";
}
}
$worker = new Worker( '127.0.0.1:2181' );$worker->run();
打開至少3個終端,在每個終端中運行以下腳本:
# term1
$ php worker.php
I'm registred as: w-0000000001Nobody here, I'm the leader
Leading
# term2
$ php worker.php
I'm registred as: w-0000000002I'm watching w-0000000001
Working
# term3
$ php worker.php
I'm registred as: w-0000000003I'm watching w-0000000002
Working
現在模擬Leader崩潰的情形。使用Ctrl+c或其他方法退出第一個腳本。剛開始不會有任何變化,worker可以繼續工作。後來,ZooKeeper會發現超時,並選舉出新的leader。
雖然這些腳本很容易理解,但是還是有必要對已使用的Zookeeper標志作注釋。
$this->znode = $this->create( self::CONTAINER . '/w-', null, $this->acl, Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE );
每個znode都是EPHEMERAL和SEQUENCE的。
EPHEMRAL代表當客戶端失去連接時移除該znode。這就是為何PHP腳本會知道超時。SEQUENCE代表在每個znode名稱後添加順序標識。我們通過這些唯一標識來標記worker。
在PHP部分還有些問題要注意。該擴展目前還是beta版,如果使用不當很容易發生segmentation fault。比如,不能傳入普通函數作為回調函數,傳入的必須為方法。我希望更多PHP社區的同仁可以看到Apache ZooKeeper的好,同時該擴展也會獲得更多的支持。
ZooKeeper是一個強大的軟體,擁有簡潔和簡單的API。由於文檔和示例都做的很好,任何人都可以很容易的編寫分布式軟體。讓我們開始吧,這會很有趣的。
B. Zookpeer是什麼在系統中如何起作用
Zookeeper分布式服務框架是Apache Hadoop的一個子項目,它主要是用來解決分布式應用中經常遇到的一些數據管理問題。如:統一命名服務、狀態同步服務、集群管理、分布式應用配置項的管理等。
我們先看看它都提供了哪些功能,然後再看看使用它的這些功能能做點什麼。
簡單的說,zookeeper=文件系統+通知機制。
Zookeeper維護一個類似文件系統的數據結構:
每個子目錄項如 NameService 都被稱作為 znode,和文件系統一樣,我們能夠自由的增加、刪除znode,在一個znode下增加、刪除子znode,唯一的不同在於znode是可以存儲數據的。
客戶端注冊監聽它關心的目錄節點,當目錄節點發生變化(數據改變、被刪除、子目錄節點增加刪除)時,zookeeper會通知客戶端。
這個似乎最簡單,在zookeeper的文件系統里創建一個目錄,即有唯一的path。在我們使用tborg無法確定上遊程序的部署機器時即可與下遊程序約定好path,通過path即能互相探索發現,不見不散了。
程序總是需要配置的,如果程序分散部署在多台機器上,要逐個改變配置就變得困難。
可以把這些配置全部放到zookeeper上去,保存在 Zookeeper 的某個目錄節點中,然後所有相關應用程序對這個目錄節點進行監聽,一旦配置信息發生變化,每個應用程序就會收到 Zookeeper 的通知,然後從 Zookeeper 獲取新的配置信息應用到系統中就好。
集群管理無在乎兩點:是否有機器退出和加入、選舉master。
對於第一點,所有機器約定在父目錄GroupMembers下創建臨時目錄節點,然後監聽父目錄節點的子節點變化消息。一旦有機器掛掉,該機器與 zookeeper的連接斷開,其所創建的臨時目錄節點被刪除,所有其他機器都收到通知:某個兄弟目錄被刪除,於是,所有人都知道:它下船了。當然又會有新機器加入,也是類似:所有機器收到通知---新兄弟目錄加入,highcount又有了,有人上船了。
對於第二點,我們假設機器創建臨時順序編號目錄節點,每次選取編號最小的機器作為master就好。
有了zookeeper的一致性文件系統,鎖的問題變得容易。鎖服務可以分為兩類,一個是保持獨占,另一個是控制時序。
對於第一類,我們將zookeeper上的一個znode看作是一把鎖,通過createznode的方式來實現。所有客戶端都去創建 /distribute_lock 節點,最終成功創建的那個客戶端也即擁有了這把鎖。廁所有言:來也沖沖,去也沖沖,用完刪除掉自己創建的distribute_lock 節點就釋放出鎖。
對於第二類, /distribute_lock 已經預先存在,所有客戶端在它下面創建臨時順序編號目錄節點,和選master一樣,編號最小的獲得鎖,用完刪除,依次方便。
兩種類型的隊列:
1、 同步隊列,當一個隊列的成員都聚齊時,這個隊列才可用,否則一直等待所有成員到達。
2、隊列按照 FIFO 方式進行入隊和出隊操作。
第一類,在約定目錄下創建臨時目錄節點,監聽節點數目是否是我們要求的數目。
第二類,和分布式鎖服務中的控制時序場景基本原理一致,入列有編號,出列按編號。
Zookeeper中的角色主要有以下三類:
系統模型如圖所示:
Zookeeper的核心是原子廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫做Zab協議。Zab協議有兩種模式,它們分 別是恢復模式(選主)和廣播模式(同步)。當服務啟動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數Server完成了和 leader的狀態同步以後,恢復模式就結束了。狀態同步保證了leader和Server具有相同的系統狀態。
為了保證事務的順序一致性,zookeeper採用了遞增的事務id號(zxid)來標識事務。所有的提議(proposal)都在被提出的時候加上 了zxid。實現中zxid是一個64位的數字,它高32位是epoch用來標識leader關系是否改變,每次一個leader被選出來,它都會有一個 新的epoch,標識當前屬於那個leader的統治時期。低32位用於遞增計數。
每個Server在工作過程中有三種狀態:
當leader崩潰或者leader失去大多數的follower,這時候zk進入恢復模式,恢復模式需要重新選舉出一個新的leader,讓所有的 Server都恢復到一個正確的狀態。Zk的選舉演算法有兩種:一種是基於basic paxos實現的,另外一種是基於fast paxos演算法實現的。系統默認的選舉演算法為fast paxos。先介紹basic paxos流程:
通過流程分析我們可以得出:要使Leader獲得多數Server的支持,則Server總數必須是奇數2n+1,且存活的Server的數目不得少於n+1.
選完leader以後,zk就進入狀態同步過程。
Leader主要有三個功能:
PING消息是指Learner的心跳信息;REQUEST消息是Follower發送的提議信息,包括寫請求及同步請求;ACK消息是 Follower的對提議的回復,超過半數的Follower通過,則commit該提議;REVALIDATE消息是用來延長SESSION有效時間。
Leader的工作流程簡圖如下所示,在實際實現中,流程要比下圖復雜得多,啟動了三個線程來實現功能。
Follower主要有四個功能:
Follower的消息循環處理如下幾種來自Leader的消息:
Follower的工作流程簡圖如下所示,在實際實現中,Follower是通過5個線程來實現功能的。
https://blog.csdn.net/xinguan1267/article/details/38422149
https://blog.csdn.net/gs80140/article/details/51496925
https://www.2cto.com/kf/201708/668587.html
https://blog.csdn.net/milhua/article/details/78931672
P.S. 這篇文章是本人對網路上關於ZK的文章閱讀之後整理所得,作為入門級的了解。個人覺得看了上面的內容就能基本了解Zookeeper的作用了,後面在結合實際項目使用加深自己的了解。
end
C. zkparking資料庫配置程序
摘要 這邊給您查詢分析到每台機器的應用程序都需要連接資料庫,而資料庫的配置信息(連接信息),這時候放在機器本地的話不方面(機器多,需要一個個改配置信息),這就用到Zookeeper,把資料庫的配置信息放到配置中心,利用Zookeeper節點可以存儲數據的特性,然後各台機器可以使用JavaAPI去獲取Zookeeper中資料庫的配置信息。每一個應用都在Zookeeper節點注冊監聽器,一旦節點信息改變,各台機器就獲取信息,使用最新的信息連接資料庫,這樣優點一是方便了管理(只放置一份數據在配置中心,沒必要放到多個機器上去),二是一旦配置改了,就做一個發布的動作即可。
D. zk框架問題
java 代碼
detail.addEventListener("fulfill", new EventListener(){
@Override
public void onEvent(Event arg0) throws Exception {
}
});
detail.addEventListener("onOpen", new EventListener(){
@Override
public void onEvent(Event arg0) throws Exception {
}
});
E. zk是什麼意思
1、ZK是一套以AJAX/XUL/Java為基礎的網頁應用程序開發框架,用於豐富網頁應用程序的使用界面。
2、ZK是科視界股份有限公司注冊申請的品牌,品牌產品有光導電子液位儀。
3、zk電影網,主要為廣大影視迷提供最新最好看的電視劇、電影、動畫片及播放服務。網站內容綠色健康,頁面簡潔大方。無廣告無彈窗,24小時安全監控,保證沒病毒沒不良內容。
ZK發展理念
zk電影網自2012年07月上線以來,經歷了種種不同在困難,在風格設計與內容發布方面我投入了不少成本與精力之外,更是網站管理人員對網站的精心維護,然而,網站的日流量正在日益飆升,努力打造成為一個廣大網民喜歡的無廣告彈窗、綠色安全的電影體驗網站。
ZK程序包含了一個以AJAX為基礎、事件驅動(event-driven)、高互動性的引擎。
以上內容參考:網路-ZK (程序)、網路-ZK (商標)、網路-zk電影網
F. java程序員在面試中被問到如何配置多數據源以及如何配置多數據源下的分布式事務,該怎麼回答看清再做答
你好,我來先回答你的第一個問題:
通常多數據源,在spring中配置如下,如果你想切換環境ENV 的值,在property中
<bean id="placeholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true"></property>
<property name="" value="true"></property>
<property name="nullValue" value="NULL"></property>
<property name="locations">
<list>
<value>jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="com.spring..JDBCConfig">
<property name="driverClassName" value="${${Env}.jdbc.driverClassName}"></property>
<property name="url" value="${${Env}.jdbc.url}"></property>
<property name="username" value="${${Env}.jdbc.username1}"></property>
<property name="password" value="${${Env}.jdbc.password}"></property>
</bean>
jdbc.properties
*****************************
Env=PROD
jdbc.driverClassName=${${Env}.jdbc.driverClassName}
jdbc.url=${${Env}.jdbc.url}
jdbc.username=${${Env}.jdbc.username}
jdbc.password=${${Env}.jdbc.password}
######### JDBC Configuration for DEV Environment ###############
DEV.jdbc.driverClassName=com.mysql.jdbc.Driver
DEV.jdbc.url=jdbc:mysql://localhost:3306/devportal
DEV.jdbc.username=DEVuser
DEV.jdbc.password=DEVpwd
######### JDBC Configuration for UAT Environment ############
UAT.jdbc.driverClassName=com.mysql.jdbc.Driver
UAT.jdbc.url=jdbc:mysql://localhost:3306/UATportal
UAT.jdbc.username=UATuser
UAT.jdbc.password=UATpwd
########## JDBC Configuration for PROD Environment ############
PROD.jdbc.driverClassName=com.mysql.jdbc.Driver
PROD.jdbc.url=jdbc:mysql://localhost:3306/portal
PROD.jdbc.username=root
PROD.jdbc.password=admin,
我這里有三套環境,分別是DEV,UAT和PROD,這種方式可以靈活切換的。
我再回答你的第二個問題:
還請你去http://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/html/transaction.html這里看下,很詳細,不過是英文的哦
G. 易語言寫多個配置項
.版本 2
.程序集 窗口程序集1
.程序集變數 按鈕, 按鈕, , "0"
.子程序 __啟動窗口_創建完畢
' 原理上是這樣的,但方法可以改變下~並不是說一定要寫N條代碼
' 如果按這種邏輯編程,我想很多程序的體積會比現在大上好幾倍吧!
' 下面我以按鈕為例寫N個配置項,方法可根椐需求而改變,不要局限於某種方法!
按鈕1.可視 = 假 ' 用於復制按鈕
按鈕2.標題 = 「讀配置」
按鈕3.標題 = 「寫配置」
.子程序 _按鈕2_被單擊, , , 讀配置
讀配置 ()
.子程序 _按鈕3_被單擊, , , 寫配置
寫配置 ()
.子程序 讀配置
創建按鈕 (12, 真) ' 注意使用此程序,不要重復創建了!
.子程序 銷毀按鈕
.局部變數 n, 整數型
.計次循環首 (取數組成員數 (按鈕), n)
.如果真 (是否已創建 (按鈕 [n]))
按鈕 [n].銷毀 ()
.如果真結束
.計次循環尾 ()
.子程序 創建按鈕
.參數 數量
.參數 讀, 邏輯型, 可空
.局部變數 n, 整數型
銷毀按鈕 () ' 最好在重新創建時 銷毀 之前創建的按鈕
按鈕1.可視 = 假
重定義數組 (按鈕, 假, 數量)
.計次循環首 (數量, n)
復制窗口組件 (按鈕1, 按鈕 [n])
按鈕 [n].左邊 = n × 80 - 70
.如果 (讀)
按鈕 [n].標題 = 讀配置項 (取運行目錄 () + 「\配置.ini」, 「按鈕配置」, 「按鈕」 + 到文本 (n), 「無」)
.否則
按鈕 [n].標題 = 「按鈕」 + 到文本 (取隨機數 (1, 99))
.如果結束
按鈕 [n].可視 = 真
.計次循環尾 ()
.子程序 寫配置
.局部變數 n, 整數型
創建按鈕 (12) ' 注意使用此程序,不要重復創建了!
.計次循環首 (取數組成員數 (按鈕), n)
寫配置項 (取運行目錄 () + 「\配置.ini」, 「按鈕配置」, 「按鈕」 + 到文本 (n), 按鈕 [n].標題)
.計次循環尾 ()
H. zookeeper怎麼實現分布式鎖
1. 利用節點名稱的唯一性來實現共享鎖
ZooKeeper抽象出來的節點結構是一個和unix文件系統類似的小型的樹狀的目錄結構。ZooKeeper機制規定:同一個目錄下只能有一個唯一的文件名。例如:我們在Zookeeper目錄/test目錄下創建,兩個客戶端創建一個名為Lock節點,只有一個能夠成功。
演算法思路: 利用名稱唯一性,加鎖操作時,只需要所有客戶端一起創建/test/Lock節點,只有一個創建成功,成功者獲得鎖。解鎖時,只需刪除/test/Lock節點,其餘客戶端再次進入競爭創建節點,直到所有客戶端都獲得鎖。
基於以上機制,利用節點名稱唯一性機制的共享鎖演算法流程如圖所示:
該共享鎖實現很符合我們通常多個線程去競爭鎖的概念,利用節點名稱唯一性的做法簡明、可靠。
由上述演算法容易看出,由於客戶端會同時收到/test/Lock被刪除的通知,重新進入競爭創建節點,故存在"驚群現象"。
使用該方法進行測試鎖的性能列表如下:
總結 這種方案的正確性和可靠性是ZooKeeper機制保證的,實現簡單。缺點是會產生「驚群」效應,假如許多客戶端在等待一把鎖,當鎖釋放時候所有客戶端都被喚醒,僅僅有一個客戶端得到鎖。
2. 利用臨時順序節點實現共享鎖的一般做法
首先介紹一下,Zookeeper中有一種節點叫做順序節點,故名思議,假如我們在/lock/目錄下創建節3個點,ZooKeeper集群會按照提起創建的順序來創建節點,節點分別為/lock/0000000001、/lock/0000000002、/lock/0000000003。
ZooKeeper中還有一種名為臨時節點的節點,臨時節點由某個客戶端創建,當客戶端與ZooKeeper集群斷開連接,則開節點自動被刪除。
利用上面這兩個特性,我們來看下獲取實現分布式鎖的基本邏輯:
客戶端調用create()方法創建名為「locknode/guid-lock-」的節點,需要注意的是,這里節點的創建類型需要設置為EPHEMERAL_SEQUENTIAL。
客戶端調用getChildren(「locknode」)方法來獲取所有已經創建的子節點,同時在這個節點上注冊上子節點變更通知的Watcher。
客戶端獲取到所有子節點path之後,如果發現自己在步驟1中創建的節點是所有節點中序號最小的,那麼就認為這個客戶端獲得了鎖。
如果在步驟3中發現自己並非是所有子節點中最小的,說明自己還沒有獲取到鎖,就開始等待,直到下次子節點變更通知的時候,再進行子節點的獲取,判斷是否獲取鎖。
釋放鎖的過程相對比較簡單,就是刪除自己創建的那個子節點即可。
上面這個分布式鎖的實現中,大體能夠滿足了一般的分布式集群競爭鎖的需求。這里說的一般性場景是指集群規模不大,一般在10台機器以內。
不過,細想上面的實現邏輯,我們很容易會發現一個問題,步驟4,「即獲取所有的子點,判斷自己創建的節點是否已經是序號最小的節點」,這個過程,在整個分布式鎖的競爭過程中,大量重復運行,並且絕大多數的運行結果都是判斷出自己並非是序號最小的節點,從而繼續等待下一次通知——這個顯然看起來不怎麼科學。客戶端無端的接受到過多的和自己不相關的事件通知,這如果在集群規模大的時候,會對Server造成很大的性能影響,並且如果一旦同一時間有多個節點的客戶端斷開連接,這個時候,伺服器就會像其餘客戶端發送大量的事件通知——這就是所謂的驚群效應。而這個問題的根源在於,沒有找准客戶端真正的關注點。
我們再來回顧一下上面的分布式鎖競爭過程,它的核心邏輯在於:判斷自己是否是所有節點中序號最小的。於是,很容易可以聯想的到的是,每個節點的創建者只需要關注比自己序號小的那個節點。
3、利用臨時順序節點實現共享鎖的改進實現
下面是改進後的分布式鎖實現,和之前的實現方式唯一不同之處在於,這里設計成每個鎖競爭者,只需要關注」locknode」節點下序號比自己小的那個節點是否存在即可。
演算法思路:對於加鎖操作,可以讓所有客戶端都去/lock目錄下創建臨時順序節點,如果創建的客戶端發現自身創建節點序列號是/lock/目錄下最小的節點,則獲得鎖。否則,監視比自己創建節點的序列號小的節點(比自己創建的節點小的最大節點),進入等待。
對於解鎖操作,只需要將自身創建的節點刪除即可。
具體演算法流程如下圖所示:
使用上述演算法進行測試的的結果如下表所示:
該演算法只監控比自身創建節點序列號小(比自己小的最大的節點)的節點,在當前獲得鎖的節點釋放鎖的時候沒有「驚群」。
總結 利用臨時順序節點來實現分布式鎖機制其實就是一種按照創建順序排隊的實現。這種方案效率高,避免了「驚群」效應,多個客戶端共同等待鎖,當鎖釋放時只有一個客戶端會被喚醒。
4、使用menagerie
其實就是對方案3的一個封裝,不用自己寫代碼了。直接拿來用就可以了。
menagerie基於Zookeeper實現了java.util.concurrent包的一個分布式版本。這個封裝是更大粒度上對各種分布式一致性使用場景的抽象。其中最基礎和常用的是一個分布式鎖的實現: org.menagerie.locks.ReentrantZkLock,通過ZooKeeper的全局有序的特性和EPHEMERAL_SEQUENTIAL類型znode的支持,實現了分布式鎖。具體做法是:不同的client上每個試圖獲得鎖的線程,都在相同的basepath下面創建一個EPHEMERAL_SEQUENTIAL的node。EPHEMERAL表示要創建的是臨時znode,創建連接斷開時會自動刪除; SEQUENTIAL表示要自動在傳入的path後面綴上一個自增的全局唯一後綴,作為最終的path。因此對不同的請求ZK會生成不同的後綴,並分別返回帶了各自後綴的path給各個請求。因為ZK全局有序的特性,不管client請求怎樣先後到達,在ZKServer端都會最終排好一個順序,因此自增後綴最小的那個子節點,就對應第一個到達ZK的有效請求。然後client讀取basepath下的所有子節點和ZK返回給自己的path進行比較,當發現自己創建的sequential node的後綴序號排在第一個時,就認為自己獲得了鎖;否則的話,就認為自己沒有獲得鎖。這時肯定是有其他並發的並且是沒有斷開的client/線程先創建了node。
I. zk集群數據遷移和恢復
zk集群數據遷移和恢復
一、zk數據遷移,有如下兩種方案:
1、利用zk集群超過半數仍然可用的特性,比如集群中有5個節點,可以將其中1~2個節點割裂出去,再添加1個新的節點,組成新的集群,以此實現數據遷移;
2、直接拷貝集群的元數據文件到新集群;
但第1種方案並不是最佳選擇,例如zk集群連接數負載高,如果此時再減少節點數,則會導致集群負載變得更高,甚至集群崩潰。故採用第2種方案,通過拷貝元數據的方式來實現集群數據遷移和恢復。
二、zk數據遷移和恢復的實現思路
1、搭建好新的zk集群,並且啟動集群(此時集群還沒有數據);
2、停止新集群所有節點的zk進程;
3、刪除新集群所有節點數據目錄下的文件,包括:事務日誌、快照、epoch文件
4、將老集群leader節點的事務日誌、快照、epoch文件拷貝到新集群所有節點對應的數據目錄下;
5、重新啟動新集群;
三、注意事項:
如果新集群的兩個epoch文件不刪掉的話,會造成新集群無法啟動;原因是:如果只是拷貝了老集群的快照、事務日誌到新集群,新集群的節點在啟動時會識別epoch文件中記錄的當前epoch值,然後將這個epoch值和從老集群拷貝過來的元數據中的事務ID(zxid)進行比較,發現並不匹配,就會造成新集群無法正常啟動。故需要將新集群中各個節點的epoch文件刪除,將老集群的epoch文件、快照文件、事務日誌文件一並拷貝到新集群的各個節點。
四、zk數據遷移和恢復的具體操作步驟:
1、搭建新集群:
1)、rpm -ivh jdk-8u20-linux-x64.rpm
2)、cd /data/ && tar -zxvf zk_server.tgz ###解壓到/data或者/data1
3)、cd /data/ && mv zk_server zk.1 ###myid為1的節點,家目錄為/data/zk.1、myid為2的節點,家目錄為/data/zk.2
4)、解壓之後,可以看到3個目錄:
cd /data/zk.1 && ls -l
zk_data ###保存zk快照數據的主目錄
zk_log ###保存zk事務日誌的主目錄
zookeeper ###程序路徑,包含配置文件
5)、cd /data/zk.1/zk_data && echo 1 > myid ###配置節點myid,myid為1的節點配置成1,myid為2的節點配置成2,myid為3的節點配置3
6)、cd /data/zk.1/zookeeper/conf && cp -ar zoo.cfg.template zoo.cfg
7)、vim zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
clientPort=2181
autopurge.snapRetainCount=500
autopurge.purgeInterval = 48
dataDir=/data/zk.1/zk_data ###myid為2則配置為/data/zk.2/zk_data
dataLogDir=/data/zk.1/zk_log ###myid為2則配置為/data/zk.2/zk_log
server.1=節點1的IP:8880:7770 #節點1的配置
server.2=節點2的IP:8880:7770 #節點2的配置
server.3=節點3的IP:8880:7770 #節點3的配置
8)、其餘2個節點的安裝部署方法也是一樣
9)、依次啟動3個節點,並檢查狀態
啟動:
cd /data/zk.1/zookeeper/bin/ && nohup sh zkServer.sh start > zookeeper.out &
檢查節點狀態:
cd /data/zk.1/zookeeper/bin/ && ./zkServer.sh status
連接本地節點,查看數據:
cd /data/zk.1/zookeeper/bin/ && ./zkCli.sh -server 127.0.0.1:2181
2、停止新集群所有節點的zk進程:
cd /data/zk.1/zookeeper/bin/ && sh zkServer.sh stop
cd /data/zk.2/zookeeper/bin/ && sh zkServer.sh stop
cd /data/zk.3/zookeeper/bin/ && sh zkServer.sh stop
3、刪除新集群所有節點數據目錄下的文件,包括:事務日誌、快照、epoch文件(以節點1為例):
cd /data/zk.1/zk_data/version-2 && rm -f snapshot.* && rm -f acceptedEpoch && rm -f currentEpoch
cd /data/zk.1/zk_log/version-2 && rm -f log.*
4、將老集群leader節點的事務日誌、快照、epoch文件拷貝到新集群所有節點對應的數據目錄下(以leader節點的數據為准):
1)、備份老集群leader節點的數據目錄下的文件(拷貝到本地的新建的zk_meta_dir目錄)
最新的log事務日誌文件 ###不是所有的log文件,而是最新的log文件
最新的snapshot文件 ###不是所有的snapshot文件,而是最新的snapshot文件
acceptedEpoch文件
currentEpoch文件
2)、將leader節點zk_meta_dir目錄的log文件、snapshot文件、Epoch文件分發到新集群每個節點對應的目錄,例如節點1的/data/zk.1/zk_data/version-2、/data/zk.1/zk_log/version-2
5、重新啟動新集群:
以節點1為例:
cd /data/zk.1/zookeeper/bin/ && nohup sh zkServer.sh start > zookeeper.out &