zookeeper是分散式開源框架, 是Google Chubby的一個實現, 主要作為分散式系統的協調服務. Dobbo等框架使用了其功能.
zookeeper特性
- 順序一致性: 事務請求最終會嚴格按順序執行
- 原子性:
- 可靠性:
- 實時性:
- 單一檢視:
安裝
使用windows的linux子系統時: cd /mnt/e/chromedownload/
轉到windows下載路徑
拷貝 cp /mnt/e/chromedownload/zookeeper.tar.gz /program/zookeeper.tar.gz
轉到 cd /program
如果沒有的先mkdir program
解壓 tar -zxvf zookeeper.tar.gz
轉到 cd ZK_HOME/conf
拷貝 cp zoo_sample.cfg zoo.cfg
轉到 cd ZK_HOME/bin
啟動 sh zkServer.sh start
叢集搭建
- zoo.cfg中配置叢集, 配置格式如
server.id=ip:port:port
; 有幾臺就配置幾個.例如:
server.1=192.168.1.145:2897:3181
server.2=192.168.1.146:2897:3181
server.3=192.168.1.147:2897:3181 - zoo.cfg中的dataDir所配置的目錄下新增myid檔案, 值對應server.id的id
- zoo.cfg中, 如果需要observer,則新增
peerType=observer
, 並且修改server.id=ip:port:port:observer
zoo.cfg 配置
- tickTime=2000 zk的方法最小時間單位
- initTime=10 時長為10*tickTime, follow節點和leader節點同步的時間
- syncLimit=5 時長為5*tickTime, leader和follow幾點進行心跳檢測的最大延遲時間
- dataDir=/tmp/zookeeper 儲存快照檔案的目錄
- dataLogDir = /log/zookeeper 事務日誌的儲存路徑, 預設在dataDir下
- clientPort=2181 連線zookeeper的預設埠
zookeeper幾個概念
- znode: zookeeper資料儲存為樹形結構, 深度層級沒有限制, znode是資料儲存節點,是zookeeper的最小儲存單元. 分為1.持久化節點;2.持久化有序節點;3.臨時節點;4.臨時有序節點;
- 臨時節點, 是會話時生成的節點, 會話結束後節點會自動刪除.
客戶端命令操作
help
可以檢視客戶端支援的命令
- create [-s] [-e] node : 建立節點 -s是有序 -e臨時節點
- get path [watch] 獲取節點的資訊
- set path data [version] : 修改節點的值
- delete path [version] : 刪除節點, 當節點有子節點時無法刪除
[version] 樂觀鎖
[Watcher] 提供了釋出/訂閱, 允許客戶端向伺服器端註冊一個監聽, 當服務端觸發指定事件時會觸發watcher,服務端向客戶端傳送一個通知.
Watcher是一次性的, 觸發一次後自動失效
資訊節點
- stat path 可以檢視節點的資訊
cversion=0 子節點的版本
AclVersion=0 acl的版本號, 許可權控制相關
dataVersion=1 資料的版本號
cxid 建立的事務id
mzxid 最後一次修改的事務id
pzxid 子節點最後一次修改的事務id
ephemeralOwner 臨時會話的id
dataLength 資料長度
numChidren 子節點數量
java開發
引用依賴
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.4-beta</version>
</dependency>
java程式碼
/**
* 定義Watcher
*/
public class MyWatcher implements Watcher {
@Override
public void process(WatchedEvent event) {
if(event.getState()== Event.KeeperState.SyncConnected){
System.out.println("---->>>>>"+event.getType());
System.out.println("---->>>>>"+event.getPath());
}
}
}
//測試
public class MySession {
private static final String CONN = "localhost:2181";
private static Stat stat = new Stat();
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
ZooKeeper zooKeeper = new ZooKeeper(CONN,1000,new MyWatcher());
// 建立
zooKeeper.create("/xlx","this is a string".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 查詢,註冊watcher
byte[] rst = zooKeeper.getData("/xlx",true,stat);
System.out.println(new String(rst));
//刪除(只能刪除永久節點)
zooKeeper.delete("/xlx",-1);
// 建立
zooKeeper.create("/xlx","this is a string".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 查詢
byte[] rs = zooKeeper.getData("/xlx",true,stat);
System.out.println(new String(rs));
// 註冊watcher
zooKeeper.exists("/xlx/yy",true);
// 建立
zooKeeper.create("/xlx/yy","this is a sub child string".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 獲取子節點
List<String> children = zooKeeper.getChildren("/xlx", true);
System.out.println(children);
//刪除(只能刪除永久節點)
zooKeeper.delete("/xlx/yy",-1);
// 修改
zooKeeper.setData("/xlx","this is a modified string".getBytes(),-1);
byte[] rss = zooKeeper.getData("/xlx",true,stat);
System.out.println(new String(rss));
//刪除(只能刪除永久節點)
zooKeeper.delete("/xlx",-1);
// watcher 非同步的, 這裡停留段時間才可以檢視到watcher列印的資訊
Thread.sleep(2000);
}
}
三方API
- zkClient
- curator 這個用的較多,Netflix開源
curator開發
特點:
- 抽象層次更高
- 鏈式程式設計風格
- 非同步回撥
public class CuratorSession {
private static final String CONN = "localhost:2181";
public static void main(String[] args) throws Exception {
// 建立
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(CONN, 2000, 5000, new ExponentialBackoffRetry(1000,3));
curatorFramework.start();
System.out.println(curatorFramework.getState());
//另一種方式
//curatorFramework = CuratorFrameworkFactory.builder().build();
// 新增節點
curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/curator/chd/dd","dfadfe".getBytes());
//讀取
byte[] data = curatorFramework.getData().forPath("/curator/chd/dd");
java.lang.String string = new java.lang.String(data);
System.out.println(string);
// 修改
Stat stat = curatorFramework.setData().forPath("/curator/chd/dd","fdaefv".getBytes());
System.out.println(stat);
curatorFramework.setData().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println(event);
}
}).forPath("/curator/chd/dd","fafdae".getBytes());
//刪除
curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath("/curator/chd");
Thread.sleep(2000);
}
}
zookeeper應用場景
分散式鎖
可以使用redis,資料庫,zookeeper等實現.
排他鎖(寫鎖), 共享鎖(讀鎖)釋出/訂閱(配置中心)
配置中心資料的特點: 1. 資料量比較小;2.各臺伺服器的配置內容一致;3.配置資訊在執行時會發生變更;
配置中心有兩種實現方式: pull,push; zookeeper結合這兩種方式實現配置中心. 具體為: 客戶端向伺服器註冊自己關注的節點, 當節點發生變化時, 伺服器向客戶端傳送watcher事件通知,再由客戶端pull變化的節點資料.
負載均衡
leader處理請求, 並將請求路由到特定節點. 核心是負載均衡的演算法.
- leader選舉
- 命名服務
分散式佇列
分散式鎖實現(java api)
原理: 當需要鎖時, 客戶端會向服務端的節點A寫有序的子節點, 此時可能有其他的客戶端也同時需要獲取鎖,因此節點A下可能會有若干子節點001,002,003...., 然後客戶端獲取父節點A的所有子節點,並取得其中最小的節點001, 如果001是當前客戶端建立的, 則當前客戶端獲取到鎖;如果001不是當前客戶端建立, 比如當前客戶端建立了003, 則取003的前一個節點,也就是002新增監控watcher, 當002監控事件為刪除時, 建立了003的客戶端得到一個通知, 說明前一個節點已經釋放, 此時003可以獲取到鎖.而002節點則監控001, 也就是說每個節點監控其前一個節點, 前一個節點釋放, 則表示當前節點可以獲取到鎖了.
public class DistributeLock {
private static final String ROOT_LOCKS="/LOCK";
private ZooKeeper zooKeeper;
private int sessionTimeout;
private String lockID;
private final static byte[] date={1,2};
private CountDownLatch countDownLatch = new CountDownLatch(1);
public DistributeLock() throws IOException, InterruptedException {
this.zooKeeper = ZookeeperClient.getZookeeperClient();
this.sessionTimeout = ZookeeperClient.SESSIONTIMEOUT;
}
public boolean lock(){
try{
lockID = zooKeeper.create(ROOT_LOCKS+"/",date, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(Thread.currentThread().getName()+"->成功建立了lock節點["+lockID+"], 開始去競爭鎖");
List<String> childrenNodes = zooKeeper.getChildren(ROOT_LOCKS,true);
SortedSet<String> sortedSet = new TreeSet<>();
for(String children:childrenNodes){
sortedSet.add(ROOT_LOCKS+"/"+children);
}
String first = sortedSet.first();
if (first.equals(lockID)){
System.out.println(Thread.currentThread().getName()+"->成功獲得鎖,lock節點為:["+lockID+"]");
return true;
}
SortedSet<String> lessThanLockId = ((TreeSet<String>) sortedSet).headSet(lockID);
if (!lessThanLockId.isEmpty()){
String prevLockID = lessThanLockId.last();
zooKeeper.exists(prevLockID,new LockWatcher(countDownLatch));
countDownLatch.await(sessionTimeout,TimeUnit.MILLISECONDS);
System.out.println(Thread.currentThread().getName()+" 成功獲取鎖:["+lockID+"]");
}
}catch (Exception e){e.printStackTrace();}
return false;
}
public boolean unlock(){
System.out.println(Thread.currentThread().getName()+"->開始釋放鎖:["+lockID+"]");
try {
zooKeeper.delete(lockID,-1);
System.out.println("節點["+lockID+"]成功被刪除");
return true;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
return false;
}
public static void main(String[] args) {
final CountDownLatch latch=new CountDownLatch(10);
Random random=new Random();
for(int i=0;i<10;i++){
new Thread(()->{
DistributeLock lock=null;
try {
lock=new DistributeLock();
latch.countDown();
latch.await();
lock.lock();
Thread.sleep(random.nextInt(500));
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock!=null){
lock.unlock();
}
}
}).start();
}
}
}
這些程式碼寫下來就算入門了, curator真正有用的使用場景還沒接觸到, 比如分散式鎖,leader選舉等, curator有示例程式, 可以在github上檢視Curator原始碼.