zoukankan      html  css  js  c++  java
  • 详解 服务发现 的基本实现

    YouzgLogo

    发展历程

    对于网络通信,总共有如下三个历程

    单服务器 ———— 垂直应用架构

    和本人《C/SFramework》专栏中所使用的方式一样:
    只存在一个服务器,这就需要服务器客户端之间保持“长连接”,
    那么,就客户端需要通过配置文件来获取服务器的ipport
    如下图所示:
    长连接 展示
    而这样的形势是完全不合理的!
    因为若是客流量较大,会非常容易导致服务器崩溃
    而若是我们限制了客流量,则会导致用户体验较差


    因此,基于上述不满,网络通信,发展为如下形式:

    多服务器 ———— 分布式服务架构

    开设多个服务器,共同来处理客户端的请求
    这种形式下,客户端还是需要一个配置文件,来存储每一个服务器ipport
    如下图所示:
    多服务器 展示
    正如本人在上图中所讲那样:
    这时若是新增一个服务器
    那么,所有客户端都需要修改配置文件,这样做是十分困难的!
    而且,由于配置文件 是 写死的,因此可能会存在大量客户端同时访问其中一个服务器,而存在其它服务器 空闲的情况!


    那么,为了解决上述问题,网络通信衍变为如下形式:

    注册中心 + 多服务器 ———— 流动计算架构

    再次添加一个用于负载均衡(依照当前每一个APP服务器的“健康状态”来分配ip和port)的服务器 —— 分流服务器
    如下图所示:
    负载均衡 展示
    至此,网络通信不再需要客户端存储访问APP服务器配置文件,只需要存储分流服务器相关信息
    这样,我们既能简化客户端的操作,也能够实现负载均衡

    而最后一种网络通信形式,就被称之为“服务发现

    服务发现

    何为“服务发现”?

    所有服务器(无论是某一种APP的多个服务器,还是不同APP的多个服务器)在启动时,
    都需要在“注册中心”进行注册
    客户端需要从“注册中心”获取它的请求所属APP的服务器(组)地址信息

    客户端角度看,注册中心起到以下两个作用

    1、是否存在某个APP的服务器;
    2、获取某个APP服务器(组)的地址信息

    所以,客户端角度,这是一种“服务发现”的机制

    服务发现的 妙用

    1、服务器热插拔
    (即:在系统运行状态下,单个服务器发生异常情况,并不会影响全局)
    2、服务器在线升级;
    (基于 服务器热插拔 的性质)
    3、容错机制;
    (基于 服务器热插拔 的性质)
    4、负载均衡


    那么,在本篇博文中,本人将来基本实现服务发现

    基本思路

    在上文的讲解中,我们也能发现:
    服务发现的核心 就是 注册中心
    那么,我们来思考下注册中心要完成的功能

    注册中心需求分析

    1. 注册中心本身是一个服务器
    2. 注册中心对服务提供者提供的一些功能;
    3. 注册中心对服务消费者提供的另一些功能

    那么,我们能够从上面的讲解中,了解到:
    注册中心的真正核心在于能够对APP服务器池进行轮询负载均衡


    那么,依照上述的思想,现在我们就先基本实现下 负载均衡轮询

    轮询 与 负载均衡 的基本实现

    首先是 一个用于获取 保存在 注册中心 的 APP服务器网络节点信息接口规范:

    网络节点 —— INetNode接口:

    package edu.youzg.balance.core;
    
    /**
     * 用于取出 “服务器池” 中的 服务器的ip和port
     */
    public interface INetNode {
    	String getIp();
    	int getPort();
    }
    

    接下来是 基本实现了INetNode接口的 实现类

    网络节点实现类 —— DefaultNetNode类:

    (注:该类仅是基本实现,并没有任何逻辑,是以便后续的操作而产生的)

    package edu.youzg.balance.core;
    
    /**
     * 默认的 网络节点,存储每一个服务器的ip和port
     */
    public class DefaultNetNode implements INetNode {
    	private String ip;
    	private int port;
    
    	public DefaultNetNode() {
    	}
    	
    	public DefaultNetNode(String ip, int port) {
    		this.ip = ip;
    		this.port = port;
    	}
    
    	public void setIp(String ip) {
    		this.ip = ip;
    	}
    
    	public void setPort(int port) {
    		this.port = port;
    	}
    
    	@Override
    	public String getIp() {
    		return ip;
    	}
    
    	@Override
    	public int getPort() {
    		return port;
    	}
    
    	@Override
    	public int hashCode() {
    		final int prime = 31;
    		int result = 1;
    		result = prime * result + ((ip == null) ? 0 : ip.hashCode());
    		result = prime * result + port;
    		return result;
    	}
    
    	@Override
    	public boolean equals(Object obj) {
    		if (this == obj) {
    			return true;
    		}
    		if (obj == null) {
    			return false;
    		}
    		if (getClass() != obj.getClass()) {
    			return false;
    		}
    
    		DefaultNetNode other = (DefaultNetNode) obj;
    		if (ip == null) {
    			if (other.ip != null) {
    				return false;
    			}
    		} else if (!ip.equals(other.ip)) {
    			return false;
    		}
    		if (port != other.port) {
    			return false;
    		}
    		return true;
    	}
    
    	@Override
    	public String toString() {
    		return "[" + ip + ":" + port + "]";
    	}
    
    }
    

    可以看到:该类仅是一个用于保存 “注册过的”服务器信息的类


    接下来是 完善DefaultNetNode类,并附加“轮询功能”的网络节点类 —— PollingNetNode类

    用于 轮询 的 网络节点 —— PollingNetNode类:

    package edu.youzg.balance.core;
    
    /**
     * 能够形成 顺序链(“双向链表”,以便我们取出 前驱和后继节点) 的 “网络节点”
     */
    public class PollingNetNode extends DefaultNetNode {
    	private PollingNetNode next;
    	private PollingNetNode pre;
    	
    	public PollingNetNode() {
    		this.next = this;
    		this.pre = this;
    	}
    
    	public PollingNetNode(String ip, int port) {
    		super(ip, port);
    		this.next = this;
    		this.pre = this;
    	}
    
    	public void setNext(PollingNetNode next) {
    		this.next = next;
    	}
    
    	public void setPre(PollingNetNode pre) {
    		this.pre = pre;
    	}
    
    	public PollingNetNode getNext() {
    		return this.next;
    	}
    	
    	public PollingNetNode getPre() {
    		return this.pre;
    	}
    	
    }
    

    接下来是 一个 规范操作网络节点的接口:

    负载均衡器 —— INetNodeBalance类:

    package edu.youzg.balance.core;
    
    /**
     * 规范 “增、删、查” 网络节点 的接口(负载均衡器)
     */
    public interface INetNodeBalance {
    	void addNode(INetNode node);
    	INetNode removeNode(INetNode node);
    	INetNode getNode();
    }
    

    然后是 基本实现该接口,并给出一个保存每一个注册过的服务器抽象类

    未实现 获取节点方式 的 负载均衡器 —— AbstractNetNodeBalance抽象类:

    package edu.youzg.balance.core;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * 提供 “网络节点池子”,以及这个池子的相关操作方法
     */
    public abstract class AbstractNetNodeBalance implements INetNodeBalance {
    	protected final Map<Integer, INetNode> nodePool;
    
    	/**
    	 * 初始化 “网络节点池子”
    	 */
    	public AbstractNetNodeBalance() {
    		this.nodePool = new ConcurrentHashMap<>();
    	}
    	
    	@Override
    	public void addNode(INetNode node) {
    		if (node == null) {
    			return;
    		}
    		
    		int nodeHashCode = node.hashCode();
    		if (nodePool.containsKey(nodeHashCode)) {
    			return;
    		}
    		
    		nodePool.put(nodeHashCode, node);
    	}
    	
    	public boolean isNodePoolEmpty() {
    		return nodePool.isEmpty();
    	}
    	
    	@Override
    	public INetNode removeNode(INetNode node) {
    		if (node == null) {
    			return null;
    		}
    		return nodePool.remove(node.hashCode());
    	}
    	
    }
    

    那么,现在我们就来完成下实现“轮询功能”的双向链表类:

    轮询实现 —— PollingBalance类:

    package edu.youzg.balance.core;
    
    /**
     * 实现 能够 “顺序轮询” 的 网络节点的 “双向链表”
     */
    public class PollingBalance extends AbstractNetNodeBalance {
    	private PollingNetNode pollingNode;
    	private PollingNetNode currentNode;
    
    	/**
    	 * 初始化 AbstractNetNodeBalance的map
    	 */
    	public PollingBalance() {
    		super();
    	}
    
    	/**
    	 * “链表”操作 —— 将所传网络节点 加入到 双向链表 的表头之前(即:最后一个节点后)<br/>
    	 * 并更新 AbstractNetNodeBalance 中的map
    	 * @param node 要进行加入的 网络节点
    	 */
    	@Override
    	public synchronized void addNode(INetNode node) {
    		PollingNetNode newNode = new PollingNetNode(node.getIp(), node.getPort());
    		
    		if (pollingNode == null) {
    			pollingNode = newNode;
    			currentNode = pollingNode;
    			super.addNode(newNode);
    			return;
    		}
    		newNode.setPre(pollingNode.getPre());
    		newNode.setNext(pollingNode);
    		pollingNode.setPre(newNode);
    		newNode.getPre().setNext(newNode);
    		
    		super.addNode(newNode);
    	}
    
    	/**
    	 * “链表”操作 —— 将所传网络节点 从 双向链表 中删除掉<br/>
    	 * 并更新 AbstractNetNodeBalance 中的map
    	 * @param node 要删除的 网络节点
    	 * @return 成功删除 的 节点信息
    	 */
    	@Override
    	public synchronized INetNode removeNode(INetNode node) {
    		PollingNetNode target = new PollingNetNode(node.getIp(), node.getPort());
    		target = (PollingNetNode) super.removeNode(target);
    		
    		if (target == null) {
    			return null;
    		}
    		
    		if (isNodePoolEmpty()) {
    			pollingNode = null;
    			currentNode = null;
    			return pollingNode;
    		}
    		
    		if (currentNode == target) {
    			currentNode = target.getNext();
    		}
    		
    		if (pollingNode == target) {
    			pollingNode = target.getNext();
    		}
    		
    		target.getPre().setNext(target.getNext());
    		target.getNext().setPre(target.getPre());
    		target.setPre(target);
    		target.setNext(target);
    		
    		return target;
    	}
    
    	/**
    	 * 按照“加入时间顺序” 轮询 双向链表
    	 * @return
    	 */
    	@Override
    	public synchronized INetNode getNode() {
    		INetNode result = currentNode;
    		
    		if (currentNode != null) {
    			currentNode = currentNode.getNext();
    		}
    		
    		return result;
    	}
    
    }
    

    最后,由于本人说过:注册中心的一个核心功能 就是 负载均衡
    而实现负载均衡,我们需要查询每一个App服务器的“健康状态
    最主要的还是高并发情况的处理
    这就需要一套精妙的算法

    由于本篇博文的目的是介绍以及初步实现服务发现
    因此在这里本人就使用极简的方式,来优化下客户端获取APP服务器信息的操作,来初步实现 负载均衡

    负载均衡实现 —— RandomBalance类:

    package edu.youzg.balance.core;
    
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * 随机访问(初步模拟“负载均衡”)的 双向链表
     */
    public class RandomBalance extends AbstractNetNodeBalance {
    	private final List<INetNode> nodeList;
    
    	/**
    	 * 初始化 AbstractNetNodeBalance的map 和 nodeList
    	 */
    	public RandomBalance() {
    		super();
    		nodeList = new LinkedList<>();
    	}
    
    	/**
    	 * 按照顺序加入 网络节点
    	 * 并更新 AbstractNetNodeBalance的map
    	 * @param node 要进行加入的网络节点
    	 */
    	@Override
    	public void addNode(INetNode node) {
    		super.addNode(node);
    		if (node == null || nodeList.contains(node)) {
    			return;
    		}
    		nodeList.add(node);
    	}
    
    	/**
    	 * 删除指定的 网络节点
    	 * 并更新 AbstractNetNodeBalance的map
    	 * @param node
    	 * @return
    	 */
    	@Override
    	public INetNode removeNode(INetNode node) {
    		if (node == null || !nodeList.contains(node)) {
    			return null;
    		}
    		nodeList.remove(node);
    		return super.removeNode(node);
    	}
    
    	/**
    	 * 通过 随机数,来随机访问 nodeList中的网络节点
    	 * @return 随机取出的一个 网络节点
    	 */
    	@Override
    	public INetNode getNode() {
    		if (isNodePoolEmpty()) {
    			return null;
    		}
    		
    		int index = (int) (Math.random() * (nodeList.size() + 1));
    		return this.nodeList.get(index);
    	}
    
    }
    

    那么,既然轮询以及负载均衡基本实现了,
    现在本人就来铺垫下,以便我们之后实现注册中心

    注册中心 的 铺垫

    首先,本人给出一个用于保存某项服务的信息:

    单项服务节点 —— ServiceNode类:

    package edu.youzg.about_service_discovery.core;
    
    import edu.youzg.balance.core.INetNode;
    
    /**
     * 用于保存 某项服务所需的 ip、port 和 服务名
     */
    public class ServiceNode {
    	private String service;
    	private INetNode node;
    	
    	public ServiceNode() {
    	}
    
    	public ServiceNode(String service, INetNode node) {
    		this.service = service;
    		this.node = node;
    	}
    
    	public String getService() {
    		return service;
    	}
    
    	public void setService(String service) {
    		this.service = service;
    	}
    
    	public INetNode getNode() {
    		return node;
    	}
    
    	public void setNode(INetNode node) {
    		this.node = node;
    	}
    
    	@Override
    	public int hashCode() {
    		final int prime = 31;
    		int result = 1;
    		result = prime * result + ((node == null) ? 0 : node.hashCode());
    		result = prime * result + ((service == null) ? 0 : service.hashCode());
    		return result;
    	}
    
    	@Override
    	public boolean equals(Object obj) {
    		if (this == obj) {
    			return true;
    		}
    		if (obj == null) {
    			return false;
    		}
    		if (getClass() != obj.getClass()) {
    			return false;
    		}
    		ServiceNode other = (ServiceNode) obj;
    		if (node == null) {
    			if (other.node != null) {
    				return false;
    			}
    		} else if (!node.equals(other.node)) {
    			return false;
    		}
    		if (service == null) {
    			if (other.service != null) {
    				return false;
    			}
    		} else if (!service.equals(other.service)) {
    			return false;
    		}
    		return true;
    	}
    
    	@Override
    	public String toString() {
    		return service + " : " + node;
    	}
    	
    }
    

    之后我们向“池子”中 增加/删除APP服务器,都是基于单项服务节点的


    那么,基于上述的单项服务节点,本人再来提供给一个接口:

    基于单项服务节点的 基本操作 规范 —— IServiceNodeAction接口:

    package edu.youzg.about_service_discovery.core;
    
    import java.util.List;
    
    /**
     * 操作 单项服务节点 的 action
     */
    public interface IServiceNodeAction {
    	void registryService(String nodeId, ServiceNode service);
    	void registryService(String nodeId, List<ServiceNode> serviceList);
    	
    	void logout(String nodeId);
    }
    

    相应地,本人来给出上述接口的实现类:

    基于单项服务节点的 基本操作 实现 —— ServiceNodeAction类:

    package edu.youzg.about_service_discovery.core;
    
    import java.util.List;
    
    /**
     * 实现 操作单项服务节点 的基本action
     */
    public class ServiceNodeAction implements IServiceNodeAction {
    	
    	public ServiceNodeAction() {
    	}
    
    	@Override
    	public void registryService(String nodeId, ServiceNode service) {
    		ServicePool.addService(service.getService(), service.getNode());
    		NodeServicePool.registryService(nodeId, service);
    	}
    
    	@Override
    	public void registryService(String nodeId, List<ServiceNode> serviceList) {
    		if (serviceList == null || serviceList.isEmpty()) {
    			return;
    		}
    		for (ServiceNode service : serviceList) {
    			registryService(nodeId, service);
    		}
    	}
    
    	@Override
    	public void logout(String nodeId) {
    		List<ServiceNode> serviceList = NodeServicePool.logout(nodeId);
    		if (serviceList == null || serviceList.isEmpty()) {
    			return;
    		}
    		
    		for (ServiceNode service : serviceList) {
    			ServicePool.removeService(service.getService(), service.getNode());
    		}
    	}
    	
    }
    

    可能会出现这样的情况:
    一个服务器,提供多种服务
    那么,我们若是想要 增删等操作一个服务器 的时候,就会很麻烦
    因此,本人在这里给出一个 容器类:

    网络节点池 —— NodeServicePool类:

    package edu.youzg.about_service_discovery.core;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class NodeServicePool {
    	private static final Map<String, List<ServiceNode>> nodeServicePool
    			= new HashMap<String, List<ServiceNode>>();	// 以注册中心分配给该服务器的 id为键, 该服务器所提供的 服务列表 为值
    	
    	public NodeServicePool() {
    	}
    
    	/**
    	 * 通过 目标服务器的 id,从map中移除该服务器的信息
    	 * @param nodeId 目标服务器的 id
    	 * @return 该服务器的 服务列表
    	 */
    	public static List<ServiceNode> logout(String nodeId) {
    		return nodeServicePool.remove(nodeId);
    	}
    
    	/**
    	 *
    	 * @param nodeId 目标服务器的 id
    	 * @param serviceNode 该项服务的 ip、port 和 服务映射名
    	 */
    	public static void registryService(String nodeId, ServiceNode serviceNode) {
    		List<ServiceNode> serviceNodeList = nodeServicePool.get(nodeId);
    		if (serviceNodeList == null) {
    			serviceNodeList = new ArrayList<ServiceNode>();
    			nodeServicePool.put(nodeId, serviceNodeList);
    		}
    		if (!serviceNodeList.contains(serviceNode)) {
    			serviceNodeList.add(serviceNode);
    		}
    	}
    	
    }
    

    由于我们接下来的操作,会对客户端发送的请求,进行解析,
    然后将按照 处理该请求的服务,发送给客户端 提供这些服务的APP服务器的信息
    因此,本人现在来给出一个 容器,来按照功能 分别存储 每一类服务器

    服务池 —— ServicePool类:

    package edu.youzg.about_service_discovery.core;
    
    import edu.youzg.balance.core.INetNode;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class ServicePool {
    	private static final Map<String, List<INetNode>> servicePool
    			= new HashMap<>();	// 以 APP服务器的功能名 为键,APP服务器的节点信息 为值,存储的一个map
    
    	public ServicePool() {
    	}
    
    	/**
    	 * 增加一个 APP服务器
    	 * @param serviceName 目标APP服务器的 功能名
    	 * @param node 目标APP服务器的 节点信息
    	 */
    	public void addService(String serviceName, INetNode node) {
    		List<INetNode> serviceList = servicePool.get(serviceName);
    		if (serviceList == null) {
    			serviceList = new ArrayList<>();
    			servicePool.put(serviceName, serviceList);
    		}
    		if (!serviceList.contains(node)) {
    			serviceList.add(node);
    		}
    	}
    
    	/**
    	 * 删除一个 APP服务器
    	 * @param serviceName 目标APP服务器的 功能名
    	 * @param node 目标APP服务器的 节点信息
    	 */
    	public void removeService(String serviceName, INetNode node) {
    		List<INetNode> serviceList = servicePool.get(serviceName);
    		if (serviceList == null || !serviceList.contains(node)) {
    			return;
    		}
    		serviceList.remove(node);
    	}
    
    	/**
    	 * 从“服务器池”中获取一个 满足相应功能需求的 APP服务器的网络节点信息
    	 * @param serviceName
    	 * @return
    	 */
    	public List<INetNode> getService(String serviceName) {
    		return servicePool.get(serviceName);
    	}
    	
    }
    

    那么,所有的铺垫工作就做好了,
    现在本人来基本讲解,并基本实现下注册中心逻辑功能

    首先,我们要明确的几个知识点是:

    1. 客户端APP服务器 相对于 注册中心,都是客户端
    2. 由于我们需要持续获知当前APP服务器的状态
      因此,APP服务器注册中心 之间,必须是长连接
    3. 在现代已经成熟并被广泛使用的服务发现机制中,
      客户端注册中心之间的关系大致如下:
      注册中心保存着客户端的Socket,将其放在一个容器中,
      每隔一段时间,将当前的APP服务器的状态轮询广播给这个容器中的每一个客户端的Socket

    那么,本人先来完成APP服务器的基本功能:

    APP服务器

    由于 APP服务器 作为服务的提供者,
    需要完成的基本功能为:

    1. 注册其功能
    2. 下线(告知注册中心:本APP服务器停止提供任何服务)

    那么,依照上述思想,本人先来给出一个 用于规范APP服务器基本操作的接口:

    IServiceProvider接口:

    package edu.youzg.about_service_discovery.core;
    
    import java.util.List;
    
    public interface IServiceProvider {
    	void registryService(String nodeId, ServiceNode service);
    	void registryService(String nodeId, List<ServiceNode> serviceList);
    	
    	void logout(String nodeId);
    }
    

    ServiceProvider类:

    相应地,本人来给出这个接口的实现类

    package edu.youzg.about_service_discovery.core;
    
    import java.util.List;
    
    public class ServiceProvider implements IServiceProvider {
    
    	public ServiceProvider() {
    	}
    
    	/**
    	 * 注册服务
    	 * @param nodeId 提供该服务的APP服务器的id(由注册中心分配)
    	 * @param service 提供的服务
    	 */
    	@Override
    	public void registryService(String nodeId, ServiceNode service) {
    		ServicePool.addService(service.getService(), service.getNode());
    		NodeServicePool.registryService(nodeId, service);
    	}
    
    	/**
    	 * 注册服务列表
    	 * @param nodeId 提供该服务的APP服务器的id(由注册中心分配)
    	 * @param serviceList 提供的服务列表
    	 */
    	@Override
    	public void registryService(String nodeId, List<ServiceNode> serviceList) {
    		if (serviceList == null || serviceList.isEmpty()) {
    			return;
    		}
    		for (ServiceNode service : serviceList) {
    			registryService(nodeId, service);
    		}
    	}
    
    	/**
    	 * 注销 APP服务器
    	 * @param nodeId  要注销的APP服务器的id(由注册中心分配)
    	 */
    	@Override
    	public void logout(String nodeId) {
    		List<ServiceNode> serviceList = NodeServicePool.logout(nodeId);
    		if (serviceList == null || serviceList.isEmpty()) {
    			return;
    		}
    
    		for (ServiceNode service : serviceList) {
    			ServicePool.removeService(service.getService(), service.getNode());
    		}
    	}
    
    }
    

    那么,接下来,接下来就是客户端的实现:

    但是呢,由于写到这里,篇幅已经够长了
    而且本文的目的,不是完全实现 服务发现机制
    而是带领同学们 了解服务发现,熟悉服务发现的基本实现流程

    因此,在这里,本人仅初步实现下客户端与注册中心之间的基本任务

    客户端(若需完成请自行思考)

    思路:

    注册中心需要对客户端提供哪些服务呢?

    1. 客户端向注册中心进行“注册”(即“服务请求”)
    2. 刷新服务地址列表”功能,
      可以通过配置完成“服务发现框架”(例如:Netty框架)的启动
      也可以启动“后台”执行的“刷新服务地址列表”的功能
    3. 负载均衡”功能,
      可以通过配置实现“负载均衡(策略)”的激活

    IServiceCustomer接口:

    客户端与注册中心之间,最基本要实现的功能就是 获取指定服务APP链

    package edu.youzg.about_service_discovery.core;
    
    import edu.youzg.balance.core.INetNode;
    
    import java.util.List;
    
    public interface IServiceCustomer {
    	List<INetNode> getServiceAddressList(String service);
    }
    

    相应地,本人来给出实现类:

    ServiceCustomer类:

    package edu.youzg.about_service_discovery.core;
    
    import edu.youzg.balance.core.INetNode;
    
    import java.util.List;
    
    public class ServiceCustomer implements IServiceCustomer {
    
    	public ServiceCustomer() {
    	}
    	
    	@Override
    	public List<INetNode> getServiceAddressList(String service) {
    		return ServicePool.getService(service);
    	}
    
    }
    

    在这里本人再次声明
    此处的客户端实现的是短链接模式,与实际实现模式有很大区别
    而且很多的逻辑没有实现
    仅是为了后文的注册中心的基本代码而给出的 不完整代码


    注册中心

    铺垫了这么久,
    但是,本人还是要遗憾地告诉同学们:

    在这里,注册中心也是没有完全实现的
    仅是在模仿,且为了实现方便,模拟也不会跟现有的完全一样
    由于客户端的逻辑太过于复杂,因此在本文中只实现APP服务器与注册中心之间的基本逻辑

    “基本”实现注册中心 —— RegistryCenter类:

    package edu.youzg.about_service_discovery.core;
    
    import edu.youzg.netframework.core.INetListener;
    import edu.youzg.netframework.core.IServerAction;
    import edu.youzg.netframework.core.Server;
    import edu.youzg.netframework.core.ServerConversation;
    import edu.youzg.rmi.core.RMIFactory;
    import edu.youzg.rmi.core.RMIServer;
    import edu.youzg.util.IMecView;
    import edu.youzg.util.PropertiesParser;
    import edu.youzg.util.ViewTool;
    
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.FlowLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTextArea;
    import javax.swing.JTextField;
    import javax.swing.border.TitledBorder;
    
    public class RegistryCenter implements IMecView, INetListener {
    	private static final int VIEW_WIDTH = 600;
    	private static final int VIEW_HTIGHT = 400;
    	private static final int VIEW_MIN_WIDTH = 600;
    	private static final int VIEW_MIN_HTIGHT = 400;
    
    	private Server server;
    	private RMIServer serviceProvider;
    	private RMIServer serviceCustomer;
    
    	private static int serverPort;
    	private static int serviceProviderPort;
    	private static int serviceCustomerPort;
    
    	private JFrame jfrmView;
    	private JTextField jtxtCommand;
    	private JTextArea jtatMessage;
    
    	static {
    		readConfig();
    	}
    
    	public RegistryCenter() {
    	}
    
    	public static void readConfig(String configPath) {
    		PropertiesParser parser = new PropertiesParser();
    		parser.loadProperties(configPath);
    		serverPort = Integer.valueOf(parser.value("port"));
    		serviceProviderPort = Integer.valueOf(parser.value("provider_port"));
    		serviceCustomerPort = Integer.valueOf(parser.value("customer_port"));
    
    		RMIFactory.scanRMIMapping("src/RegistryCenterRMIMapping.xml");
    	}
    
    	public static void readConfig() {
    		readConfig("src/netcfg.properties");
    	}
    
    	@Override
    	public void reinit() {
    		jtatMessage.setFocusable(false);
    		jtatMessage.setEditable(false);
    		jtxtCommand.requestFocus();
    
    		initServer();
    	}
    
    	private void initServer() {
    		server = new Server();
    		server.setServerAction(new IServerAction() {
    			@Override
    			public void clientAbnormalDrop(ServerConversation client) {
    				new ServiceProvider().logout(client.getId());
    			}
    		});
    		server.setPort(serverPort);
    		server.addListener(this);
    
    		serviceProvider = new RMIServer();
    		serviceCustomer = new RMIServer();
    		serviceProvider.setPort(serviceProviderPort);
    		serviceCustomer.setPort(serviceCustomerPort);
    	}
    
    	private void closeView() {
    		if (server == null || server.isStartup()) {
    			ViewTool.showMessage(jfrmView, "服务器为null或服务器未宕机");
    			return;
    		}
    		jfrmView.dispose();
    		System.exit(0);
    	}
    
    	@Override
    	public void dealEvent() {
    		jfrmView.addWindowListener(new WindowAdapter() {
    			@Override
    			public void windowClosing(WindowEvent e) {
    				closeView();
    			}
    		});
    
    		jtxtCommand.addActionListener(new ActionListener() {
    			@Override
    			public void actionPerformed(ActionEvent e) {
    				String command = jtxtCommand.getText().trim();
    				if (command.length() > 0) {
    					dealCommand(command);
    				}
    				jtxtCommand.setText("");
    			}
    		});
    	}
    
    	private void startupServer() {
    		server.startup();
    		serviceProvider.startup();
    		serviceCustomer.startup();
    	}
    
    	private void shutdownServer() {
    		server.shutdown();
    		if (server.isStartup()) {
    			return;
    		}
    		serviceProvider.shutdown();
    		serviceCustomer.shutdown();
    	}
    
    	private void dealCommand(String command) {
    		if (command.equalsIgnoreCase("startup")
    				|| command.equalsIgnoreCase("st")) {
    			startupServer();
    		} else if (command.equalsIgnoreCase("shutdown")
    				|| command.equalsIgnoreCase("sd")) {
    			shutdownServer();
    		} else if (command.equalsIgnoreCase("exit")
    				|| command.equalsIgnoreCase("x")) {
    			closeView();
    		} else if (command.equalsIgnoreCase("fd")
    				|| command.equalsIgnoreCase("forcedown")) {
    			// TODO 注册中心强行宕机,这里的操作需要进一步讨论
    		}
    	}
    
    	@Override
    	public JFrame getFrame() {
    		return jfrmView;
    	}
    
    	@Override
    	public synchronized void dealMessage(String message) {
    		jtatMessage.append(message);
    		jtatMessage.append("
    ");
    		jtatMessage.setCaretPosition(jtatMessage.getText().length());
    	}
    
    	@Override
    	public void init() {
    		jfrmView = new JFrame("服务发现 - 注册中心 - 监视器");
    		jfrmView.setSize(VIEW_WIDTH, VIEW_HTIGHT);
    		jfrmView.setMinimumSize(new Dimension(VIEW_MIN_WIDTH, VIEW_MIN_HTIGHT));
    		jfrmView.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    		jfrmView.setLocationRelativeTo(null);
    		jfrmView.setLayout(new BorderLayout());
    
    		JLabel jlblCenterTopic = new JLabel("服务发现 - 注册中心 - 监视器", JLabel.CENTER);
    		jlblCenterTopic.setFont(topicFont);
    		jlblCenterTopic.setForeground(topicColor);
    		jfrmView.add(jlblCenterTopic, BorderLayout.NORTH);
    
    		JPanel jpnlLeftBlank = new JPanel();
    		jfrmView.add(jpnlLeftBlank, BorderLayout.WEST);
    		JPanel jpnlRightBlank = new JPanel();
    		jfrmView.add(jpnlRightBlank, BorderLayout.EAST);
    
    		jtatMessage = new JTextArea();
    		jtatMessage.setFont(normalFont);
    		JScrollPane jscpMessage = new JScrollPane(jtatMessage);
    		jfrmView.add(jscpMessage, BorderLayout.CENTER);
    		TitledBorder ttbdMessage = new TitledBorder("系统消息");
    		ttbdMessage.setTitleFont(normalFont);
    		ttbdMessage.setTitleColor(Color.red);
    		ttbdMessage.setTitlePosition(TitledBorder.ABOVE_TOP);
    		ttbdMessage.setTitleJustification(TitledBorder.CENTER);
    		jscpMessage.setBorder(ttbdMessage);
    
    		JPanel jpnlFooter = new JPanel(new FlowLayout(FlowLayout.CENTER));
    		jfrmView.add(jpnlFooter, BorderLayout.SOUTH);
    		JLabel jlblCommand = new JLabel("命令");
    		jlblCommand.setFont(normalFont);
    		jpnlFooter.add(jlblCommand);
    
    		jtxtCommand = new JTextField(30);
    		jtxtCommand.setFont(normalFont);
    		jpnlFooter.add(jtxtCommand);
    	}
    
    }
    

    上面的代码,引用了很多本人之前的小工具,
    若有疑问,请观看本人往期博文


    RMI配置文件 —— RegistryCenterRMIMapping.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <RmiMapping>
    	<mapping interface="com.mec.service_discovery.core.IServiceProvider"
    	class="com.mec.service_discovery.core.ServiceProvider"></mapping>
    </RmiMapping>
    

    初始化配置文件 —— netcfg.properties:

    port=54188
    provider_port=54189
    customer_port=54190
    max_client_count=20
    autoStartup=true
    
    

    结果展示

    那么,本人在这里仅展示下界面:
    界面 展示


  • 相关阅读:
    linux进程间通信之消息队列
    本地安装discuz x2.5(论坛站)程序
    缩小IO/CPU瓶颈:linux平台加速编译速度的几种方法
    php mcrypt
    Nginx工作原理和优化、漏洞。
    Linux下两种TCP网络服务器实现方式:循环服务&并发服务
    version `GLIBC_2.14' not found 解决方法.
    Flex Ant自动构建
    函数传指针和传引用
    JEECG 列表行编辑模式下实现文本的xheditor富文本框编辑器
  • 原文地址:https://www.cnblogs.com/codderYouzg/p/12915469.html
Copyright © 2011-2022 走看看