dubbo學習小結html
參考:java
https://blog.csdn.net/paul_wei2008/article/details/19355681node
https://blog.csdn.net/liweisnake/article/details/63251252linux
https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/git
https://www.jianshu.com/p/b9f3f6a16911web
Dubbo基本原理機制redis
分佈式服務框架:
–高性能和透明化的RPC遠程服務調用方案
–SOA服務治理方案
-Apache MINA 框架基於Reactor模型通訊框架,基於tcp長鏈接
Dubbo缺省協議採用單一長鏈接和NIO異步通信,
適合於小數據量大併發的服務調用,以及服務消費者機器數遠大於服務提供者機器數的狀況
分析源代碼,基本原理以下:
zookeeper入門shell
zookeeper可謂是目前使用最普遍的分佈式組件了。其功能和職責單一,但卻很是重要。數據庫
在現今這個年代,介紹zookeeper的書和文章可謂多如牛毛,本人不才,試圖經過本身的理解來介紹zookeeper,但願經過一個初學者的視角來學習zookeeper,以期讓人更加深刻和平穩的理解zookeeper。其中參考了很多教程和書,相關書目列在文末,也感謝這些做者。apache
學習新的框架,先讓咱們搞清楚他是什麼,這是它的內涵,而後再介紹它能作什麼,這是它的外延,內涵和外延共同來定義框架自己,會對框架有較爲深入的理解,在應用層面上知道如何用。其次再搞清楚zookeeper相關的理論基礎,其目的是知道zookeeper是如何被髮明的,是否可以借鑑以便從此本身可以用到其餘地方。最後搞清楚zookeeper中一些設計的原理和細節,目的也是搞清前因後果,學會「術」從而應用到別的地方。固然了,加深的理解一樣可以幫助認識zookeeper自己,在使用時才知道爲何這樣用。
首先,
zookeeper究竟是什麼?
zookeeper其實是yahoo開發的,用於分佈式中一致性處理的框架。最初其做爲研發hadoop時的副產品。因爲分佈式系統中一致性處理較爲困難,其餘的分佈式系統沒有必要 費勁重複造輪子,故隨後的分佈式系統中大量應用了zookeeper,以致於zookeeper成爲了各類分佈式系統的基礎組件,其地位之重要,可想而知。著名的hadoop,kafka,dubbo 都是基於zookeeper而構建。
要想理解zookeeper究竟是作啥的,那首先得理解清楚,什麼是一致性。
所謂的一致性,實際上就是圍繞着「看見」來的。誰能看見?可否看見?何時看見?舉個例子:淘寶後臺賣家,在後臺上架一件大促的商品,經過服務器A提交到主數據庫,假設剛提交後立馬就有用戶去經過應用服務器B去從數據庫查詢該商品,就會出現一個現象,賣家已經更新成功了,然而買家卻看不到;而通過一段時間後,主數據庫的數據同步到了從數據庫,買家就能查到了。
假設賣家更新成功以後買家立馬就能看到賣家的更新,則稱爲強一致性;
若是賣家更新成功後買家不能看到賣家更新的內容,則稱爲弱一致性;
而賣家更新成功後,買家通過一段時間最終能看到賣家的更新,則稱爲最終一致性。
更多的一致性例子能夠參考文獻2,裏面列舉了10種一致性的例子,若是要給一致性下個定義,能夠是分佈式系統中狀態或數據保持同步和一致。特別須要注意一致性跟事務的區別,能夠記得學習數據庫時特別強調ACID,故而知足ACID的數據庫可以作事務,其中C便是一致性,所以,事務是一致性的一種特例,比起一致性更難達成。
如何保證在分佈式環境下數據的最終一致,這個就是zookeeper須要解決的問題。對於這些問題,有哪些挑戰,zookeeper又是如何解決這些挑戰的,下一篇文章將會主要涉及這個主題。
一些常見的解決一致性問題的方式:
1. 查詢重試補償。對於分佈式應用中不肯定的狀況,先使用查詢接口查詢到當前狀態,若是當前狀態不一致則採用補償接口對狀態進行重試推動,或者回滾接口對業務作回滾。典型的場景如銀行跟支付寶之間的交互。支付寶發送一個轉帳請求到銀行,如一直未收到響應,則能夠經過銀行的查詢接口查詢該筆交易的狀態,如該筆交易對方未收到,則採起補償的模式進行推送。
2. 定時任務推送。對於上面的狀況,有可能一次推送搞不定,因而須要2次,3次推送。不要懷疑,支付寶內最初掉單率很高,全靠後續不斷的定時任務推送增長成功率。
3. TCC。try-confirm-cancel。其實是兩階段協議,第二階段的能夠實現提交操做或是逆操做。
zookeeper到底能作什麼?
在業界的實際應用是什麼?瞭解這些應用,會對zookeeper可以作的事有更直觀的認識。
hadoop:
鼻祖級應用,ResourceManager在整個hadoop中算是單點,爲了實現其高可用,分爲主備ResourceManager,zookeeper在其中管理整個ResourceManager。
能夠想象,主備ResourceManager最初是主RM提供服務,若是一切安好,則zookeeper無用武之地。然而,總歸會出現主RM提供不了服務的狀況。因而會出現主備切換的狀況,而zookeeper正是爲主備切換保駕護航。
先來推理一下,主備切換會出現什麼問題。傳統的主備切換,可讓主備之間維持心跳鏈接,一旦備機發現主機心跳檢測不到了,則本身切換爲主機,原來的主機等待救援。這種方式有兩個問題,一是因爲網絡抖動,負載過大等問題,備機檢測不到心跳並不能說明主機必定掛了,有可能必定時間後主機或網絡恢復,這時候主機並不知道備機已經切換爲主機,2臺主機互相爭用,可能形成腦裂;二是若是一些數據集中在主機上面,則備機切換時因爲同步延時勢必會損失掉一部分的數據。
如何解決這些問題?早期的方式提供了很多解決方案,好比備機一旦切換爲主機,則經過電源控制直接切斷主機電源,簡單粗暴,可是此刻備機已是單點,若是主機是由於量撐不住而掛,那備機有可能會重蹈覆轍,最終致使整個服務不可用。
zookeeper又是如何解決這個問題的呢?
1. zookeeper做爲第三方集羣參與到主備節點中去,當主備啓動時會在zookeeper上競爭建立一個臨時鎖節點,爭用成功者則充當主機,其他備機
2. 全部備機會監聽該臨時鎖節點,一旦主機與zookeeper間session失效,則臨時節點被刪除
3. 一旦臨時節點被刪除,備機開始從新申請建立臨時鎖節點,從新爭用爲主機;
4. 用zookeeper如何解決腦裂?實際上主機爭用到節點後經過對根節點作一個ACL權限控制,則其餘搶佔的機器因爲沒法更新臨時鎖節點,只有放棄成爲備機。
zookeeper使用了很是簡單又現成的方式來解決的這個問題,比起其餘方案方便很多,這也是爲啥zookeeper流行的緣由。說白了,就是把複雜操做封裝化精簡化
dubbo:
做爲業界知名的分佈式soa框架,dubbo的主要的服務註冊發現功能即是由zookeeper來提供的。
對於一個服務框架,註冊中心是其核心中的核心,雖然暫時掛掉並不會致使整個服務出問題,可是一旦掛掉,總體風險就很高。考慮通常狀況,註冊中心就是單臺機器的時候,其實現很容易,全部機器起來都去註冊服務給它,而且全部調用方都跟它保持長鏈接,一旦服務有變,即經過長鏈接來通知到調用方。可是當服務集羣規模擴大時,這事情就不簡單了,單機保持鏈接數有限,並且容易故障。
做爲一個穩定的服務化框架,dubbo能夠選擇並推薦zookeeper做爲註冊中心。其底層將zookeeper經常使用的客戶端zkclient和curator封裝成爲ZookeeperClient。
1. 當服務提供者服務啓動時,向zookeeper註冊一個節點
2. 服務消費者則訂閱其父節點的變化,諸如啓動中止都可以經過節點建立刪除得知,異常狀況好比被調用方掉線也能夠經過臨時節點session 斷開自動刪除得知
3. 服務消費方同時也會將本身訂閱的服務以節點建立的方式放到zookeeper
4. 因而能夠獲得映射關係,諸如誰提供了服務,誰訂閱了誰提供的服務,基於這層關係再作監控,就能輕易得知整個系統狀況。
zookeeper的基本數據模型
一句話,相似linux文件系統的節點模型
其節點有以下有趣而又重要的特性:
1. 同一時刻多臺機器建立同一個節點,只有一個會爭搶成功。利用這個特性能夠作分佈式鎖。
2. 臨時節點的生命週期與會話一致,會話關閉則臨時節點刪除。這個特性常常用來作心跳,動態監控,負載等動做
3. 順序節點保證節點名全局惟一。這個特性能夠用來生成分佈式環境下的全局自增加id
經過zookeeper提供的原語服務,能夠對zookeeper能作的事情有個精確和直觀的認識
zookeeper提供的原語服務
1. 建立節點。
2. 刪除節點
3. 更新節點
4. 獲取節點信息
5. 權限控制
6. 事件監聽
實際上,就是對節點的增刪查改加上權限控制與事件監聽,可是經過對這些原語的組合以及不一樣場景的使用,能夠實現不少用法。參考文獻5
1. 數據發佈訂閱。即註冊中心,見上面dubbo用法。主要經過對節點管理作到發佈以及事件監聽作到訂閱
2. 負載均衡。見上面kafka用法
3. 命名服務。zookeeper的節點結構自然支持命名服務,即把信息集中存儲,並以樹狀管理,方便統一查閱
4. 分佈式協調通知。協調通知實際上與發佈訂閱相似,因爲引入的第三方的zookeeper,實際上對不少種協調通知作了解耦,好比參考文獻4中提到的消息推送,心跳檢測等
5. 集羣管理與master選舉。經過上面的第二點特性,能夠輕易得知集羣機器存活情況,從而輕鬆管理集羣;經過上面第一點特性,能夠作出master爭搶。
6. 分佈式鎖。實際上就是第一點特性的應用。
7. 分佈式隊列。實際上就是第三點特性的應用。
8. 分佈式的併發等待。相似於多線程的join問題,主任務的執行依賴於其餘子任務所有執行完畢,在單機多線程裏能夠用join,可是分佈式環境下如何實現呢。利用zookeeper,能夠建立一個主任務節點,旗下子任務一旦執行完畢,則在主任務節點下掛一個子任務節點,等節點數量足夠,則認爲主任務能夠開始執行。
能夠發現,全部的原語就是zookeeper的基礎,而其餘的用法總結無非是將原語放到不一樣場景下的歸類罷了。
相信到這裏你對zookeeper應該有個初步的瞭解和大體的印象了。
本系列文章分爲:
zookeeper入門系列-理論基礎-zab協議
zookeeper入門系列-理論基礎-raft協議
zookeeper入門系列-設計細節
參考文獻:
保證分佈式系統數據一致性的6種方案 http://weibo.com/ttarticle/p/show?id=2309403965965003062676
解決分佈式系統的一致性問題,咱們須要瞭解哪些理論? http://mp.weixin.qq.com/s/hGnpHfn7a7yxjPBP78i4bg
分佈式系統的事務處理 http://coolshell.cn/articles/10910.html
ZooKeeper典型應用場景一覽 http://jm.taobao.org/2011/10/08/1232/
zookeeper中的基本概念 http://www.hollischuang.com/archives/1280
zookeeper入門使用 http://www.importnew.com/23025.html
本文介紹的 Zookeeper 是以 3.2.2 這個穩定版本爲基礎,最新的版本能夠經過官網 http://hadoop.apache.org/zookeeper/來獲取,Zookeeper 的安裝很是簡單,下面將從單機模式和集羣模式兩個方面介紹 Zookeeper 的安裝和配置。
單機安裝很是簡單,只要獲取到 Zookeeper 的壓縮包並解壓到某個目錄如:/home/zookeeper-3.2.2 下,Zookeeper 的啓動腳本在 bin 目錄下,Linux 下的啓動腳本是 zkServer.sh,在 3.2.2 這個版本 Zookeeper 沒有提供 windows 下的啓動腳本,因此要想在 windows 下啓動 Zookeeper 要本身手工寫一個,如清單 1 所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
setlocal
set ZOOCFGDIR=%~dp0%..\conf
set ZOO_LOG_DIR=%~dp0%..
set ZOO_LOG4J_PROP=INFO,CONSOLE
set CLASSPATH=%ZOOCFGDIR%
set CLASSPATH=%~dp0..\*;%~dp0..\lib\*;%CLASSPATH%
set CLASSPATH=%~dp0..\build\classes;%~dp0..\build\lib\*;%CLASSPATH%
set ZOOCFG=%ZOOCFGDIR%\zoo.cfg
set ZOOMAIN=org.apache.zookeeper.server.ZooKeeperServerMain
java "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%"
-cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %*
endlocal
|
在你執行啓動腳本以前,還有幾個基本的配置項須要配置一下,Zookeeper 的配置文件在 conf 目錄下,這個目錄下有 zoo_sample.cfg 和 log4j.properties,你須要作的就是將 zoo_sample.cfg 更名爲 zoo.cfg,由於 Zookeeper 在啓動時會找這個文件做爲默認配置文件。下面詳細介紹一下,這個配置文件中各個配置項的意義。
1
2
3
|
tickTime=2000
dataDir=D:/devtools/zookeeper-3.2.2/build
clientPort=2181
|
當這些配置項配置好後,你如今就能夠啓動 Zookeeper 了,啓動後要檢查 Zookeeper 是否已經在服務,能夠經過 netstat – ano 命令查看是否有你配置的 clientPort 端口號在監聽服務。
Zookeeper 不只能夠單機提供服務,同時也支持多機組成集羣來提供服務。實際上 Zookeeper 還支持另一種僞集羣的方式,也就是能夠在一臺物理機上運行多個 Zookeeper 實例,下面將介紹集羣模式的安裝和配置。
Zookeeper 的集羣模式的安裝和配置也不是很複雜,所要作的就是增長几個配置項。集羣模式除了上面的三個配置項還要增長下面幾個配置項:
1
2
3
4
|
initLimit=5
syncLimit=2
server.1=192.168.211.1:2888:3888
server.2=192.168.211.2:2888:3888
|
除了修改 zoo.cfg 配置文件,集羣模式下還要配置一個文件 myid,這個文件在 dataDir 目錄下,這個文件裏面就有一個數據就是 A 的值,Zookeeper 啓動時會讀取這個文件,拿到裏面的數據與 zoo.cfg 裏面的配置信息比較從而判斷究竟是那個 server。
Zookeeper 會維護一個具備層次關係的數據結構,它很是相似於一個標準的文件系統,如圖 1 所示:
Zookeeper 這種數據結構有以下這些特色:
Zookeeper 做爲一個分佈式的服務框架,主要用來解決分佈式集羣中應用系統的一致性問題,它能提供基於相似於文件系統的目錄節點樹方式的數據存儲,可是 Zookeeper 並非用來專門存儲數據的,它的做用主要是用來維護和監控你存儲的數據的狀態變化。經過監控這些數據狀態的變化,從而能夠達到基於數據的集羣管理,後面將會詳細介紹 Zookeeper 可以解決的一些典型問題,這裏先介紹一下,Zookeeper 的操做接口和簡單使用示例。
客戶端要鏈接 Zookeeper 服務器能夠經過建立 org.apache.zookeeper. ZooKeeper 的一個實例對象,而後調用這個類提供的接口來和服務器交互。
前面說了 ZooKeeper 主要是用來維護和監控一個目錄節點樹中存儲的數據的狀態,全部咱們可以操做 ZooKeeper 的也和操做目錄節點樹大致同樣,如建立一個目錄節點,給某個目錄節點設置數據,獲取某個目錄節點的全部子目錄節點,給某個目錄節點設置權限和監控這個目錄節點的狀態變化。
這些接口以下表所示:
除了以上這些上表中列出的方法以外還有一些重載方法,如都提供了一個回調類的重載方法以及能夠設置特定 Watcher 的重載方法,具體的方法能夠參考 org.apache.zookeeper. ZooKeeper 類的 API 說明。
下面給出基本的操做 ZooKeeper 的示例代碼,這樣你就能對 ZooKeeper 有直觀的認識了。下面的清單包括了建立與 ZooKeeper 服務器的鏈接以及最基本的數據操做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// 建立一個與服務器的鏈接
ZooKeeper zk = new ZooKeeper("localhost:" + CLIENT_PORT,
ClientBase.CONNECTION_TIMEOUT, new Watcher() {
// 監控全部被觸發的事件
public void process(WatchedEvent event) {
System.out.println("已經觸發了" + event.getType() + "事件!");
}
});
// 建立一個目錄節點
zk.create("/testRootPath", "testRootData".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
// 建立一個子目錄節點
zk.create("/testRootPath/testChildPathOne", "testChildDataOne".getBytes(),
Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
System.out.println(new String(zk.getData("/testRootPath",false,null)));
// 取出子目錄節點列表
System.out.println(zk.getChildren("/testRootPath",true));
// 修改子目錄節點數據
zk.setData("/testRootPath/testChildPathOne","modifyChildDataOne".getBytes(),-1);
System.out.println("目錄節點狀態:["+zk.exists("/testRootPath",true)+"]");
// 建立另一個子目錄節點
zk.create("/testRootPath/testChildPathTwo", "testChildDataTwo".getBytes(),
Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
System.out.println(new String(zk.getData("/testRootPath/testChildPathTwo",true,null)));
// 刪除子目錄節點
zk.delete("/testRootPath/testChildPathTwo",-1);
zk.delete("/testRootPath/testChildPathOne",-1);
// 刪除父目錄節點
zk.delete("/testRootPath",-1);
// 關閉鏈接
zk.close();
|
輸出的結果以下:
1
2
3
4
5
6
7
8
|
已經觸發了 None 事件!
testRootData
[testChildPathOne]
目錄節點狀態:[5,5,1281804532336,1281804532336,0,1,0,0,12,1,6]
已經觸發了 NodeChildrenChanged 事件!
testChildDataTwo
已經觸發了 NodeDeleted 事件!
已經觸發了 NodeDeleted 事件!
|
當對目錄節點監控狀態打開時,一旦目錄節點的狀態發生變化,Watcher 對象的 process 方法就會被調用。
Zookeeper 從設計模式角度來看,是一個基於觀察者模式設計的分佈式服務管理框架,它負責存儲和管理你們都關心的數據,而後接受觀察者的註冊,一旦這些數據的狀態發生變化,Zookeeper 就將負責通知已經在 Zookeeper 上註冊的那些觀察者作出相應的反應,從而實現集羣中相似 Master/Slave 管理模式,關於 Zookeeper 的詳細架構等內部細節能夠閱讀 Zookeeper 的源碼
下面詳細介紹這些典型的應用場景,也就是 Zookeeper 到底能幫咱們解決那些問題?下面將給出答案。
分佈式應用中,一般須要有一套完整的命名規則,既可以產生惟一的名稱又便於人識別和記住,一般狀況下用樹形的名稱結構是一個理想的選擇,樹形的名稱結構是一個有層次的目錄結構,既對人友好又不會重複。說到這裏你可能想到了 JNDI,沒錯 Zookeeper 的 Name Service 與 JNDI 可以完成的功能是差很少的,它們都是將有層次的目錄結構關聯到必定資源上,可是 Zookeeper 的 Name Service 更加是普遍意義上的關聯,也許你並不須要將名稱關聯到特定資源上,你可能只須要一個不會重複名稱,就像數據庫中產生一個惟一的數字主鍵同樣。
Name Service 已是 Zookeeper 內置的功能,你只要調用 Zookeeper 的 API 就能實現。如調用 create 接口就能夠很容易建立一個目錄節點。
配置的管理在分佈式應用環境中很常見,例如同一個應用系統須要多臺 PC Server 運行,可是它們運行的應用系統的某些配置項是相同的,若是要修改這些相同的配置項,那麼就必須同時修改每臺運行這個應用系統的 PC Server,這樣很是麻煩並且容易出錯。
像這樣的配置信息徹底能夠交給 Zookeeper 來管理,將配置信息保存在 Zookeeper 的某個目錄節點中,而後將全部須要修改的應用機器監控配置信息的狀態,一旦配置信息發生變化,每臺應用機器就會收到 Zookeeper 的通知,而後從 Zookeeper 獲取新的配置信息應用到系統中。
Zookeeper 可以很容易的實現集羣管理的功能,若有多臺 Server 組成一個服務集羣,那麼必需要一個「總管」知道當前集羣中每臺機器的服務狀態,一旦有機器不能提供服務,集羣中其它集羣必須知道,從而作出調整從新分配服務策略。一樣當增長集羣的服務能力時,就會增長一臺或多臺 Server,一樣也必須讓「總管」知道。
Zookeeper 不只可以幫你維護當前的集羣中機器的服務狀態,並且可以幫你選出一個「總管」,讓這個總管來管理集羣,這就是 Zookeeper 的另外一個功能 Leader Election。
它們的實現方式都是在 Zookeeper 上建立一個 EPHEMERAL 類型的目錄節點,而後每一個 Server 在它們建立目錄節點的父目錄節點上調用 getChildren(String path, boolean watch) 方法並設置 watch 爲 true,因爲是 EPHEMERAL 目錄節點,當建立它的 Server 死去,這個目錄節點也隨之被刪除,因此 Children 將會變化,這時 getChildren上的 Watch 將會被調用,因此其它 Server 就知道已經有某臺 Server 死去了。新增 Server 也是一樣的原理。
Zookeeper 如何實現 Leader Election,也就是選出一個 Master Server。和前面的同樣每臺 Server 建立一個 EPHEMERAL 目錄節點,不一樣的是它仍是一個 SEQUENTIAL 目錄節點,因此它是個 EPHEMERAL_SEQUENTIAL 目錄節點。之因此它是 EPHEMERAL_SEQUENTIAL 目錄節點,是由於咱們能夠給每臺 Server 編號,咱們能夠選擇當前是最小編號的 Server 爲 Master,假如這個最小編號的 Server 死去,因爲是 EPHEMERAL 節點,死去的 Server 對應的節點也被刪除,因此當前的節點列表中又出現一個最小編號的節點,咱們就選擇這個節點爲當前 Master。這樣就實現了動態選擇 Master,避免了傳統意義上單 Master 容易出現單點故障的問題。
這部分的示例代碼以下,完整的代碼請看附件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
void findLeader() throws InterruptedException {
byte[] leader = null;
try {
leader = zk.getData(root + "/leader", true, null);
} catch (Exception e) {
logger.error(e);
}
if (leader != null) {
following();
} else {
String newLeader = null;
try {
byte[] localhost = InetAddress.getLocalHost().getAddress();
newLeader = zk.create(root + "/leader", localhost,
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
} catch (Exception e) {
logger.error(e);
}
if (newLeader != null) {
leading();
} else {
mutex.wait();
}
}
}
|
共享鎖在同一個進程中很容易實現,可是在跨進程或者在不一樣 Server 之間就很差實現了。Zookeeper 卻很容易實現這個功能,實現方式也是須要得到鎖的 Server 建立一個 EPHEMERAL_SEQUENTIAL 目錄節點,而後調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是否是就是本身建立的目錄節點,若是正是本身建立的,那麼它就得到了這個鎖,若是不是那麼它就調用 exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到本身建立的節點是列表中最小編號的目錄節點,從而得到鎖,釋放鎖很簡單,只要刪除前面它本身所建立的目錄節點就好了。
同步鎖的實現代碼以下,完整的代碼請看附件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
void getLock() throws KeeperException, InterruptedException{
List<
String
> list = zk.getChildren(root, false);
String[] nodes = list.toArray(new String[list.size()]);
Arrays.sort(nodes);
if(myZnode.equals(root+"/"+nodes[0])){
doAction();
}
else{
waitForLock(nodes[0]);
}
}
void waitForLock(String lower) throws InterruptedException, KeeperException {
Stat stat = zk.exists(root + "/" + lower,true);
if(stat != null){
mutex.wait();
}
else{
getLock();
}
}
|
Zookeeper 能夠處理兩種類型的隊列:
同步隊列用 Zookeeper 實現的實現思路以下:
建立一個父目錄 /synchronizing,每一個成員都監控標誌(Set Watch)位目錄 /synchronizing/start 是否存在,而後每一個成員都加入這個隊列,加入隊列的方式就是建立 /synchronizing/member_i 的臨時目錄節點,而後每一個成員獲取 / synchronizing 目錄的全部目錄節點,也就是 member_i。判斷 i 的值是否已是成員的個數,若是小於成員個數等待 /synchronizing/start 的出現,若是已經相等就建立 /synchronizing/start。
用下面的流程圖更容易理解:
同步隊列的關鍵代碼以下,完整的代碼請看附件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void addQueue() throws KeeperException, InterruptedException{
zk.exists(root + "/start",true);
zk.create(root + "/" + name, new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
synchronized (mutex) {
List<
String
> list = zk.getChildren(root, false);
if (list.size() < size) {
mutex.wait();
} else {
zk.create(root + "/start", new byte[0], Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
}
}
|
當隊列沒盡是進入 wait(),而後會一直等待 Watch 的通知,Watch 的代碼以下:
1
2
3
4
5
6
7
8
|
public void process(WatchedEvent event) {
if(event.getPath().equals(root + "/start") &&
event.getType() == Event.EventType.NodeCreated){
System.out.println("獲得通知");
super.process(event);
doAction();
}
}
|
FIFO 隊列用 Zookeeper 實現思路以下:
實現的思路也很是簡單,就是在特定的目錄下建立 SEQUENTIAL 類型的子目錄 /queue_i,這樣就能保證全部成員加入隊列時都是有編號的,出隊列時經過 getChildren( ) 方法能夠返回當前全部的隊列中的元素,而後消費其中最小的一個,這樣就能保證 FIFO。
下面是生產者和消費者這種隊列形式的示例代碼,完整的代碼請看附件:
1
2
3
4
5
6
7
8
9
|
boolean produce(int i) throws KeeperException, InterruptedException{
ByteBuffer b = ByteBuffer.allocate(4);
byte[] value;
b.putInt(i);
value = b.array();
zk.create(root + "/element", value, ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT_SEQUENTIAL);
return true;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
int consume() throws KeeperException, InterruptedException{
int retvalue = -1;
Stat stat = null;
while (true) {
synchronized (mutex) {
List<
String
> list = zk.getChildren(root, true);
if (list.size() == 0) {
mutex.wait();
} else {
Integer min = new Integer(list.get(0).substring(7));
for(String s : list){
Integer tempValue = new Integer(s.substring(7));
if(tempValue < min) min = tempValue;
}
byte[] b = zk.getData(root + "/element" + min,false, stat);
zk.delete(root + "/element" + min, 0);
ByteBuffer buffer = ByteBuffer.wrap(b);
retvalue = buffer.getInt();
return retvalue;
}
}
}
}
|
Zookeeper 做爲 Hadoop 項目中的一個子項目,是 Hadoop 集羣管理的一個必不可少的模塊,它主要用來控制集羣中的數據,如它管理 Hadoop 集羣中的 NameNode,還有 Hbase 中 Master Election、Server 之間狀態同步等。
本文介紹的 Zookeeper 的基本知識,以及介紹了幾個典型的應用場景。這些都是 Zookeeper 的基本功能,最重要的是 Zoopkeeper 提供了一套很好的分佈式集羣管理的機制,就是它這種基於層次型的目錄樹的數據結構,並對樹中的節點進行有效管理,從而能夠設計出多種多樣的分佈式的數據管理模型,而不只僅侷限於上面提到的幾個經常使用應用場景。
Netty 是一個利用 Java 的高級網絡的能力,隱藏其背後的複雜性而提供一個易於使用的 API 的客戶端/服務器框架。
Netty 是一個普遍使用的 Java 網絡編程框架(Netty 在 2011 年得到了Duke's Choice Award,見https://www.java.net/dukeschoice/2011)。它活躍和成長於用戶社區,像大型公司 Facebook 和 Instagram 以及流行 開源項目如 Infinispan, HornetQ, Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其強大的對於網絡抽象的核心代碼。
以上是摘自《Essential Netty In Action》這本書,本文的內容也是本人讀了這本書以後的一些整理心得,若有不當之處歡迎大蝦們指正
Netty和Tomcat最大的區別就在於通訊協議,Tomcat是基於Http協議的,他的實質是一個基於http協議的web容器,可是Netty不同,他能經過編程自定義各類協議,由於netty可以經過codec本身來編碼/解碼字節流,完成相似redis訪問的功能,這就是netty和tomcat最大的不一樣。
有人說netty的性能就必定比tomcat性能高,其實否則,tomcat從6.x開始就支持了nio模式,而且後續還有arp模式——一種經過jni調用apache網絡庫的模式,相比於舊的bio模式,併發性能獲得了很大提升,特別是arp模式,而netty是否比tomcat性能更高,則要取決於netty程序做者的技術實力了。
如第一部分所述,netty是一款收到大公司青睞的框架,在我看來,netty可以受到青睞的緣由有三:
Netty是一款基於NIO(Nonblocking I/O,非阻塞IO)開發的網絡通訊框架,對比於BIO(Blocking I/O,阻塞IO),他的併發性能獲得了很大提升,兩張圖讓你瞭解BIO和NIO的區別:
Selector
。
除了BIO和NIO以外,還有一些其餘的IO模型,下面這張圖就表示了五種IO模型的處理流程:
以上摘自Linux IO模式及 select、poll、epoll詳解
Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝。咱們知道,Java的內存有堆內存、棧內存和字符串常量池等等,其中堆內存是佔用內存空間最大的一塊,也是Java對象存放的地方,通常咱們的數據若是須要從IO讀取到堆內存,中間須要通過Socket緩衝區,也就是說一個數據會被拷貝兩次才能到達他的的終點,若是數據量大,就會形成沒必要要的資源浪費。
Netty針對這種狀況,使用了NIO中的另外一大特性——零拷貝,當他須要接收數據的時候,他會在堆內存以外開闢一塊內存,數據就直接從IO讀到了那塊內存中去,在netty裏面經過ByteBuf能夠直接對這些數據進行直接操做,從而加快了傳輸速度。
下兩圖就介紹了兩種拷貝方式的區別,摘自Linux 中的零拷貝技術,第 1 部分
上文介紹的ByteBuf是Netty的一個重要概念,他是netty數據處理的容器,也是Netty封裝好的一個重要體現,將在下一部分作詳細介紹。
要說Netty爲何封裝好,這種用文字是說不清的,直接上代碼:
public class PlainOioServer { public void serve(int port) throws IOException { final ServerSocket socket = new ServerSocket(port); //1 try { for (;;) { final Socket clientSocket = socket.accept(); //2 System.out.println("Accepted connection from " + clientSocket); new Thread(new Runnable() { //3 @Override public void run() { OutputStream out; try { out = clientSocket.getOutputStream(); out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8"))); //4 out.flush(); clientSocket.close(); //5 } catch (IOException e) { e.printStackTrace(); try { clientSocket.close(); } catch (IOException ex) { // ignore on close } } } }).start(); //6 } } catch (IOException e) { e.printStackTrace(); } } }
public class PlainNioServer { public void serve(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); //1 Selector selector = Selector.open(); //2 serverChannel.register(selector, SelectionKey.OP_ACCEPT); //3 final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes()); for (;;) { try { selector.select(); //4 } catch (IOException ex) { ex.printStackTrace(); // handle exception break; } Set<SelectionKey> readyKeys = selector.selectedKeys(); //5 Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); try { if (key.isAcceptable()) { //6 ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate()); //7 System.out.println( "Accepted connection from " + client); } if (key.isWritable()) { //8 SocketChannel client = (SocketChannel)key.channel(); ByteBuffer buffer = (ByteBuffer)key.attachment(); while (buffer.hasRemaining()) { if (client.write(buffer) == 0) { //9 break; } } client.close(); //10 } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { // 在關閉時忽略 } } } } } }
public class NettyOioServer { public void server(int port) throws Exception { final ByteBuf buf = Unpooled.unreleasableBuffer( Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8"))); EventLoopGroup group = new OioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); //1 b.group(group) //2 .channel(OioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() {//3 @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { //4 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);//5 } }); } }); ChannelFuture f = b.bind().sync(); //6 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); //7 } } }
從代碼量上來看,Netty就已經秒殺傳統Socket編程了,可是這一部分博大精深,僅僅貼幾個代碼豈能說明問題,在這裏給你們介紹一下Netty的一些重要概念,讓你們更理解Netty。
Channel
數據傳輸流,與channel相關的概念有如下四個,上一張圖讓你瞭解netty裏面的Channel。
他有三種使用模式:
除此以外,他還提供一大堆api方便你使用,在這裏我就不一一列出了,具體參見ByteBuf字節緩存。
Netty入門教程2——動手搭建HttpServer
Netty入門教程3——Decoder和Encoder
Netty入門教程4——如何實現長鏈接
以上就是我對《Netty實戰》這本書的一些心得和書外的一些相關知識整理,若是有不一樣的看法,歡迎討論!