文章目錄
1. zookeeper客戶端命令操作
1.啓動zookeeper客戶端
zkCli.sh
2.創建普通節點
create /iweb "jianhau"
3.獲取節點的值
get /iweb
4.創建短暫節點
create -e /iweb
6.監聽節點的變化
ls /iweb watch
5.退出
quit
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-v9afaE3X-1570004773361)(11886AE15C5E4EE284904D287986A1BD)]
2. zookeeper內部原理
2.1 持久化節點和臨時節點
- 持久化節點是當客戶端和zookeeper斷開連接後,該節點依舊存在
- 臨時節點是當客戶端和zookeeper斷開連接後,節點自動刪除
2.2 Stat結構體
[zk: localhost:2181(CONNECTED) 5] get /sanguo
jinlian
cZxid = 0x100000003 ==創建節點的事務id==
ctime = Wed Aug 29 00:03:23 CST 2018 ==被創建的好毫秒數==
mZxid = 0x100000003 ==最後更新的事務==
mtime = Wed Aug 29 00:03:23 CST 2018 ==最後更改的毫秒數==
pZxid = 0x100000004 ==最後更新的子節點id==
cversion = 1 ==子節點修改次數==
dataVersion = 0 ==數據變化號==
aclVersion = 0 ==訪問被控制列表的變化號==
ephemeralOwner = 0x0 ==如果是臨時節點,這個是擁有者的session id 如果不是臨時節點就是0==
dataLength = 7 ==數據長度==
numChildren = 1 ==子節點數量==
2.3 監聽原理
- 首先創建一個main線程
- 在main線程中創建一個客戶端,這是就會創建兩個線程connect,Listener,一負責通信,一個負責監聽
- 通過connect註冊監聽
- 在zookeeper的註冊監聽器列表中添加註冊的監聽事件
- 監聽到有數據或者路徑變化時通過,就會將消息發送給listener線程
- listener線程內部調用了process方法
常見的監聽
1.監聽數據
get path [watch]
2.監聽子節點的增減變化
ls path [watch]
2.4paxos算法
- 當節點發生變化後,會彙報給zkserver,此時zkserver收到一個zxid
- 如果zxid大於自己當前的zxid,先記錄下來,然後同步給其他的zkserver如果超過半數的zkserver同意後即生效,更新後同步給其他的zkserver,修改自己的zxid
2.5選舉機制
- 半數機制:集羣中半數以上的集羣存活,集羣可用,所以安裝奇數臺服務器
- 雖然在配置文件中沒有指定的主從,但是會選舉產生一個leader和其他的follower
- 如果有五臺機器
- 第一臺上線後先投自己一票
- 第二臺上線後,先頭自己一票,由於他的id比第一臺的大,所以第一臺改投第二臺,此時第一臺0票,第二臺2票,但是少於3,仍然不能成爲leader
- 第三臺上線後,此時服務器都會改選票給服務器3,此時他又三票,成爲leader,其他狀態爲follower
- 第四臺啓動,123不會交換選票信息,第四臺只有一票少數服從多數
2.6寫數據流程
- client向server1發送寫數據請求,如果server1不是leader,那麼server1會把請求轉發給leader
- leader把寫請求廣播給follow
- follow返回信息並把請求放入待寫隊列中,並返回成功信息
- 當leader收到半數以上的成功信息後,說明該寫操作可以執行
- leader向各個server發送提交信息,各個server收到後落實寫請求,操作成功
- 返回給客戶端操作成功
3.API操作
3.1zk客戶端操作
public class Zkutils {
private static int Session_Time_Out = 300000;
private static ZooKeeper zk = null;
//創建節點
public static void CreateNodes() throws KeeperException, InterruptedException {
String path = "/test";
byte [] bytes = "hello zk ".getBytes();
String result = zk.create(path, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(result);
}
//判斷節點是否存在
public static void NodeExist()throws Exception{
String path ="/test";
Stat stat = zk.exists(path, false);
System.out.println(stat);
}
//獲取數據
public static void getData() throws KeeperException, InterruptedException {
String path = "/test";
Stat stat = new Stat();
byte[] data = zk.getData(path, false, stat);
System.out.println(new String(data, Charset.forName("UTF-8")));
}
//存儲數據
public static void setData() throws KeeperException, InterruptedException {
String path ="/test";
Stat stat = zk.setData(path, "hellozk".getBytes(), -1);
System.out.println(stat);
}
//刪除節點
public static void DeleteNode() throws KeeperException, InterruptedException {
String path ="/test";
zk.delete(path,-1);
}
//測試
public static void main(String[] args) {
try {
zk = new ZooKeeper("bigdata1:2181,bigdata2:2181,bigdata3:2181", Session_Time_Out, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.toString());
}
});
// CreateNodes();
// setData();
// getData();
DeleteNode();
NodeExist();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
try {
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.2動態上下線
public class DynamicUpDown {
private ZooKeeper zk = null;
private static int SESSION_TIME_OUT = 300000;
private static String HOSTS = "bigdata1:2181,bigdata2:2181,bigdata3:2181";
private static List<ACL> ACL = ZooDefs.Ids.OPEN_ACL_UNSAFE;
private static String PRAENT_NODE = "/hosts";
private List<String> serverlist = new ArrayList<>();
public void Init() throws Exception{
zk = new ZooKeeper(HOSTS, SESSION_TIME_OUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
String path = watchedEvent.getPath();
Event.EventType type = watchedEvent.getType();
//打印出當前監聽事件類型
System.out.println(type);
//判斷子節點的變化
if (Event.EventType.NodeChildrenChanged==type&&path.equals(PRAENT_NODE)){
try {
//更新列表
UpdateServerList();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
//創建父節點
Stat stat = zk.exists(PRAENT_NODE, false);
if (stat == null){
zk.create(PRAENT_NODE,"父節點".getBytes(),ACL, CreateMode.PERSISTENT);
}
UpdateServerList();
}
private void UpdateServerList() throws Exception{
List<String> newServerList = new ArrayList<>();
List<String> children = zk.getChildren(PRAENT_NODE,true);
children.forEach(child->{
try {
//獲取當前子節點的目錄
byte[] data = zk.getData(PRAENT_NODE + "/" + child, false, null);
newServerList.add(new String(data));
serverlist = newServerList;
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
serverlist.forEach(list->{
System.out.println(list);
});
}
//關閉資源
public void close() {
try {
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
DynamicUpDown server = new DynamicUpDown();
try {
server.Init();
Thread.sleep(Long.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}finally {
server.close();
}
}
}
3.3同步線程鎖
//抽取任務接口
public interface CustomTask {
void doSomething();
}
//定義自己的任務
public class Mytask implements CustomTask{
private String name;
public Mytask(String name){
this.name =name;
}
@Override
public void doSomething() {
for (int i = 1;i<=5;i++){
System.out.println("做事情"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
指定個數的客戶端訪問服務器的資源
1.上線就向zookeeper客戶端註冊
2.判斷是否只有一個客戶端工作,若只有一個,便可以處理業務
3.獲取父節點下注冊的所有的鎖,通過判斷自己是否是號碼最小的那把鎖,如果是則可以處理業務
*/
public class DistributeLock {
private ZooKeeper zk= null;
private CustomTask task = null;
private final List<org.apache.zookeeper.data.ACL> ACL = ZooDefs.Ids.OPEN_ACL_UNSAFE;
private final int sessionTimeOut = 5000;
private final String parent_node = "/locks";
private String connectstring = null;
private volatile String currentPath = null;
public DistributeLock(String connectstring){
this.connectstring = connectstring;
}
public DistributeLock(){
this("bigdata1:2181,bigdata2:2181,bigdata3:2181");
}
public void setTask(CustomTask task){
this.task = task;
}
//獲取客戶端
public void getClient() throws Exception{
zk =new ZooKeeper(connectstring,sessionTimeOut,event->{
//監聽子節點的變化
if (event.getType()==Watcher.Event.EventType.NodeChildrenChanged&&
event.getPath().equals(parent_node)){
try {
//拿到所有的子節點
List<String> child = zk.getChildren(parent_node,true);
//判斷自己是否是最小的節點
String currentNode = currentPath.substring(parent_node.length() + 1);
//排序
Collections.sort(child);
if (child.indexOf(currentNode)==0){
task.doSomething();
//釋放鎖
deleteLock();
//註冊新鎖
registerLock();
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//註冊鎖
public void registerLock()throws Exception{
//使用CreateMode.EPHEMERAL_SEQUENTIAL臨時順序型
currentPath = zk.create(parent_node+"/lock",null,ACL,CreateMode.EPHEMERAL_SEQUENTIAL);
}
//判斷是否只有一個節點在線,若只有自己一個節點,則調用業務處理的方法
public void watchParent() throws Exception{
List<String> children = zk.getChildren(parent_node, false);
if (children!=null&&children.size()==1){
task.doSomething();
deleteLock();
}else {
Thread.sleep(Long.MAX_VALUE);
}
}
public void deleteLock() throws Exception{
zk.delete(currentPath,-1);
}
}
//測試
public class Test {
public static void main(String[] args) throws Exception{
//獲取客戶端連接
DistributeLock distributeLock = new DistributeLock();
CustomTask customTask = new Mytask(UUID.randomUUID().toString());
//設置任務
distributeLock.setTask(customTask);
distributeLock.getClient();
//註冊鎖
distributeLock.registerLock();
//監聽父節點
distributeLock.watchParent();
}
}