ZooKeeper 學習 (六) ZooKeeper實現數據發佈訂閱(即配置中心)

數據發佈/訂閱(Publish/Subscribe)系統,即所謂的配置中心,顧名思義就是發佈者將數據發佈到ZooKeeper的一個或一系列節點上,供訂閱者進行數據訂閱,進而達到動態獲取數據的目的,實現配置信息的集中式管理和數據的動態更新。

發佈/訂閱系統一般有兩種設計模式,分別是推(Push)模式和拉(Pull)模式。ZooKeeper採用的是推拉相結合的方式:客戶端向服務端註冊自己需要關注的節點,一旦該節點的數據發生變更,那麼服務端就會向相應的客戶端發送Watcher事件通知,客戶端接收到這個消息通知後,需要主動到服務端獲取最新的數據。

發佈訂閱模式在分佈式系統的典型應用有, 配置管理和服務發現。


配置管理:是指如果集羣中機器擁有某些相同的配置,並且這些配置信息需要動態的改變,我們可以使用發佈訂閱模式,對配置文件做統一的管理,讓這些機器各自訂閱配置文件的改變,當配置文件發生改變的時候這些機器就會得到通知,把自己的配置文件更新爲最新的配置
    

服務發現:是指對集羣中的服務上下線做統一的管理,每個工作服務器都可以作爲數據的發佈方,向集羣註冊自己的基本信息,而讓模型機器作爲訂閱方,訂閱工作服務器的基本信息,當工作服務器的基本信息發生改變時如上下線,服務器的角色和服務範圍變更,監控服務器就會得到通知,並響應這些變化。


以服務發現的形式實現代碼:

package com.tlk.zk.chapter5.configCenter;
/**
 * 用於記錄工作服務器的基本信息
 */
public class ServerData {
	private String address;
	private Integer id;
	private String name;
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString() {
		return "ServerData [address=" + address + ", id=" + id + ", name=" + name + "]";
	}
	
	
}

package com.tlk.zk.chapter5.configCenter;

import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNoNodeException;

import com.alibaba.fastjson.JSON;

/**
 * 服務提供者,註冊服務到serversPath下
 * @author tanlk
 * @date 2017年8月13日 下午3:51:54
 */
public class Provider {
	
	private String serversPath;
	private ZkClient zkClient;
    private ServerData serverData;
    
    public Provider() {

    }
    
    /**
     * 
     * @param serversPath provider要存的地址
     * @param zkClient ZooKeeper連接
     * @param serverData provider要存的信息
     */
    public Provider(String serversPath, ZkClient zkClient, ServerData serverData) {
		super();
		this.serversPath = serversPath;
		this.zkClient = zkClient;
		this.serverData = serverData;
	}


	/**
	 * 服務的啓動
	 */
	public void start(){
		System.out.println("provider server start...");
		initRunning();
	}
		
	/**
	 * 服務器的初始化
	 */
	private void initRunning(){
		registMeToZookeeper();
	}
	
	/**
	 * 啓動時向zookeeper註冊自己
	 */
	private void registMeToZookeeper(){
		//向zookeeper中註冊自己的過程其實就是向servers節點下注冊一個臨時節點
		//構造臨時節點
		String mePath = serversPath.concat("/").concat(serverData.getAddress());
	    try{
	    	//存入是將json序列化
			zkClient.createEphemeral(mePath, JSON.toJSONString(serverData).getBytes());	
	    } catch (ZkNoNodeException e) {
	    	//父節點不存在
			zkClient.createPersistent(serversPath, true);
			registMeToZookeeper();
		}
		
	}
	
}




package com.tlk.zk.chapter5.configCenter;

import java.util.List;

import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;

/**
 * 服務消費者,監聽serversPath的子節點的變化,有就更新
 * @author tanlk
 * @date 2017年8月13日 下午3:59:00
 */
public class Consumer {

	private String serversPath;
	private ZkClient zkClient;
	//用於監聽zookeeper中servers節點的子節點列表變化
	private IZkChildListener childListener;
	//provider的列表
	private List<String> providerServerList;
	
	public Consumer() {
	}

	public Consumer(String serversPath, ZkClient zkClient) {
		super();
		this.serversPath = serversPath;
		this.zkClient = zkClient;
		this.childListener = new IZkChildListener() {
			
			public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
				providerServerList = currentChilds;
				System.out.println("provider server list changed, new list is ");
				execList();
			}
		};
	}
	
	private void execList() {
		
		System.out.println(providerServerList.toString());
	}
	
	public void start() {
		initRunning();
	}

	public void stop() {
		//取消訂閱servers節點的列表變化
		zkClient.unsubscribeChildChanges(serversPath, childListener);
	}
	
	/**
	 * 初始化
	 */
	private void initRunning() {
		//執行訂閱servers節點的列表變化
		zkClient.subscribeChildChanges(serversPath, childListener);
	
	}
	
	
}



package com.tlk.zk.chapter5.configCenter;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.BytesPushThroughSerializer;

/**
 * 測試類
 * 先啓動一個consumer監聽SERVERS_PATH的子節點
 * 再一個個的啓動provider查看consumer的監聽情況
 * 
 * @author tanlk
 * @date 2017年8月13日 下午4:07:08
 */
public class ConfigServerMain {

	private static final String  ZOOKEEPER_SERVER = "127.0.0.1:2181";
	private static final String SERVERS_PATH = "/servers";
	
	public static void main(String[] args) {
		//用來存儲所有的clients,最後close使用
        List<ZkClient>  clients = new ArrayList<ZkClient>();
        Consumer consumer = null;
		try {
			ZkClient clientManage = new ZkClient(ZOOKEEPER_SERVER, 5000, 5000, new BytesPushThroughSerializer());
			consumer = new Consumer(SERVERS_PATH,clientManage);
			consumer.start();
			
			for(int i = 0; i < 5; i++){
				ZkClient client = new ZkClient(ZOOKEEPER_SERVER, 5000, 5000, new BytesPushThroughSerializer());
	            clients.add(client);
				ServerData serverData = new ServerData();
				serverData.setAddress("192.168.1." +i);
				serverData.setId(i);
				serverData.setName("provider&&" + i);
				Provider provider = new Provider(SERVERS_PATH, client, serverData);
				provider.start();
				
				System.out.println("敲回車鍵繼續添加provider!\n");
		        new BufferedReader(new InputStreamReader(System.in)).readLine();
			}
			
			System.out.println("敲回車鍵退出!\n");
	        new BufferedReader(new InputStreamReader(System.in)).readLine();
			
		} catch (Exception e) {
			
		}finally {
			for (ZkClient zkClient : clients) {
				zkClient.close();
			}
		}
		
	}
}


ps:如果實現配置管理模式,只需要有個配置提供者修改配置參數(對應的是當前節點的值),配置使用着去監聽節點的數據變化(IZkDataListener),而非上訴的監聽子節點的數據變化(IZkChildListener



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章