背景介绍
项目上需要用到一个类似与zookeeper的心跳加节点数据同步的功能,但由于项目上不使用zookeeper,便决定自己实现一套,以便以后有类似需求可以直接使用。
这是一个可以处理节点间心跳和基于心跳进行数据同步的框架,此框架重在心跳实现,如果要实现数据同步需要人工处理,下面会说明如何基于此框架实现数据同步。
数据流转图
- msgQueue: 预同步数据队列
- receiveQueue: 已同步数据队列
- ackMap: 消息确认机制
结构介绍
- tonatyw.heart.cancer
-
base 实现协议需要继承的抽象类包,目前只实现了udp协议
AbstractAgreement 协议抽象类
-
constants 常量类包
Constants 常量类,存储一些文件路径和共用的字符串
-
handle 接收参数时的处理包
- base 消息处理基类包
BaseHandle 消息处理基类
AckHandle 在发送方接收到接收方返回的回执消息时调用
ReceiveHandle 在接收方接收到发送方发送的消息时调用
- base 消息处理基类包
-
thread 线程包
Receive 接收线程
Send 发送线程
StatusThread 节点状态更新线程
-
util 工具包
HeartBox 心跳盒子,存储心跳期间需要使用的变量
ProcessEngine 暂不用,留着
Udp udp协议实现,udp心跳入口
-
数据流
- msgQueue: 预同步数据队列
- receiveQueue: 已同步数据队列
- ackMap: 消息确认机制
使用
启动心跳
启动心跳线程前,需要将heartBox放在全局的地方,因为所有的信息都在于操作这一个对象,比如
public class Sets {
private Sets(){}
/** 心跳相关 */
public static HeartBox heartBox = new HeartBox();
/** 同步列表 结构为 标识->消息*/
public static Map<String,Map<String,Object>> synMap = new HashMap<String,Map<String,Object>>();
}
int port = 8108;
String pIp = "127.0.0.1";
int pPort = 8191;
long timeout = 10000;
// 启动心跳线程
new Udp(ip, port, pIp, pPort, timeout, Sets.heartBox).begin();
如果没有父级
new Udp(ip, port, timeout, heartBox).begin();
数据同步
// 此步为数据同步需要,启动消息同步线程
new Thread(new MessageSynThread(Sets.heartBox)).start();
messageSynThread内容如下
/**
* 消息同步线程
* @ClassName MessageSynThread
* @Description: 消息同步线程
* @date 2019年6月12日 下午5:19:07
*/
public class MessageSynThread implements Runnable{
/** 心跳相关信息 */
private HeartBox heartBox;
public MessageSynThread(HeartBox heartBox) {
this.heartBox = heartBox;
}
@Override
public void run() {
// 轮询取同步消息
while(true){
try {
/**
同步消息 分为三个字段
同步数据:data;
同步数据类型:type -> task,resource;
同步方式: method -> add update del
*/
// receiveQueue为当前节点接收到的同步消息
String data = heartBox.getReceiveQueue().take();
JSONObject json = JSON.parseObject(data);
if(!Sets.synMap.containsKey(json.getString("type"))){
Sets.synMap.put(json.getString("type"), new HashMap<String,Object>());
}
Map<String,Object> typeMap = Sets.synMap.get(json.getString("type"));
try {
Map<String,Object> dataMap = JSON.parseObject(json.getString("data"), new HashMap<String,Object>().getClass());
ReflectUtil.processMethod(MessageSynHandle.class, json.getString("method"), typeMap,dataMap);
} catch (Exception e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
MessageSynHandle
/**
* 数据同步操作
* @ClassName MessageSynHandle
* @Description: 数据同步操作
* @author Tangjc
* @date 2019年6月12日 下午7:54:07
*/
public class MessageSynHandle {
/**
* 添加操作
* @Title: add
* @Description: 添加操作
* @author Tangjc
* @param synMap 同步集合
* @param dataMap 待同步数据
* @return void
*/
public void add(HashMap<String,Object> synMap,HashMap<String,Object> dataMap){
synMap.putAll(dataMap);
}
/**
* 修改操作
* @Title: add
* @Description: 修改操作
* @author Tangjc
* @param synMap 同步集合
* @param dataMap 待同步数据
* @return void
*/
public void update(HashMap<String,Object> synMap,HashMap<String,Object> dataMap){
synMap.putAll(dataMap);
}
/**
* 修改操作
* @Title: add
* @Description: 修改操作
* @author Tangjc
* @param synMap 同步集合
* @param dataMap 待同步数据
* @return void
*/
public void del(HashMap<String,Object> synMap,HashMap<String,Object> dataMap){
dataMap.forEach((key,value)->{
synMap.remove(key);
});
}
}
发送同步消息
在此心跳程序和同步线程启动好后,如果有需要同步的数据,请调出heartBox.getMsgQueueMap()
// 接收该同步消息的的ip 只有建立了心跳才能同步
String ip = "192.168.1.100"
JSONObject json = new JSONObject();
// 该此同步为 添加 除此之外还有update 修改,del 删除
json.put("method", "add");
// 标识 根据业务需要来 同步过去后变为 synMap的key
json.put("type", "resource");
// 数据 规定使用map格式
Map<String,String> dataMap = new HashMap<String,String>();
dataMap.put("userId",1);
json.put("data", dataMap);
// 根据ip选择需要同步的节点
Sets.heartBox.getMsgQueueMap().get(ip).put(json.toJSONString());
心跳线程会根据每一次心跳读取队列同步消息
接收同步消息
MessageSynThread同步线程里已经将ReceiveQueue的消息放入了synMap里这时候取出就好
String type = "resource"
Object userId = Sets.synMap.get(type).get("userId");
下一次更新计划
- 基于用户实现数据同步
- 增加http协议心跳同步
- 修复bug(如果有- -)
由于工作关系,看反响如何(如果有…),随缘更新