目录
概述
框架
服务发现
服务发现的基本思想
代码及详解
通信的基本类
ENetCommand
NetMessage
NetNode
注册中心&提供者&消费者
注册中心与提供者
注册中心与服务消费者
代码模拟及详解
服务提供者
ProviderClient
IClientAction
服务消费者
CustomerServerClient
INetNodeSelector
负载均衡详述
注册中心
RegistryCenter
ProviderServer
PollList
ProviderNodePool
RegistryAction
CheckAlive
最后
框架,于我而言,就好似于一间房的框架,其外部是规范统一的,其内部是可以装饰任意风格的。就好像有木结构房子架构、砖混结构的、钢筋混凝土架构的,以及现在所听到的3D打印的房架构都均是立体的建筑框架。而内部的装饰则是可以随个人的喜好变化的。概括下来框架就是给人感觉的看似呆滞但又活泼的词汇。就好像我们在写Java代码的时候要求要给外部统一使用的接口(类比于墙体架构),但内部可由各自需求选择可使用的方案(类比于个人装饰)。从而实现了软件工程中锁提出的高内聚低耦合的概念。
而这些在我们现在对各个功能的需求演变,我们需要将以前所有代码冗杂在一起的进行拆分,变成一个个相对较小且独立的功能单元,每个单元专注于一个或几个功能合为一种服务,每个服务之间进行低耦合,服务又组为一个项目。
1.服务发现的思想解说:
其中服务为一,发现服务为二。
即可以理解为有提供服务的一方,和有需要发现服务的需求方。其中还有一个中心管理调节着两方(就好像婚介所,有想娶的找婚介所有想嫁的找婚介所)服务提供方在中心注册服务,消费方在中心寻找服务的这种关系。
2.为什么有服务发现:
(通过上面的婚介所例子大家应该有一丝丝自己的理解吧)
在传统的项目中,每个服务都是直接被部署到相关的服务器上,也就是相当于定死了服务的地址,或出现变化需要人工及时的修改配置,但是在现在这样一个分布式时代背景下,一种服务往往不是对应一台服务器,而是有代理服务器去随机动态选择集群中的一台服务器。这时定死的服务地址当然是会被淘汰的。由此服务发现产生了,用户只用通过某种服务标签去中心寻找需要的服务,由中心自动匹配。不仅仅由此效果,服务发现还提高了应用的容灾性。在传统的当某个服务器宕机,其所注册的服务必然也不能使用,若通过服务发现,在发现此问题时可以重新寻找新的拥有相同服务功能的服务器来继续进行。
其中:
*** 三方: 服务提供方 服务消费方 注册中心
* 注册中心负责维护服务提供者和服务消费者之间的联系(本方案用Map管理)。
* 服务提供者在需要注册服务时主动与注册中心联系
* 服务消费者在需要服务时与注册中心联系
(消费方与服务方是短连接)
(关于RPC有关文档可参考RMI(Java的RPC))
/**
* 枚举类
* 通信协议中的命令;
*
* @author quan
*/
public enum ENetCommand {
//注册
REGISTRY,
//注销
OUT,
//在线
IS_ON_LINE,
//注册失败
REGISTRY_FAIL,
//注销失败
OUT_FAIL,
//注册成功
REGISTRY_SUCCESS,
//注销成功
OUT_SUCCESS,
}
/**
* 消息转换类
* 1、将消息类型与消息内容整合成json字符串;
* 2、将收到的字符串转化为的对象;
* 3、用来规范通信的协议;
*
* @author quan
*/
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 void setCommand(ENetCommand command) {
this.command = command;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getPara() {
return para;
}
public void setPara(String para) {
this.para = para;
}
@Override
public String toString() {
StringBuffer result = new StringBuffer(command.name());
result.append('.');
result.append(action == null ? " " : action).append('.');
result.append(para);
return result.toString();
}
}
/**
* 结点类
* 用来保存通信结点的结点信息;
*
* @author quan
*/
public class Node implements INetNode{
private String ip;
private int port;
private int sendTime;
public Node() {
}
public Node(int port) {
this.port = port;
}
public Node(String ip, int port) {
this.ip = ip;
this.port = port;
}
@Override
public void setIp(String ip) {
this.ip = ip;
}
@Override
public String getIp() {
return ip;
}
@Override
public void setPort(int port) {
this.port = port;
}
@Override
public int getPort() {
return port;
}
@Override
public int setSendTime() {
return this.sendTime++;
}
@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;
Node other = (Node)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 "Node [ip=" + ip + ", port=" + port + ", sendTime=" + sendTime + "]";
}
@Override
public int getSendTime() {
return sendTime;
}
}
在服务提供者上线后选择自己要注册的服务联系服务中心进行注册,其中它们采用的是长连接,这样注册中心可以实时监测到服务的在线或宕机情况,其中基本通信类为Communication,其中使用了一个InputStream的一个方法available(),这个方法可以监测到通信对方是否有已经发过来的的信息流(因为我使用的是阻塞式的流而非非阻塞式的NIO大家也可以自行用NIO做一下),但是大家可以想一下我们为什么使用available()而不用read()呢?
那是因为有一个方案式这样的:dis.available(),这个方法将代替注册中心注册中心对每个服务提供者开启一个线程去read(),而是将这些服务提供者的通信请求放在一个池子里(PollList)去轮询的检测是否有注册或注销的消息它可以检测对端是否发送了信息,而避免了dis.read()时若没有消息则造成线程的阻塞,而若当服务提供者数量庞大时,需要维护的数量很大即开启线程很多,而且大多线程出去阻塞状态浪费线程资源。而轮询的方式可以减少线程开启的数量,虽然可能会造成其它的影响,比如轮询会造成服务响应时间变慢,但考虑到服务提供者不会频繁的注册、注销,两种平衡方案下来,轮询有极大的好处。
对于轮询产生的时延,可以这样做,若连接注册中心的的服务提供者数量比较大,我们可以在轮询池(PollList)中采用分组去轮询的方法,例如有五千个服务提供者在一个轮询池和五千个服务提供者分为五个轮询组去并行处理。
下面是整个服务提供者与注册中心的草图
注册中心与服务消费者采用的是RMI(RPC)的通信方式通过远程方法调用去获取服务地址。
/**
* 服务提供方
* 1、向注册中心注册/注销服务;
* 2、注册中心宕机,采取重连的方式,直到连接成功为止;
* 3、支持ip、port的配置;
*
* @author quan
*/
public class Client {
private static final int DEFAULT_PORT = 54188;
private static final String DEFAULT_IP = "192.168.79.1";
private int port;
private String ip;
private Socket client;
private ClientConversation clientConversation;
private IDealNetmessage dealNetmessage;
private IClientAction clientAction;
public Client() {
init();
}
public void setDealNetMessage(IDealNetmessage dealNetMessage) {
this.dealNetmessage = dealNetMessage;
}
private void init() {
this.port = DEFAULT_PORT;
this.ip = DEFAULT_IP;
}
public void setClientAction(IClientAction clientAction) {
this.clientAction = clientAction;
}
public boolean connectToServer() {
if (clientAction == null || dealNetmessage == null) {
System.out.println("请先设置clientAction或者dealNetmessage");
return false;
}
if (ip != null && port != 0) {
try {
client = new Socket(ip, port);
clientConversation = new ClientConversation(this, client);
clientConversation.setDealNetMessage(this.dealNetmessage);
new MThreadPool().newInstance().execute(clientConversation);
return true;
} catch (Exception e) {
clientAction.canotConnect(this);
return false;
}
}
return false;
}
/**
* 注册服务,若发送失败告知view层;
* @return
*/
*/
public void registry(String ServiceName, INetNode node) {
try {
node.setIp(String.valueOf(InetAddress.getLocalHost().getHostAddress()));
} catch (UnknownHostException e) {
}
clientConversation.registryNode(ServiceName, node);
if (node == null) {
clientAction.registryFail(ServiceName, node);
}
}
/**
* 注销服务,若发送失败告知view层;
* @return
*/
*/
public void out(String ServiceName, INetNode node) {
try {
node.setIp(String.valueOf(InetAddress.getLocalHost().getHostAddress()));
} catch (UnknownHostException e) {
}
INetNode returnNode = clientConversation.outNode(ServiceName, node);
if (returnNode == null) {
clientAction.outFail(ServiceName, node);
}
}
public void close() {
clientConversation.close();
}
}
/**
*
*
* 功能:处理服务提供者连接、注册、注销事后服务。
* - canotConnect不能连接接口
* - registryFail注册失败接口
* - outFail注销失败接口
*
* @author Quan
* @date 2020/03/05
* @version 0.0.1
*/
public interface IClientAction {
void canotConnect(Client client);
void registryFail(String ServiceName, INetNode node);
void outFail(String ServiceName, INetNode node);
}
/**
*
* 功能:服务消费方
* - 和注册中心联系获取连接服务端口
* - 端口选择策略
*
* @author Quan
* @date 2020/01/20
* @version 0.0.1
*/
public class ServerClient {
private RMIClient rmiClient;
private ClientProxy clientProxy;
private static Map> clientNodePool;
private INetNodeSelector nodeSelector;
static {
clientNodePool = new HashMap>();
}
public ServerClient() {
}
public INetNodeSelector getNodeSelector() {
return nodeSelector;
}
public void setNodeSelector(INetNodeSelector nodeSelector) {
this.nodeSelector = nodeSelector;
}
public RMIClient getRmiClient() {
return rmiClient;
}
public void setRmiClient(RMIClient rmiClient) {
this.rmiClient = rmiClient;
}
/**
* 连接注册中心获取节点
* @param serviceName
* @return
*/
private boolean getPortsByName(String serviceName) {
clientProxy = new ClientProxy();
clientProxy.setRmiClient(rmiClient);
IGetPorts getPorts = clientProxy.getProxy(IGetPorts.class, true);
Object obj = getPorts.getPorts(serviceName);
List nodeList = null;
if (obj != null) {
nodeList = (List) obj;
for (int i = 0; i < nodeList.size(); i++) {
registryServiceToCatch(serviceName, nodeList.get(i));
}
return true;
}
return false;
}
/**
* 从缓冲池中获取节点,或没有则去连接获取服务节点
* @param serviceName
* @return
*/
public INetNode getOnePortToConnect(String serviceName) {
Queue nodes = null;
if (serviceName != null && !serviceName.equalsIgnoreCase("{}")) {
nodes = clientNodePool.get(serviceName);
} else {
return null;
}
if (nodes == null || nodes.size() == 0) {
boolean update = getPortsByName(serviceName);
if (update == false) {
return null;
}
return getOnePortToConnect(serviceName);
}
if (nodes.size() == 1) {
return nodes.poll();
}
//TODO 策略选择节点
//此时为连接服务端策略选择
return nodeSelector.getRightNode();
}
/**
* 将注册中心获取的节点注册到缓冲池
* @param serviceName
* @param node
* @return
*/
private boolean registryServiceToCatch(String serviceName, INetNode node) {
if (!clientNodePool.containsKey(serviceName)) {
Queue nodeQueue = new SynchronousQueue();
clientNodePool.put(serviceName, nodeQueue);
nodeQueue.add(node);
System.out.println(nodeQueue.size());
return true;
}
Queue tmpQueue = clientNodePool.get(serviceName);
if (tmpQueue == null) {
tmpQueue = new SynchronousQueue();
}
tmpQueue.add(node);
return true;
}
}
/**
*
*
* 功能:节点选择策略
* - 当从缓冲中获取服务器列表后,消费者需要自行选择结点
* - 为避免注册中心负载压力过大的问题,负载均衡放在消费端进行;
* - 负载均衡实现的手法个不同,留接口自行实现
*
* @author Quan
* @date 2020/03/05
* @version 0.0.1
*/
public interface INetNodeSelector {
INetNode getRightNode();
}
负载均衡节点选择方案有很多,旨在去平衡地访问每个服务提供方服务器,避免一个服务器上访问过多,负载太重,我们将负载均衡放在消费方,避免注册中心压力过大,使注册中心功能单一,可以利用hashmap中的思想进行散列选择,也可以循环选择,或者在缓冲池中定时检测压力较小的服务器。
/**
*
*
* 功能:注册中心拥有与服务提供方的交互服务器
* 和与服务消费方交互的服务器。
* - 与服务提供者的注册注销
* - 与服务消费方的节点需求
* - 与轮询池的心跳检测
*
* @author Quan
* @date 2020/03/05
* @version 0.0.1
*/
public class RegistryCenter implements Runnable {
private ThreadPoolExecutor threadPool = new MThreadPool().newInstance();
private Server server;
private RMIServer rmiServer;
private CheckAlive checkalive;
public RegistryCenter() {
checkalive = new CheckAlive();
init();
}
public void init() {
threadPool = new MThreadPool().newInstance();
server = new Server();
server.setThreadPool(threadPool);
server.setDealNetMessage(new ServerConversationDeal());
rmiServer = new RMIServer();
rmiServer.setThreadPool(threadPool);
rmiServer.startRMIServer();
//开启轮询池的心跳检测
threadPool.execute(checkalive);
}
public void closermiServer() {
rmiServer.shutDown();
}
@Override
public void run() {
boolean serverOk = server.startServer();
boolean rmiServerOk = rmiServer.startRMIServer();
if (serverOk) {
System.out.println("长连接服务器启动成功");
} else {
System.out.println("长连接服务器启动失败");
}
if (rmiServerOk) {
System.out.println("短连接服务器启动成功");
} else {
System.out.println("短连接服务器启动失败");
}
}
}
/**
* 与服务端连接的长连接
*
* 功能:
* - 开启
* - 关闭
* - 接受连接socket放置于轮询队列
*
* @author Quan
* @date 2020/01/17
* @version 0.0.1
*/
public class Server implements Runnable {
private static final int LIST_NUM = 5;
private static final int DEFAULT_PORT = 54188;
private static final String DEFAULT_IP = "192.168.79.1";
private static final int DEFAULT_LIST_NUM = 1;
private static final int DEFAULT_ONE_LIST_MAXNUM = 10;
private static int oneListMaxnum = DEFAULT_ONE_LIST_MAXNUM;
static volatile int onLineCount;
static volatile int listNum = DEFAULT_LIST_NUM;
public static Map> pollMap
= new HashMap>();
private int port;
private String ip;
private ThreadPoolExecutor threadPool;
private ServerSocket serverSocket;
private PollPool pollPool;
private volatile boolean goon;
private Object objectLock;
private IDealNetmessage dealNetmessage;
static {
for (int i = 0; i < LIST_NUM; i++) {
pollMap.put(i+1, new CopyOnWriteArrayList());
}
}
public Server() {
goon = false;
port = DEFAULT_PORT;
ip = DEFAULT_IP;
objectLock = new Object();
}
public void setDealNetMessage(IDealNetmessage dealNetMessage) {
this.dealNetmessage = dealNetMessage;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public ThreadPoolExecutor getThreadPool() {
return threadPool;
}
public void setThreadPool(ThreadPoolExecutor threadPool) {
this.threadPool = threadPool;
}
public boolean startServer() {
if (dealNetmessage == null) {
System.out.println("请先设置dealNetmessage");
return false;
}
if (goon) {
System.out.println("与服务端服务器已经开启");
return true;
}
if (threadPool == null) {
System.out.println("与服务端服务器请先设置线程池");
return false;
}
if (port == 0) {
System.out.println("与服务端服务器端口号未设置");
return false;
}
try {
goon = true;
serverSocket = new ServerSocket(port);
System.out.println(serverSocket);
synchronized (objectLock) {
pollPool = new PollPool();
threadPool.execute(pollPool);
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
new Thread(this, "服务端侦听线程").start();
System.out.println("与服务端服务器已开启......");
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
public boolean shutDown() {
if (!goon) {
System.out.println("与服务端服务器未启动");
return false;
}
goon = false;
return true;
}
public boolean isShutDown() {
return !goon;
}
@Override
public void run() {
Socket serverClient = null;
while (goon) {
try {
System.out.println("与服务端服务器开始侦听......");
serverClient = serverSocket.accept();
System.out.println("与服务端服务器收到一个客户端连接请求");
if (serverClient == null) {
System.out.println("服务端与服务端服务器未连接");
} ServerConversation conversationNode;
try {
conversationNode = new ServerConversation(serverClient);
conversationNode.setDealNetMessage(dealNetmessage);
pollPool.add(conversationNode);
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
if (goon) {
System.out.println("与服务端服务器异常宕机");
goon = false;
close();
}
e.printStackTrace();
}
}
close();
}
public void close() {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
serverSocket = null;
}
}
}
/**
* 轮询池
*
* 功能:
* - 轮询连接队列检测是否有消息需要处理
* - 将需要处理的连接给线程运行
* - 维护轮询队列
*
* @author Quan
* @date 2020/01/17
* @version 0.0.1
*/
class PollPool implements Runnable {
public PollPool() {
onLineCount = 0;
}
public int getOnLineCount() {
return onLineCount;
}
public void setOnLineCount(int onLineCount) {
Server.onLineCount = onLineCount;
}
public ThreadPoolExecutor getThreadPool() {
return threadPool;
}
public int getListNum() {
return listNum;
}
public void setListNum(int listNum) {
Server.listNum = listNum;
}
public int getOneListMaxnum() {
return oneListMaxnum;
}
public void setOneListMaxnum(int oneListMaxnum) {
Server.oneListMaxnum = oneListMaxnum;
}
public synchronized boolean add(ServerConversation serverClientNode) {
List cList = pollMap.get(listNum);
if (cList.size() < DEFAULT_ONE_LIST_MAXNUM ) {
cList.add(serverClientNode);
System.out.println("列表里Conversation数量:"+cList.size());
onLineCount++;
return true;
} else if (listNum < LIST_NUM) {
listNum++;
List newList = new CopyOnWriteArrayList();
newList.add(serverClientNode);
threadPool.execute(new PollList(newList));
pollMap.put(listNum, newList);
onLineCount++;
return true;
} else {
System.out.println("拒绝连接策略");
}
return false;
}
public boolean remove(ServerConversation conversationNode) {
if (conversationNode == null) {
return false;
}
synchronized (pollMap) {
for (int index = 0; index < pollMap.size(); index ++) {
List nodeList = pollMap.get(index+1);
for (ServerConversation conversationNode2 : nodeList) {
if (conversationNode.equals(conversationNode2)) {
nodeList.remove(conversationNode);
return true;
}
}
}
}
return false;
}
@Override
public void run() {
synchronized (objectLock) {
objectLock.notify();
}
System.out.println("开启轮询子线程");
List nodeList = pollMap.get(1);
PollList pollList = new PollList(nodeList);
threadPool.execute(pollList);
}
}
}
/**
*
*
* 功能:轮询池
* - 轮询节点
*
* @author Quan
* @date 2020/03/05
* @version 0.0.1
*/
public class PollList implements Runnable {
List nodeList;
private volatile boolean shutDown;
public PollList(List nodeList) {
this.nodeList = nodeList;
shutDown = false;
}
public void setShutDown(boolean shutDown) {
this.shutDown = shutDown;
}
/**
* checkStatus中写有available()检测,遗憾的是,发现不了对端宕机
*/
@Override
public void run() {
while (!shutDown) {
Iterator iterator = nodeList.iterator();
while (iterator.hasNext()) {
ServerConversation node = iterator.next();
try {
node.checkStatus();
} catch (IOException e) {
}
}
}
}
}
/**
* 服务提供者节点操作
*
* 功能
* - 增加服务
* - 增加服务节点
* - 删除服务节点
* - 定期维护服务节点关系
*
* @author Quan
* @date 2020/01/16
* @version 0.0.1
*/
public class ProviderNodePool {
private static final int DEFAULT_PORT_NUM = 5;
private int portNum = DEFAULT_PORT_NUM;
private static Map> providerNodePool;
private ICenterPortGet centerPortGet;
public ProviderNodePool() {
providerNodePool = new HashMap>();
centerPortGet = new CenterPortGet();
}
public void setCenterPortGet(ICenterPortGet centerPortGet) {
this.centerPortGet = centerPortGet;
}
public static boolean addService(String serviceName, INetNode node) {
if (!providerNodePool.containsKey(serviceName)) {
List nodeList = new CopyOnWriteArrayList();
providerNodePool.put(serviceName, nodeList);
nodeList.add(node);
return true;
}
List tmpList = providerNodePool.get(serviceName);
if (tmpList == null) {
tmpList = new CopyOnWriteArrayList();
}
tmpList.add(node);
return true;
}
public static INetNode removeNetNode(String serviceName, INetNode netNode) {
if (!providerNodePool.containsKey(serviceName)) {
return null;
}
List tmpList = providerNodePool.get(serviceName);
if (tmpList == null) {
return null;
}
if (tmpList.contains(netNode)) {
tmpList.remove(netNode);
return netNode;
}
return null;
}
List getServerPort(String serviceName) {
if (serviceName == null || serviceName.equalsIgnoreCase("")) {
return null;
}
List nodeList = providerNodePool.get(serviceName);
if (nodeList.size() == 0) {
return null;
}
if (nodeList.size() > portNum) {
List resultList = new ArrayList(nodeList);
return centerPortGet.get(resultList, portNum);
}
System.out.println(nodeList);
return nodeList;
}
public static Collection> getValues() {
return providerNodePool.values();
}
}
/**
*
*
* 功能:获取port的实现
* - 用户可以自己选择port获取策略
*
*
*
* @author Quan
* @date 2020/03/05
* @version 0.0.1
*/
@RMIInterfaces(rmiInterfaces = {IGetPorts.class})
public class RegistryAction implements IGetPorts {
private static ProviderNodePool providerNodePool;
static {
providerNodePool = new ProviderNodePool();
}
public RegistryAction() {
}
@MethodDialog(caption = "获取端口号中")
@Override
public List getPorts(String serviceName) {
return providerNodePool.getServerPort(serviceName);
}
public void setCenterPortGetStrategy(ICenterPortGet centerPortGet) {
providerNodePool.setCenterPortGet(centerPortGet);
}
public static boolean registryService(String serviceName, INetNode node) {
return providerNodePool.addService(serviceName, node);
}
public static INetNode outService(String serviceName, INetNode node) {
return providerNodePool.removeNetNode(serviceName, node);
}
}
这个类就是因为avaliable()方法有一个问题,它不能发现对端掉线,所以我们需要一个心跳检测去实时测试对端是否掉线
/**
*
*
* 功能:心跳检测
* @author Quan
* @date 2020/03/05
* @version 0.0.1
*/
public class CheckAlive implements Runnable {
private Map> nodeMap;
private Timer2 timer2;
public CheckAlive() {
nodeMap = Server.pollMap;
}
private void removePoolNode(List nodeList, INetNode node) {
if (nodeList.contains(node)) {
nodeList.remove(node);
}
}
private void check() {
for (int index = 1; index <= 5; index++) {
List nodeList = nodeMap.get(index);
for (ServerConversation node : nodeList) {
if (!node.checkAlive()) {
nodeList.remove(node);
Collection> nodecoll = ProviderNodePool.getValues();
for (List nodes : nodecoll) {
removePoolNode(nodes, node.getNetNode());
}
}
}
}
}
@Override
public void run() {
ITimer task = new ITimer() {
@Override
public void work() {
check();
}
@Override
public void end() {}
};
System.out.println(task);
timer2 = new Timer2(1000, task);
timer2.startWork();
}
}
对于注册中心的宕机,最简单的方式是实现注册中心的热插拔即注册中心宕机后,服务提供者会持续重连直至连接成功,重新注册,服务消费者会先选择自己缓存的服务节点,服务消费者可以星跳检测定时更新自己的节点,
在集群的服务中,消费中心往往不只一个,可以以容灾方式去自动选择另一个注册中心。