【JavaEE】自主设计实现服务(资源)发现框架

为什么要实现这个框架?

对于服务发现,百度百科中给出了这样的定义:服务发现是指使用一个注册中心来记录分布式系统中的全部服务的信息,以便其他服务能够快速的找到这些已注册的服务。《服务发现》
显而易见,服务发现起到了一个“管理者”的作用,它对于服务进行了系统的管理。这样一来,方便了其他服务去找已经注册的服务。
现在我之所以要完成服务发现这一框架,是因为它会在我的《多文件自平衡云模式文件传输》项目中作为一个很重要的角色。简单介绍一个这个项目,这个项目其实也是一个很大的框架,我希望app开发者用这个框架,可以更容易的开发出更多的项目,比如说,可以开发一个观看视频的系统,那么就会存在众多客户端、视频服务器端、存放视频的数据库服务器这几个身份,这样一来,由于客户端会不定时的去访问服务器,会给服务器造成巨大的压力(所谓的服务器压力,当有多个客户端“同时”向服务器请求视频文件,那么,服务器需要向多个客户端“同时”传输视频文件)。服务器端每向一个客户端发送一个视频文件,都需要一个线程的支持。
对于上述问题的解决,先引入了服务发现。至于项目中如何解决客户端、服务器、注册中心之间的传输文件等问题,项目中会做针对性介绍。其实这里主要介绍的是众多服务里面的一种情况——资源,对于服务发现,会作为我的一个项目,在项目中会进行详细介绍。而这里的资源发现也是服务发现的一个具体实例。

服务(资源)发现框架介绍

对于上述所说的服务,在这个框架中,其实也就是资源。关于“资源信息”的定义:作为工具,可以用在任何需要《资源发现》的场合,因此,资源的定义绝对不能狭隘(也就是说,不能将资源这个概念太实在化!)。
在这里,我们可以将资源定义为:
app;
id;
version。
那之后,我将会用资源来代替服务。
首先明确,这个框架会有三个身份存在,即:资源拥有者资源请求者注册中心。(在这里,我们已经不会在区分谁是客户端,谁是服务器端了,因为,不论是资源请求者还是资源拥有者,都有可能称为客户端或者服务器端。)这三个角色一起构成的这个小框架,应该需要实现以下的核心功能
“资源”管理;
“资源拥有者”注册;
“资源拥有者”注销;
“资源请求者”请求资源;
资源拥有者与资源之间的关系管理;
资源拥有者的管理。

可以配置的功能:
负载均衡策略;
资源拥有者的实时管理。

上述的分析已经很明确,这个框架不需要去考虑其他事情,只需要把上面的核心功能实现了。既然涉及到了三个角色,下面,我们来分析一下他们三个角色各自有什么样的功能。
资源拥有者
向注册中心注册自己;
向注册中心注销自己;
注册完成后,服务提供者采用一个心跳的方式通知注册中心其健壮性;
资源请求者
获取资源列表;
资源请求者来负责负载均衡,选择服务对象,进行服务调用;
注册中心
进行资源的管理;
提供服务注册;
提供服务注销;
提供资源请求者资源列表;
根据心跳检测结果,处理异常的资源拥有者;

讨论如何实现该框架

在资源拥有者端,需要填写上述资源信息(app,ip,version)用以区分不同的资源;而资源拥有者在注册资源时,必须提供上述资源信息方便注册中心去管理;资源注册中心将以上述内容的综合(HashCode)作为资源编号进行键值对存储,之后不管是删除还是检查,方便取出。

关于刚才提到的version的应用场景会有以下两种:
1.APP的自动版本下载。
2.客户端向服务器请求最新资源,检查版本号,若发现更新版本,则,自动启动新版本资源下载。
资源拥有者注册到“资源注册中心”的最终目的是,在未来,使其可以成为“资源提供者”,那么,需要提供ip和port;这个ip和port,是能够让其它网络节点与其进行信息交互的ip和port。这里说到的ip和port是来自资源请求者的ip和port,为什么资源请求者要作为服务器端,是因为,资源请求者可能需要连接很多个服务器进行资源传输,是一对多的关系,那么传输的时候,如果它作为客户端,那就需要提供很多的服务器端的ip和port,如果作为服务器端的话,会非常好的去管理,只需要提供一个port就好。

资源拥有者的管理
目的:主要是保证接入的资源拥有者是可用的;或者说,能及时发现资源拥有者不可用,并将其从资源表中剔除。

资源拥有者与资源注册中心能否维持“长连接”?可有通过长连接来实现,这样的方式很“灵敏”,但是长连接是非常耗资源的,若接收不到消息,他就会一直在循环等待。所以我们这里采取短链接的模式,当然,资源注册中心可以通过仅保存其Socket,并通过定期扫描,检查其可用性,来实现上述基本目的。

