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

本文详细介绍了微服务架构中的服务发现原理和实现,包括服务发现的作用、服务提供者与消费者如何与注册中心交互。此外,文章讨论了注册中心的设计,如心跳检测、负载均衡策略,并提出了注册中心宕机的处理方案。最后,作者分享了自实现的RPC框架在服务发现中的应用。
摘要由CSDN通过智能技术生成

概述

微服务

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

服务发现

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;

/**
 * 通信层<br>
 * 1、提供基本的收发信息的手段;<br>
 * 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;
		}
	}
	
	/**
	 * 将整合的消息发送给对端;<br>
	 * 如果通信断裂,则从缓存中移除该socket(抛异常);
	 * @param message
	 * @throws IOException
	 */
	public void sendMessage(NetMessage message) throws IOException {
		dos.writeUTF(message.toString());
	}
	
	/**
	 * 判断是否有可读消息;如果有消息,将消息发送给线程执行;<br>
	 * 如果通信断裂,则从缓存中移除该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;

/**
 * 结点类<br>
 * 用来保存通信结点的结点信息;
 * 
 * @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;

/**
 * 消息转换类<br>
 * 1、将消息类型与消息内容整合成字符串;<br>
 * 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;

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

注册中心

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值