1.主节点选举
以下面的ip和port为例子:
ip | port |
---|---|
localhost | 10000 |
localhost | 11001 |
localhost | 12002 |
- 那么三个节点中ip为localhost,port为10000的节点为主节点。
- 规定主节点必须先运行,然后再运行其他节点。
- 当主节点开启后,开启其他节点,主节点在收到了其他节点向主节点发送的注册消息后开始向其他节点发送保活数据包。
- 当主节点down后,其他节点等待一段时间后将会开始选举新的主节点,那么按照规则就是ip为localhost,port为12002的节点为主节点。
- 选举主节点的规则是将ip和port拼接起来,即ip:port为uid,然后判断哪一个节点的uid更小变为主节点。
- 主节点一旦选举出便不可以被替代,知道主节点down。
- 非主节点会通过java原生的rpc工具jmi获取主节点的实例,注解顶也会通过jmi获取非主节点的实例。
2.节点的退出与加入
- 当某个非主节点down后,主节点向其发送保护包便会失败,那么就会记录这个节点,并向其他的节点发送有关于这个节点已经down的信息,其他节点收到后会将这个节点从自己的选举列表中删除,防止在主节点down后选举这个节点。
- 当一个新的节点加入后,主节点会将这个节点发送给其他的节点,节点收到主节点发送的新节点添加后将这个节点添加到自己的选举列表中。
3.客户端的添加、修改和删除操作
- 添加操作:当主节点收到了客户端发来的添加操作时会直接添加并将数据同步给其他的节点,这个更新是增量更新,通过节点的创建时间来确定。如果不是主节点收到的来自客户端的添加数据那么就需要将数据发送给主节点,由主节点添加并同步。
- 修改操作:和第一点相同。
- 删除操作:同上。
4.节点监听
此时也需要分为两种:
- 主节点收到了客户端的关于某个节点的监听,那么就直接监听并将这个节点放置到关于某个节点的监听列表中。
- 如果是非主节点收到了客户端的关于某个节点的监听,那么就会将这个节点发送给主节点,由主节点来设置对于这个节点的监听。
5.本地和远程选项
在配置类中有一个选项是本地还是远程,如果是本地那么这个值为false,代码中的所有暴露和获取的ip都是localhost,如果是远程那么这个值就是true,代码中的所有暴露和获取的ip都是节点的ip。
6.分布式锁
也是分为两种:
- 若收到了客户端的请求分布式锁的消息是主节点那么就直接将分布式锁对象暴露给客户端。
- 如果不是主节点收到了客户端的请求分布式锁的消息,那么就先将这个数据发送给主节点,主节点收到了数据后将分布式锁对象暴露后,返回到非主节点,先获取到主节点暴露的分布式锁对象,在将分布式锁对象暴露给客户端,这样就不需要客户端只能去主节点那哪里获取分布式锁对象了。
选取主节点可以通过获取分布式锁来实现,最先获取锁的节点救世主节点。
这里是通过两次暴露来实现对于客户端的透明,即此刻链接的是什么节点就从什么节点上拿取自己所需的一切,当然也可以实现一次暴露,那么就需要在客户端开启额外的rpc连接去连接主节点并获取对象,此时的主节点的信息都是通过此刻链接的节点告知给客户端的。
7.获取全局唯一的id
也是分为两种:
- 如果是主节点收到了客户端获取全局唯一的id消息,直接返回当前的递增id给客户端。
- 如果不是主节点收到了客户端获取全局唯一的id消息,那么就先将数据发送给主节点,主节点返回给非主节点,非主节点在返回给客户端。
8.节点权限设置
只有两个钟权限:
- 有密码:拥有节点密码的客户端,对节点由完全操作,密码在节点创建的时候就设置了,由创建这个节点的客户端设置。
- 没有密码:没有密码的客户端只有读权限。
9.源码
源码被我放在github上了,链接如下:
https://github.com/mcl973/MyDistributeCoordinationTool
入口类是在run下的start.java文件。
客户端其实是在节点代码的基础上:
public class SayHello extends NodeChild {
public SayHello(NodeInterface nodeChild) throws RemoteException{
this(nodeChild.getThisOwner(),
nodeChild.getThisNodeType(),
nodeChild.getThisCurrentNode(),
nodeChild.getThisUpdateTime(),
nodeChild.getThisPassword(),
nodeChild.getThisNodevalve(),0);
}
public SayHello(String owner, String nodeType, String currentNode, long updateTime, String password,DateNode dateNode,int port) throws RemoteException{
super(owner,nodeType,currentNode,updateTime,password,dateNode,port);
}
public SayHello(String owner, String nodeType, String currentNode, long updateTime, String password,TreeSet<DateNode> nodevalve,int port) throws RemoteException{
super(owner,nodeType,currentNode,updateTime,password,nodevalve,port);
}
@Override
public void doWatch() throws RemoteException {
System.out.println("监听的数据发生了改变。。。");
}
@Override
public void doWatch(String nodeType) throws RemoteException {
}
@Override
public void doWatch(NodeChild nodeChild) throws RemoteException {
TreeSet<DateNode> thisNodevalve = nodeChild.getThisNodevalve();
Iterator<DateNode> iterator = thisNodevalve.iterator();
while (iterator.hasNext()){
DateNode next = iterator.next();
byte[] values = next.getValues();
System.out.println(new String(values));
}
}
}
配置文件:
public class nodeAllConfig {
public static int port = 20000;
public static String localhost = "localhost";
public static String thisuid = localhost+":"+port;
}
客户端的start类:
public class start {
public static void main(String[] args) {
Export export = new Export();
Achieve achieve = new Achieve();
try {
DominInterface domin = (DominInterface)achieve.getRemoteObjectForDomin("domin", "localhost",nodeConfig.port);
TreeMap<String, NodeInterface> allNodes = domin.getAllNodes();
if (allNodes.size()>0) {
NodeInterface create = allNodes.get("create");
DateNode dateNode = new DateNode();
dateNode.setValues("hello".getBytes());
SayHello sayHello = new SayHello(create.createNode(nodeAllConfig.thisuid, "say_hello",dateNode,"test"));
export.registryNewPort(nodeAllConfig.port);
export.exportObjectsForNewRegistry(sayHello,sayHello.getThisNodeType());
domin.addNode(sayHello);
domin.setWatch(sayHello.getThisNodeType(),sayHello.getThisCurrentNode(),"test");
sayHello.getThisNodevalve().clear();
dateNode.setValues("再来一次".getBytes());
sayHello.getThisNodevalve().add(dateNode);
domin.modifyNode(sayHello);
System.out.println(domin.getGlobalIndex());
boolean say_hello = domin.setGlobalLockForNode("say_hello");
DistributeLock remoteObjectForDistributeLock =
achieve.getRemoteObjectForDistributeLock(nodeConfig.distributeLock + "_say_hello",
"localhost",nodeConfig.port);
NodeChild nodeChild = new NodeChild();
remoteObjectForDistributeLock.tryLock(nodeChild);
System.out.println("/");
remoteObjectForDistributeLock.tryRelease(nodeChild);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
服务端的start类:
public class start {
public static DominNode dominNode;
static {
try {
dominNode = new DominNode();
} catch (RemoteException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Export export = new Export();
export.registryNewPort(nodeConfig.port);
NodeChild nodeChild = new NodeChild();
/**
* 改变属性值
*/
//改变updateTime
Class<? extends NodeChild> aClass = nodeChild.getClass();
Field updateTime = aClass.getDeclaredField("updateTime");
updateTime.setAccessible(true);
updateTime.set(nodeChild,Long.MAX_VALUE);
/改变nodeType
Field nodeType = aClass.getDeclaredField("nodeType");
nodeType.setAccessible(true);
nodeType.set(nodeChild,nodeConfig.create);
///暴露nodechild
export.exportObjectsForNewRegistry(nodeChild,nodeConfig.create);
//将create装填进domin
TreeMap<String,NodeInterface> allNodes = dominNode.getAllNodes();
allNodes.put(nodeChild.getThisNodeType(),nodeChild);
/**
* 暴露dominnode
*/
export.exportObjectsForNewRegistry(dominNode,nodeConfig.domin);
startToSelect startToSelect = new startToSelect();
startToSelect.startSelect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
补充:
在第三点和第六点中选择的是在协调节点内部实现暴露而不是将信息告诉给client,再由客户端来连接远程的主节点来实现节点数据的修改和分布式锁的获取。
主要的原因就是如果有一个节点有100个客户端去连接,那么选择第一种方式只需要在非协调主节点和协调主节点之间建立一个新的暴露即可,但是如果选择第二种方式会导致协调主节点多了100个新的连接。如果不是100个客户端呢,如10000个那么在协调主节点的原本的业务上会多了很多的业务处理。所以会影响协调主节点的性能。
所以本工具采用了暗度陈仓的方式来是的对于客户端来说是透明的方式。这样既可以实现数据的修改,也可以以高效的方式运行。