服务发现框架
想必看到这篇博客,一定对服务发现有一定的了解。
我眼中的服务发现就是通过一个标志进行服务列表的获取,并且从服务列表中进行服务实例的选择,对于客户端来说,有充分的选择余地,同时对于响应服务的服务器来说也是松了一口气。
1.服务提供端
功能:
1.注册服务:在启动是通过长连接的方式采用request进行请求注册中心进行注册
2.注销服务:采用request请求注册中心进行注销服务。
3.续约:注册完成后,服务提供者采用一个心跳的方式通知注册中心其健壮性(我采用的是在注册中心进行检测服务器提供者健壮性的方案,当然也可以放在服务提供者端来进行通知)
主要类:
1.Provider.java.
2.Providercommuncation.java.
3.Conversation.java.
2.服务消费端
功能:
1.获取服务列表:通过RPC的方式发送请求给服务注册中心,获取服务清单。
2.服务调用:由客户端来负责负载均衡,根据服务清单进行选择服务对象,进行服务调用。
3.定期的更新本地的服务列表:当出现服务器宕机的时候不至于进行长时间的等待响应。
主要的类:
1.Consumer.java
2.CacheUpdate.java
3.LocalCache.java
4.NodeSelect.java
3.注册中心
功能:
1.处理服务注册
2.处理服务注销
3.提供服务列表:提供给消费者合理的服务列表。
4.处理失效服务:服务端并不一定会正常下线,由于网络故障等因素是的服务不能正常提供,在注册中心启动的时候采用一个定时的心跳,每隔一段时间将当前不能工作的服务进行剔除。
主要的类:
1.RegisterServer.java
2.Register.java
3.CommucationPool.java
4.CommucationAndNode.java
5.Commucation.java
6.CheckAlive.java
对于服务发现框架来说,三者之间的连接关系没有一个明确的规定,我采用的方式为:
- 注册中心与消费者:短连接的方式
- 注册中心与生产者:长连接的方式
- 消费者与生产者:短连接的方式
注册中心和生产者:
因为生产者同样也就是服务端,相较消费者来说很少,而对于长连接的话对于注册中心的服务端来说消耗一定的资源,如果长连接过多,那势必会对注册中心服务端的资源消耗很严重,所以长连接适用于链接个数不太多的情况,所以注册中心与生产者之间采用长连接的方式,而注册中心和消费者之间没有采用长连接。由于生产者和注册中心之间,生产者服务的提供不是特别的频繁,所以注册中心采用轮询的方式与生产者进行通信。
注册中心与消费者:
注册中心不用维持和消费者之间的会话,只是一求一应的效果,对于注册中心服务端来说,由于消费者的数量较多,采用短连接的方式节省服务端的资源。
消费者和生产者:
生产者对于消费者来说是消费者请求服务的一个服务端,那么其本质是服务器,生产者相当于客户端,道理同注册中心和消费者相同。
其实不管是对于长连接还是短连接来说,都是基于连接的,那么自然少不了在建立连接的时候要建立TCP链接,断开的时候断开TCP链接。都会产生TCP的三次握手和四次挥手的过程,那这样的话频繁的短连接可能由于请求的实际内容较少,时间都花费在建立连接的过程,效率不够高,那难道应该采用长连接,但是长连接不是更消耗服务端的资源吗?????文章最后会说本人的另一种思路。
其实不只是一种实现思路,上面的链接模型只是其一,没有更好只有适合。
注册中心
IRegister
/*
* 注册中心的主要功能,需要完成注册中心的任务实现的接口类
* 1.提供服务列表
* 2.注册服务
* 3.注销服务
*/
public interface IRegister {
List<NetNode> getServerList(String seviceDemand);
void registServer(NetNode serverNode, List<String> serverList);
void logOutServer(NetNode serverNode);
}
Register(接口实现类)
/*
* 作为提供者和消费者进行服务调用的响应类
* 在注册中心启动的时候将其见进行放入bean工厂
* 对提供者和消费者的请求进行响应
*/
@rmiInterface(rmiter= {IRegister.class})
public class Register implements IRegister{
private static final Map<String, List<NetNode>> serverMap
= new ConcurrentHashMap<String, List<NetNode>>();
public Register() {
}
@Override
public List<NetNode> getServerList(String serverDemand) {
return serverMap.get(serverDemand);
}
/*
*注册节点拥有的服务和节点信息
*/
@Override
public void registServer(NetNode serverNode, List<String> serverList) {
if(serverList == null) {
return;
}
for(String one : serverList) {
if(!serverMap.containsKey(one)) {
List<NetNode> nodeList = new ArrayList<NetNode>();
nodeList.add(serverNode);
serverMap.put(one, nodeList);
}else {
List<NetNode> nodeList = serverMap.get(one);
nodeList.add(serverNode);
}
}
}
/*
*注销节点
*/
@Override
public void logOutServer(NetNode serverNode) {
Set<String> keys = serverMap.keySet();
Iterator<String> keyIter = keys.iterator();
while(keyIter.hasNext()) {
String key = keyIter.next();
List<NetNode> nodeList =serverMap.get(key);
Iterator<NetNode> values = nodeList.iterator();
while(values.hasNext()) {
NetNode one = values.next();
values.remove();
if(nodeList.isEmpty()) {
keyIter.remove();
}
}
}
}
}
RegisterServer
当RegisterServer启动,进行侦听,当侦听到一个提供者的链接,为其产生一个CommucationAndNode作为通信的媒介,CommucationAndNode类继承了commucation,事实上是采用commucation中的socket进行通信,将CommucationAndNode对象放入到轮询通信的池子CommucationPool中,等待轮询处理请求。
在注册中心开启的时候,开启心跳检测,对每个提供者发送验证消息,询问是否Alive.如果在发送消息没有对方回应的情况,就进行注销提供者。
public class RegisterServer implements Runnable{
private int port;
private ServerSocket server;
private boolean goon;
private CommucationPool Cp;
private Register rt;
private rmiServer rs;
private CheckAlive ca;
public RegisterServer() {
Cp = new CommucationPool();
rs = new rmiServer();
rs.setPort(54199);
rt = new Register();
ca = new CheckAlive();
ca.setCp(Cp);
}
public void shudown() {
close();
ca.stopCheak();
rs.close();
}
public CommucationPool getCp() {
return Cp;
}
public void setCp(CommucationPool cp) {
Cp = cp;
}
public void setPort(int port) {
this.port = port;
}
public void close() {
if(goon == false) {
return;
}
goon = false;
if(server != null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
server = null;
}
}
}
public void start() {
if(goon == true) {
return;
}
goon = true;
try {
server = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
rs.start();
new Thread(this).start();
Cp.start();
ca.startCheak();
}
@Override
public void run() {
while(goon) {
try {
Socket socket = server.accept();
//侦听到链接,通过socket形成一个commucationAndNode对象
//相当于生成一个Commucation对象
CommucationAndNode cnd = new CommucationAndNode(socket);
cnd.setReg(rt);
cnd.setRs(this);
//将commucationAndNode 加入到注册中心的轮询池中,等待轮询通信
Cp.add(cnd);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
CommucationPool
存储与提供者通信的类对象CommucationAndNode,CommucationPool采用轮询专门检查每个通信通道是否是消息传输,但是不需要进行消息的处理。判断是否有消息传输的方法在Commucation类中,CommucationAndNode继承了Commucation类,那么自然拥有这个方法,Commucation还负责read和write。Commucation中有dealMessage()抽象方法,由继承它的类CommucationAndNode实现,当然数据的处理自然而然交给CommucationAndNode中的抽象方法实现来完成。
所以在轮询的过程中只需要采用CommucationAndNode类中方法hasMessage()进行判断是否有消息产生,hasMessage()方法返回true或者false,当返回false时,其实在内部已经做了判断,此节点为非法节点,在轮询层将其存储对应节点的通信信道关闭,并且将该节点注销。
如果返回为true,则在轮询层继续轮询,而在true返回前,hasMessage()内部已经开启了一个线程进行与提供者的通信,顺其自然的读取由CommucationAndNode类进行数据的处理,由Commucationhon类进行底层数据读取和发送,
public class CommucationPool implements Runnable{
private static final List<CommucationAndNode> nodeList
= new CopyOnWriteArrayList<CommucationAndNode>();
private boolean goon;
public CommucationPool() {
}
public void add(CommucationAndNode node) {
nodeList.add(node);
}
public List<CommucationAndNode> getCommucationList(){
return nodeList;
}
public void start() {
if(goon == true) {
return;
}
goon = true;
new Thread(this).start();
}
@Override
public void run() {
while(goon) {
if(nodeList.size() > 0) {
for(CommucationAndNode one : nodeList) {
//判断当前的commucation是否有通信的交流
boolean has = one.hasMessage();
if(!has) {
remove(one);
}
}
}
}
}
public void remove(CommucationAndNode node) {
nodeList.remove(node);
}
}
CommucationAndNode
/*
*继承于Commucation
*实现其抽象方法dealMessage(),errorDrop(),
*基于Commucation之上,专门进行数据响应的处理,和异常的处理
*相当于Handler的功能。
*/
public class CommucationAndNode extends Commucation{
private NetNode node;
private Register reg;
private RegisterServer rs;
private static final Type type = new TypeToken<List<String>>(){}.getType();
public CommucationAndNode(Socket socket) {
super(socket);
node = new NetNode();
this.node.setIp(socket.getInetAddress().toString().substring(1));
}
public RegisterServer getRs() {
return rs;
}
public void setRs(RegisterServer rs) {
this.rs = rs;
}
public INetNode getNode() {
return node;
}
public Register getReg() {
return reg;
}
public void setReg(Register reg) {
this.reg = reg;
}
/*
*处理注册事件
*/
public void dealRegister(Message message) {
String action = message.getAction();
int nodeMessage = Integer.valueOf(message.getPara());
this.node.setPort(nodeMessage);
List<String> serverList = makePar.gson.fromJson(action, type);
reg.registServer(node, serverList);
}
/*
*处理注销事件
*/
public void dealLogout() {
reg.logOutServer(node);
}
/*
*根据在Commucation层传递的conventMessage消息
*进行对应的消息处理
*/
@Override
void dealMessage(Message conventMessage) {
dealCommand.dealAllCommand(this, conventMessage);
}
@Override
void errorDrop() {
//当服务端异常宕机,
//进行节点的删除,
dealLogout();//先在服务注册表中删除该服务节点
rs.getCp().remove(this);//在轮询表中删除
}
}
Commucation
此类为通信底层的类,专注于read和write,因为其包含有socket对象,所以是检查通道是否消息的关键,我们采用的是socket.getInputstream().available().此方法虽然在每次进行轮询进行通道检查的时候,或根据有无数据进行返回。但是无法检测到对端是否掉线,这是一个缺陷,所以我们在注册中心采用了心跳检测的方式来弥补。
防止那些已经掉线的提供者,不能及时的被发现,导致可能作为消费者的服务列表的一员,给消费者带来请求上的时延。
public abstract class Commucation implements Runnable{
private static int MAX_UDERTAKE_TIME = 50;
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private boolean rightNode;
private int MaxUdertakeTime;
private long BUILD_TIME;
private boolean readed;
public Commucation(Socket socket) {
//记录建立通信信道时的当前时间
//目的是为了检测链接者是否是非法链接者
BUILD_TIME = System.currentTimeMillis();
this.socket = socket;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
setMaxUdertakeTime();
} catch (IOException e) {
e.printStackTrace();
}
}
public Socket getSocket() {
return socket;
}
public void setMaxUdertakeTime() {
this.MaxUdertakeTime = MAX_UDERTAKE_TIME;
}
public void setMaxUdertakeTime(int times) {
this.MaxUdertakeTime = times;
}
public void setReaded() {
readed = false;
}
/*
*判断是否有消息传输
*采用current来进行第一轮询的时间,wait通道建立到第一次通道被轮询的时间差
*当wait的时间超过默认的等待时间,并且连接者一次也没有发送过消息,将将该
*链接者剔除
*但凡在默认的等待时间内发过一次消息,之后不管是否超时,都认为是正常的链接者
*/
public boolean hasMessage() {
long current = System.currentTimeMillis();
long wait = current - BUILD_TIME;
try {
if(dis.available() > 1) {
//判断有消息,设置为正常节点
this.rightNode = true;
if(!readed) {
new ThreadPool().Execute(this);
}
readed = true;
return true;
}else {
//当一次消息都没有发送过,并且超出默认的等待时间
//返回false,交给轮询层进行节点删除
if(!rightNode && wait >= MaxUdertakeTime) {
return false;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
public void sendMessage(String check) {
try {
dos.writeUTF(check);
} catch (IOException e) {
}
}
public void sendMessage(Message message) {
try {
dos.writeUTF(message.toString());
} catch (IOException e) {
errorDrop();
}
}
abstract void dealMessage(Message conventMessage);
abstract void errorDrop();
@Override
public void run() {
try {
String message = dis.readUTF();
setReaded();
dealMessage(new Message().parser(message));
} catch (IOException e) {
errorDrop();
}
}
}
CheckAlive
心跳检测:
采用的是一个定时器来控制心跳检测,功能就是为了检查提供者的健壮性,能够实时的获取到提供者的信息,采用心跳检测通过检查服务提供者来保证给消费者提供相对有效的服务节点。
public class CheckAlive {//在注册中心一启动的情况下,就开始进行检测功能
private CommucationPool cp;
private Timer Timer;
public CheckAlive() {
Timer = new Timer();
Timer.setIevent(new check());
}
public void setCp(CommucationPool cp) {
this.cp = cp;
}
public void startCheak() {
Timer.start();
}
public void stopCheak() {
Timer.stop();
}
class check implements IEvent{
@Override
public void dealEvent() {
List<CommucationAndNode> nodeList = cp.getCommucationList();
for(CommucationAndNode one : nodeList) {
one.sendMessage(new Message().setCommand(EnetMessage.ON_ALIVE));
}
}
}
}
服务消费者
Consumer
服务消费者通过链接注册中心进行服务列表的获取,同时还需要进行负载均衡进行服务节点的选择。
服务消费者从提供者地址列表中,基于负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
对于负载均衡来说放在服务消费端,对于注册中心来讲,注册中心负责服务的注册与查找,服务提供者和消费者只在启动时与注册中心交互,注册中心的功能单一,压力较小。
注册中心宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表,消费者进行不断连接注册中心,直到连接上为止,对于消费者来说不会有太大的影响,在链接上重新获取列表之前,消费者还可以正常的调用服务。
public class Consumer {
private static int PORT = 54188;
private static String IP = "192.168.230.1";
private rmiCilent rc;
private CilentProxy cp;
private INodeSelect nodeSelect;
private String currentService;
private LocalCache lc;
private CacheUpdate cacheupdate;
public Consumer() {
this.cp = new CilentProxy();
rc = new rmiCilent();
rc.setPort(PORT);
rc.setId(IP);
cp.setRmicilent(rc);
lc = new LocalCache();
defaultInit();
}
public void start() {
cacheupdate = new CacheUpdate(this);
cacheupdate.startUpdtate();
}
/*
*采用负载均衡策略,选取合适服务节点
*/
public NetNode getServerNode(String resorceName){
this.currentService = resorceName;
if(!lc.containsService(resorceName)) {
IRegister ir = this.cp.getProxy(IRegister.class);
lc.saveServerList(resorceName, ir.getServerList(resorceName));
}
return nodeSelect.getProperNode(lc.getServiceList(resorceName));
}
/*private方法 供getServerNode方法调用
*通过服务名称获取服务节点列表
*/
private List<NetNode> getNodeList(String resourceName){
IRegister ir = this.cp.getProxy(IRegister.class);
return ir.getServerList(resourceName);
}
/*
*更新节点的缓存列表
*/
public void UpdateLocalCache() {
Collection<String> serviceList = lc.getAllService();
if(serviceList != null) {
Iterator<String> list = serviceList.iterator();
while(list.hasNext()) {
String service = list.next();
lc.saveServerList(service, getNodeList(service));
}
}
}
private void defaultInit() {//采用默认节点选择和分配策略
nodeSelect = new NodeSelect();
}
public void setNodeSelect(INodeSelect nodeselect) {
nodeSelect = nodeselect;
}
public INodeSelect getNodeSelect() {
return nodeSelect;
}
class RequstNodeException implements ICilentException{
@Override
public void peerAbnormalDrop() {
getServerNode(currentService);
}
}
}
CacheUpdate
进行消费者缓存列表的定期更新工作,目的就是实时提供给消费者有效的服务节点 。
/*
* 对消费者本地的缓存列表进行定期的更新
* 当注册中心宕机时,可根据缓存列表进行找取服务进行调用
* 当服务器宕机的时候,由于列表的不断更新,减少了因为列表中服务器端都链接不上为造成
* 的消费者在一段时间内服务无法响应的情况。
*/
public class CacheUpdate {
private Consumer consumer;
private Timer timer;
public CacheUpdate(Consumer consumer) {
timer = new Timer();
timer.setIevent(new Update());
}
public void startUpdtate() {
timer.start();
}
public void stopUpdate() {
timer.stop();
}
class Update implements IEvent{
public Update() {
}
@Override
public void dealEvent() {
consumer.UpdateLocalCache();
}
}
}
LocalCache
这是消费者本地缓存的节点列表,用来保存每项服务对应的服务节点列表。
public class LocalCache {
private static final Map<String, List<NetNode>> localMap = new ConcurrentHashMap<String, List<NetNode>>();
public LocalCache() {
}
public void saveServerList(String seviceName, List<NetNode> nodelist) {
localMap.put(seviceName, nodelist);
}
public List<NetNode> getServiceList(String serviceName) {
return localMap.get(serviceName);
}
public boolean containsService(String serviceName) {
return localMap.containsKey(serviceName);
}
public Collection<String> getAllService(){
return localMap.keySet();
}
}
NodeSelect
此类实现了INodeSelect,用于服务消费者端的节点选择。同样也是负责均衡的策略,负载均衡的策略为了减轻服务端的压力,可以进行随机的挑选服务节点,也可以采用循环的方式进行。
策略只是相对,而不是绝对。其实都存在一定的缺陷,只是针对不同的场景我们采用不同的策略罢了。
如果采用的是循环进行选择,当多个客户端选择恰巧是同一台服务器,那么在进行并发发文服务器的时候,会出现服务器压力的问题。
可以采用随机数的方法,进行随机选取,但是若随机的比较均匀和轮询的方式差不多,但可能造成并发的的可能性相对小一些。对于随机和轮询的方法
可以采用最小链接次数,那么此时服务器节点需要添加条件为链接次数,我们进行筛选服务节点被链接次数最少的服务器,但是也存在一定的缺陷,如果有像个服务器,一个性能相对较好,而另一个性能相对较差,如果根据链接次数少儿选择了性能差的服务器,那么无疑对服务器来说是一种压力,并且会是性能好的服务器造成资源的浪费。
可以采用加权的方式进行链接,加权随机法也根据服务器的配置,系统的负载分配不同的权重,对于服务器的性能差异性比较大的话,能起到一定的缓解作用,是比较适合的场景。
对于轮询和随机来说,比较适合服务器性能相差不大的情况。由于我采用的NetNode的模型只是简单的拥有IP和Port,所以采用了最简单的轮询的手法。
INodeSelect
public interface INodeSelect {
NetNode getProperNode(List<NetNode> nodeList);
}
NodeSelect
public class NodeSelect implements INodeSelect{
private static int chooseNum;
public NodeSelect() {
chooseNum = 0;
}
@Override
public NetNode getProperNode(List<NetNode> nodeList) {
if(chooseNum > nodeList.size()) {
chooseNum = 0;
}
return nodeList.get(chooseNum++);
}
}
服务提供者
Provider
在此服务发现的框架中,provider相当于一个C/S的一个客户端, 该类也是提供者的核心类,采用长连接的方式与注册中心建立连接和通信请求。
功能:进行服务的注册和注销
当注册中心宕机之后,服务提供不断进行链接注册中心,当连接成功后,重新注册服务。
public class Provider {
private rmiCilent rc;
private static int PORT = 54200;
private static String IP = "127.0.0.1";
private CilentProxy cp;
private int RegisterPort = PORT;
private String RegisterIp = IP;
private int selfPort;
private static NetNode self = new NetNode();
static {
self.setIp(getSelfIp());
self.setPort(54188);
}
public Provider() {
this.cp = new CilentProxy();
this.rc = new rmiCilent();
this.rc.setId(RegisterIp);
this.rc.setPort(RegisterPort);
this.cp.setRmicilent(this.rc);
}
public void setPort(int selfPort) {
self.setPort(selfPort);
}
public int getPort() {
return self.getPort();
}
/*
*本地资源注册方法
*/
public void register(String name) {
List<String> list = new ArrayList<String>();
list.add(name);
IRegister ir = cp.getProxy(IRegister.class);//进行短连接注册中心,进行注册
ir.registServer(self, list);
}
/*
*本地资源注销方法
*/
public void LogoutResource() {
IRegister ir = cp.getProxy(IRegister.class);
ir.logOutServer(self);
}
public void registeAll(List<String> nameList) {
IRegister ir = cp.getProxy(IRegister.class);//进行短连接注册中心,进行注册
ir.registServer(self, nameList);
}
/*
*获取到本地IP
*/
public static String getSelfIp() {
try {
InetAddress address = InetAddress.getLocalHost();
String ip = address.getHostAddress();
return ip;
} catch (UnknownHostException e) {
e.printStackTrace();
}
return null;
}
}
Conversation
同注册中心的CommucationAndNode的功能相同,属于通信会话层,用来处理注册中心发送的消息。
同时可以处理与注册中心会话过程中产生异常的情况。
public class Conversation extends Providercommuncation{//会话层
private Provider cilent;
private boolean errorLink;
Conversation(Provider cilent, Socket socket) {
super(socket);
setCilent(cilent);
}
public boolean isErrorLink() {
this.errorLink = true;
return errorLink;
}
public void setErrorLink(boolean errorLink) {
this.errorLink = errorLink;
}
public Provider getCilent() {
return cilent;
}
public void setCilent(Provider cilent) {
this.cilent = cilent;
}
@Override
public void errorPeer() {
cilent.connectAgain();
}
@Override
public void dealMessage(Message message) {
sendMessage(new Message().setCommand(EnetMessage.ON_ALIVE));
return;
}
}
Providercommuncation
此类和注册中心的Commucation的功能相同,属于通信中的传输层,进行read和write的操作简单操作。
public abstract class Providercommuncation implements Runnable {
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private volatile boolean goon;
Providercommuncation(Socket socket) {
this.socket = socket;
goon = true;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
new Thread(this).start();
}
public boolean isGoon() {
return goon;
}
public void sendMessage(Message message) {//向对端发送消息,具体的内容是由上一层决定,本层只负责传送
try {
dos.writeUTF(message.toString());
} catch (IOException e) {
close();//对端出现异常,关闭自己
}
}
public abstract void dealMessage(Message message);//对于对端发来消息的处理
public abstract void errorPeer();
@Override
public void run() {//侦听到一个客户机开启一个线程
while (goon) {
String message = null;
try {
message = dis.readUTF();//不管是对端异常掉线还是自己关闭自己都会触发异常
dealMessage(new Message().parser(message));
} catch (IOException e) {
//处理异常掉线的问题
if(goon == true) {
goon = false;//对端异常掉线,表明已经获取不到对方发的消息
errorPeer();//交给服务器或者客户端去处理
}
}//对于信道来说,只是负责信息的收和发,对信息的处理不做处理
}
}
}
开头已经说过此服务发现的框架是采用两个短连接的形式,一个长连接的形式。
注册中心和服务提供者
也可以采用短连接的方式,因为对于服务提供者来说不会进行频繁的注册和注销的的动作,而我们采用的长连接的方式,因为不想根据一个服务提供端建立线程保持持久的通信,我们采用了轮询的方式,采用轮询方式的同时因为不能及时的检测到服务提供者,在注册中心采用了心跳检测的方式来检查服务提供者的健壮性,感觉像是自己挖了一个坑,自己又填上的感觉。所有也可以采用短连接加心跳检测的方式。
注册中心和服务消费者
也可以采用长连接的方式。
开头说过长连接适用于链接次数相对较少的时候,为什么现在又说可以采用长连接的方式。
我猜想我们对于长连接的概念都不陌生,并且我们平时大多数时编程采用的长连接模式都是一个链接请求开辟一个线程进行维持通信,对于一个链接一个线程的情况。我们都知道,每个线程拥有自己独自的数据结构栈,消耗系统内存,并且随着线程数量的增加,会导致系统话费大量的时间来处理线程上下问转换和线程管理,拥有较少的时间来处理链接任务。后来我们采用的长连接的模式会基于线程池的模式,是可以解决产生大量线程的问题,但是如果线程池创建的线程过少,对于消费者来说可能等待很长时间才能获取服务,我们也采用过轮询的方式。
但是对于上述来说都是我们平常使用的长连接的模式采用的是阻塞IO建立的长连接,有一定的局限性,我们完全可以采用IO复用的模式,同样也可以建立长连接的模式,而且开头说过,不管是长连接还是短连接都是面向连接的,所以必定在链接前先要进行TCP链接,而对于消费者来说,获取服务列表的操作相对提供者注册注销服务额操作较为频繁,所以在消费者在进行短连接从注册中心获取服务列表的时候,会不断的进行建立连接和断开链接的操作。采用长连接的话可以减少TCP握手,同时采用IO复用的方式,减少了原本长连接带来的大量线程开销问题,而且对于和消费者通信采用的是系统轮询的方式,可以采用一个线程管理多个通道,相对节省CPU的资源。
当然对于长连接的实现方式有很多,也可以采用异步IO的方式进行实现。目的就是优化我们之前长连接的线程模式所带来的缺陷。让我们能够使用长连接的方式来维持高性能的通信模式。
服务发现的框架中三者之间的链接关系有不同的实现方式,只不过每种实现方式应用情况不同,但其核心的功能不会变化。