【java】手写微服务框架之服务发现

目录

    • 概述
      • 微服务
      • 服务发现
    • 框架基本思想
    • 通信层
      • Communication
      • NetNode
      • NetMessage
      • EMessageType
    • 注册中心
      • RegistrationCenter
      • CenterConversation
      • RoundRobin
      • IDealMessage-DealMessage
      • ServiceCache
      • Heartbeat
      • ConsumerAction
    • 服务提供者
      • Provider
      • ProviderConversation
      • ITimingQuery-TimingQuery
    • 服务消费者
      • Consumer
      • ClientCache
      • IConsumerAction
      • INodeStrategy
      • 负载均衡
        • CirculateNodeStrategy
        • HashNodeStrategy
    • 注册中心宕机
    • 补充

概述

微服务

微服务可以理解为一种架构风格,它将一个大型复杂的软件应用拆分成多个服务,每个服务专注于一个功能点,然后将业务流程拆分为几个不同的服务之间的组合,从而实现高内聚低耦合的效果;服务是指一个或者一组相对较小且独立的功能单元,是用户可以感知的最小功能集。举例来说,微信是一个大型应用,而朋友圈就是包含在其中的一个服务。

服务发现

1、什么是服务发现
在微服务体系结构中,所谓的服务发现就是用户可以通过服务标签,在注册中心找到可以提供正常服务的实例的网络地址(即ip地址和端口号)。这种根据服务标签发现服务的可用地址的机制就叫做服务发现。
2、服务发现的作用
在传统应用中,每个服务都被固定的部署在某个机器上,所以服务器的ip和端口号相对都是固定的,可以通过配置文件来修改。但是,在微服务体系中,由于服务的实例有可能出现增加、重启、宕机升级等情况,导致这些服务实例对应的网络地址是在动态变化的;若依旧采用修改配置文件的方式,那么无疑问题是复杂且难以解决的。
服务发现的作用就是服务消费者不用再对服务实例的物理地址硬性编码,只需知道服务标签就可以使用服务,而通过服务标签找到合适的服务实例则由内部实现。由于服务消费者不知道实际服务实例的物理地址,因此可以从可用服务池中添加或者移除服务实例。
服务发现还提高了容灾性,在传统应用中,若某服务器宕机,那么与这台服务器连接的所有客户端都将面临着无法使用服务的问题;而通过服务发现,服务消费者则可以在服务实例异常宕机后,再次寻找新的服务实例享受服务。

框架基本思想

在这里插入图片描述

  • 注册中心负责维护一张Map(key为服务标签,value为对应的服务提供者列表),并检测每个服务提供者是否宕机,宕机后将它从表中删除;注册中心支持服务与服务实例的注册和注销;当服务消费者申请某服务时,将该服务对应的全部服务提供者列表发送给对应服务消费者;
  • 服务提供者上线后主动连接注册中心,并注册本实例提供的所有服务,服务提供者与注册中心之间采用长连接;
  • 服务消费者上线后先通过RPC获取全部服务列表,然后由用户决定使用哪项服务,再向注册中心申请该服务对应的服务提供者列表,服务消费者通过本端的负载均衡策略选择合适的服务提供者申请服务;服务消费者还会定时向注册中心更新服务列表和服务对应的服务提供者列表;服务消费者与注册中心,服务消费者与服务提供者之间都采用RPC通信;

本篇博文使用的RPC框架是博主自己实现的,若有兴趣,可见RPC与RMI框架。

通信层

Communication

