手写框架之服务发现

本文详细探讨了服务发现的思想,包括服务提供者、消费者和注册中心的角色。服务发现能动态匹配服务地址,提高应用容灾性。文中介绍了服务提供者通过长连接与注册中心交互,注册和注销服务,并讨论了负载均衡策略。此外,还阐述了服务消费者通过RPC与注册中心获取服务地址,以及如何处理注册中心宕机的情况。
摘要由CSDN通过智能技术生成

目录

概述

框架

服务发现

服务发现的基本思想

代码及详解

通信的基本类

ENetCommand 

NetMessage

NetNode

注册中心&提供者&消费者

注册中心与提供者

注册中心与服务消费者

代码模拟及详解

服务提供者

ProviderClient

IClientAction

服务消费者

CustomerServerClient

INetNodeSelector

负载均衡详述

注册中心

RegistryCenter

ProviderServer

PollList

ProviderNodePool

RegistryAction

CheckAlive

最后


概述

框架

框架,于我而言,就好似于一间房的框架,其外部是规范统一的,其内部是可以装饰任意风格的。就好像有木结构房子架构、砖混结构的、钢筋混凝土架构的,以及现在所听到的3D打印的房架构都均是立体的建筑框架。而内部的装饰则是可以随个人的喜好变化的。概括下来框架就是给人感觉的看似呆滞但又活泼的词汇。就好像我们在写Java代码的时候要求要给外部统一使用的接口(类比于墙体架构),但内部可由各自需求选择可使用的方案(类比于个人装饰)。从而实现了软件工程中锁提出的高内聚低耦合的概念。

而这些在我们现在对各个功能的需求演变,我们需要将以前所有代码冗杂在一起的进行拆分,变成一个个相对较小且独立的功能单元,每个单元专注于一个或几个功能合为一种服务,每个服务之间进行低耦合,服务又组为一个项目。

服务发现

1.服务发现的思想解说:

其中服务为一,发现服务为二。

即可以理解为有提供服务的一方,和有需要发现服务的需求方。其中还有一个中心管理调节着两方(就好像婚介所,有想娶的找婚介所有想嫁的找婚介所)服务提供方在中心注册服务,消费方在中心寻找服务的这种关系。

2.为什么有服务发现:

(通过上面的婚介所例子大家应该有一丝丝自己的理解吧)

在传统的项目中,每个服务都是直接被部署到相关的服务器上,也就是相当于定死了服务的地址,或出现变化需要人工及时的修改配置,但是在现在这样一个分布式时代背景下,一种服务往往不是对应一台服务器,而是有代理服务器去随机动态选择集群中的一台服务器。这时定死的服务地址当然是会被淘汰的。由此服务发现产生了,用户只用通过某种服务标签去中心寻找需要的服务,由中心自动匹配。不仅仅由此效果,服务发现还提高了应用的容灾性。在传统的当某个服务器宕机,其所注册的服务必然也不能使用,若通过服务发现,在发现此问题时可以重新寻找新的拥有相同服务功能的服务器来继续进行。

服务发现的基本思想

在这里插入图片描述

其中:

***   三方: 服务提供方 服务消费方 注册中心

*  注册中心负责维护服务提供者和服务消费者之间的联系(本方案用Map管理)。

  • 注册中心可以接受服务提供者的注册注销
  • 注册中心可以接受服务消费者的请求服务需求
  • 注册中心可以通过策略选择清除已失效的服务节点
  • (一开始博主是将要发送节点的策略也放在中心,不过后来放在了消费端,后面会提到)

*   服务提供者在需要注册服务时主动与注册中心联系

  • 服务提供者与注册中心是长连接(考虑到服务提供者与注册中心之间的对应连接少,且注册中心需要实时的观察服务的在线情况,但在考虑到服务提供者长连接占用资源其中还使用的轮询池,后面会提到)
  • 服务提供者可以向注册中心注册自己的服务
  • 服务提供者可以向注册中心注册自己的服务
  • 若注册中心宕机则一直等待注册中心上线

*   服务消费者在需要服务时与注册中心联系

  • 服务消费者与注册中心是短连接RPC
  • 服务消费者向注册中心请求服务

(消费方与服务方是短连接)

(关于RPC有关文档可参考RMI(Java的RPC))

代码及详解

通信的基本类

ENetCommand 

/**
 * 枚举类<br>
 * 通信协议中的命令;
 * 
 * @author quan
 */
public enum ENetCommand {
	//注册
    REGISTRY,
    //注销
    OUT,
    //在线
    IS_ON_LINE,
    //注册失败
    REGISTRY_FAIL,
    //注销失败
    OUT_FAIL,
    //注册成功
    REGISTRY_SUCCESS,
    //注销成功
    OUT_SUCCESS, 
}

NetMessage

/**
 * 消息转换类<br>
 * 1、将消息类型与消息内容整合成json字符串;<br>
 * 2、将收到的字符串转化为的对象;<br>
 * 3、用来规范通信的协议;<br>
 * 
 * @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();
	}
}

NetNode

/**
 * 结点类<br>
 * 用来保存通信结点的结点信息;
 * 
 * @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()时若没有消息则造成线程的阻塞,而若当服务提供者数量庞大时,需要维护的数量很大即开启线程很多,而且大多线程出去阻塞状态浪费线程资源。而轮询的方式可以减少线程开启的数量,虽然可能会造成其它的影响,比如轮询会造成服务响应时间变慢,但考虑到

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值