目录
1、概念
1、1 解释1
命名服务是分布式系统中比较常见的一类场景。命名服务是分布式系统最基本的公共服务之一。在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等——这些我们都可以统称它们为名字(Name),其中较为常见的就是一些分布式服务框架(如RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据指定名字来获取资源的实体、服务地址和提供者的信息等。
Java中的JNDI便是一种典型的命名服务。JNDI是Java命名与目录接口(Java Naming and Irectory Interface)的缩写,是J2EE体系中重要的规范之一,标准的J2EE容器都提供了对JNDI规范的实现。因此,在实际开发中,开发人员常常使用应用服务窗口自带的JNDI实现来完成数据源的配置与管理——使用JNDI方式后,开发人员可以完全不需要关心数据库相关的任何信息,包括数据库类型、JDBC驱动类型及数据库账户等。
ZooKeeper的命名服务有两个应用方向:
1. ZooKeeper提供类似JNDI服务,都能够帮助应用系统通过一个资源引用的方式来实现对资源的定位与实用。
2. 利用ZooKeeper顺序节点的特性,制作分布式的ID生成器。
分布式ID生成器示例:(使用ZkClient)
TestIdMaker
package zookeeper;
import zookeeper.IdMaker.RemoveMethod;
public class TestIdMaker {
public static void main(String[] args) throws Exception {
IdMaker idMaker = new IdMaker("127.0.0.1:2181", "/NameService/IdGen", "ID");
idMaker.start();
try {
for (int i = 0; i < 10; i++) {
String id = idMaker.generateId(RemoveMethod.DELAY);
System.out.println(id);
}
} finally {
idMaker.stop();
}
}
}
IdMaker.java
package zookeeper;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.I0Itec.zkclient.serialize.BytesPushThroughSerializer;
public class IdMaker {
private ZkClient client = null;
private final String server;
// zookeeper顺序节点的父节点
private final String root;
// 顺序节点的名称
private final String nodeName;
// 标识当前服务是否正在运行
private volatile boolean running = false;
private ExecutorService cleanExector = null;
public enum RemoveMethod{
NONE,IMMEDIATELY,DELAY
}
public IdMaker(String zkServer,String root,String nodeName){
this.root = root;
this.server = zkServer;
this.nodeName = nodeName;
}
// 启动服务
public void start() throws Exception {
if (running)
throw new Exception("server has stated...");
running = true;
init();
}
// 停止服务
public void stop() throws Exception {
if (!running)
throw new Exception("server has stopped...");
running = false;
freeResource();
}
// 初始化服务资源
private void init(){
client = new ZkClient(server,5000,5000,new BytesPushThroughSerializer());
cleanExector = Executors.newFixedThreadPool(10);
try{
client.createPersistent(root,true);
}catch (ZkNodeExistsException e){
}
}
// 释放服务器资源
private void freeResource(){
// 释放线程池
cleanExector.shutdown();
try{
cleanExector.awaitTermination(2, TimeUnit.SECONDS);
}catch(InterruptedException e){
e.printStackTrace();
}finally{
cleanExector = null;
}
if (client!=null){
client.close();
client=null;
}
}
// 检测当前服务是否正在运行
private void checkRunning() throws Exception {
if (!running)
throw new Exception("请先调用start");
}
// 从顺序节点名中提取我们要的ID值
private String ExtractId(String str){
int index = str.lastIndexOf(nodeName);
if (index >= 0){
index+=nodeName.length();
return index <= str.length()?str.substring(index):"";
}
return str;
}
// 生成ID
public String generateId(RemoveMethod removeMethod) throws Exception{
checkRunning();
// 构造顺序节点的完整路径
final String fullNodePath = root.concat("/").concat(nodeName);
// 创建持久化顺序节点
final String ourPath = client.createPersistentSequential(fullNodePath, null);
// 避免zookeeper的顺序节点暴增,直接删除掉刚创建的顺序节点
if (removeMethod.equals(RemoveMethod.IMMEDIATELY)){ // 立即删除
client.delete(ourPath);
}else if (removeMethod.equals(RemoveMethod.DELAY)){ // 延迟删除
cleanExector.execute(new Runnable() { // 用线程池执行删除,让generateId()方法尽快返回
public void run() {
client.delete(ourPath);
}
});
}
//node-0000000000, node-0000000001
return ExtractId(ourPath);
}
}
验证效果:
说明:上面运用了到了zookeeper API - createPersistentSequential接口来创建持久顺序接口
参考:ZkClient之创建节点-https://blog.csdn.net/en_joker/article/details/78774349
1、2 解释2
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,既对人友好又不会重复。说到这里你可能想到了 JNDI,没错 Zookeeper 的 Name Service 与 JNDI 能够完成的功能是差不多的,它们都是将有层次的目录结构关联到一定资源上,但是 Zookeeper 的 Name Service 更加是广泛意义上的关联,也许你并不需要将名称关联到特定资源上,你可能只需要一个不会重复名称,就像数据库中产生一个唯一的数字主键一样。
Name Service 已经是 Zookeeper 内置的功能,你只要调用 Zookeeper 的 API 就能实现。如调用 create 接口就可以很容易创建一个目录节点。
1、3 解释3
在日常开发中,我们会遇到这样的场景:服务A(开发者:小李)需要访问服务B(开发者:小王),但是服务B还在开发过程中(未完成),那么服务A(此时已完成)就不知道如何获取服务B的访问路径了(假设我们使用http服务),小李还有其他工作要做,该怎么办呢?使用zookeeper的服务就可以简单解决:小李和小王约定好:服务B部署成功后,先到zookeeper注册服务(即在zookeeper添加节点/service/B和节点数据)。服务A开发结束后,部署到服务器,然后服务A监控zookeeper服务节点/service/B,如果发现节点数据了,那么服务A就可以访问服务B了。
在服务A中添加代码,用于观察节点/service/B是否存在以及节点数据的变化。
CuratorFramework client = CuratorFrameworkFactory
.builder()
.connectString("192.168.1.123:2181")
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
client.start();
Stat stat = new Stat();
byte[] data = client.getData().storingStatIn(stat).forPath("/jike");
if (data != null) {
System.out.println("B'Node data:"+new String(data));
//TODO 执行服务A调用服务B的代码
}
ZooKeeper的命名服务有两个应用方向:
1. ZooKeeper提供类似JNDI服务,都能够帮助应用系统通过一个资源引用的方式来实现对资源的定位与实用。
2. 利用ZooKeeper顺序节点的特性,制作分布式的ID生成器。
2、实战(1)-简单统一命名服务实现
统一命名服务(Name Service)
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,既对人友好又不会重复。说到这里你可能想到了 JNDI,没错 Zookeeper 的 Name Service 与 JNDI 能够完成的功能是差不多的,它们都是将有层次的目录结构关联到一定资源上,但是 Zookeeper 的 Name Service 更加是广泛意义上的关联,也许你并不需要将名称关联到特定资源上,你可能只需要一个不会重复名称,就像数据库中产生一个唯一的数字主键一样。
Name Service 已经是 Zookeeper 内置的功能,你只要调用 Zookeeper 的 API 就能实现。如调用 create 接口就可以很容易创建一个目录节点。
在zk系统中创建的节点可以保证在分布式服务器上是全局唯一的。
命名服务提供注册、注销和查看命名等接口。
命名服务的关键逻辑
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;
public class Naming {
private ZooKeeper zk = null; // ZooKeeper对象
private String nameroot = "/NameService";
private String namerootvalue = "IsNameService";
private String namevalue = "IsName";
/**
* @函数:命名服务构造函数
* @参数:zk的地址端口 描述:初始化zk实例,创建命名服务根路径
*/
public Naming(String url) {
try {
// 初始化,如果当前有alive的zk连接则先关闭
if (zk != null && zk.getState().isAlive() == true)
zk.close();
zk = new ZooKeeper(url, 30000, null); // 重新建立连接
System.out.println("zookeeper connect success:url=" + url);
} catch (Exception e) {
e.printStackTrace();
}
// 判断是否有/NameService,如果没有,则创建该路径,用来作为所有的集中配置信息的根目录
try {
if (zk.exists(nameroot, false) == null) {
zk.create(nameroot, namerootvalue.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(nameroot + " create success!");
}
} catch (KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
}
}
/**
* @函数: 注销zk实例
*/
public void UnNaming() {
if (zk != null) {
try {
zk.close();
System.out.println("zookeeper close success!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
}
zk = null;
}
}
/**
* @函数:注册一个全局名字
* @描述:待注册的名字字符串name,在zk中创建一个/NameService/name的znode路径
* @参数: 待注册的名字字符串name
* @返回值: 0 表示注册成功 -1 表示出错 1 表示该命名已被注册
*/
@SuppressWarnings("finally")
public int Registered(String name) {
String path = nameroot + "/" + name;
int ret = 0;
try {
if (zk.exists(path, false) == null) {
zk.create(path, namevalue.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(name + " registered success!");
} else {
ret = 1;
System.out.println(name + " is exists, can not regist again!");
}
} catch (KeeperException e) {
// TODO Auto-generated catch block
ret = -1;
e.printStackTrace();
System.out.println(e.getMessage());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
ret = -1;
e.printStackTrace();
System.out.println(e.getMessage());
} finally {
return ret;
}
}
/**
* @函数:注销一个全局名字
* @描述:待注销的名字字符串name,在zk中删除/NameService/name的znode路径
* @参数: 待注销的名字字符串name
* @返回值: 0 表示注销成功 -1 表示出错 1 表示该命名未注册,不存在命名服务系统中
*/
@SuppressWarnings("finally")
public int Canceled(String name) {
String path = nameroot + "/" + name;
int ret = 0;
try {
if (zk.exists(path, false) != null) {
zk.delete(path, -1);
System.out.println(name + " canceled success!");
} else {
ret = 1;
System.out.println(name + " is not exists, can not canceled!");
}
} catch (KeeperException e) {
// TODO Auto-generated catch block
ret = -1;
e.printStackTrace();
System.out.println(e.getMessage());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
ret = -1;
e.printStackTrace();
System.out.println(e.getMessage());
} finally {
return ret;
}
}
/**
* @函数:获取命名服务系统的所有命名
* @描述:
* @参数:
* @返回值:命名列表
*/
public List<String> Readall() {
List<String> namelist = new ArrayList<String>();
try {
namelist = zk.getChildren(nameroot, false);
} catch (KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return namelist;
}
}