文章目录
前言
《资源发现》框架的实现是本人的一个编程训练项目,为了提升本人的编程能力、JAVA 编程思想,基于框架的角度出发,完成资源请求端可以从资源注册中心得到资源拥有者的地址列表信息,所以从客户端角度看,这是一种“服务发现”的机制,基于此机制之下,完成《资源发现》框架的编程。
此《资源发现》框架**“不重复造方轮子”**,只是自我的编程训练的项目。
资源发现
《资源发现》概述
资源发现是对《服务发现》的一种模拟,也是下一个项目《多文件云传输系统》的框架基础,资源发现是指使用一个资源注册中心来记录分布式系统中的全部资源的信息,以便其他请求资源的客户端能够快速的找到已注册的资源,基于多服务器、多 APP 资源处理的资源共享框架。
在如今的网络社会中,资源的传递需求极大,由此会产生不同的资源传输功能的 APP 和技术,而庞大的数据传输需要多个服务器进行,但是多个服务器需要解决服务器地址分发、负载均衡等问题,在这种情况下,为了解决上述多服务器系统的要求,并尽可能降低系统实现代价,《服务发现》的思路诞生。其拥有很好的容错性能,有服务器热插拔的优点,可以支持跨应用服务!
《资源发现》是基于《服务发现》的思路,实现对其的一种模拟,其独立于某个 APP 而产生的框架,其强大之处在于适用于不同的 APP,不被约束。他处理的是不同 APP 中以资源编号产生的资源,而不关心具体的资源形式和内容是什么。
资源发现由:
- 1、资源持有者;
- 2、资源请求者;
- 3、资源注册中心。
三大部分组成。
技术难点
-
基于个人开发的《MecRMI》框架(详见HB个人博客多文件云传输框架之模拟实现RMI)
-
两池一表的线程安全问题(多线程安全问题)
-
节点选择策略实现
-
单例模式和适配器模式应用
-
僵尸地址的处理
僵尸地址:资源拥有者下线或异常掉线时对它的资源节点地址
《资源发现》基本思想
《资源发现》基于《服务发现》,它是使用一个资源注册中心来记录分布式系统中的全部资源的信息,资源请求者发出资源请求,资源注册中心提供拥有该资源的资源拥有者节点列表,资源请求者可以在资源注册中心注册和注销资源,三者各司其职实现资源发现。
《资源发现》框架思考
需求分析
服务发现
《服务发现》的架构中有主要的三者组成:注册中心、服务提供者、服务消费者,如下:
注册中心是《服务发现》的核心,注册中心的基本功能为:
1、服务注册,其核心任务是:提交服务提供者的地址(服务器地址);
2、服务注销;
3、提供(服务)地址,即其本质是,向客户机提供服务器地址。
资源发现
基于《服务发现》,我们的资源发现也就有的雏形,也就有了核心的三大角色:资源注册中心,资源请求者,资源拥有者。
根据需求分析,可初步明确他们的功能如下:
- 资源注册中心:
它是《资源发现》的核心,它要为资源请求者和资源拥有者提供服务,主要的功能是资源注册、资源注销、提供资源节点地址列表、并且对僵尸地址(资源拥有者下线或异常掉线时对它的资源节点地址)进行处理。
- 资源拥有者:
资源拥有者可以拥有多个资源,且,这些资源可能分属不同的APP。它的主要功能就是注册资源和注销资源。
- 资源请求者:
资源请求者向资源注册中心发出获取所需要的资源的资源节点地址列表,它的主要功能就是请求资源地址、僵尸地址汇报(汇报给资源注册中心)。
如下:
所以我们《资源发现》所希望实现的功能也就如下:
当一个资源请求者客户端请求资源时,会从资源注册中心得到拥有该资源的所有网络节点地址列表,该资源请求者会根据我们所提供的健康值属性,选出当前压力最小的K个发送者,对他们请求这个资源的不同部分,并将有下线或异常掉线的资源节点地址汇报给资源注册中心,资源注册中心将该资源地址注销,最终资源请求者得到他所请求的资源。
技术选择
- 使用长链接(一对多的 C/S 模式),系统实时性更强,但,容量更差。
- 使用短连接,有两种实现方案:
1、NIO模式;(严格地说,NIO是逻辑长连接)
优点:注册中心能发现资源拥有者的异常掉线。
缺点:相对复杂,稳定性较差。
2、RMI模式。
优点:简单、效率高。
缺点:注册中心对资源拥有者的异常掉线,几乎没有可能发现!
若实现互为RMI服务器、客户机,也可以解决上述问题。
这里我们最终选择 RMI 实现模式,具体实现过程及分析见 HB个人博客:多文件云传输框架基础之模拟实现RMI
《资源发现》技术难点实现
- 两池一表的线程安全问题(多线程安全问题)
SoucePool 中使用了ConcurrentHashMap
来保证线程安全。
private static Map<Source, List<Node>> addressPool = new ConcurrentHashMap<Source, List<Node>>();
所有涉及到对 HolderList、HolderPool、SourcePool 中的内容进行操作(add、remove)时,都使用 synchronized 对要操作的对象加锁,来保证线程安全。
部分线程安全操作示例代码如下:
public class SourcePool {
private static Map<Source, List<Node>> addressPool = new ConcurrentHashMap<Source, List<Node>>();
synchronized static void addSource(Node address, Source source) {
List<Node> addressList = addressPool.get(source);
if (addressList == null) {
addressList = new ArrayList<>();
addressPool.put(source, addressList);
}
if (!addressList.contains(address)) {
addressList.add(address);
}
}
synchronized static void addSource(Node address, List<Source> sourceList) {
for (Source source : sourceList) {
addSource(address, source);
}
}
synchronized static void removeSource(Node address, Source source) {
List<Node> addressList = addressPool.get(source);
if (addressList == null) {
return;
}
if (!addressList.contains(address)) {
return;
}
addressList.remove(address);
}
synchronized static void removeSource(Node address, List<Source> sourceList) {
for (Source source : sourceList) {
removeSource(address, source);
}
}
synchronized static void removeSource(Node address) {
List<Source> sourceList = HolderPool.getSourceList(address);
removeSource(address, sourceList);
}
synchronized static List<Node> getAddress(Source source) {
return addressPool.get(source);
}
synchronized static String getHolderInfo() {
StringBuffer res = new StringBuffer();
List<Source> sourceList = getSourceList();
int sourceCount = sourceList.size();
res.append("当前").append(sourceCount).append("个资源:");
for (int index = 0; index < sourceCount; index++) {
res.append("\n").append(index + 1).append("、");
Source source = sourceList.get(index);
res.append(source).append(":");
List<Node> holderList = addressPool.get(source);
for (Node holder : holderList) {
res.append("\n\t").append(holder);
}
}
res.append("\n");
return res.toString();
}
private static List<Source> getSourceList() {
List<Source> sourceList = new ArrayList<>();
for (Source source : addressPool.keySet()) {
sourceList.add(source);
}
return sourceList;
}
}
Map<Source, List> addressPool 在代码中是static的,这个类主要是对其进行处理,因为这个类是多线程的,为了保证数据的可靠性和安全性,需要对方法加锁(synchronized),保证同一时刻只有一个线程可以访问其中的某一个方法。
- 深克隆
在上述部分线程安全操作示例代码中,最后一个方法,获取List时,用深克隆的方法保护原对象:创造一个新的对象,为其分配内存空间,不会返回原有的List的地址,也就不会对原有的List更改。
- 节点选择策略实现
节点选择策略接口类,方便未来 app 编写属于自己的节点选择策略。
public interface ISelectNodeStrategy {
int SELECT_ALL = -1;
List<Node> selectedSourceHolderList(List<Node> addressList, int maxCount);
}
默认节点选择策略接口对应的实现类:
public class SelectNodeStrategyAdapter implements ISelectNodeStrategy {
public SelectNodeStrategyAdapter() {
}
@Override
public List<Node> selectedSourceHolderList(List<Node> addressList, int maxCount) {
int holderCount = addressList.size();
if (holderCount <= maxCount) {
return addressList;
}
List<Node> selectedAddressList = new ArrayList<Node>();
List<List<Node>> healthArray = new ArrayList<>(Node.MAX_HEALTH_VALUE + 1);
for (int index = 0; index < addressList.size(); index++) {
Node address = addressList.get(index);
int health = address.getHealth();
List<Node> indexList = healthArray.get(health);
if (indexList == null) {
indexList = new ArrayList<>();
healthArray.set(health, indexList);
}
indexList.add(address);
}
int count = 0;
int index = 0;
while (count < maxCount) {
List<Node> addrList = healthArray.get(index);
for (Node address : addrList) {
selectedAddressList.add(address);
if (++count >= maxCount) {
break;
}
}
index++;
}
return addressList;
}
}
- 僵尸地址的处理
资源请求者在接收到资源注册中心发来的资源节点类表中,如果发现已经掉线或发生异常的资源持有者,向资源资源注册中心汇报,资源注册中心将僵尸地址删除。
public interface ISourceRequesterFunction {
void setISelectNodeStrategy(ISelectNodeStrategy selectNodeStrategy);
List<Node> getAddressList(Source source);
List<Node> getAddressList(Source source, int maxSourceHolderCount);
void reportCorpse(Node address);
}
随后在对应接口的实现类 SourceHolderRequesterFunction
中完成接口中要实现的方法 reportCorpse(Node address)
:
@Override
public void reportCorpse(Node address) {
SourcePool.removeSource(address);
HolderPool.removeHolder(address);
HolderList.removeHolder(address);
}
《资源发现》框架实现
资源发现基础类实现
- 资源拥有者网络节点
因为网络节点这个东西,博主考虑到在以后只要涉及到网络节点编程的框架中,就需要用到,从面向对象的编程角度出发,所以博主将其工具化,产生 NodeAddress
网络节点这个工具类:
public class NodeAddress {
private String ip; //ip地址
private int port; //端口号
public NodeAddress() {
}
public NodeAddress(String ip, int port) {
this.ip = ip;
this.port = port;
}
…………一系列getter、setter、toString、hashCode、equals方法
}
基于 NodeAddress
产生我们资源发现中的资源拥有者网络节点类 Node
:
package com.mec.source.core;
import com.mec.util.io.NodeAddress;
public class Node extends NodeAddress {
public static final int MAX_HEALTH_VALUE = 1000;
private int health;
public Node() {
}
}
- 资源的描述
资源由 id 和 appName 组成。
public class Source {
private String appName;
private String id;
public Source() {
}
public Source(String str) {
int index = str.indexOf("@");
if (index == -1) {
return;
}
this.appName = str.substring(0, index);
this.id = str.substring(index + 1);
}
public Source(String appName, String id) {
setAppName(appName);
setId(id);
}
…………一系列getter、setter、toString、hashCode、equals方法
}
“ id、AppName ”中不能出现@符,所以在初始化的时候我们要进行一个简单的判断,并抛异常:
if (appName.contains("@")) {
throw new RuntimeException("AppName中不能出现@符!");
}
资源注册中心的实现
资源注册中心这里使用 RMI 服务器,资源持有者、请求者(客户端)和资源注册中心(服务器端)是短连接模式,由客户端向服务器端发起“请求”,服务器连接客户端,并解析客户端传来的请求内容,并执行相关操作以得到“响应”信息,并将响应回送给该客户端,然后就立刻切断网络连接,实现了一个短而快的”请求/响应“过程。
因为资源注册中心只有一个,而资源请求者和资源拥有者会有很多,如果资源注册中心的功能过于繁琐可能会负载过大,所以本人打算将资源注册中心尽可能的“简单”化,因此资源注册中心不会关心任何的业务逻辑,只需要注册/销毁资源持有者,并且向资源请求者返回资源持有者的地址列表即可。
-
资源注册中心拥有的两池一表:
-
资源持有者网络节点列表:
public class HolderList {
private static List<Node> nodeList = new LinkedList<Node>();
//获取资源持有者
synchronized static Node getHolder(Node address) {
int index = nodeList.indexOf(address);
if (index == -1) {
return null;
}
return nodeList.get(index);
}
// 添加资源持有者
synchronized static void addHolder(Node address) {
if (!nodeList.contains(address)) {
nodeList.add(address);
}
}
// 删除资源持有者
synchronized static void removeHolder(Node address) {
if (nodeList.contains(address)) {
nodeList.remove(address);
}
}
//设置节点健康值
synchronized static void setHealth(Node address) {
int index = nodeList.indexOf(address);
if (index == -1) {
return;
}
Node theNode = nodeList.get(index);
theNode.setHealth(address.getHealth());
}
//获取资源持有者列表
synchronized static List<Node> getHolderList() {
List<Node> nodeList = new ArrayList<Node>();
for (Node address : HolderList.nodeList) {
nodeList.add(address);
}
return nodeList;
}
}
- 资源持有者和所持有资源列表的池子:
键为:资源拥有者节点;
值为:该节点所拥有的资源列表
public class HolderPool {
private static final Map<Node, List<Source>> holderPool
= new ConcurrentHashMap<Node, List<Source>>();
//给资源拥有者节点添加单个资源
synchronized static void addSource(Node address, Source source) {
List<Source> sourceList = holderPool.get(address);
if (sourceList == null) {
sourceList = new ArrayList<Source>();
holderPool.put(address, sourceList);
}
if (!sourceList.contains(source)) {
sourceList.add(source);
}
}
//给资源拥有者节点添加多个资源
synchronized static void addSource(Node address, List<Source> sourceList) {
for (Source source : sourceList) {
addSource(address, source);
}
}
//注销单个资源
synchronized static void removeSource(Node address, Source source) {
List<Source> sourceList = holderPool.get(address);
if (sourceList.contains(source)) {
sourceList.remove(source);
}
}
//注销多个资源
synchronized static void removeSource(Node address, List<Source> sourceList) {
for (Source source : sourceList) {
removeSource(address, source);
}
}
//注销某资源持有者节点(包括其所拥有的资源)
synchronized static void removeHolder(Node address) {
holderPool.remove(address);
}
//获取资源拥有者节点持有的资源列表
synchronized static List<Source> getSourceList(Node address) {
return holderPool.get(address);
}
//获取资源全部信息(资源持有者数量、所有资源持有者拥有的全部资源信息)
synchronized static String getSourceInfo() {
StringBuffer res = new StringBuffer();
List<Node> holderList = HolderList.getHolderList();
int holderCount = holderList.size();
res.append("共有").append(holderCount).append("个资源持有者:\n");
for (Node address : holderList) {
res.append(address).append("\n");
List<Source> sourceList = holderPool.get(address);
for (Source source : sourceList) {
res.append("\t").append(source).append("\n");
}
}
return res.toString();
}
}
- 资源和拥有该资源的节点列表的池子:
键为:资源;
值为:拥有该资源的所有资源拥有者节点列表
public class SourcePool {
private static Map<Source, List<Node>> addressPool = new ConcurrentHashMap<Source, List<Node>>();
//兼顾性能和线程安全的,支持高并发更新与查询的哈希表
//添加资源及拥有该资源的单个节点
synchronized static void addSource(Node address, Source source) {
List<Node> addressList = addressPool.get(source);
if (addressList == null) {
addressList = new ArrayList<>();
addressPool.put(source, addressList);
}
if (!addressList.contains(address)) {
addressList.add(address);
}
}
//添加资源及拥有该资源的节点列表
synchronized static void addSource(Node address, List<Source> sourceList) {
for (Source source : sourceList) {
addSource(address, source);
}
}
//删除资源及拥有该资源的单个节点
synchronized static void removeSource(Node address, Source source) {
List<Node> addressList = addressPool.get(source);
if (addressList == null) {
return;
}
if (!addressList.contains(address)) {
return;
}
addressList.remove(address);
}
//删除某一个资源节点(从每一个资源对应的资源节点列表中删除)
synchronized static void removeSource(Node address, List<Source> sourceList) {
for (Source source : sourceList) {
removeSource(address, source);
}
}
//删除资源及拥有该资源的节点列表
synchronized static void removeSource(Node address) {
List<Source> sourceList = HolderPool.getSourceList(address);
removeSource(address, sourceList);
}
//获取拥有某个资源的所有节点列表
synchronized static List<Node> getAddress(Source source) {
return addressPool.get(source);
}
//获取资源节点全部信息(资源数量、拥有该资源的所有资源节点信息)
synchronized static String getHolderInfo() {
StringBuffer res = new StringBuffer();
List<Source> sourceList = getSourceList();
int sourceCount = sourceList.size();
res.append("当前").append(sourceCount).append("个资源:");
for (int index = 0; index < sourceCount; index++) {
res.append("\n").append(index + 1).append("、");
Source source = sourceList.get(index);
res.append(source).append(":");
List<Node> holderList = addressPool.get(source);
for (Node holder : holderList) {
res.append("\n\t").append(holder);
}
}
res.append("\n");
return res.toString();
}
//获取资源列表
private static List<Source> getSourceList() {
List<Source> sourceList = new ArrayList<>();
for (Source source : addressPool.keySet()) {
sourceList.add(source);
}
return sourceList;
}
}
- 资源注册中心
这里采用观察者和侦听者模式,实现IListener
和ISpeaker
两个接口,时刻侦听关注着资源请求者和持有者的请求和信息。
public class SourceRegistryCenter implements ISpeaker, IListener {
public static final String DEFAULT_SOURCE_REGISTRY_CENTER_IP = "127.0.0.1";
public static final int DEFAULT_SOURCE_REGISTRY_CENTER_PORT = 54186;
private RmiServer server;
private List<IListener> listenerList;
public SourceRegistryCenter() throws Exception {
RmiImplClassFactory.initRmi("/actionmapping.xml");
this.server = new RmiServer();
this.server.setPort(SourceRegistryCenter.DEFAULT_SOURCE_REGISTRY_CENTER_PORT);
this.listenerList = new ArrayList<IListener>();
this.server.addListener(this);
}
public boolean isStartup() {
return this.server.isStartup();
}
public void startup() throws IOException {
this.server.startup();
}
public void shutdown() {
this.server.shutdown();
}
public String getHolderInfo() {
return SourcePool.getHolderInfo();
}
public String getSourceInfo() {
return HolderPool.getSourceInfo();
}
public void setPort(int port) {
this.server.setPort(port);
}
@Override
public void acceptMessage(String message) {
publish(message);
}
@Override
public void addListener(IListener listener) {
if (!this.listenerList.contains(listener)) {
this.listenerList.add(listener);
}
}
@Override
public void removeListener(IListener listener) {
if (this.listenerList.contains(listener)) {
this.listenerList.remove(listener);
}
}
@Override
public void publish(String message) {
for (IListener listener : this.listenerList) {
listener.acceptMessage(message);
}
}
}
资源持有者和资源请求者
- 资源持有者
public class SourceHolder {
private String selfIp;
private RmiClient rmiClient;
private ISourceHolderFunction sourceHolderFunction;
private Node address;
public SourceHolder() throws UnknownHostException {
this.selfIp = MecIo.getIp();
RmiProxy rmiProxy = new RmiProxy();
this.rmiClient = rmiProxy.getClient();
this.rmiClient.setServerAddress(SourceRegistryCenter.DEFAULT_SOURCE_REGISTRY_CENTER_IP,
SourceRegistryCenter.DEFAULT_SOURCE_REGISTRY_CENTER_PORT);
this.sourceHolderFunction = rmiProxy.getProxy(ISourceHolderFunction.class);
}
public void registry(Source source) {
this.sourceHolderFunction.registry(this.address, source);
}
public void registry(List<Source> sourceList) {
this.sourceHolderFunction.registry(this.address, sourceList);
}
public void logout(Source source) {
this.sourceHolderFunction.logout(this.address, source);
}
public void logout(List<Source> sourceList) {
this.sourceHolderFunction.logout(this.address, sourceList);
}
public void logout() {
this.sourceHolderFunction.logout(this.address);
}
public void setHealth(int health) {
this.address.setHealth(health);
this.sourceHolderFunction.setHealth(this.address);
}
public void setAddress(Node address) {
this.address = address;
}
public String getSelfIp() {
return this.selfIp;
}
public void setSourceRegistryCenterAddress(String ip, int port) {
this.rmiClient.setServerAddress(ip, port);
}
}
- 资源请求者
public class SourceRequester {
private RmiClient rmiClient;
private ISourceRequesterFunction sourceRequesterFunction;
public SourceRequester() {
RmiProxy rmiProxy = new RmiProxy();
this.rmiClient = rmiProxy.getClient();
this.rmiClient.setServerAddress(SourceRegistryCenter.DEFAULT_SOURCE_REGISTRY_CENTER_IP,
SourceRegistryCenter.DEFAULT_SOURCE_REGISTRY_CENTER_PORT);
this.sourceRequesterFunction = rmiProxy.getProxy(ISourceRequesterFunction.class);
}
public void setSelectNodeStrategy(ISelectNodeStrategy selectNodeStrategy) {
this.sourceRequesterFunction.setISelectNodeStrategy(selectNodeStrategy);
}
public void setSourceRegistryCenterAddress(String ip, int port) {
this.rmiClient.setServerAddress(ip, port);
}
public List<Node> getAddresses(Source source, int maxCount) {
return this.sourceRequesterFunction.getAddressList(source, maxCount);
}
public List<Node> getAddresses(Source source) {
return this.sourceRequesterFunction.getAddressList(source);
}
public void reportCorpse(Node address) {
this.sourceRequesterFunction.reportCorpse(address);
}
}
资源持有者和资源请求者功能具体实现
资源持有者的注册和注销资源的一系列处理:
public interface ISourceHolderFunction {
void registry(Node address, Source source);
void registry(Node address, List<Source> sourceList);
void logout(Node address, Source source);
void logout(Node address, List<Source> sourceList);
void logout(Node address);
void setHealth(Node address);
}
资源请求者在请求资源等一系列处理:
public interface ISourceRequesterFunction {
void setISelectNodeStrategy(ISelectNodeStrategy selectNodeStrategy);
List<Node> getAddressList(Source source);
List<Node> getAddressList(Source source, int maxSourceHolderCount);
void reportCorpse(Node address);
}
资源持有者和资源请求者所要实现功能接口对应的实现类:
public class SourceHolderRequesterFunction implements ISourceHolderFunction, ISourceRequesterFunction {
private ISelectNodeStrategy selectNodeStrategy;
public SourceHolderRequesterFunction() {
this.selectNodeStrategy = new SelectNodeStrategyAdapter();
}
@Override
public List<Node> getAddressList(Source source) {
List<Node> addressList = SourcePool.getAddress(source);
return addressList;
}
@Override
public List<Node> getAddressList(Source source, int maxSourceHolderCount) {
List<Node> addressList = SourcePool.getAddress(source);
int currentAddressCount = addressList.size();
if (currentAddressCount <= maxSourceHolderCount) {
return addressList;
}
List<Node> selectedAddressList = this.selectNodeStrategy.selectedSourceHolderList(
addressList, maxSourceHolderCount);
return selectedAddressList;
}
@Override
public void reportCorpse(Node address) {
SourcePool.removeSource(address);
HolderPool.removeHolder(address);
HolderList.removeHolder(address);
}
@Override
public void registry(Node address, Source source) {
Node holder = HolderList.getHolder(address);
if (holder == null) {
holder = address;
HolderList.addHolder(holder);
}
SourcePool.addSource(holder, source);
HolderPool.addSource(holder, source);
}
@Override
public void registry(Node address, List<Source> sourceList) {
Node holder = HolderList.getHolder(address);
if (holder == null) {
holder = address;
HolderList.addHolder(address);
}
SourcePool.addSource(holder, sourceList);
HolderPool.addSource(holder, sourceList);
}
@Override
public void logout(Node address, Source source) {
SourcePool.removeSource(address, source);
HolderPool.removeSource(address, source);
}
@Override
public void logout(Node address, List<Source> sourceList) {
SourcePool.removeSource(address, sourceList);
HolderPool.removeSource(address, sourceList);
}
@Override
public void logout(Node address) {
HolderList.removeHolder(address);
SourcePool.removeSource(address);
HolderPool.removeHolder(address);
}
@Override
public void setHealth(Node address) {
HolderList.setHealth(address);
}
@Override
public void setISelectNodeStrategy(ISelectNodeStrategy selectNodeStrategy) {
this.selectNodeStrategy = selectNodeStrategy;
}
}