对于资源拥有者是否有效,可以让资源请求者加以判断;若该资源拥有者无效,还应该告知资源注册中心,将其从现在的资源列表中剔除。

当前是通过“短连接”完成资源注册中心的,希望不要再混用长连接;在这种考量下,可以让资源拥有者创建一个RMI服务器,供资源注册中心进行有效性问答。

总的来说,每个资源拥有者都需要向资源注册中心进行注册,主动告知“我”拥有什么样的资源,资源注册中心再对这些信息进行管理。资源请求者在得到资源之前,需要先向资源注册中心获取资源列表,然后根据资源列表进行负载均衡,然后选择发送端进行发送,之后进行会消息的收发。资源请求者和资源拥有者之间通过短链接进行收发消息。
综上所述,我们已经大致讨论清楚了这个框架的实现的大致情况。下面我们来具体实现。

实现该框架

针对注册中心,我们需要先定义接口来明确它的功能:

public interface IResourceCenter {
	void registry(ResourceInfo res, INetNode addr);
	void logout(ResourceInfo res, INetNode addr);
	List<INetNode> getAddressList(ResourceInfo res);
}

可以很清楚的看到,资源注册中心提供的三个方法都得这里,注册、注销、获取资源的列表。解释一下,其中的ResourceInfo类,是我们定义的资源的信息:

public class ResourceInfo {
	private String app;
	private String id;
	private String version;

INetNode类是我们在(【JavaEE】自主设计实现负载均衡框架)实现的一个接口:

public interface INetNode {
	String getIp();
	int getPort();
}

它提供了服务器端的ip和port,
接下来,将注册中心接口进行默认的实现(默认的意思是,之后如果用户可以有自己的实现方式,可以进行set方法):

public class ResourceCenterImpl implements IResourceCenter {
	
	public ResourceCenterImpl() {
	}
	
	@Override
	public boolean registry(ResourceInfo res, DefaultNetNode addr) {
		ResourceNodePool.registry(res, addr);
		
		return true;
	}

	@Override
	public boolean logout(ResourceInfo res, DefaultNetNode addr) {
		ResourceNodePool.logout(res, addr);
		
		return true;
	}

	@Override
	public List<DefaultNetNode> getAddressList(ResourceInfo res) {
		List<DefaultNetNode> result = new ArrayList<DefaultNetNode>();
		
		List<INetNode> nodeList = ResourceNodePool.getAddressList(res);
		if (nodeList == null || nodeList.isEmpty()) {
			return result;
		}
		
		for (INetNode node : nodeList) {
			result.add((DefaultNetNode) node);
		}
		
		return result;
	}

}

代码中提到了ResourceNodePool类,这个类是存放资源和资源持有者关系的pool,一个资源,会有多个资源持有者,将他们形成一个List作为值进行存放。

public class ResourceNodePool {
	private static final Map<Integer, List<INetNode>> rnPool
			= new ConcurrentHashMap<Integer, List<INetNode>>();
	
	public ResourceNodePool() {
	}

	public static void registry(ResourceInfo res, INetNode addr) {
		int key = res.hashCode();
		List<INetNode> addrList = null;
		synchronized (rnPool) {
			addrList = rnPool.get(key);
			if (addrList == null) {
				addrList = new CopyOnWriteArrayList<INetNode>();
				rnPool.put(key, addrList);
			}
		}
		addrList.add(addr);
		NodePool.addNode(addr);
	}
	
	public static void logout(ResourceInfo res, INetNode addr) {
		int key = res.hashCode();
		List<INetNode> addrList = null;
		synchronized (rnPool) {
			addrList = rnPool.get(key);
			if (addrList == null) {
				return;
			}
			addrList.remove(addr);
			if (addrList.isEmpty()) {
				rnPool.remove(key);
			}
		}
	}
	
	public synchronized static void logout(INetNode addr) {
		Iterator<List<INetNode>> nodeListList = rnPool.values().iterator();
		while (nodeListList.hasNext()) {
			List<INetNode> nodeList = nodeListList.next();
			nodeList.remove(addr);
		}
		NodePool.removeNode(addr);
	}
	
	public static List<INetNode> getAddressList(ResourceInfo res) {
		int key = res.hashCode();
		List<INetNode> nodeList = null;
		synchronized (rnPool) {
			nodeList = rnPool.get(key);
		}
		
		return nodeList;
	}
}

这里还会涉及到一个关键的类,用它来专门存放资源持有者,目的是每个资源持有者进行定期轮询,检查是否还在线,若不在线,直接进行注销。

public class NodePool {
	// TODO 最好给一个可以配置的扫描时间,方便用户进行调优。
	private static final long DEFAULT_DELAY_TIME = 3000;
	