package com.dl.netWork;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 通信层
* 1、提供基本的收发信息的手段;
* 2、仅进行消息的收发,不进行任何逻辑的处理; * * @author dl * */ public class Communication { private Socket socket; private DataInputStream dis; private DataOutputStream dos; public Communication() { } public Communication(Socket socket, DataInputStream dis, DataOutputStream dos) { this.socket = socket; this.dis = dis; this.dos = dos; } public DataInputStream getDis() { return dis; } public void setDis(DataInputStream dis) { this.dis = dis; } public DataOutputStream getDos() { return dos; } public void setDos(DataOutputStream dos) { this.dos = dos; } public void setSocket(Socket socket) { this.socket = socket; } public Socket getSocket() { return socket; } public void close() { try { if (!socket.isClosed() && socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } finally { socket = null; } try { if (dis != null) { dis.close(); } } catch (IOException e) { e.printStackTrace(); } finally { dis = null; } try { if (dos != null) { dos.close(); } } catch (IOException e) { e.printStackTrace(); } finally { dos = null; } } /** * 将整合的消息发送给对端;
* 如果通信断裂,则从缓存中移除该socket(抛异常); * @param message * @throws IOException */ public void sendMessage(NetMessage message) throws IOException { dos.writeUTF(message.toString()); } /** * 判断是否有可读消息;如果有消息,将消息发送给线程执行;
* 如果通信断裂,则从缓存中移除该socket(抛异常); * @return * @throws IOException */ public String readMessage() throws IOException { return dis.readUTF(); } /** * 检测缓存中是否有可读的信息,并返回一个估计的字节长度 * @return * @throws IOException */ public boolean isReadSuccess() throws IOException { return dis.available() > 0; } }

NetNode

package com.dl.netWork;

/**
 * 结点类
* 用来保存通信结点的结点信息; * * @author dl * */ public class NetNode { private String ip; private int port; public NetNode() { } public NetNode(String ip, int port) { super(); this.ip = ip; this.port = port; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } @Override public String toString() { return "ip: " + ip + " port: " + port; } }

NetMessage

NetMessage类是注册中心与服务提供者之间的通信协议类,规范了两端发送消息的格式,可以提升系统的安全性。

package com.dl.netWork;

/**
 * 消息转换类
* 1、将消息类型与消息内容整合成字符串;
* 2、将收到的字符串转化为指定的对象; * * @author dl * */ public class NetMessage { private EMessageType type; private String action; private String paramater; public NetMessage() { } public NetMessage(String message) { String mess = message.toString(); int index = mess.indexOf(":"); this.type = EMessageType.valueOf(mess.substring(0, index)); mess = mess.substring(index+1); index = mess.indexOf(":"); String action = mess.substring(0, index); this.action = action.equals("") ? null : action; this.paramater = mess.substring(index+1); } public EMessageType getType() { return type; } public NetMessage setType(EMessageType type) { this.type = type; return this; } public String getAction() { return action; } public NetMessage setAction(String action) { this.action = action; return this; } public String getParamater() { return paramater; } public NetMessage setParamater(String paramater) { this.paramater = paramater; return this; } @Override public String toString() { StringBuffer str = new StringBuffer(); str.append(type + ":").append((action == null ? "" : action) + ":").append(paramater); return str.toString(); } }

EMessageType

package com.dl.netWork;

/**
 * 枚举类
* 通信协议的基石; * * @author dl * */ public enum EMessageType { REGISTRY, CANCELLATION, HEARTBEAT, }

注册中心

注册中心与服务提供者
【java】手写微服务框架之服务发现_第1张图片
服务提供者上线后先连接注册中心的服务器,注册中心将连接的服务提供者打包成CenterConversation对象,放在轮询池中,在RoundRobin类中开启一个线程去遍历这个轮询池,判断有无消息;在Communication类的最后一个方法中有dis.available(),这个方法是注册中心实现遍历轮询的根本,它可以检测对端是否发送了信息,而避免了dis.read()时若没有消息则造成线程的阻塞;这个方案看似完美,但也有缺陷,后面将会讲到。
在以前的C/S模式中,两端采用长连接,则服务器端需要开启与连接的客户端数量相同的线程去保持通信,当客户端数量急剧增大时,服务器需要开启的线程也会急剧增多,这样无疑会给服务器造成巨大的压力,造成服务器的响应变慢;而采用遍历轮询的方式则可以减少线程的开启数量,虽然可能会造成处理信息时延,但由于服务提供者注册、注销服务的行为并不频繁,并且服务注册、注销消耗的时间与服务被消费者使用的时间比起来非常小,所以就算造成一点时延也是可以接受的,毕竟减少大量的线程获得的收益很大。
这里不得不说的一个问题是,若连接注册中心的服务提供者数量非常大,那么用一个线程来遍历轮询就可能会造成处理信息的巨大时延;解决办法是,注册中心采用分组轮询的方法,深入面向对象思想,使用多个RoundRobin对象去遍历轮询。举例来说,将每一千个服务提供者归为一组,放进一个轮询池中去轮询,而这个数字完全可以由用户配置,也可以在系统中统计一段时间的服务实例数量的变化规律,做到动态调控。

注册中心与服务消费者
【java】手写微服务框架之服务发现_第2张图片

RegistrationCenter

RegistrationCenter 是注册中心的最高层,也是注册中心的核心类,它负责服务器等的开启与关闭。

package com.dl.registry.core;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import com.dl.rpc.server.RPCServer;
import com.parser_reflect.util.PropertiesParser;
import com.util.ThreadPoolFactory;

/**
 * 注册中心
* 1、开启注册中心服务器和RPC服务器;
* 2、开启心跳检测,开启线程池;
* 3、两个服务器的ip、port都可配置,心跳时延也可配置;
* 4、开启侦听连接线程,开启轮询线程;
* 5、支持对所有的开启项进行关闭; * * @author dl * */ public class RegistrationCenter implements Runnable { public static final int DEFAULT_PORT = 55550; private ServerSocket centerServer; private RPCServer rpcServer; private int port; private IDealMessage dealMessage; private RoundRobin roundRobin; private Heartbeat heartbeat; private volatile boolean goon; public RegistrationCenter() { this.port = DEFAULT_PORT; heartbeat = new Heartbeat(); rpcServer = new RPCServer(); readConfig("/SDConfig.properties"); } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public void setDealMessage(IDealMessage dealMessage) { this.dealMessage = dealMessage; } public void readConfig(String path) { String portStr = PropertiesParser.findElement("port"); try { if (portStr != null && !portStr.equals("")) { int port = Integer.valueOf(portStr); if (port > 0 && port < 65536) { this.port = port; } } } catch (Exception e) { e.printStackTrace(); } String timeStr = PropertiesParser.findElement("heartBeatTime"); try { if (timeStr != null && !portStr.equals("")) { int heartBeatTime = Integer.valueOf(timeStr); if (heartBeatTime > 0 && heartBeatTime < 65536) { heartbeat.setTime(heartBeatTime); } } } catch (Exception e) { e.printStackTrace(); } } public void startup() { try { roundRobin = new RoundRobin(); roundRobin.setDealMessage(dealMessage); centerServer = new ServerSocket(port); goon = true; ThreadPoolFactory.execute(new Thread(this)); ThreadPoolFactory.execute(new Thread(roundRobin)); heartbeat.startHeartBeat(); rpcServer.startup(); } catch (IOException e) { e.printStackTrace(); } } public void shutdown() { goon = false; roundRobin.stopRound(); try { if (!centerServer.isClosed() && centerServer != null) { centerServer.close(); } } catch (IOException e) { e.printStackTrace(); } finally { centerServer = null; } ThreadPoolFactory.shutdown(false); heartbeat.shutdown(); rpcServer.shutdown(); } /** * 服务器连接侦听线程,侦听到有结点连接则打包成CenterConversation放进轮询池 */ @Override public void run() { while (goon) { try { Socket socket = centerServer.accept(); roundRobin.addCommunication(socket); } catch (IOException e) { e.printStackTrace(); } } } }

CenterConversation

package com.dl.registry.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

import com.util.ThreadPoolFactory;

/**
 * 注册中心会话层
* 1、保存通信信道;
* 2、收到消息时,开启新的线程进行处理;
* * @author dl * */ public class CenterConversation extends Communication { private IDealMessage dealMessage; private NetNode netNode; private RoundRobin roundRobin; private volatile boolean success; public CenterConversation(Socket socket, DataInputStream dis, DataOutputStream dos) { super(socket, dis, dos); String ip = socket.getInetAddress().getHostAddress(); int port = socket.getPort(); NetNode netNode = new NetNode(); netNode.setIp(ip); netNode.setPort(port); this.netNode = netNode; } public NetNode getNetNode() { return netNode; } public void setRoundRobin(RoundRobin roundRobin) { this.roundRobin = roundRobin; } public void setDealMessage(IDealMessage dealMessage) { this.dealMessage = dealMessage; } public void dealMessage(String message) { dealMessage.dealMessage(netNode, message); } /* * 当线程多次isReadSuccess成功,就会开启多个线程 * 而只有一个线程可以读到消息,其余的线程会阻塞 * 因此需要success标志来加以判断 */ public void judgeRead() throws IOException { // 这里可以通过标志来判断是否有任务 if (isReadSuccess() && !success) { success = true; InnerDealMessage inner = new InnerDealMessage(); ThreadPoolFactory.execute(new Thread(inner)); } } /** * 开启线程处理对端的消息 * @author dl * */ private class InnerDealMessage implements Runnable { public InnerDealMessage() { } @Override public void run() { String message; try { message = readMessage(); dealMessage(message); success = false; } catch (IOException e) { roundRobin.removeConversation(CenterConversation.this); } } } }

RoundRobin

package com.dl.registry.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 轮询
 * 1、将所有连接到注册中心的服务器放在列表中;
* 2、轮询列表中的所有网络结点,依次判断是否有消息;
* 3、若有消息,则开启一个新的线程去处理消息;
* 4、若某结点宕机,则删除该结点; * * @author dl * */ public class RoundRobin implements Runnable { // 线程安全的列表 private static final List coPool = new CopyOnWriteArrayList<>(); private IDealMessage dealMessage; private ServiceCache serviceCache; private volatile boolean goon; public RoundRobin() { serviceCache = new ServiceCache(); goon = true; } public void setDealMessage(IDealMessage dealMessage) { this.dealMessage = dealMessage; } public List getCoList() { return new ArrayList(coPool); } /** * 通过传递进来的socket来打包形成一个CenterConversation对象并放入池子中 * @param socket */ public void addCommunication(Socket socket) { try { DataInputStream dis = new DataInputStream(socket.getInputStream()); DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); CenterConversation conversation = new CenterConversation(socket, dis, dos); conversation.setDealMessage(dealMessage); conversation.setRoundRobin(this); coPool.add(conversation); } catch (IOException e) { e.printStackTrace(); } } void removeConversation(CenterConversation conversation) { coPool.remove(conversation); serviceCache.remove(conversation.getNetNode()); } public void stopRound() { goon = false; } @Override public void run() { while (goon) { Iterator iterator = coPool.iterator(); while (iterator.hasNext()) { CenterConversation co = iterator.next(); try { co.judgeRead(); } catch (IOException e) { // 通信断裂,删除该服务器 removeConversation(co); } } } } }

IDealMessage-DealMessage

/**
 * 处理消息的接口
 * @author dl
 *
 */
public interface IDealMessage {
	void dealMessage(NetNode netNode, String message);
}
package com.dl.registry.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;

import com.google.gson.reflect.TypeToken;
import com.util.ArgumentMaker;

/**
 * 接口实现类
* 1、处理服务器发送的消息;
* 2、通过反射机制来避免多个switch-case;
* 3、使用反射机制的前提是要使用特定规则的方法名和传递相同的参数; * * @author dl * */ public class DealMessage implements IDealMessage { private ServiceCache serviceCache; private Type type = new TypeToken>() {}.getType(); public DealMessage() { serviceCache = new ServiceCache(); } @Override public void dealMessage(NetNode netNode, String message) { NetMessage netMessage = new NetMessage(message); Class klass = DealMessage.class; String type = netMessage.getType().toString(); String methodName = "deal" + type.substring(0, 1) + type.substring(1).toLowerCase(); try { Method method = klass.getDeclaredMethod(methodName, new Class[]{NetNode.class, NetMessage.class}); method.invoke(this, netNode, netMessage); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } @SuppressWarnings("unused") private void dealRegistry(NetNode netNode, NetMessage netMessage) { String para = netMessage.getParamater(); List tagList = ArgumentMaker.gson.fromJson(para, type); for (String registryTag : tagList) { serviceCache.put(registryTag, netNode); } } @SuppressWarnings("unused") private void dealCancellation(NetNode netNode, NetMessage netMessage) { String para = netMessage.getParamater(); List tagList = ArgumentMaker.gson.fromJson(para, type); for (String registryTag : tagList) { serviceCache.remove(registryTag, netNode); } } }

ServiceCache

package com.dl.registry.core;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.dl.netWork.NetNode;
import com.util.ThreadPoolFactory;

/**
 * 服务缓存类
* 1、对注册的服务提供者和服务列表进行缓存;
* 2、将信息缓存到以服务标签为键,服务提供者列表为值的map中;
* 3、支持服务和服务提供者的增删; * * @author dl * */ public class ServiceCache { private static final ConcurrentHashMap> netPool; static { netPool = new ConcurrentHashMap<>(); } public void put(String registryTag, NetNode netNode) { System.out.println(netNode); List nodeList = netPool.get(registryTag); if (nodeList == null) { nodeList = new ArrayList(); netPool.put(registryTag, nodeList); } nodeList.add(netNode); } public List getAllServiceTags() { return new ArrayList<>(netPool.keySet()); } public void remove(String serviceTag, NetNode netNode) { ThreadPoolFactory.execute(new Thread(new InnerRemove(serviceTag, netNode))); } public void remove(NetNode netNode) { ThreadPoolFactory.execute(new Thread(new InnerRemove(null, netNode))); } public List getNodeList(String serviceTag) { return netPool.get(serviceTag); } /** * 内部删除类
* 1、删除某服务对应的某结点;
* 2、删除所有服务中的某结点;
* 3、由于删除的过程比较耗时,所有放在线程中删除是值得的; * @author dl * */ private class InnerRemove implements Runnable { private String serviceTag; private NetNode netNode; public InnerRemove(String serviceTag, NetNode netNode) { this.serviceTag = serviceTag; this.netNode = netNode; } @Override public void run() { if (serviceTag != null) { List tagList = netPool.get(serviceTag); tagList.remove(netNode); } else { Set set = netPool.keySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()) { String tag = (String) iterator.next(); List nodeList = netPool.get(tag); if (nodeList != null) { nodeList.remove(netNode); if (nodeList.isEmpty()) { iterator.remove(); } } } } } } }

Heartbeat

Heartbeat的产生就是解决前面Communication类中所说的问题,由于dis.available()方法无法检测对端掉线,因此注册中心采用了心跳检测的方式去检测服务提供者是否宕机。

package com.dl.registry.core;

import java.io.IOException;
import java.util.List;

import com.timer.util.Timer;
import com.timer.util.UserAction;

/**
 * 心跳检测
* 1、由于available()无法检测对端异常掉线,因此使用心跳检测;
* 2、定时向服务提供者列表发送一条消息,判断对端是否在线;
* 3、时延可设置; * * @author dl * */ public class Heartbeat { public static final int DEFAULTTIME = 60*60*1000; private Timer timer; private int heartBeatTime; private RoundRobin roundRobin; public Heartbeat() { roundRobin = new RoundRobin(); heartBeatTime = DEFAULTTIME; } public void setTime(int heartBeatTime) { this.heartBeatTime = heartBeatTime; } public void startHeartBeat() { timer = new Timer(heartBeatTime); timer.setUserAction(new UserAction() { @Override public void userAction() { List conList = roundRobin.getCoList(); NetMessage netMessage = new NetMessage(); netMessage.setType(EMessageType.HEARTBEAT); for (CenterConversation centerConversation : conList) { try { centerConversation.sendMessage(netMessage); } catch (IOException e) { // 对端掉线 roundRobin.removeConversation(centerConversation); } } } }); try { timer.startThread(); } catch (Exception e) { e.printStackTrace(); } } public void shutdown() { timer.stopThread(); } }

ConsumerAction

ConsumerAction是注册中心与服务消费者之间进行RPC通信的类

package com.dl.registry.core;

import java.util.List;

import com.dl.consumer.core.IConsumerAction;
import com.dl.netWork.NetNode;
import com.dl.rpc.server.ProxyAnntotation;

@ProxyAnntotation(interfaces = { IConsumerAction.class })
public class ConsumerAction implements IConsumerAction {
	private ServiceCache serviceCache;
	
	public ConsumerAction() {
		serviceCache = new ServiceCache();
	}
	
	
	@Override
	public List updataServiceTags() {
		return serviceCache.getAllServiceTags();
	}

	@Override
	public List updataNews(String serviceTag) {
		return serviceCache.getNodeList(serviceTag);
	}

}

服务提供者

Provider

Provider类是服务提供端的核心类,提供注册、注销服务的方法。

package com.dl.provider.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.List;

import com.parser_reflect.util.PropertiesParser;

/**
 * 服务提供者
* 1、向注册中心注册/注销服务;
* 2、注册中心宕机,采取定时重连的方式,直到连接成功为止;
* 3、支持ip、port的配置; * * @author dl * */ public class Provider { public static final String DEFAULT_IP = "127.0.0.1"; public static final int DEFAULT_PORT = 55550; private Socket client; private int port; private String ip; private ITimingQuery timingQuery; private ProviderConversation conversation; public Provider() { this.ip = DEFAULT_IP; this.port = DEFAULT_PORT; } public int getPort() { return port; } public void setTimingQuery(ITimingQuery timingQuery) { this.timingQuery = timingQuery; } public void setPort(int port) { this.port = port; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public void readConfig(String path) { PropertiesParser.load(path); String ip = PropertiesParser.findElement("ip"); if (ip != null && !ip.equals("")) { this.ip = ip; } String portStr = PropertiesParser.findElement("port"); try { if (portStr != null && !portStr.equals("")) { int port = Integer.valueOf(portStr); if (port > 0 && port < 65536) { this.port = port; } } } catch (Exception e) { e.printStackTrace(); } } public boolean startup() { return startup(false); } boolean startup(boolean timing) { try { client = new Socket(ip, port); DataInputStream dis = new DataInputStream(client.getInputStream()); DataOutputStream dos = new DataOutputStream(client.getOutputStream()); conversation = new ProviderConversation(client, dis, dos); conversation.setTimingQuery(timingQuery); conversation.setProvider(this); new Thread(conversation).start(); } catch (Exception e) { if (!timing) { timingQuery.dealTimingQuery(this); } return false; } return true; } public void shutdown() { conversation.shutdown(); } /** * 注册服务,由高层负责,可能会发送失败;
* 失败后告知上一层; * @param serviceList * @return */ public boolean registryService(List serviceList) { return conversation.registryService(serviceList); } /** * 注销服务,由高层负责,可能会发送失败;
* 失败后告知上一层; * @param serviceList * @return */ public boolean cancellationService(List serviceList) { return conversation.cancellationService(serviceList); } }

ProviderConversation

package com.dl.provider.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.List;

import com.dl.netWork.Communication;
import com.dl.netWork.EMessageType;
import com.dl.netWork.NetMessage;
import com.util.ArgumentMaker;

/**
 * 服务提供者的会话层
* 1、与注册中心会话层通信;
* 2、提供与注册中心会话层类似的功能;
* 3、支持注册中心宕机后的操作; * * @author dl * */ public class ProviderConversation extends Communication implements Runnable { private ITimingQuery timingQuery; private Provider provider; private volatile boolean goon; public ProviderConversation() { goon = true; } public ProviderConversation(Socket client, DataInputStream dis, DataOutputStream dos) { super(client, dis, dos); goon = true; } public void setProvider(Provider provider) { this.provider = provider; } public void setTimingQuery(ITimingQuery timingQuery) { this.timingQuery = timingQuery; } private String serviceMessage(List serviceList) { return ArgumentMaker.gson.toJson(serviceList); } public boolean registryService(List serviceList) { NetMessage netMessage = new NetMessage() .setType(EMessageType.REGISTRY) .setParamater(serviceMessage(serviceList)); try { sendMessage(netMessage); } catch (IOException e) { // 通信中断 timingQuery.dealTimingQuery(provider); return false; } return true; } public boolean cancellationService(List serviceList) { NetMessage netMessage = new NetMessage() .setType(EMessageType.CANCELLATION) .setParamater(serviceMessage(serviceList)); try { sendMessage(netMessage); } catch (IOException e) { // 通信中断 timingQuery.dealTimingQuery(provider); return false; } return true; } public void shutdown() { goon = false; close(); } // 保留方法 public void dealMessage(String message) { } @Override public void run() { while (goon) { try { String message = readMessage(); dealMessage(message); } catch (IOException e) { timingQuery.dealTimingQuery(provider); } } } }

ITimingQuery-TimingQuery

package com.dl.provider.core;

/**
 * 处理注册中心宕机的接口
 * 
 * @author dl
 *
 */
public interface ITimingQuery {
	void dealTimingQuery(Provider provider);
}
package com.dl.provider.core;

/**
 * 接口实现类
* 当注册中心宕机后,将使用本类去不断连接注册中心; * @author dl * */ public class TimingQuery implements ITimingQuery { @Override public void dealTimingQuery(Provider provider) { provider.startup(); } }

服务消费者

Consumer

package com.dl.consumer.core;

import java.util.List;

import com.dl.netWork.NetNode;
import com.dl.rpc.client.ClientProxy;
import com.dl.rpc.client.RPCClient;
import com.parser_reflect.util.PropertiesParser;
import com.timer.util.Timer;
import com.timer.util.UserAction;

/**
 * 服务消费者
* 1、上线即向注册中心申请全部服务列表;
* 2、定时从注册中心更新服务列表和对应服务提供者列表;
* 3、通过注册中心给的服务提供者列表进行负载均衡;
* 4、注册中心宕机,采取定时重连的方式,直到连接成功为止; * * @author dl * */ public class Consumer { private RPCClient rpcClient; private ClientCache cache; private Timer timer; private int delayTime; private IConsumerAction action; private INodeStrategy strategy; public Consumer() { rpcClient = new RPCClient(); ClientProxy clientProxy = new ClientProxy(); clientProxy.setClient(rpcClient); action = clientProxy.jdkProxy(IConsumerAction.class); cache = new ClientCache(); } public void setStrategy(INodeStrategy strategy) { this.strategy = strategy; } public void readConfig(String path) { PropertiesParser.load(path); String delayTimeStr = PropertiesParser.findElement("delayTime"); try { if (delayTimeStr != null && !delayTimeStr.equals("")) { int delayTime = Integer.valueOf(delayTimeStr); if (delayTime > 0 && delayTime < 65536) { this.delayTime = delayTime; } } } catch (Exception e) { e.printStackTrace(); } } public void startup() { try { cache.addServiceTags(action.updataServiceTags()); startTimeUpdata(); } catch(Exception e) { // 通信中断,定时更新起到了定时连接的功能 } } public void shutdown() { if (timer != null) { timer.stopThread(); } } /** * 通过服务标签得到对应的服务提供者
* 1、先从本地缓存中查询结点列表;
* 2、如果本地缓存为空,则向注册中心申请;
* 3、通过消费端的负载均衡策略找到合适的服务提供者;
* 4、要求用户必须设置INodeStrategy对象; * @param serviceTag 服务标签 * @return */ public NetNode getServer(String serviceTag) { List nodeList = cache.getServerList(serviceTag); if (nodeList == null) { nodeList = action.updataNews(serviceTag); if (nodeList == null) { return null; } cache.addService(serviceTag, nodeList); } return strategy.ServerBalance(this, serviceTag, nodeList); } /** * 开启定时更新服务和服务提供者列表的方法 */ public void startTimeUpdata() { try { timer = new Timer(); if (delayTime > 0) { timer.setDelayTime(delayTime); } timer.setUserAction(new UserAction() { @Override public void userAction() { List tagList = action.updataServiceTags(); cache.addServiceTags(tagList); List nodeList; for (String serviceTag : tagList) { nodeList = action.updataNews(serviceTag); cache.addService(serviceTag, nodeList); } } }); timer.startThread(); } catch (Exception e) { e.printStackTrace(); } } }

ClientCache

package com.dl.consumer.core;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.dl.netWork.NetNode;

/**
 * 服务消费者缓存器
* 1、可对申请到的服务和服务提供者列表进行缓存;
* 2、与注册中心的缓存器功能一致;
* 3、服务消费者缓存器的存在避免了注册中心宕机后无法使用服务的问题; * * @author dl * */ public class ClientCache { private static final Map> netPool = new ConcurrentHashMap<>(); public ClientCache() { } public void addServiceTags(List serviceTags) { netPool.clear(); for (String string : serviceTags) { netPool.put(string, null); } } public void addService(String serviceTag, List nodeList) { netPool.put(serviceTag, nodeList); } public List getServerList(String serviceTag) { return netPool.get(serviceTag); } public Set keySet() { return netPool.keySet(); } }

IConsumerAction

package com.dl.consumer.core;

import java.util.List;

import com.dl.netWork.NetNode;

/**
 * 提供RPC远程方法调用的接口
 * 
 * @author dl
 *
 */
public interface IConsumerAction {
	List updataServiceTags();
	List updataNews(String serviceTag);
}

INodeStrategy

package com.dl.consumer.core;

import java.util.List;

import com.dl.netWork.NetNode;

/**
 * 结点选择策略接口
* 1、当从注册中心获取服务器列表后,消费者需要自行选择结点;
* 2、为避免单个服务器负载压力过大的问题,需要进行负载均衡;
* 3、负载均衡实现的手法较多,因此给用户留了接口; * * @author dl * */ public interface INodeStrategy { NetNode ServerBalance(Consumer consumer, String serviceTag, List nodeList); }

负载均衡

对于服务器负载均衡问题的解决办法较多,博主这里提供两种方案作为参考

CirculateNodeStrategy

package com.dl.consumer.core;

import java.util.List;

import com.dl.netWork.NetNode;

/**
 * 相同服务循环使用服务器
* 优点:结点选择较为简单
* 缺点:当大量的客户端结点选择同步时,依然会造成服务器的过载 * * @author dl * */ public class CirculateNodeStrategy implements INodeStrategy { private static int SerialNumber; public CirculateNodeStrategy() { SerialNumber = 0; } @Override public NetNode ServerBalance(Consumer consumer, String serviceTag, List nodeList) { if (SerialNumber >= nodeList.size()) { SerialNumber = 0; } return nodeList.get(SerialNumber++); } }

HashNodeStrategy

package com.dl.consumer.core;

import java.util.List;

import com.dl.netWork.NetNode;

/**
 * 通过哈希算法来实现负载均衡
* 优点:使用散列算法可以一定程度的分散客户端的访问
* 缺点:若散列算法性能较差,负载均衡的性能也会很差 * * @author dl * */ public class HashNodeStrategy implements INodeStrategy { @Override public NetNode ServerBalance(Consumer consumer, String serviceTag, List nodeList) { int tmp = consumer.hashCode() + serviceTag.hashCode(); int hashCode = Math.abs(tmp ^ (tmp >>> 16)); int size = nodeList.size(); return nodeList.get(hashCode % (size - 1)); } }

除了上述两种方案,还有一种比较好的方案就是,服务消费端采用心跳检测的方式,统计每个服务提供者的响应时间,以辨别每个服务器的健康状况,选择响应时间最小的服务器作为结点返回;心跳检测最好在消费端运行过程中去做,而不是在用户申请服务的时候去挨个检测,在检测过程中,若多个服务器掉线,那就会造成用户使用服务的响应时间过长,造成用户的体验很差。

注册中心宕机

通过上面对于服务发现的解析,可以发现,在服务发现框架中最重要、最核心的也就是注册中心了,如果注册中心宕机该怎么办?
最简单粗暴的方式就是实现注册中心的热插拔,若注册中心宕机,服务提供者和服务消费者会不断地连接注册中心,直到连接成功为止;消费端缓存的有注册中心宕机前的最后一次更新信息,只要这些服务提供者没有全部宕机,那么消费者就依然可以享受服务,只是无法连接最新上线的服务提供者和享受最新发布的服务。当注册中心上线后,服务提供者就可以正常的与注册中心通信,进行服务的注册和注销,而服务消费者自带的定时更新功能也会更新到注册中心最新的信息。博主的服务发现框架使用的就是这种方案,虽然简单但也能解决问题。
在一些大型应用的网络结构中,通常注册中心不止一个,若采用集群方式来部署注册中心,那么当任意一台注册中心宕机后,服务提供者和服务消费者可以自动切换至另一台,若注册中心全部宕机后,那就只能采取热插拨的方式了。

补充

由于实现服务发现的手法较多,所以博主还实现了三端之间都采用RPC通信的服务发现框架,若有兴趣,可在评论区留言。

你可能感兴趣的:(Java)