上一篇进行了近期面试的简单总结,同时也在上上一篇进行了基于zookeeper集群实现分布式锁的工程实践,这两篇是在同一天发布的,日更一篇其实有点难度,坚持吧。本篇进行了基于zookeeper集群实现分布式id生成器的工程实践。之前学习redis的时候也进行了id生成器的实践这里给出链接:https://blog.csdn.net/u010504064/article/details/104124865
一、定义分布式id的抽象对象
package com.coderman.zookeeper.clusterdemo.idgeneratordemo;
/**
* @description:
* @author: Fanchunshuai
* @time: 2020/2/19 15:28
*/
public class IdGeneratorBean {
/**
* 集群节点目录一级分组
*/
private String group;
/**
* 集群中的应用名称
*/
private String appName;
/**
* 功能模块名称
*/
private String busName;
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getBusName() {
return busName;
}
public void setBusName(String busName) {
this.busName = busName;
}
}
二、定义生成分布式id的抽象类
package com.coderman.zookeeper.clusterdemo.idgeneratordemo;
/**
* @description:
* @author: Fanchunshuai
* @time: 2020/2/19 15:54
* 定义抽象id生成器方法
*/
public abstract class AbstractZKIdGenerator {
/**
* 获取id
* @param idGeneratorBean
* @return
*/
public abstract String getNextId(IdGeneratorBean idGeneratorBean);
/**
* 获取持久顺序节点
* @param idGeneratorBean
* @return
*/
public abstract String getPersisteSequenceNode(IdGeneratorBean idGeneratorBean);
}
三、实现基于节点路径内容的分布式id生成器
package com.coderman.zookeeper.clusterdemo.idgeneratordemo;
import com.coderman.zookeeper.clusterdemo.ZKClientUtils;
import org.I0Itec.zkclient.ZkClient;
/**
* @description:
* @author: Fanchunshuai
* @time: 2020/2/19 15:57
*/
public class SimpleIdGenerator extends AbstractZKIdGenerator {
private ZKClientUtils zkClientUtils = new ZKClientUtils();
@Override
public String getNextId(IdGeneratorBean idGeneratorBean) {
ZkClient zkClient = zkClientUtils.getZkClient();
String nodePath = getPersisteSequenceNode(idGeneratorBean);
if(!zkClient.exists(nodePath)){
//创建持久节点
zkClient.createPersistent(nodePath,true);
}
//创建一个持久顺序节点
String generatePath = zkClient.createPersistentSequential(nodePath+"/",false);
//这里返回后需要异步删除节点,否则会占用zk内存
System.out.println("generatePath ============== "+generatePath);
return generatePath;
}
@Override
public String getPersisteSequenceNode(IdGeneratorBean idGeneratorBean) {
return "/"+ idGeneratorBean.getGroup()+"/"+idGeneratorBean.getAppName()+"/"+idGeneratorBean.getBusName();
}
}
四、实现基于节点版本号的分布式id生成器
package com.coderman.zookeeper.clusterdemo.idgeneratordemo;
import com.coderman.zookeeper.clusterdemo.ZKClientUtils;
import org.I0Itec.zkclient.ZkClient;
import org.apache.zookeeper.data.Stat;
/**
* @description:
* @author: Fanchunshuai
* @time: 2020/2/19 16:31
*/
public class VersionIdGenerator extends AbstractZKIdGenerator {
private ZKClientUtils zkClientUtils = new ZKClientUtils();
@Override
public String getNextId(IdGeneratorBean idGeneratorBean) {
String nodePath = getPersisteSequenceNode(idGeneratorBean);
ZkClient zkClient = zkClientUtils.getZkClient();
//第一次与zk交互
if(!zkClient.exists(nodePath)){
zkClient.createPersistent(nodePath,true);
zkClient.writeData(nodePath,"abc");
}
Stat stat = new Stat();
//第二次与zk交互
zkClient.readData(nodePath,stat);
//这里返回的是纯数字,如果需要有意义的比如,xxx0001,xxx0002则需要自己再次加工一下
int version = stat.getVersion();
System.out.println("version = "+version);
//第三次与zk交互
zkClient.writeData(nodePath,"abc");
return stat.getVersion()+"";
}
@Override
public String getPersisteSequenceNode(IdGeneratorBean idGeneratorBean) {
return "/"+ idGeneratorBean.getGroup()+"/"+idGeneratorBean.getAppName()+"/"+idGeneratorBean.getBusName();
}
}
五、实践方法
package com.coderman.zookeeper.clusterdemo.idgeneratordemo;
/**
* @description:
* @author: Fanchunshuai
* @time: 2020/2/19 16:15
*/
public class IdGeneratorDemo {
public static void main(String[] args) {
//AbstractZKIdGenerator zkIdGenerator = new SimpleIdGenerator();
IdGeneratorBean idGeneratorBean = new IdGeneratorBean();
idGeneratorBean.setAppName("xxxsystem");
idGeneratorBean.setBusName("xfunction");
idGeneratorBean.setGroup("companyx");
/*for (int i =0;i< 10000;i++){
zkIdGenerator.getNextId(idGeneratorBean);
}*/
idGeneratorBean.setBusName("xfunction-4");
AbstractZKIdGenerator versionIdGenerator = new VersionIdGenerator();
for (int i =0;i< 10000;i++){
versionIdGenerator.getNextId(idGeneratorBean);
}
}
}
六、初始化工具类
import org.I0Itec.zkclient.ZkClient;
/**
* @description:
* @author: Fanchunshuai
* @time: 2020/2/15 16:15
* 封装本地连接zookeeper的工具类避免每次实例代码都拷贝配置重新初始化zk客户端
*/
public class ZKClientUtils {
private static StringBuffer buffer = new StringBuffer();
private static final int CONNECT_TIMEOUT = 3000;
private static final int SEESSION_TIMEOUT = 5000;
ZkClient zkClient = new ZkClient(buffer.toString(), CONNECT_TIMEOUT, SEESSION_TIMEOUT);
static {
buffer.append("192.168.1.8:2184,");
buffer.append("192.168.1.8:2181,");
buffer.append("192.168.1.8:2182,");
buffer.append("192.168.1.8:2183,");
buffer.append("192.168.1.8:2185");
}
/**
* 初始化zookeeper客户端
*
* @return
*/
public ZkClient getZkClient() {
return zkClient;
}
总结:
zookeeper实现分布式id生成器
分布式id生成器的实现方案有很多种,其中就有基于zookeeper集群实现的,实现方案也有两个不同的形式
方案一:基于持久顺序节点,实现大概步骤如下:
1.创建持久节点
2.创建持久顺序节点
3.异步删除创建的顺序节点
4.处理并返回持久顺序节点的节点目录
方案二:基于节点版本号
1.创建持久节点
2.修改数据
3.读取数据并读取版本号
4.处理并返回版本号
如果读者也按上面的代码跑一下流程大概就知道方案一可以做到只与zk集群进行一次交互即可完成id的获取
方案二则最少需要两次这样的话性能上可能有些损失,选择方案二则可能需要进行严格的性能测试来评估是否能
满足需求。
PS:网上的其他实现方案或者样例采用了Curator框架或者原生api进行实践,但是原理和实现步骤都是一样的。读者可以自行学习。当然如果博文中有不对的地方欢迎指正哈。