分佈式協調工具--ZooKeeper實現動態負載均衡

什麼是Zookeeper

Zookeeper是一個分佈式開源框架,提供了協調分佈式應用的基本服務,它向外部應用暴露一組通用服務——分佈式同步(Distributed Synchronization)、命名服務(Naming Service)、集羣維護(Group Maintenance)等,簡化分佈式應用協調及其管理的難度,提供高性能的分佈式服務。ZooKeeper本身可以以單機模式安裝運行,不過它的長處在於通過分佈式ZooKeeper集羣(一個Leader,多個Follower),基於一定的策略來保證ZooKeeper集羣的穩定性和可用性,從而實現分佈式應用的可靠性。

1、Zookeeper是爲別的分佈式程序服務的

2、Zookeeper本身就是一個分佈式程序(只要有半數以上節點存活,zk就能正常服務)

3、Zookeeper所提供的服務涵蓋:主從協調、服務器節點動態上下線、統一配置管理、分佈式共享鎖、統> 一名稱服務等

4、雖然說可以提供各種服務,但是zookeeper在底層其實只提供了兩個功能:

管理(存儲,讀取)用戶程序提交的數據(類似namenode中存放的metadata); 
併爲用戶程序提供數據節點監聽服務;

Zookeeper集羣機制

Zookeeper集羣的角色: Leader 和 follower 
只要集羣中有半數以上節點存活,集羣就能提供服務

Zookeeper特性

1、Zookeeper:一個leader,多個follower組成的集羣

2、全局數據一致:每個server保存一份相同的數據副本,client無論連接到哪個server,數據都是一致的

3、分佈式讀寫,更新請求轉發,由leader實施

4、更新請求順序進行,來自同一個client的更新請求按其發送順序依次執行

5、數據更新原子性,一次數據更新要麼成功,要麼失敗

6、實時性,在一定時間範圍內,client能讀到最新數據

Zookeeper數據結構

1、層次化的目錄結構,命名符合常規文件系統規範(類似文件系統) 

2、每個節點在zookeeper中叫做znode,並且其有一個唯一的路徑標識 
 

3、節點Znode可以包含數據和子節點(但是EPHEMERAL類型的節點不能有子節點)

節點類型 
a、Znode有兩種類型:

短暫(ephemeral)(create -e /app1/test1 “test1” 客戶端斷開連接zk刪除ephemeral類型節點) 
持久(persistent) (create -s /app1/test2 “test2” 客戶端斷開連接zk不刪除persistent類型節點)

b、Znode有四種形式的目錄節點(默認是persistent )

PERSISTENT 
PERSISTENT_SEQUENTIAL(持久序列/test0000000019 ) 
EPHEMERAL 
EPHEMERAL_SEQUENTIAL

c、創建znode時設置順序標識,znode名稱後會附加一個值,順序號是一個單調遞增的計數器,由父節點維護 
d、在分佈式系統中,順序號可以被用於爲所有的事件進行全局排序,這樣客戶端可以通過順序號推斷事件的順序

Zookeeper應用場景

數據發佈與訂閱(配置中心)

發佈與訂閱模型,即所謂的配置中心,顧名思義就是發佈者將數據發佈到ZK節點上,供訂閱者動態獲取數據,實現配置信息的集中式管理和動態更新。例如全局的配置信息,服務式服務框架的服務地址列表等就非常適合使用。
 

負載均衡

這裏說的負載均衡是指軟負載均衡。在分佈式環境中,爲了保證高可用性,通常同一個應用或同一個服務的提供方都會部署多份,達到對等服務。而消費者就須要在這些對等的服務器中選擇一個來執行相關的業務邏輯,其中比較典型的是消息中間件中的生產者,消費者負載均衡。

消息中間件中發佈者和訂閱者的負載均衡,linkedin開源的KafkaMQ和阿里開源的 metaq都是通過zookeeper來做到生產者、消費者的負載均衡。這裏以metaq爲例如講下:

生產者負載均衡:metaq發送消息的時候,生產者在發送消息的時候必須選擇一臺broker上的一個分區來發送消息,因此metaq在運行過程中,會把所有broker和對應的分區信息全部註冊到ZK指定節點上,默認的策略是一個依次輪詢的過程,生產者在通過ZK獲取分區列表之後,會按照brokerId和partition的順序排列組織成一個有序的分區列表,發送的時候按照從頭到尾循環往復的方式選擇一個分區來發送消息。

