【RPC系列】5、向Zookeeper上註冊服務(用netty、zk手寫RPC第二步)

上一篇使用自定義xsd定義了發佈、訂閱的標籤
這一篇就是用標籤的解析並想zk上註冊服務
廢話不多說,直接上註冊中心的代碼

package com.kaer.rpc.netty.register.zk;

import java.util.Iterator;
import java.util.concurrent.ConcurrentMap;
import org.apache.curator.framework.CuratorFramework;
...
import org.slf4j.LoggerFactory;
import com.kaer.rpc.demo.config.ProviderConfig;

/**
 * Zookeeper註冊中心處理類
 * 
 * @author kaer
 *
 */
public class ZkRegistryCenter {
	
	public static final Logger logger = LoggerFactory.getLogger(ZkRegistryCenter.class);////日誌打印
	private static String basePath = "/kaer-rpc";//zk是以目錄的形式創建節點
	private static CuratorFramework client;//Curator操作zk的客戶端
	private final ConcurrentMap<String, ProviderConfig> services = Maps.newConcurrentMap();//服務的集合,暫時只發佈一個服務,後面可擴展
	private static final ConnectionStateListener connectionStateListener;//監聽器對zk的狀態實時處理,暫不實現
	
	static {
		connectionStateListener = new ConnectionStateListener() {
			
			@Override
			public void stateChanged(CuratorFramework client, ConnectionState newState) {
				// 狀態的變化處理
				logger.info("zkClient state change to " + newState);
			}
		};
	}
	
	public static void start(String zookeeperUrl) {
		//序列化工具
		//初始化連接客戶端,即連接到了本地zk的127.0.0.1:2181
		client = CuratorFrameworkFactory.newClient(zookeeperUrl, new ExponentialBackoffRetry(1000, 3));
		client.getConnectionStateListenable().addListener(ZkRegistryCenter.connectionStateListener);
		client.start();//zk客戶端啓動
		try {
			//zk是以目錄的形式存儲數據,而且頂向下一節一節實現的
			if((Stat) client.checkExists().forPath(basePath) == null) {
				((ACLBackgroundPathAndBytesable<?>) client.create().creatingParentsIfNeeded()
						.withMode(CreateMode.PERSISTENT)).forPath(basePath);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		logger.info("start service register......");
	}
	
	//註冊服務,這個發生的時間是在容器的bean加載,還記得之前自定義的標籤麼,有對provider標籤的解析類ProviderBean
	public static void registerService(String serviceId, String alias, byte[] info) {
		//創建目錄節點,類似kaer-rpc/{api},數據目錄爲kaer-rpc/{api}/data
		String nodePath = ZKPaths.makePath(basePath, serviceId);
		String fullPath = ZKPaths.makePath(nodePath, "data");
		//存放目錄數據
		try {
			Stat stat = (Stat) client.checkExists().forPath(nodePath);
			if(stat == null) {
				((ACLBackgroundPathAndBytesable<?>) client.create().creatingParentsIfNeeded()
						.withMode(CreateMode.PERSISTENT)).forPath(nodePath);
				((ACLBackgroundPathAndBytesable<?>) client.create().creatingParentsIfNeeded()
						.withMode(CreateMode.PERSISTENT)).forPath(fullPath);
			}else {
				stat = (Stat) client.checkExists().forPath(fullPath);
				if(stat == null) {
					((ACLBackgroundPathAndBytesable<?>) client.create().creatingParentsIfNeeded()
							.withMode(CreateMode.PERSISTENT)).forPath(fullPath);
				}
			}
			client.setData().forPath(fullPath, info);//這裏的info使用Json序列化的,具體要見ProviderConfig
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	//刪除服務,暫不實現
	public void unRegisterService(String serviceId) {
		String path = basePath + "serviceId";
		try {
			((ChildrenDeletable) client.delete().guaranteed()).forPath(path);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("Could not unregister instance: s%", serviceId);
		}
	}
	
	
	public void close() {
		ExceptionAccumulator accumulator = new ExceptionAccumulator();
		Iterator<ProviderConfig> arg2 = this.services.values().iterator();

		while (arg2.hasNext()) {
			ProviderConfig service = (ProviderConfig) arg2.next();
			unRegisterService(service.getApi());
		}

		client.getConnectionStateListenable().removeListener(ZkRegistryCenter.connectionStateListener);
		CloseableUtils.closeQuietly(client);
		accumulator.propagate();
	}

}

由上一篇中分別對三個自定義標籤進行了解析處理
下面是註冊服務的處理邏輯

package com.kaer.rpc.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.kaer.rpc.netty.register.zk.ZkRegistryCenter;

/**
 * 服務提供者配置
 * 
 * @author kaer
 *
 */
public class ProviderConfig {

	public static Logger logger = LoggerFactory.getLogger(ProviderConfig.class);
	private String api;
	private String mapper;
	private String alias;
	//發佈,假設模擬一個服務
	protected void doExport() {
		logger.info("生產者信息:端口[" + api + "],映射[" + mapper + "],別名[" + alias + "]。");
		RpcProviderConfig rpcProviderConfig = new RpcProviderConfig();
		rpcProviderConfig.setApi(api);
		rpcProviderConfig.setMapper(mapper);
		rpcProviderConfig.setAlias(alias);
		rpcProviderConfig.setHost("127.0.0.1");
		rpcProviderConfig.setPort(9999);
		//註冊生產者
//		long count = RedisRegistryCenter.registryProvider(api, alias, JSON.toJSONString(rpcProviderConfig));
		ZkRegistryCenter.registerService(api, alias, JSON.toJSONBytes(rpcProviderConfig));
		logger.info("註冊生產者:{} {} {}", api, alias, null);
	}

	public String getApi() {
		return api;
	}
	public void setApi(String api) {
		this.api = api;
	}
	public String getMapper() {
		return mapper;
	}
	public void setMapper(String mapper) {
		this.mapper = mapper;
	}
	public String getAlias() {
		return alias;
	}
	public void setAlias(String alias) {
		this.alias = alias;
	}
}

消費者的配置(ConsumerConfig)類似提供者的這個配置,暫不實現。

/**
 * 實現服務提供的依賴注入
 * @author kaer
 *
 */
public class ProviderBean extends ProviderConfig implements ApplicationContextAware {
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		// 發佈
		doExport();
	}
}

其中有需要依賴的包(Curator對ZK的支持):

<!-- Curator對ZK的支持 -->
<dependency>
	<groupId>org.apache.curator</groupId>
	<artifactId>curator-framework</artifactId>
	<version>4.0.0</version>
</dependency>

本地啓動zookeeper
測試類如下:

public class ApiTest {
	@SuppressWarnings("resource")
	public static void main(String[] args) throws UnknownHostException, IOException {
		String[] configs = { "rpc/test.xml" };
		new ClassPathXmlApplicationContext(configs);
	}
}

執行完了,通過可視化界面去看看是否註冊成功(當然也可以用命令去操作,zkCli.exe)。
這裏用到了zkUI的一個工具,需要的在附件中獲取。
在這裏插入圖片描述
可以登陸進去可以看到有代碼中創建的目錄了
在這裏插入圖片描述
一層一層點進去,可以看到最終註冊上去的服務的信息,在實際開發中,應獲取應用本機的ip和端口,再附加服務的唯一性數據,訂閱就是個獲取並解析然後再進行netty通訊的步驟
在這裏插入圖片描述
本文結束,後面使用consul進行服務註冊玩玩


附件:zkUI可視化工具

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