上幾回博客,我說了一下Zookeeper的簡單使用和API的使用,咱們接下來看一下他的真實場景。node
1、分佈式集羣管理✨✨✨web
咱們如今有這樣一個需求,請先拋開Zookeeper是集羣仍是單機的概念,下面提到的都是以Zookeeper集羣來講的。redis
1. 主動查看線上服務節點小程序
2. 查看服務節點資源使用狀況緩存
3. 服務離線通知服務器
4. 服務資源(CPU、內存、硬盤)超出閥值通知網絡
咱們先來看一下代碼實現流程吧。主要分爲兩個部分的,一個部分是寫入Zookeeper集羣,另外一部分是獲取Zookeeper集羣內部的數據。多線程
寫入Zookeeper集羣部分: 併發
寫入的信息包括該服務器的內存使用狀況,CPU使用狀況等信息。app
public void init() { zkClient = new ZkClient(server, 5000, 10000); System.out.println("zk鏈接成功" + server); // 建立根節點 buildRoot(); // 建立臨時節點 createServerNode(); // 啓動更新的線程 stateThread = new Thread(() -> { while (true) { updateServerNode();//數據寫到 當前的臨時節點中去 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }, "zk_stateThread"); stateThread.setDaemon(true); stateThread.start(); }
//建立根節點,若是根節點已經存在,則再也不重複建立 public void buildRoot() { if (!zkClient.exists(rootPath)) { zkClient.createPersistent(rootPath); } }
// 生成服務節點 public void createServerNode() { nodePath = zkClient.createEphemeralSequential(servicePath, getOsInfo()); System.out.println("建立節點:" + nodePath); }
每個服務都有本身的惟一的臨時序號節點。
// 獲取服務節點狀態 public String getOsInfo() { OsBean bean = new OsBean(); bean.lastUpdateTime = System.currentTimeMillis(); bean.ip = getLocalIp(); bean.cpu = CPUMonitorCalc.getInstance().getProcessCpu(); MemoryUsage memoryUsag = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); bean.usedMemorySize = memoryUsag.getUsed() / 1024 / 1024; bean.usableMemorySize = memoryUsag.getMax() / 1024 / 1024; bean.pid = ManagementFactory.getRuntimeMXBean().getName(); ObjectMapper mapper = new ObjectMapper(); try { return mapper.writeValueAsString(bean); } catch (JsonProcessingException e) { throw new RuntimeException(e); } }
// 數據寫到 當前的臨時節點中去 public void updateServerNode() { zkClient.writeData(nodePath, getOsInfo()); }
當每一個服務開啓的時候,咱們就應該向咱們的Zookeeper發送咱們的信息,也就是優先在根節點下建立一個臨時序號節點,而且寫入服務器的相關信息。就這樣咱們的Zookeeper集羣中就有了咱們的服務器相關的信息。
讀取Zookeeper集羣信息部分:
咱們對於咱們的節點遞歸監聽就能夠了。監聽過程能夠寫入咱們的閾值限制,從而達到報警的目的。
// 初始化訂閱事件 public void initSubscribeListener() { zkClient.unsubscribeAll(); // 獲取全部子節點 zkClient.getChildren(rootPath) .stream() .map(p -> rootPath + "/" + p)// 得出子節點完整路徑 .forEach(p -> { zkClient.subscribeDataChanges(p, new DataChanges());// 數據變動的監聽 }); // 監聽子節點,的變動 增長,刪除 zkClient.subscribeChildChanges(rootPath, (parentPath, currentChilds) -> initSubscribeListener()); }
咱們也能夠將咱們獲取到的信息寫入到咱們的web頁面中去。做爲咱們Zookeeper集羣對於服務器健康信息管理的小程序。(我服務器到期了,要不就給大家一套完整的代碼演示了,過幾天補全)
總結:就是每一個服務器往咱們的Zookeeper寫入數據,在寫入以前建立根節點,而後建立咱們的臨時序號節點再來寫入咱們的數據,也是利用了臨時序號節點的特性,不會重複,並且斷開鏈接會清理掉。也能夠將server服務器和咱們的Zookeeper部署在同一個服務器也是不會影響的。(自行考慮內存,CPU,網絡等問題)
2、分佈式註冊中心✨✨✨✨
不少分佈式項目,並非使用Spring Clould的Eureka的,自我以爲Eureka和Zookeeper勢均力敵吧。咱們來看一下咱們的需求。
現有一個積分系統,因爲使用人數巨大,咱們須要同時部署四臺服務器才能承載住咱們的併發壓力。那麼咱們的請求來了,由誰來控制請求哪臺服務器呢?這時就有了咱們的Zookeeper註冊中心(需結合dubbo)。
我來大體用圖解的形式說一下原理,一會再說細節。
分佈式註冊中心原理:
說到分佈式註冊中心,咱們須要知道幾個名詞。
註冊中心:註冊中心是指咱們的Zookeeper集羣,主要是用來存儲咱們的接口信息和監聽咱們服務提供者是否正常運行的。而且還保存了咱們服務消費者的相關信息。
服務提供者:誰提供了這些接口,誰就是提供者。
服務消費者:誰想調用這些接口,誰就是消費者。
工做流程:
1.服務啓動,也就是咱們的接口啓動了,優先去咱們的註冊中心去註冊咱們的接口信息,也就是用臨時序號節點來存咱們的接口信息。
2.之後咱們的服務提供者會持續的發送消息到註冊中心去,持續的告訴咱們的註冊中心,咱們仍是可用的,仍是活着的。
3.服務消費者來調用咱們的接口了,第一次,須要到咱們的註冊中心去找一個合適接口。(具體如何分配,並非由Zookeeper來控制的),並將咱們的註冊中心的提供服務IP列表緩存到本身的服務器上。
4.只要服務提供方節點沒有變更,咱們的消費者之後的調用,只許讀取本身本地的緩存便可,不在須要去註冊中心讀取咱們的服務提供者IP列表。
這裏有一個最直觀的好處就是,原來咱們寫接口須要指定去哪一個IP調用,若是接口服務器IP變了,咱們還須要調整咱們的程序,這裏咱們只須要調用Zookeeper便可,再也不須要調整程序了。
注意:保存消費者,我暫時理解的是爲了方便直觀的看到當前都有哪些在調用咱們的接口。
3、分佈式JOB✨✨✨
分佈式JOB,我第一次遇到這個名字的時候是懵的,我還記得我當時作項目要弄一個自動發送郵件的這樣一個需求,可是咱們是橫向部署的,三臺服務器都有這段代碼,每到半夜11.30的時候都會發三份徹底一致的郵件,有人會提出,咱們只寫一個自動任務,一臺服務器部署不就能夠了嗎?請你弄死他,咱們要的高可用,你一臺服務器怎麼保證高可用,這樣的程序是明顯不合理的。說到這咱們就有了咱們的分佈式Job,分佈式Job就是要解決這樣相似的問題的。
仍是先看一下實現原理和思路。
這樣咱們就能保證只有master服務器能執行咱們的自動任務,若是master宕機了,咱們會有候補隊員保證咱們的高可用。
4、分佈式鎖✨✨✨✨✨✨
咱們單機的程序,來使用synchronized關鍵字是能夠實現多線程爭搶的問題,分佈式鎖不少是redis集羣來實現的,咱們來使用Zookeeper也是能夠的實現的。
程序內的鎖通常分爲共享讀鎖和排它寫鎖,也就是咱們加了共享讀鎖的時候,其它線程能夠來讀,可是不能改,而咱們的排它寫鎖,其它線程是不能進行任何操做的。
咱們能夠這樣來設計。
來一個線程就往咱們的lock節點內添加一個臨時序號節點,值設置爲readLock或者是writeLock,標記咱們得到是什麼類型的鎖,當咱們再來線城時,優先監聽咱們的Lock節點的數據,來判斷咱們是否能夠獲得鎖的資源,感受還不錯,能夠實現。但這樣的實現並非很合理的,咱們圖中畫了三個等待的線程還好,若是等待的線程是100個1000個的話,lock節點數據變化了,也就是上一個鎖釋放掉了,咱們那1000個線程會瘋搶咱們的鎖(羊羣效應),能夠想象1000個大媽在超市搶雞蛋的樣子,可怕....
咱們換一個實現的思路再來試試。
咱們此次改成只監聽比其小的節點數據便可,以圖爲例來講,咱們的Tread3想得到寫鎖,一定等待Tread1和Tread2的讀鎖所有釋放,咱們才能夠給Tread3添加寫鎖,咱們持續監聽Tread2線程,當Tread2線程鎖釋放掉,咱們的Tread3會繼續監聽到Tread1的使用狀況,直到沒有比他小的在使用鎖資源,咱們纔得到咱們的寫鎖資源。
感受這個和咱們的分佈式JOB差很少,最小的序號得到鎖。只不過有一個共享讀鎖和排它寫鎖的區別而已。
等我服務器續費的,上代碼,下次博客繼續來講說Zookeeper的源碼。
最進弄了一個公衆號,小菜技術,歡迎你們的加入