首先来浅谈一下服务发现:即何为服务发现?
所有服务器(无论是某一种APP的多个服务器,还是不同APP的多个服务器)在启动时,都需要在“注册中心”进行注册;客户端需要从“注册中心”获取它所属APP的服务器(组)的地址信息;
从客户端角度看,注册中心起到的作用包含两个:
1、是否存在某个APP的服务器;
2、获取某个APP服务器(组)的地址信息。
所以,从客户端角度,这是一种“服务发现”的机制。
当然服务发现有很多妙用。此处就不进行阐述。回归正题,在学习完相关知识之后实现了一个“资源发现”。同样三种角色:资源注册中心,资源持有者,资源请求者。三者关系如下:
资源持有者 将自己持有的资源提交到注册中心。 资源请求者想要请求某个资源时必须先从“资源注册中心”获取 该资源对应的资源持有者地址集合。
实现思路及准备:
整个网络通信用的是短连接模式,利用之前编写的RMI工具,前面文章链接:
最基本的数据基础:
1.资源信息:ResourceInfo类(包含 app名称 + id(资源id)+ version(app版本号))。以三者的的hashCode作为该对象的相等条件。
2.网络节点:DefaultNetNode类(ip + port),首先一个网络节点能够被连接的条件是能够建立服务器(ip,port)。
3 资源节点映射表(ResourceNodePool类):首先它应该是一对多结构。因为一个资源可以由多个资源持有者提供。我使用的
是ConcurrentHashMap<Integer, List<DefaultNetNode>>。这里的 键:资源信息“ResourceInfo的hashCode” 值:值:网络节点集合
其所具有的基本功能如下:
1>.存储注册者所注册资源与其网络地址(也即下面的registry方法)
2>.注销资源持有者某一资源(也即下面的logout方法)
3>.获取某一资源对应的网络节点集合(getAddressList方法)
4>.删除某个资源持有者所注册过的所有资源(removeNode)
代码如下:
public class ResourceNodePool {
private static final Map<Integer, List<DefaultNetNode>> rnPool =
new ConcurrentHashMap<Integer, List<DefaultNetNode>>();
public static void registry(ResourceInfo resourceInfo, DefaultNetNode addr) {
int resourceId = resourceInfo.hashCode();
List<DefaultNetNode> nodeList = rnPool.get(resourceId);
//若没有该资源信息的记录 则此处应该创建 且是线程安全的
if(nodeList == null) {
synchronized (rnPool) {
if(nodeList == null) {
//创建资源 CopyOnWriteArrayList 是线程安全的
nodeList = new CopyOnWriteArrayList<DefaultNetNode>();
rnPool.put(resourceId, nodeList);
}
}
}
nodeList.add(addr);
//这里NodePool的作用后面会介绍到
NodePool.add(addr);
}
/**
* 此处注销的是一个网络节点对应的 某一个资源
* @param resourceInfo 要注销的资源信息
* @param addr 自己的资源地址
*/
public static void logout(ResourceInfo resourceInfo, DefaultNetNode addr) {
int resourceId = resourceInfo.hashCode();
List<DefaultNetNode> nodeList = null;
synchronized (rnPool) {
nodeList = rnPool.get(resourceId);
if(nodeList == null) {
//不存在该资源
return;
}
nodeList.remove(addr);
if(nodeList.isEmpty()) {
rnPool.remove(resourceId);
}
}
}
//资源持有者宕机 应该移除其所有注册的资源
public static synchronized void removeNode(DefaultNetNode node) {
Iterator<List<DefaultNetNode>> nodeListList = rnPool.values().iterator();
while(nodeListList.hasNext()) {
List<DefaultNetNode> nodeList = nodeListList.next();
nodeList.remove(node);
}
NodePool.remove(node);
}
/*
* 获取某个资源对应的网络节点集合
*/
public static List<DefaultNetNode> getAddressList(ResourceInfo resourceInfo){
int resourceId = resourceInfo.hashCode();
List<DefaultNetNode> nodeList = null;
synchronized(rnPool) {
nodeList = rnPool.get(resourceId);
}
return nodeList;
}
}
做了上述准备之后就需要考虑 资源注册中心ResourceRegistryCenter类 的实现了:
首先资源注册中心要能响应如下几种最基本请求:
1.资源持有者发起的资源注册请求。
2.资源持有者发起的注销某一资源的请求。
3.资源请求者发起的资源获取请求(获取该资源对应的网络节点地址)。
上述方法都是远程方法,即方法的本体只存在于“资源注册中心”一端。而对于资源请求者和资源持有者都是利用RMI远程调用这些方法。相关的RMI接口如下:
IResourceCenter:
public interface IResourceCenter {
//资源注册方法(提供要注册的资源信息 + 自己的网络节点地址)
boolean registry(ResourceInfo resourceInfo, DefaultNetNode node);
//单个资源注销方法
boolean logout(ResourceInfo resourceInfo, DefaultNetNode node);
//根据某一资源信息可获取相关网络节点
List<DefaultNetNode> getAddressList(ResourceInfo resourceInfo);
}
该接口的实现类。也即远程方法本体的存在:
ResourceCenterImpl类:
public class ResourceCenterImpl implements IResourceCenter {
public ResourceCenterImpl() {
}
@Override
public boolean registry(ResourceInfo resourceInfo, DefaultNetNode node) {
ResourceNodePool.registry(resourceInfo, node);
return true;
}
@Override
public boolean logout(ResourceInfo resourceInfo, DefaultNetNode node) {
ResourceNodePool.logout(resourceInfo, node);
return true;
}
@Override
public List<DefaultNetNode> getAddressList(ResourceInfo resourceInfo) {
List<DefaultNetNode> result = new ArrayList<DefaultNetNode>();
List<DefaultNetNode> nodeList = ResourceNodePool.getAddressList(resourceInfo);
if(nodeList == null || nodeList.isEmpty()) {
return result;
}
for(DefaultNetNode node : nodeList) {
result.add(node);
}
return result;
}
}
除此之外他还应该完成:若某一资源持有者宕机(下线)要从相应的资源(该网络节点提供的所有资源)中删除该节点信息(注意是找出对应资源删除该节点而非直接注销整个资源,因为可能该资源还有其他的资源持有者)
ResourceRegistryCenter:
public class ResourceRegistryCenter {
public static final int DEFAULT_PORT = 54200;
//加载RMI配置文件
static {
RMIFactory.scanRmiMapping("/RMIMapping.xml");
}
private RMIServer rmiServer;
private volatile boolean startUp;
public ResourceRegistryCenter() {
this(DEFAULT_PORT);
}
public ResourceRegistryCenter(int registryPort) {
startUp = false;
this.rmiServer = new RMIServer();
this.rmiServer.setPort(registryPort);
}
public void setRegistryPort(int port) {
this.rmiServer.setPort(port);
}
/**
* 启动资源注册中心服务器
*/
public void startUp() {
if(startUp) {
return;
}
startUp = true;
this.rmiServer.startUp();
NodePool.startScanNode();
}
/**
* 关闭资源注册中心(宕机)
*/
public void shutdown() {
if(!startUp) {
return;
}
startUp = false;
this.rmiServer.shutdown();
NodePool.stopScanNode();
}
}
接着再来分析 资源持有者 和 资源请求者:
这两者有一点共同的地方,即:
1.首先它们两者都需要与“资源注册中心”主动通信。 即,他们两者都是“资源注册中心”的
RMIClient。
2.基于上述一点,故二者也都需要定义IResourceCenter接口。通过该接口来获取代理,
执行远程方法。
基于这个相同点出发,首先定义出二者的共同父类:
Resourcer:
public class Resourcer {
protected RMIClient rmiClient;
protected IResourceCenter irc;
public Resourcer() {
this.rmiClient = new RMIClient();
irc = rmiClient.getProxy(IResourceCenter.class);
}
public Resourcer(String registryIp, int registryPort) {
this.rmiClient = new RMIClient(registryIp, registryPort);
irc = rmiClient.getProxy(IResourceCenter.class);
}
public void setRegistryIp(String ip) {
this.rmiClient.setRmiServerIp(ip);
}
public void setRegistryPort(int port) {
this.rmiClient.setRmiServerPort(port);
}
}
在分析 资源持有者 之前先要思考一个问题:
由于资源持有者与资源注册中心没有保持长链接,而采用的是短连接模式,那么在该模式下如果资源持有者主动下线
还好,可以注销其所注册的资源。但是若它是异常宕机。资源注册中心是无法发现的。总不能给资源请求者响应一个
包含已经异常宕机的网络地址吧。出此考虑,想到解决的一个方法(不能说彻底避免该问题的发生,只能尽量降低该问题发生的概率)即:能不能将这些资源持有者的网络地址收集在一起。(封装在一个容器中存储起来)。然后每隔一段时间检测其是否异常宕机,我处理的方法是主动连接他们,如果超出一定时间没有连接上,则就将其视为异常宕机状态。而这个检测我是在启动资源注册中心服务器时候就开启的。在资源注册中心宕机时关闭。
因此才想到做了NodePool这个类。 而且这个容器的填充是资源持有者在注册资源时做的。
基于上述考虑基本可以确定 资源持有者(ResourceHolder)的基本功能了:
1.如果要接受资源注册中心以上述方式进行健康检查。则他必须要建立RMIServer。
2.它需要与“资源注册中心”主动通信。提交注册资源,注销资源的请求。故他需要继承Resourcer类
3.提供资源的注册,注销方法。
针对第一点的实现,先来定义“RMI接口”:
IResourceHolder:
public interface IResourceHolder {
boolean isActive() throws Exception;
}
实现类ResourceHolderImpl:
public class ResourceHolderImpl implements IResourceHolder {
public ResourceHolderImpl() {
}
/*
* 这个方法什么都不用做其实已经完成了他的功能了。
* 他就是用来检测的。如果此方法执行不成功他会掉入连接异常。也就达到了检测的目的
* 至于返回值其实就根本就不在乎
*/
@Override
public boolean isActive() throws Exception {
return true;
}
}
为避免篇幅太长,后面的内容会在下篇中进行介绍。