消費負載均衡: 在消費過程中,一個消費者會消費一個或多個分區中的消息,但是一個分區只會由一個消費者來消費。MetaQ的消費策略是:

1. 每個分區針對同一個group只掛載一個消費者。

2. 如果同一個group的消費者數目大於分區數目,則多出來的消費者將不參與消費。

3. 如果同一個group的消費者數目小於分區數目,則有部分消費者需要額外承擔消費任務。

在某個消費者故障或者重啓等情況下,其他消費者會感知到這一變化(通過 zookeeper watch消費者列表),然後重新進行負載均衡,保證所有的分區都有消費者進行消費。

命名服務(Naming Service)

 

命名服務也是分佈式系統中比較常見的一類場景。在分佈式系統中,通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等信息。被命名的實體通常可以是集羣中的機器,提供的服務地址,遠程對象等等——這些我們都可以統稱他們爲名字(Name)。其中較爲常見的就是一些分佈式服務框架中的服務地址列表。通過調用ZK提供的創建節點的API,能夠很容易創建一個全局唯一的path,這個path就可以作爲一個名稱。

阿里巴巴集團開源的分佈式服務框架Dubbo中使用ZooKeeper來作爲其命名服務,維護全局的服務地址列表, 點擊這裏查看Dubbo開源項目。在Dubbo實現中:

服務提供者在啓動的時候,向ZK上的指定節點/dubbo/${serviceName}/providers目錄下寫入自己的URL地址,這個操作就完成了服務的發佈。

服務消費者啓動的時候,訂閱/dubbo/${serviceName}/providers目錄下的提供者URL地址, 並向/dubbo/${serviceName} /consumers目錄下寫入自己的URL地址。

注意,所有向ZK上註冊的地址都是臨時節點,這樣就能夠保證服務提供者和消費者能夠自動感應資源的變化。 另外,Dubbo還有針對服務粒度的監控,方法是訂閱/dubbo/${serviceName}目錄下所有提供者和消費者的信息。

 

分佈式通知/協調

ZooKeeper中特有watcher註冊與異步通知機制,能夠很好的實現分佈式環境下不同系統之間的通知與協調,實現對數據變更的實時處理。使用方法通常是不同系統都對ZK上同一個znode進行註冊,監聽znode的變化(包括znode本身內容及子節點的),其中一個系統update了znode,那麼另一個系統能夠收到通知,並作出相應處理

1. 另一種心跳檢測機制:檢測系統和被檢測系統之間並不直接關聯起來,而是通過zk上某個節點關聯,大大減少系統耦合。

2. 另一種系統調度模式:某系統有控制檯和推送系統兩部分組成,控制檯的職責是控制推送系統進行相應的推送工作。管理人員在控制檯作的一些操作,實際上是修改了ZK上某些節點的狀態,而ZK就把這些變化通知給他們註冊Watcher的客戶端,即推送系統,於是,作出相應的推送任務。

3. 另一種工作彙報模式:一些類似於任務分發系統,子任務啓動後,到zk來註冊一個臨時節點,並且定時將自己的進度進行彙報(將進度寫回這個臨時節點),這樣任務管理者就能夠實時知道任務進度。

總之,使用zookeeper來進行分佈式通知和協調能夠大大降低系統之間的耦合

集羣管理與Master選舉

1. 集羣機器監控:這通常用於那種對集羣中機器狀態,機器在線率有較高要求的場景,能夠快速對集羣中機器變化作出響應。這樣的場景中,往往有一個監控系統,實時檢測集羣機器是否存活。過去的做法通常是:監控系統通過某種手段(比如ping)定時檢測每個機器,或者每個機器自己定時向監控系統彙報“我還活着”。 這種做法可行,但是存在兩個比較明顯的問題:

1. 集羣中機器有變動的時候,牽連修改的東西比較多。

2. 有一定的延時。

利用ZooKeeper有兩個特性,就可以實現另一種集羣機器存活性監控系統:

