微服务框架之服务发现
- 概述
- 微服务
- 服务发现
- 基本思想
- 通信层
- Communication
- NetMessage
- ENetCommand
- DealNetMessage
- 注册中心
- 注册中心与服务提供者
- 注册中心与服务消费者
- RegistryCenter
- RegistryProviderConversation
- NetNodePollPool
- ServiceMap
- IRegistryCenterService
- RegistryCenterService
- HeartBeat
- 服务提供者
- Provider
- ProviderRegisteryConversation
- IProviderService
- 服务消费者
- Consumer
- ConsumerCache
- INodeSelectStrategy
- 负载均衡
- HashBalance
- RandomBalance
- 一些补充
概述
微服务
关于微服务可以理解为一种架构,也可以理解为一种设计理念。对于一个复杂的软件系统,将其拆分为几个服务模块,每个模块的功能独立,每个专注于某一功能点。从而将业务流程拆分为几个服务模块的组合。微服务的难点在于服务的划分,各家对服务的划分也是各执其词,但按照求同存异来看,按业务来划分是肯定的,比如参考业务的关联程度或者职责范围来划分。这里不做过多描述。有兴趣者可参见
如何给女朋友解释什么是微服务。
服务发现
在微服务体系中,服务发现指的就是用户可以通过服务标识,在注册中心找到提供对应的服务的实例的网络地址(ip+port)。
那么通过服务发现能干什么呢?传统项目中,每个服务被直接部署到对应的服务器上,服务器实例的ip和port都是相对固定的,那么,当需要变化的时候,就需要去修改配置文件。然而,在微服务架构中,一种服务往往不是对应着只有一台服务器,因此服务实例是在动态变化的,如果是采取配置文件的方式的话,那么就显得很复杂了。
这个时候就能体现出服务发现的作用了,用户只用通过服务标识去可以找到对应的服务实例,至于如何找到一个合适的服务实例则由内部实现。而且,通过服务发现,还提高了容灾性。在传统的当某个服务器宕机,其所开启的所有服务都不可用,使得所有享受这台服务器提供服务的客户端都面临无法使用服务器所提供的服务,但通过服务发现,当发现问题时,可以找寻其它提供相同服务的服务器实例继续消费服务。还有一个特别的特点就是,注册中心如果宕机,并不影响已注册服务器和客户端的APP业务。
因此,结合各方面的优点,服务发现有这些妙用:
1、服务器热插拔;
2、服务器在线升级;
3、容错机制;
4、负载均衡。
基本思想
注册中心:负责维护一张表Map<key为服务标识,value为提供该服务的实例列表>,包含检测每个服务实例是否正常工作,如果不正常(宕机等情况),则将其从列表中删除;注册中心对服务提供者提供了服务实例的注册与注销,对服务消费者提供了可以根据服务标识获取该服务的服务实例列表。
服务提供者:上线后连接注册中心,并选择要注册的服务进行注册。采用长连接通信。
服务消费者:上线后,先去注册中心获取所有的服务列表,选择要消费的服务,然后再向注册中心申请该服务对应的服务实例列表,服务消费者通过自己的负载均衡策略选择合适的服务实例消费服务。服务消费者也会定时向注册中心更新服务列表以及服务实例(服务提供者实例)列表。服务消费者与注册中心、服务消费者与服务提供者之间都采用RPC通信,本博文所描述的微服务服务发现框架是基于博主自己实现的RPC与RMI框架,读者有兴趣者自取。
通信层
短连接的通信在实现的RPC框架里面有所实现,读者可自行查看。这里主要说明服务提供者和注册中心的通信。
Communication
该类是服务提供者和注册中心的会话层对应类的父类。
package com.fwfx.message;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
/**
* 通信层(抽象类)
* 1.提供了消息接受和发送的基本功能。
* 2.提供了消息处理的抽象方法,由具体的子类来实现。
* @author dx
*
*/
public abstract class Communication {
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
protected Communication(Socket socket) throws IOException {
this.socket = socket;
this.dis = new DataInputStream(socket.getInputStream());
this.dos = new DataOutputStream(socket.getOutputStream());
}
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;
}
protected abstract void dealNetMessage(NetMessage message);//处理对端消息
/**
* 发送消息
* @param netMessage
* @throws IOException
*/
public void sendMessage(NetMessage netMessage) throws IOException {
dos.writeUTF(netMessage.toString());
}
/**
* 读取消息
* @return
* @throws IOException
*/
public String readMessage() throws IOException {
return dis.readUTF();
}
/**
* available检测是否有可读的消息,如果有,返回1,如果没有,返回值为0
* @return
* @throws IOException
*/
protected boolean isReadable() throws IOException {
return dis.available()>0;
}
/**
* 关闭连接
*/
protected void close() {
try {
if (this.dis != null) {
this.dis.close();
}
} catch (IOException e) {
} finally {
this.dis = null;
}
try {
if (this.dos != null) {
this.dos.close();
}
} catch (IOException e) {
} finally {
this.dos = null;
}
try {
if (this.socket != null && !this.socket.isClosed()) {
this.socket.close();
}
} catch (IOException e) {
} finally {
this.socket = null;
}
}
}
NetMessage
该类是服务提供者和注册中心的通信协议类,规范了交互的信息格式,增强系统安全性。
package com.fwfx.message;
/**
* 消息转换类
* 1.将消息类别、内容等整合成字符串;
* 2.将字符串转换成消息对象;
* @author dx
*
*/
public class NetMessage {
private ENetCommand command;
private String action;
private String para;
public NetMessage() {
}
public NetMessage(String message) {
int dotIndex;
dotIndex = message.indexOf('.');
if (dotIndex < 0) {
return;
}
String str = message.substring(0, dotIndex);
this.command = ENetCommand.valueOf(str);
message = message.substring(dotIndex + 1);
dotIndex = message.indexOf('.');
if (dotIndex < 0) {
this.command = null;
return;
}
str = message.substring(0, dotIndex);
this.action = str.equals(" ") ? null : str;
message = message.substring(dotIndex + 1);
this.para = message;
}
public ENetCommand getCommand() {
return command;
}
public NetMessage setCommand(ENetCommand command) {
this.command = command;
return this;
}
public String getAction() {
return action;
}
public NetMessage setAction(String action) {
this.action = action;
return this;
}
public String getPara() {
return para;
}
public NetMessage setPara(String para) {
this.para = para;
return this;
}
@Override
public String toString() {
StringBuffer result = new StringBuffer(command.name());
result.append('.');
result.append(action == null ? " " : action).append('.');
result.append(para);
return result.toString();
}
}
ENetCommand
该类是服务提供者和注册中心的通信协议类的通信消息的类别部分,针对不同的消息,做出不同的处理。
package com.fwfx.message;
/**
* 枚举类
* 通信协议的消息类别部分,也可叫做信令
* @author dx
*
*/
public enum ENetCommand {
REGISTRY_SERVICE,//注册单个服务
REGISTRY_SERVICES,//注册多个服务
LOG_OUT,//注销所有服务
LOG_OUT_SERVICE,//注销一个服务
HEARTBEAT,//心跳检测
}
DealNetMessage
该类通过反射来避免多个switch-case,前提是对应着不同的消息类别的处理方法需要遵守一定的规则。
package com.fwfx.message;
import java.lang.reflect.Method;
/**
* 消息处理类
* @author dx
*
*/
public class DealNetMessage{
public DealNetMessage() {
}
/**
* 根据传送过来的命令参数,查找对应的方法
* @param command
* @return
*/
private static String getMethodName(String command) {
StringBuffer result = new StringBuffer("deal");
String[] words = command.split("_");
int wordCount = words.length;
for (int i = 0; i < wordCount; i++) {
result.append(words[i].substring(0, 0+1).toUpperCase());
result.append(words[i].substring(1).toLowerCase());
}
return result.toString();
}
/**
* 反射执行
* @param object
* @param message
*/
public static void dealCommand(Object object, NetMessage message) {
Class<?> klass = object.getClass();
String methodName = getMethodName(message.getCommand().name());
Method method;
try {
method = klass.getMethod(methodName, NetMessage.class);
method.invoke(object, message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注册中心
注册中心与服务提供者
服务提供者发起连接请求后,注册中心将其封装成RegistryProviderConversation类加入到轮询池里面,然后在轮询池(NetNodePollPool)里面开启一个线程遍历轮询池,检测服务提供者是否发送了消息,检测到有服务提供者发送消息后,则开启一个新线程去处理消息。
传统的C/S模式,两端采用长连接,则服务器需要开启与连接客户端数目相同的线程来维持通信,当客户端数量急剧增大时,服务器开启的线程也会急剧增多,给服务器产生了很大的压力,性能会下降。而采用轮询遍历的方式则可以减少线程的开启数量,虽然在客户端连接数目过多时会造成处理消息时延过大,但服务提供者的服务注册与注销并不是很频繁,因此造成一些时延是可以接受的。
如果说,连接注册中心的服务提供者数量很大,那么在处理信息时的延迟也还是比较大的,如何解决呢?可以采用分组轮询的方式,每一个轮询池的大小固定(可配置),当超过设定的固定值时,就将连接请求加入到另一个新开的轮询池去遍历轮询。当然,也可以开启多个线程去轮询轮询池,每个线程只对应处理某一种类型的消息,比如一个线程轮询时只处理服务提供者的注册服务请求,另一个线程只处理服务提供者的注销服务请求。
注册中心与服务消费者
package com.fwfx.center;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import com.fwfx.core.HeartBeat;
import com.mec.util.PropertiesParser;
import com.rmi.core.RMIServer;
/**
* 注册中心
*1.包含注册中心服务器与RPC服务器
*2.包含心跳检测器,支持心跳检测时间的配置
*3.支持注册中心服务器和RPC服务器的端口的配置
*4.包含轮询池
* @author dx
*
*/
public class RegistryCenter implements Runnable{
private static int DEFAULT_CENTER_PORT=54199;
private static int DEFAULT_RMI_CENTER_PORT=54188;
private int rmiCenterPort;
private int centerPort;
private ServerSocket serverSocket;
//节点轮询池
private NetNodePollPool netNodeCollector;
//心跳检测
private HeartBeat heartBeat;
//rmiServer
private RMIServer rmiServer;
private volatile boolean goon;
public RegistryCenter() {
this.centerPort=DEFAULT_CENTER_PORT;
this.rmiCenterPort=DEFAULT_RMI_CENTER_PORT;
rmiServer=new RMIServer();
heartBeat=new HeartBeat();
}
/**
* 加载配置文件
* @param configPath
*/
public void loadConfig(String configPath) {
PropertiesParser.loadProperties(configPath);
//rmi端口
String rmiPort=PropertiesParser.value("rmiPort");
if (rmiPort!=null&&!rmiPort.equals("")) {
int rmiRequestPort=Integer.valueOf(rmiPort);
if (rmiRequestPort>0&&rmiRequestPort<65536) {
this.rmiCenterPort=rmiRequestPort;
}
}
//centerport
String centerPort=PropertiesParser.value("centerPort");
if (centerPort!=null&&!centerPort.equals("")) {
int longCenterPort=Integer.valueOf(centerPort);
if (longCenterPort>0&&longCenterPort<65536) {
this.centerPort=longCenterPort;
}
}
//心跳检测时间
String heartBeatTime=PropertiesParser.value("heartBeatTime");
if (heartBeatTime!=null&&!heartBeatTime.equals("")) {
int beatTime=Integer.valueOf(heartBeatTime);
if (beatTime>0&&beatTime<Integer.MAX_VALUE) {
this.heartBeat.setBeatTime(beatTime);
}
}
}
/**
* 开启rmiServer
*/
private void startRmiServer() {
System.out.println("开始启动RMI服务器...");
this.rmiServer.setRmiPort(rmiCenterPort);
this.rmiServer.startRmiServer();
}
/**
* 开启
*/
public void startUp() {
if (goon==true) {
return;
}
goon=true;
try {
serverSocket=new ServerSocket(centerPort);
System.out.println("开启注册中心...");
} catch (IOException e) {
e.printStackTrace();
}
//开启节点轮询
netNodeCollector=new NetNodePollPool(1000);
netNodeCollector.startCollect();
//开启心跳检测
heartBeat.setNetNodePool(netNodeCollector);
heartBeat.startHearBeat();
//开启rmiServer
startRmiServer();
new Thread(this,"服务发现-注册中心").start();
}
/**
* 将侦听到的服务提供者加入到轮询池
*/
@Override
public void run() {
while(goon) {
try {
Socket socket=serverSocket.accept();
netNodeCollector.appendConversation(socket);
} catch (IOException e) {
continue;
}
}
}
/**
* 关闭
*/
public void shutDown() {
if (goon==false) {
return;
}
netNodeCollector.stopCollect();
heartBeat.stopBeat();
rmiServer.stopRmiServer();
try {
if (!serverSocket.isClosed()&&serverSocket!=null) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
serverSocket=null;
}
}
}
RegistryCenter
RegistryProviderConversation
package com.fwfx.center;
import java.io.IOException;
import java.net.Socket;
import java.util.List;
import com.fwfx.core.ThreadPoolFactory;
import com.fwfx.message.Communication;
import com.fwfx.message.DealNetMessage;
import com.fwfx.message.ENetCommand;
import com.fwfx.message.NetMessage;
import com.google.gson.reflect.TypeToken;
import com.mec.util.ArgumentMaker;
import com.rmi.model.NetNode;
/**
* 注册中心与服务器的会话层
* 1.建立通信信道
* 2.包含对服务提供者各种消息的处理以及响应
*
* @author dx
*
*/
public class RegistryProviderConversation extends Communication{
private volatile boolean ok;
private NetNode serverNode;
public RegistryProviderConversation(Socket socket) throws IOException {
super(socket);
this.serverNode=new NetNode();
}
public NetNode getServerNode() {
return serverNode;
}
/**
* 处理连接事件
* @param message
*/
public void dealConnect(NetMessage message) {
judegeServerNode(message);
}
/**
* 处理注册单个服务请求
* @param message
*/
public void dealRegistryService(NetMessage message) {
String para=message.getPara();
ServiceMap.registeryService(para, serverNode);
System.out.println("注册(:"+para+":"+serverNode);
}
/**
* 处理注销单个服务请求
* @param message
*/
public void dealLogOutService(NetMessage message) {
String para=message.getPara();
ServiceMap.logOutService(para, serverNode);
System.out.println("注销(:"+para+":"+serverNode);
}
/**
* 获取服务提供者发送过来的其开启的RPC服务器节点信息
* @param para
* @return
*/
private void judegeServerNode(NetMessage message) {
String para=message.getPara();
int index=para.lastIndexOf('-');
String ipStr=para.substring(0,index);
String portStr=para.substring(index+1);
if (ipStr!=null&&ipStr.length()>0) {
serverNode.setIp(ipStr);
}
int port=Integer.valueOf(portStr);
if (port>0) {
serverNode.setPort(port);
}
}
/**
* 处理注册多个服务请求
* @param message
*/
public void dealRegistryServices(NetMessage message) {
String para=message.getPara();
List<String> services=
ArgumentMaker.gson.fromJson(para, new TypeToken<List<String>>() {}.getType());
for (String service : services) {
ServiceMap.registeryService(service, serverNode);
System.out.println("注册(:"+service+":"+serverNode);
}
}
/**
* 处理注销多个服务请求
* @param message
*/
public void dealLogOut(NetMessage message) {
System.out.println("注销(:"+serverNode);
ServiceMap.logOutNetNode(serverNode);
}
/**
* 处理心跳检测响应事件
* @param message
*/
public void dealHeartbeatResponse(NetMessage message) {
if (message.getCommand()==ENetCommand.HEARTBEAT_RESPONSE) {
System.out.println(serverNode+"正常工作中...");
}
}
/*
* 检查接收状况
* 由于轮询很快,会多次dis.available()成功,为了避免重复处理
* 加入ok标志
*/
public void checkReceive() throws IOException {
if (isReadable()&&!ok) {
ok=true;
ThreadPoolFactory.excute(new InnerMessageDealer());
}
}
/**
* 处理消息
*/
@Override
public void dealNetMessage(NetMessage message) {
DealNetMessage.dealCommand(this, message);
}
/**
* 异常掉线处理
*/
protected void dealAbnormalDrop() {
System.out.println(serverNode+"异常掉线!");
}
/**
* 消息处理的内部线程
* @author dx
*
*/
private class InnerMessageDealer implements Runnable{
public InnerMessageDealer() {
}
@Override
public void run() {
String message;
try {
message = readMessage();
dealNetMessage(new NetMessage(message));
ok=false;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
NetNodePollPool
package com.fwfx.center;
import java.io.IOException;
import java.net.Socket;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import com.fwfx.center.RegistryProviderConversation;
import com.fwfx.center.ServiceMap;
/**
* 节点轮询池
* 1.轮询轮询池里面的服务提供者,判断是否有消息
* 2.有消息则开启一个线程去处理消息
* 3.如果有服务提供者宕机或者未正常工作,则将其从轮询池删除
* @author dx
*/
public class NetNodePollPool implements Runnable {
private static int DEFAULT_CAPACITY=52;
private int capacity;
private Queue<RegistryProviderConversation> netNodePool;
private volatile boolean goon;
public NetNodePollPool(int capacity) {
if (capacity==0) {
this.capacity=DEFAULT_CAPACITY;
}else {
this.capacity = capacity;
}
this.netNodePool=new LinkedBlockingQueue<>(capacity);
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public List<RegistryProviderConversation> getNetNodePool(){
return new LinkedList<>(netNodePool);
}
/**
* 初始化轮询池收集器
*/
public void initCollector() {
if (netNodePool==null) {
synchronized(NetNodePollPool.class) {
if (netNodePool==null) {
netNodePool=new LinkedBlockingQueue<>(capacity);
}
}
}
}
/**
* 加入轮询池
*/
public void appendConversation(Socket socket) throws IOException {
RegistryProviderConversation cNode=new RegistryProviderConversation(socket);
netNodePool.add(cNode);
}
/**
* 从轮询池移除
* @param conversationNode
*/
public void removeConversation(RegistryProviderConversation conversationNode) {
netNodePool.remove(conversationNode);
ServiceMap.logOutNetNode(conversationNode.getServerNode());
}
/**
* 开启轮询池
*/
public void startCollect() {
if (goon==true) {
return;
}
goon=true;
initCollector();
System.out.println("开始启动轮询池...");
new Thread(this,"节点轮询").start();
}
/**
* 停止收集
*/
public void stopCollect() {
if (goon == false) {
return;
}
goon = false;
}
@Override
public void run() {
while (goon) {
Iterator<RegistryProviderConversation> iterator=netNodePool.iterator();
while (iterator.hasNext()) {//轮询轮询池
RegistryProviderConversation conversationNode = (RegistryProviderConversation) iterator.next();
try {
conversationNode.checkReceive();
} catch (IOException e) {//通信断裂,将服务提供者从轮询池里面删除
conversationNode.dealAbnormalDrop();
this.removeConversation(conversationNode);
}
}
}
}
}
ServiceMap
package com.fwfx.center;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import com.rmi.model.NetNode;
/**
* 1.对注册的服务提供者以及服务列表进行缓存;
* 2.将信息缓存到以服务标识为键,服务提供者列表为值的map中;
* 3.可以根据服务名称获取对应的服务提供者列表
* @author dx
*
*/
public class ServiceMap {
private static final Map<String, List<NetNode>> servicesMap;
static {
servicesMap=new ConcurrentHashMap<String, List<NetNode>>();
}
public ServiceMap() {
}
public Map<String, List<NetNode>> getServicesMap() {
return servicesMap;
}
/**
* 注册节点以及服务
* @param service
* @param serverNode
*/
public static void registeryService(String service,NetNode serverNode) {
if (service==null) {
return;
}
List<NetNode> serverNodes=null;
synchronized(servicesMap) {
if (servicesMap.containsKey(service)) {
serverNodes=servicesMap.get(service);
if (serverNodes==null) {
serverNodes=new LinkedList<>();
}
serverNodes.add(serverNode);
}else {
serverNodes=new LinkedList<>();
serverNodes.add(serverNode);
}
servicesMap.put(service, serverNodes);
}
}
/**
* 注销节点以及服务
* @param service
* @param serverNode
*/
public static void logOutService(String service,NetNode serverNode) {
if (service==null) {
return;
}
List<NetNode> serverNodes=null;
synchronized(servicesMap) {
if (servicesMap.containsKey(service)) {
serverNodes=servicesMap.get(service);
if (serverNodes!=null) {
serverNodes.remove(serverNode);
servicesMap.put(service, serverNodes);
}else {
servicesMap.remove(service);
}
}
}
}
/**
* 注销节点
* @param service
* @param serverNode
*/
public static void logOutNetNode(NetNode serverNode) {
for(Entry<String,List<NetNode>> entry:servicesMap.entrySet()) {
List<NetNode> netNodes=entry.getValue();
Iterator<NetNode> iterator=netNodes.iterator();
while (iterator.hasNext()) {
NetNode netNode = (NetNode) iterator.next();
if (netNode.equals(serverNode)) {
iterator.remove();
}
}
}
}
/**
* 根据服务标识获取服务实例(提供者列表)
* @param sreviceName
* @return
*/
public static List<NetNode> getServicesByServiceName(String sreviceName){
return servicesMap.get(sreviceName);
}
/**
* 获取所有的服务列表
* @return
*/
public static List<String> getServices(){
return new ArrayList<>(servicesMap.keySet());
}
}
IRegistryCenterService
package com.fwfx.center;
import java.util.List;
import com.rmi.annotation.MecDialog;
import com.rmi.model.NetNode;
/**
* 注册中心提供给服务消费者的服务接口
* @author dx
*
*/
public interface IRegistryCenterService {
@MecDialog(caption="获取所有服务...")
List<String> getAllService();
@MecDialog(caption="正在获取服务节点列表...")
List<NetNode> getNodesByServiceName(String serviceName);
}
RegistryCenterService
package com.fwfx.center;
import java.util.List;
import com.rmi.annotation.RmiAction;
import com.rmi.model.NetNode;
/**
* 注册中心服务接口实现类
* @author dx
*
*/
@RmiAction(RmiInterfaces= {IRegistryCenterService.class})
public class RegistryCenterService implements IRegistryCenterService{
/**
* 获取所有服务列表
*/
@Override
public List<String> getAllService() {
return ServiceMap.getServices();
}
/**
* 根据服务名称获取服务提供者列表
*/
@Override
public List<NetNode> getNodesByServiceName(String serviceName) {
return ServiceMap.getServicesByServiceName(serviceName);
}
}
HeartBeat
package com.fwfx.core;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import com.fwfx.center.NetNodePollPool;
import com.fwfx.center.RegistryProviderConversation;
import com.fwfx.message.ENetCommand;
import com.fwfx.message.NetMessage;
import com.mec.timer.didadida.DidaDida;
import com.mec.timer.didadida.IDidaDidaAction;
/**
* 心跳检测
* 1.dis.available()无法检测对端异常掉线,由此使用心跳检测;
* 2.定时向服务提供者(服务实例)列表发送消息,通过对端是否正常应答,检测其是否在线。
* @author dingxiang
*
*/
public class HeartBeat {
private static final int DEFAULT_BEAT_TIME=30*30*1000;//单位ms
private DidaDida timer;
private int beatTime;
private NetNodePollPool netNodePool;
public HeartBeat() {
beatTime=DEFAULT_BEAT_TIME;
}
public void setBeatTime(int beatTime) {
this.beatTime = beatTime;
}
public void setNetNodePool(NetNodePollPool netNodePool) {
this.netNodePool = netNodePool;
}
/**
* 开始心跳检测
*/
public void startHearBeat() {
this.timer=new DidaDida(beatTime);
this.timer.setAction(new IDidaDidaAction() {
@Override
public void doSomething() {
List<RegistryProviderConversation> serverConversations=netNodePool.getNetNodePool();
NetMessage netMessage=new NetMessage();
netMessage.setCommand(ENetCommand.HEARTBEAT);
Iterator<RegistryProviderConversation> iterator=serverConversations.iterator();
while (iterator.hasNext()) {
RegistryProviderConversation registryServerConversation = (RegistryProviderConversation) iterator
.next();
try {
registryServerConversation.sendMessage(netMessage);
} catch (IOException e) {
iterator.remove();
}
}
}
});
this.timer.start();
}
/**
* 停止心跳检测器
*/
public void stopBeat() {
this.timer.stop();
}
}
服务提供者
Provider
package com.fwfx.provider;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import com.fwfx.message.ENetCommand;
import com.fwfx.message.NetMessage;
import com.rmi.model.NetNode;
/**
* 服务提供者
* 提供注册、注销服务的方法
* @author dx
*
*/
public class Provider{
private static String DEFAULT_CENTER_IP="127.0.0.1";
private static int DEFAULT_CENTER_PORT=54199;
private String registeryIp;
private int registryPort;
private Socket socket;
private ProviderRegisteryConversation serverRegiteryConversation;
private List<String> services;
private NetNode serverNode;
public Provider() {
this(DEFAULT_CENTER_IP, DEFAULT_CENTER_PORT);
}
public void setServerNode(NetNode serverNode) {
this.serverNode = serverNode;
}
public Provider(String registeryIp, int registryPort) {
this.registeryIp = registeryIp;
this.registryPort = registryPort;
this.services=new LinkedList<>();
}
public String getRegisteryIp() {
return registeryIp;
}
public void setRegisteryIp(String registeryIp) {
this.registeryIp = registeryIp;
}
public int getRegistryPort() {
return registryPort;
}
public void setRegistryPort(int registryPort) {
this.registryPort = registryPort;
}
/**
* 连接
* @return
* @throws UnknownHostException
*/
public boolean connectRegistryCenter() throws UnknownHostException {
try {
System.out.println("开始连接注册中心...");
socket=new Socket(registeryIp, registryPort);
serverRegiteryConversation=new ProviderRegisteryConversation(socket);
serverRegiteryConversation.setProvider(this);
establishConect();
System.out.println("已连接上注册中心!");
return true;
} catch (Exception e) {
System.out.println("连接注册注册中心失败");
}
return false;
}
/**
* 将服务提供者开启的RPC服务器节点信息发送至注册中心
* @throws IOException
*/
public void establishConect() throws IOException {
serverRegiteryConversation.sendMessage(new NetMessage()
.setCommand(ENetCommand.CONNECT)
.setPara(serverNode.toString()));
}
public List<String> getServices() {
return services;
}
public void setServices(List<String> services) {
this.services = services;
}
/**
* 添加到服务列表
* @param service
*/
public void addService(String service) {
services.add(service);
}
/**
* 从服务列表移除
* @param service
*/
public void removeService(String service) {
services.remove(service);
}
/**
* 提交注册单个服务请求
* @param service
* @throws IOException
*/
public void commitSingalService(String service) throws IOException {
serverRegiteryConversation.addSigalService(service);
}
/**
* 提交将服务列表里面的服务全部注册请求
* @throws IOException
*/
public void commitMulService() throws IOException {
serverRegiteryConversation.addMulServices(services);
}
/**
* 提交注销单个服务请求
* @param service
* @throws IOException
*/
public void logOutSingalService(String service) throws IOException {
serverRegiteryConversation.deleteSigalService(service);
}
/**
* 提交将服务列表里面的服务全部注销请求
* @throws IOException
*/
public void logOutMulService() throws IOException {
serverRegiteryConversation.deleteMulServices();
}
}
ProviderRegisteryConversation
package com.fwfx.provider;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List;
import com.fwfx.message.Communication;
import com.fwfx.message.DealNetMessage;
import com.fwfx.message.ENetCommand;
import com.fwfx.message.NetMessage;
import com.mec.util.ArgumentMaker;
/**
* 服务提供者与注册中心的会话类
* 1.包含对注册中心心跳检测通知的响应
* 2.包含对注册中心宕机后的处理
* 3.维护与注册中心的长连接
* 4.提供了注册、注销服务的具体通信实现
* @author dx
*
*/
public class ProviderRegisteryConversation extends Communication implements Runnable{
private Provider provider;
private volatile boolean goon;
ProviderRegisteryConversation(Socket socket) throws IOException {
super(socket);
goon=true;
new Thread(this).start();
}
public void setProvider(Provider provider) {
this.provider = provider;
}
/**
* 注册单个服务
* @param service
* @param serverNode
* @throws IOException
*/
void addSigalService(String service) throws IOException {
sendMessage(new NetMessage()
.setCommand(ENetCommand.REGISTRY_SERVICE)
.setPara(service));
}
/**
* 注销单个服务
* @param service
* @param serverNode
* @throws IOException
*/
void deleteSigalService(String service) throws IOException {
sendMessage(new NetMessage()
.setCommand(ENetCommand.LOG_OUT_SERVICE)
.setPara(service));
}
/**
* 注销多个服务
* @param services
* @param serverNode
* @throws IOException
*/
public void deleteMulServices() throws IOException {
sendMessage(new NetMessage()
.setCommand(ENetCommand.LOG_OUT));
}
/**
* 注册多个服务
* @param services
* @param serverNode
* @throws IOException
*/
public void addMulServices(List<String> services) throws IOException {
String serviceslist=null;
serviceslist=ArgumentMaker.gson.toJson(services);
sendMessage(new NetMessage()
.setCommand(ENetCommand.REGISTRY_SERVICES)
.setPara(serviceslist));
}
/**
* 处理心跳检测通知
* @param netMessage
* @throws IOException
*/
public void dealHeartbeat(NetMessage netMessage) throws IOException {
System.out.println("检测到注册中心心跳通知...");
sendMessage(new NetMessage()
.setCommand(ENetCommand.HEARTBEAT_RESPONSE));
}
/**
* 处理消息
*/
@Override
public void dealNetMessage(NetMessage message) {
DealNetMessage.dealCommand(this, message);
}
/**
* 处理注册中心异常掉线以及宕机等情况
*/
protected void dealAbnormalDrop() {
System.out.println("注册中心异常宕机!");
try {
boolean flag=false;
while (!flag) {
flag=provider.connectRegistryCenter();
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (goon) {
try {
String message=readMessage();
dealNetMessage(new NetMessage(message));
} catch (IOException e) {
if (goon) {
goon=false;
dealAbnormalDrop();//注册中心宕机或者异常掉线处理
}
}
}
}
}
IProviderService
服务提供者提供给服务消费者的服务接口,比如,当服务提供者向注册中心注册的服务类型是金融时,那么当服务消费者向注册中心申请金融服务的服务提供者列表,并根据自身配置的负载均衡算法选出某一服务提供者进行通信,这个时候就可以享受服务了,比如获取金融新闻信息等。
package com.fwfx.provider;
/**
* 服务提供者提供给服务消费者的服务接口
* 当服务消费者去注册中心根据需求获取到某一类的服务列表以后
* 再根据自身配置的负载均衡算法选取合适的服务提供者进行通信
* @author dx
*
*/
public interface IProviderService {
/**
* 获取金融新闻
* @return
*/
String getFinaltionNews();
}
服务消费者
Consumer
package com.fwfx.consumer;
import java.util.List;
import com.fwfx.center.IRegistryCenterService;
import com.mec.timer.didadida.DidaDida;
import com.mec.timer.didadida.IDidaDidaAction;
import com.mec.util.PropertiesParser;
import com.rmi.core.RMIClient;
import com.rmi.core.RMIClientProxy;
import com.rmi.model.NetNode;
/**
* 服务消费者
* 1.支持服务消费端配置文件的解析
* 2.定时更新注册中心的服务以及服务提供者列表
*
* @author dingxiang
*
*/
public class Consumer {
private ConsumerCache cache;
private RMIClient rmiClient;
private RMIClientProxy clientProxy;
private IRegistryCenterService centerService;
private INodeSelectStrategy nodeSelectStrategy;
private DidaDida didaTimer;//定时器
public Consumer(String centerIp,int centerPort) {
init();
rmiClient.setRmiIP(centerIp);
rmiClient.setRmiPort(centerPort);
}
void init() {
rmiClient=new RMIClient();
clientProxy=new RMIClientProxy();
clientProxy.setRmiClient(rmiClient);
centerService=(IRegistryCenterService) clientProxy.getProxy(IRegistryCenterService.class);
cache=new ConsumerCache();
didaTimer=new DidaDida();
}
public void setNodeSelectStrategy(INodeSelectStrategy nodeSelectStrategy) {
this.nodeSelectStrategy = nodeSelectStrategy;
}
public void startUp() {
updateConsumerCache();
}
public void stop() {
this.didaTimer.stop();
}
/**
* 加载消费端配置文件
*/
public void loadConsumerConfig(String path) {
rmiClient.loadRMIClientConfig(path);
String delayTime=PropertiesParser.value("delayTime");
if (delayTime!=null&&delayTime.length()>0) {
long time=Long.valueOf(delayTime);
if (time>0) {
this.didaTimer.setDelayTime(time);
}
}
}
/**
* 根据服务名称获取服务列表(先从本地缓存中获取,如果没有,再去注册中心获取)
* 再根据设置的负载均衡算法选择合适的服务提供者列表
*
* @param service
* @return
*/
public NetNode getNodeByService(String service) {
List<NetNode> serverNodes=cache.getNodesByService(service);
if (serverNodes==null||serverNodes.size()==0) {
serverNodes=centerService.getNodesByServiceName(service);
if (serverNodes==null) {
return null;
}
cache.addService(service, serverNodes);
}
return nodeSelectStrategy.nodeSelect(this, service, serverNodes);
}
/**
* 定时更新服务和服务提供者列表
*/
public void updateConsumerCache() {
didaTimer.setAction(new IDidaDidaAction() {
@Override
public void doSomething() {
List<String> services=centerService.getAllService();
cache.addServices(services);
for (String service : services) {
List<NetNode> serverNodes=centerService.getNodesByServiceName(service);
cache.addService(service, serverNodes);
}
}
});
didaTimer.start();
}
}
ConsumerCache
package com.fwfx.consumer;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.rmi.model.NetNode;
/**
* 服务消费者缓存
* 对申请到的服务以及服务提供者列表进行缓存
* 避免因为注册中心宕机以后无法使用服务的问题。
* @author dx
*
*/
public class ConsumerCache {
private static final Map<String, List<NetNode>> consumeCache=new ConcurrentHashMap<String, List<NetNode>>();
public ConsumerCache() {
}
public void addServices(List<String> services) {
consumeCache.clear();
for (String service : services) {
consumeCache.put(service, new LinkedList<>());
}
}
public void addService(String service,List<NetNode> serverNodes) {
consumeCache.put(service, serverNodes);
}
public List<NetNode> getNodesByService(String serviceName){
return consumeCache.get(serviceName);
}
public Set<String> getServices(){
return consumeCache.keySet();
}
}
INodeSelectStrategy
package com.fwfx.consumer;
import java.util.List;
import com.rmi.model.NetNode;
/**
* 节点选择策略接口类
* @author dx
*
*/
public interface INodeSelectStrategy {
NetNode nodeSelect(Consumer consumer,String seviceName,List<NetNode> severNodes);
}
负载均衡
负载均衡的算法很多,这里简单提供了两种可供选择,一种是随机选择算法,一种是哈希算法。也可以根据获得的服务提供者列表,对每个服务提供者发送消息,统计各自的响应时间,选择相应最快的服务提供者。
HashBalance
package com.fwfx.consumer;
import java.util.List;
import com.rmi.model.NetNode;
/**
* 哈希
* @author dx
*
*/
public class HashBalance implements INodeSelectStrategy{
@Override
public NetNode nodeSelect(Consumer consumer, String seviceName, List<NetNode> severNodes) {
return severNodes.get((consumer.hashCode()+seviceName.hashCode())%severNodes.size());
}
}
RandomBalance
package com.fwfx.consumer;
import java.util.List;
import com.rmi.model.NetNode;
/**
* 随机选择
* @author dx
*
*/
public class RandomBalance implements INodeSelectStrategy{
@Override
public NetNode nodeSelect(Consumer consumer,String seviceName,List<NetNode> severNodes) {
return severNodes.get((int)(Math.random()*10000)%severNodes.size());
}
}
一些补充
关于注册中心宕机的处理,最简单的处理方式就是热插拔,这样,如果注册中心宕机,服务提供者和服务消费者会不断的重新连接注册中心,直至再次连接上为止,服务消费者端缓存有定时更新的服务以及服务提供者列表,只要这些服务提供者还在正常运行,就可以继续享受服务,只不过无法连接上最近的服务提供者和最新发布的服务。
也可以采用集群的方式来部署注册中心,那么当某台注册中心宕机以后,服务提供者和服务消费者可以自动切换到另一个注册中心享受服务。
还有一点需要注意的是,注册中心的服务以及服务提供者列表需要保存,不然,如果注册中心宕机重启以后,以前注册中心的缓存就丢失了。可以选用文件或者数据库的方式来保存。
测试代码以及配置文件
注册中心部分
服务提供者部分
服务消费者部分