	private static final Map<Integer, INetNode> nodePool
			= new ConcurrentHashMap<Integer, INetNode>();
	private static ScanTimer scanTimer = new ScanTimer(DEFAULT_DELAY_TIME);
	
	public NodePool() {
	}
	
	public static void startScanNode() {
		scanTimer.start();
	}
	
	public static void stopScanNode() {
		scanTimer.stop();
	}
	
	public static void addNode(INetNode node) {
		int key = node.hashCode();
		if (nodePool.containsKey(key)) {
			return;
		}
		
		nodePool.put(key, new ResourceHolderNode(node));
	}
	
	public static void removeNode(INetNode node) {
		int key = node.hashCode();
		if (nodePool.containsKey(key)) {
			nodePool.remove(key);
		}
	}
	
	static class ScanTimer extends DidaDida {
		public ScanTimer() {
			super();
		}
		
		public ScanTimer(long delay) {
			super(delay);
		}
		
		@Override
		public void doing() {
			// 定时扫描资源持有者是否Active
			if (nodePool.isEmpty()) {
				return;
			}
			Iterator<INetNode> nodeList = nodePool.values().iterator();
			while (nodeList.hasNext()) {
				INetNode node = nodeList.next();
				try {
					((ResourceHolderNode) node).isActive();
				} catch (Throwable th) {
					// 发现了非活跃资源持有者,应该删除它!
					ResourceNodePool.logout(node);
				}
			}
		}
		
	}
	
}

上述展示了一些必要的类,由于这个框架涉及到的包和类有很多,我就不将代码进行详细展示,下面我将通过图对代码的构建进行展示,具体的源代码,我放在了我的GitHub上:ResourceFounder
在这里插入图片描述通过上面这幅图,可以看到,有的角色,即是服务器,也是客户端;
这个框架的大致思路是:
注册中心以服务器的身份启动,一旦有资源拥有者要向它进行注册的时候,资源拥有者作以客户端的身份通过RMI,向它进行注册,注册中心会将它存到ResourceNodePool和NodePool里面,一个是专门用来资源和资源拥有者的对应关系的,一个是专门用来检测资源拥有者的活跃度的。然后,资源请求者以客户端的身份向资源注册中心进行某个资源的请求,也是通过RMI的方式,资源注册中心在ResourceNodePool找到该资源对应的List,将其进行发送。NodePool里面有一个定时器,是我自己之前实现的,可以看我之前的博文【JavaEE】定时器(定时到毫秒级别),定时以客户端的身份向资源拥有者进行isActive()方法的请求,判断资源拥有者是否还存活,这时,资源拥有者以服务器的身份,将执行结果返回给NodePool,若失败,则将该资源持有者注销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
利用jmdns发现局域网设备,在局域网内,你要通过一台主机和其他主机进行通信,你需要知道对方的ip地址,但是有些时候,你并不知道对方的ip地址,因为一般使用DHCP动态分配ip地址的局域网内,各个主机的IP地址是由DHCP服务器来帮你分配IP地址的。所以在很多情况下,你要知道对方的IP地址是比较麻烦的。 鉴于发现这篇文章最近的浏览量比较多,晚上也有不少转载,特别声明一下,文章水平可能不大够,只是我当时的一些理解,所以希望大家以批判的角度来看,然后又什么问题欢迎讨论。真心不希望误导大家^_^ mDNS就是来解决这个问题的。通过一个约定俗成的端口号,5353。(这个端口号应该是由IETF组织约定的。)每个进入局域网的主机,如果开启了mDNS服务的话,都会向局域网内的所有主机组播一个消息,我是谁,和我的IP地址是多少。然后其他也有该服务的主机就会响应,也会告诉你,它是谁,它的IP地址是多少。当然,具体实现要比这个复杂点。 比如,A主机进入局域网,开启了mDNS服务,并向mDNS服务注册一下信息:我提供FTP服务,我的IP是192.168.1.101,端口是21。当B主机进入局域网,并向B主机的mDNS服务请求,我要找局域网内FTP服务器,B主机的mDNS就会去局域网内向其他的mDNS询问,并且最终告诉你,有一个IP地址为192.168.1.101,端口号是21的主机,也就是A主机提供FTP服务,所以B主机就知道了A主机的IP地址和端口号了。 大概的原理就是这样子,mDNS提供的服务要远远多于这个,当然服务多但并不复杂。 在Apple 的设备上(电脑,笔记本,iphone,ipad等设备)都提供了这个服务。很多Linux设备也提供这个服务。Windows的设备可能没有提供,但是如果安装了iTunes之类的软件的话,也提供了这个服务。 这样就可以利用这个服务开发一些局域网内的自动发现,然后提供一些局域网内交互的应用了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值