1. 客戶端在節點 x 上註冊一個Watcher,那麼如果 x?的子節點變化了,會通知該客戶端。

2. 創建EPHEMERAL類型的節點,一旦客戶端和服務器的會話結束或過期,那麼該節點就會消失。

例如,監控系統在 /clusterServers 節點上註冊一個Watcher,以後每動態加機器,那麼就往 /clusterServers 下創建一個 EPHEMERAL類型的節點:/clusterServers/{hostname}. 這樣,監控系統就能夠實時知道機器的增減情況,至於後續處理就是監控系統的業務了。

2. Master選舉則是zookeeper中最爲經典的應用場景了。

在分佈式環境中,相同的業務應用分佈在不同的機器上,有些業務邏輯(例如一些耗時的計算,網絡I/O處理),往往只需要讓整個集羣中的某一臺機器進行執行,其餘機器可以共享這個結果,這樣可以大大減少重複勞動,提高性能,於是這個master選舉便是這種場景下的碰到的主要問題。

利用ZooKeeper的強一致性,能夠保證在分佈式高併發情況下節點創建的全局唯一性,即:同時有多個客戶端請求創建 /currentMaster 節點,最終一定只有一個客戶端請求能夠創建成功。利用這個特性,就能很輕易的在分佈式環境中進行集羣選取了。

另外,這種場景演化一下,就是動態Master選舉。這就要用到EPHEMERAL_SEQUENTIAL類型節點的特性了。

上文中提到,所有客戶端創建請求,最終只有一個能夠創建成功。在這裏稍微變化下,就是允許所有請求都能夠創建成功,但是得有個創建順序,於是所有的請求最終在ZK上創建結果的一種可能情況是這樣: /currentMaster/{sessionId}-1 ,/currentMaster/{sessionId}-2,/currentMaster/{sessionId}-3 ….. 每次選取序列號最小的那個機器作爲Master,如果這個機器掛了,由於他創建的節點會馬上小時,那麼之後最小的那個機器就是Master了。

1. 在搜索系統中,如果集羣中每個機器都生成一份全量索引,不僅耗時,而且不能保證彼此之間索引數據一致。因此讓集羣中的Master來進行全量索引的生成,然後同步到集羣中其它機器。另外,Master選舉的容災措施是,可以隨時進行手動指定master,就是說應用在zk在無法獲取master信息時,可以通過比如http方式,向一個地方獲取master。

2. 在Hbase中,也是使用ZooKeeper來實現動態HMaster的選舉。在Hbase實現中,會在ZK上存儲一些ROOT表的地址和HMaster的地址,HRegionServer也會把自己以臨時節點(Ephemeral)的方式註冊到Zookeeper中,使得HMaster可以隨時感知到各個HRegionServer的存活狀態,同時,一旦HMaster出現問題,會重新選舉出一個HMaster來運行,從而避免了HMaster的單點問題

分佈式鎖

分佈式鎖,這個主要得益於 ZooKeeper 爲我們保證了數據的強一致性。鎖服務可以分爲兩類,一個是 保持獨佔,另一個是 控制時序。

1. 所謂保持獨佔,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖。通常的做法是把 zk 上的一個 znode 看作是一把鎖,通過 create znode 的方式來實現。所有客戶端都去創建 /distribute_lock 節點,最終成功創建的那個客戶端也即擁有了這把鎖。

2. 控制時序,就是所有視圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全局時序了。做法和上面基本類似,只是這裏 /distributelock 已經預先存在,客戶端在它下面創建臨時有序節點(這個可以通過節點的屬性控制:CreateMode.EPHEMERALSEQUENTIAL 來指定)。Zk 的父節點(/distribute_lock)維持一份 sequence, 保證子節點創建的時序性,從而也形成了每個客戶端的全局時序。

Java操作Zookeeper

Zookeeper說明

創建節點(znode) 方法:

create:
提供了兩套創建節點的方法,同步和異步創建節點方式。
同步方式:
參數1,節點路徑《名稱) : InodeName (不允許遞歸創建節點,也就是說在父節點不存在
的情況下,不允許創建子節點)
參數2,節點內容: 要求類型是字節數組(也就是說,不支持序列化方式,如果需要實現序
列化,可使用java相關序列化框架,如Hessian、Kryo框架)
參數3,節點權限: 使用Ids.OPEN_ACL_UNSAFE開放權限即可。(這個參數一般在權展
沒有太高要求的場景下,沒必要關注)
參數4,節點類型: 創建節點的類型: CreateMode,提供四種首點象型

PERSISTENT                   持久化節點

PERSISTENT_SEQUENTIAL        順序自動編號持久化節點,這種節點會根據當前已存在的節點數自動加 1

EPHEMERAL                    臨時節點, 客戶端session超時這類節點就會被自動刪除

EPHEMERAL_SEQUENTIAL         臨時自動編號節點

Zookeeper客戶端連接

public class Test001 {

	//連接地址
	private static final String ADDRES = "127.0.0.1:2181";
	//session 會話
	private static final int SESSION_OUTTIME = 2000;
	//信號量,阻塞程序執行,用戶等待zookeeper連接成功,發送成功信號,
	private static final CountDownLatch countDownLatch = new CountDownLatch(1);

	public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
		ZooKeeper zk = new ZooKeeper(ADDRES, SESSION_OUTTIME, new Watcher() {

			public void process(WatchedEvent event) {
				// 獲取事件狀態
				KeeperState keeperState = event.getState();
				// 獲取事件類型
				EventType eventType = event.getType();
				if (KeeperState.SyncConnected == keeperState) {
					if (EventType.None == eventType) {
						countDownLatch.countDown();
						System.out.println("zk 啓動連接...");
					}

				}
			}
		});
		// 進行阻塞
		countDownLatch.await();
		String result = zk.create("/itmayeidu_Lasting", "Lasting".getBytes(), Ids.OPEN_ACL_UNSAFE,
				CreateMode.PERSISTENT);
		System.out.println(result);
		zk.close();
	}

}

 創建Zookeeper節點信息


1.	創建持久節點,並且允許任何服務器可以操作
	String result = zk.create("/itmayiedu_Lasting", "Lasting".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
	System.out.println("result:" + result);
2.	創建臨時節點
	String result = zk.create("/itmayiedu_temp", "temp".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
	System.out.println("result:" + result);

Watcher

在ZooKeeper中,接口類Watcher用於表示一個標準的事件處理器,其定義了事件通知相關的邏輯,包含KeeperState和EventType兩個枚舉類,分別代表了通知狀態和事件類型,同時定義了事件的回調方法:process(WatchedEvent event)

 什麼是Watcher接口

同一個事件類型在不同的通知狀態中代表的含義有所不同,表7-3列舉了常見的通知狀態和事件類型。

KeeperState

EventType

觸發條件

說明

 

None
(-1)

客戶端與服務端成功建立連接

 

SyncConnected
(0)

NodeCreated
(1)

Watcher監聽的對應數據節點被創建

 

 

NodeDeleted
(2)

Watcher監聽的對應數據節點被刪除

此時客戶端和服務器處於連接狀態

 

NodeDataChanged
(3)

Watcher監聽的對應數據節點的數據內容發生變更

 

 

NodeChildChanged
(4)

Wather監聽的對應數據節點的子節點列表發生變更

 

Disconnected
(0)

None
(-1)

客戶端與ZooKeeper服務器斷開連接

此時客戶端和服務器處於斷開連接狀態

Expired
(-112)

Node
(-1)

會話超時

此時客戶端會話失效,通常同時也會受到SessionExpiredException異常

AuthFailed
(4)

None
(-1)

通常有兩種情況,1:使用錯誤的schema進行權限檢查 2:SASL權限檢查失敗

通常同時也會收到AuthFailedException異常

 Watcher代碼

public class ZkClientWatcher implements Watcher {
	// 集羣連接地址
	private static final String CONNECT_ADDRES = "192.168.110.159:2181,192.168.110.160:2181,192.168.110.162:2181";
	// 會話超時時間
	private static final int SESSIONTIME = 2000;
	// 信號量,讓zk在連接之前等待,連接成功後才能往下走.
	private static final CountDownLatch countDownLatch = new CountDownLatch(1);
	private static String LOG_MAIN = "【main】 ";
	private ZooKeeper zk;

	public void createConnection(String connectAddres, int sessionTimeOut) {
		try {
			zk = new ZooKeeper(connectAddres, sessionTimeOut, this);
			System.out.println(LOG_MAIN + "zk 開始啓動連接服務器....");
			countDownLatch.await();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public boolean createPath(String path, String data) {
		try {
			this.exists(path, true);
			this.zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
			System.out.println(LOG_MAIN + "節點創建成功, Path:" + path + ",data:" + data);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 判斷指定節點是否存在
	 * 
	 * @param path
	 *            節點路徑
	 */
	public Stat exists(String path, boolean needWatch) {
		try {
			return this.zk.exists(path, needWatch);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public boolean updateNode(String path,String data) throws KeeperException, InterruptedException {
		exists(path, true);
		this.zk.setData(path, data.getBytes(), -1);
		return false;
	}

	public void process(WatchedEvent watchedEvent) {

		// 獲取事件狀態
		KeeperState keeperState = watchedEvent.getState();
		// 獲取事件類型
		EventType eventType = watchedEvent.getType();
		// zk 路徑
		String path = watchedEvent.getPath();
		System.out.println("進入到 process() keeperState:" + keeperState + ", eventType:" + eventType + ", path:" + path);
		// 判斷是否建立連接
		if (KeeperState.SyncConnected == keeperState) {
			if (EventType.None == eventType) {
				// 如果建立建立成功,讓後程序往下走
				System.out.println(LOG_MAIN + "zk 建立連接成功!");
				countDownLatch.countDown();
			} else if (EventType.NodeCreated == eventType) {
				System.out.println(LOG_MAIN + "事件通知,新增node節點" + path);
			} else if (EventType.NodeDataChanged == eventType) {
				System.out.println(LOG_MAIN + "事件通知,當前node節點" + path + "被修改....");
			}
			else if (EventType.NodeDeleted == eventType) {
				System.out.println(LOG_MAIN + "事件通知,當前node節點" + path + "被刪除....");
			}

		}
		System.out.println("--------------------------------------------------------");
	}

	public static void main(String[] args) throws KeeperException, InterruptedException {
		ZkClientWatcher zkClientWatcher = new ZkClientWatcher();
		zkClientWatcher.createConnection(CONNECT_ADDRES, SESSIONTIME);
//		boolean createResult = zkClientWatcher.createPath("/p15", "pa-644064");
		zkClientWatcher.updateNode("/pa2","7894561");
	}

}

 使用Zookeeper實現負載均衡原理

思路

使用Zookeeper實現負載均衡原理,服務器端將啓動的服務註冊到,zk註冊中心上,採用臨時節點。客戶端從zk節點上獲取最新服務節點信息,本地使用負載均衡算法,隨機分配服務器。

ZkServerScoekt服務

//##ServerScoekt服務端
public class ZkServerScoekt implements Runnable {
	private int port = 18080;

	public static void main(String[] args) throws IOException {
		int port = 18080;	
		ZkServerScoekt server = new ZkServerScoekt(port);
		Thread thread = new Thread(server);
		thread.start();
	}

	public ZkServerScoekt(int port) {
		this.port = port;
	}

	public void run() {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(port);
			System.out.println("Server start port:" + port);
			Socket socket = null;
			while (true) {
				socket = serverSocket.accept();
				new Thread(new ServerHandler(socket)).start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (serverSocket != null) {
					serverSocket.close();
				}
			} catch (Exception e2) {

			}
		}
	}

}

ServerHandler

public class ServerHandler implements Runnable {
	private Socket socket;

	public ServerHandler(Socket socket) {
		this.socket = socket;
	}

	public void run() {
		BufferedReader in = null;
		PrintWriter out = null;
		try {
			in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
			out = new PrintWriter(this.socket.getOutputStream(), true);
			String body = null;
			while (true) {
				body = in.readLine();
				if (body == null)
					break;
				System.out.println("Receive : " + body);
				out.println("Hello, " + body);
			}

		} catch (Exception e) {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			if (out != null) {
				out.close();
			}
			if (this.socket != null) {
				try {
					this.socket.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				this.socket = null;
			}
		}
	}
}

 ZkServerClient

public class ZkServerClient {
	public static List<String> listServer = new ArrayList<String>();

	public static void main(String[] args) {
		initServer();
		ZkServerClient 	client= new ZkServerClient();
		BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
		while (true) {
			String name;
			try {
				name = console.readLine();
				if ("exit".equals(name)) {
					System.exit(0);
				}
				client.send(name);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	// 註冊所有server
	public static void initServer() {
		listServer.clear();
		listServer.add("127.0.0.1:18080");
	}

	// 獲取當前server信息
	public static String getServer() {
		return listServer.get(0);
	}
	
	public void send(String name) {

		String server = ZkServerClient.getServer();
		String[] cfg = server.split(":");

		Socket socket = null;
		BufferedReader in = null;
		PrintWriter out = null;
		try {
			socket = new Socket(cfg[0], Integer.parseInt(cfg[1]));
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			out = new PrintWriter(socket.getOutputStream(), true);

			out.println(name);
			while (true) {
				String resp = in.readLine();
				if (resp == null)
					break;
				else if (resp.length() > 0) {
					System.out.println("Receive : " + resp);
					break;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (out != null) {
				out.close();
			}
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (socket != null) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

改造ZkServerScoekt

public class ZkServerScoekt implements Runnable {
	private static int port = 18081;

	public static void main(String[] args) throws IOException {
		ZkServerScoekt server = new ZkServerScoekt(port);
		Thread thread = new Thread(server);
		thread.start();
	}

	public ZkServerScoekt(int port) {
		this.port = port;
	}

	public void regServer() {
		// 向ZooKeeper註冊當前服務器
		ZkClient client = new ZkClient("127.0.0.1:2181", 60000, 1000);
		String path = "/test/server" + port;
		if (client.exists(path))
			client.delete(path);
		client.createEphemeral(path, "127.0.0.1:" + port);
	}

	public void run() {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(port);
			regServer();
			System.out.println("Server start port:" + port);
			Socket socket = null;
			while (true) {
				socket = serverSocket.accept();
				new Thread(new ServerHandler(socket)).start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (serverSocket != null) {
					serverSocket.close();
				}
			} catch (Exception e2) {

			}
		}
	}

}

改造ZkServerClient

public class ZkServerClient {
	public static List<String> listServer = new ArrayList<String>();
	public static String parent = "/test";

	public static void main(String[] args) {
		initServer();
		ZkServerClient client = new ZkServerClient();
		BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
		while (true) {
			String name;
			try {
				name = console.readLine();
				if ("exit".equals(name)) {
					System.exit(0);
				}
				client.send(name);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	// 註冊所有server
	public static void initServer() {
		// listServer.add("127.0.0.1:18080");

		final ZkClient zkClient = new ZkClient("127.0.0.1:2181", 6000, 1000);
		List<String> children = zkClient.getChildren(parent);
		getChilds(zkClient, children);
		// 監聽事件
		zkClient.subscribeChildChanges(parent, new IZkChildListener() {

			public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
				getChilds(zkClient, currentChilds);
			}
		});
	}

	private static void getChilds(ZkClient zkClient, List<String> currentChilds) {
		listServer.clear();
		for (String p : currentChilds) {
			String pathValue = (String) zkClient.readData(parent + "/" + p);
			listServer.add(pathValue);
		}
		serverCount = listServer.size();
		System.out.println("從zk讀取到信息:" + listServer.toString());

	}

	// 請求次數
	private static int reqestCount = 1;
	// 服務數量
	private static int serverCount = 0;

	// 獲取當前server信息
	public static String getServer() {
		// 實現負載均衡
		String serverName = listServer.get(reqestCount % serverCount);
		++reqestCount;
		return serverName;
	}

	public void send(String name) {

		String server = ZkServerClient.getServer();
		String[] cfg = server.split(":");

		Socket socket = null;
		BufferedReader in = null;
		PrintWriter out = null;
		try {
			socket = new Socket(cfg[0], Integer.parseInt(cfg[1]));
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			out = new PrintWriter(socket.getOutputStream(), true);

			out.println(name);
			while (true) {
				String resp = in.readLine();
				if (resp == null)
					break;
				else if (resp.length() > 0) {
					System.out.println("Receive : " + resp);
					break;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (out != null) {
				out.close();
			}
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (socket != null) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

 

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