HDFS之RPC(1) 概述

6 篇文章 0 订阅


HDFS作为分布式存储系统,各个节点之间的通信必不可少, HadoppRPC是HDFS节点间的通信的基础框架。本文首先将介绍 RPC基本概念及简单的原生实现,随后将基于Hadoop源码分析HadoopRPC的实现细节。

1. RPC框架概述

1.1 RPC工作原理

RPCRemote Procedure Call)即远程过程调用,是一种通过网络从远程计算机程序上请求服务的协议。RPC允许本地程序像调用本地方法一样调用远程计算机上的应用程序,其使用常见的网络传输协议(如TCPUDP)传递RPC请求以及相应信息,使得分布式程序的开发更加容易。

RPC采用客户端/服务器模式,请求程序就是客户端,服务提供程序就是服务器。RPC框架工作原理如图1-1所示,工作流程依次见图中标号~,其结构主要包含以下部分:

  • 请求程序(client functions):请求程序会像调用本地方法一样调用客户端stub程序(如图中),然后接受stub程序的响应信息(如图中)。
  • 客户端stub程序(client stub):客户端stub程序表现得就像本地程序一样,但底层却会调用请求和参数序列化并通过通信模块发送给服务器(如图中);客户端stub程序也会等待服务器的响应信息,将响应信息反序列化并返回给请求程序(如图中⑨⑩)。
  • 通信模块(sockets):传输RPC请求和响应的网络通信模块,可以基于TCPUDP协议(如图中的③⑧)。
  • 服务端stub程序(server stub):服务端stub程序会接收客户端发送的请求和参数(如图中)并反序列化,根据调用信息触发对应的服务程序(如图),然后将服务程序的响应信息序列化并发回给客户端(如图中⑥⑦)。
  • 服务程序(server functions):服务器会接收stub程序的调用请求(如图中),执行对应的逻辑并返回执行结果(如图中)。

    图1-1 RPC框架工作原理示例图

1.2 RPC简单实现

要实现RPC,基本上要解决三大问题:

  • 函数/方法识别
  • 序列化及反序列化
  • 网络通信

下文将使用JDK动态代理、JDK自带的序列化及反序列化以及Java原生Socket通信实现基本demo,初见java全原生代码实现的雏形RPC风采。

我们将maven项目分为3个模块:API、Client、Server。API用于放公共类,Client和Server分别做客户端及服务端。

目标

通过客户端调用服务端的sayHello、getPerson方法。

1.API代码

该模块存放三部分内容:①方法接口的声明;②Java Bean(用于封装网络通信的Entity及测试实例Bean);③序列化及反序列化。主要部分代码如下:
方法的接口声明:

package org.Simple.API;
/*
	公共接口类
*/
public interface HelloService {
	String sayHello(String name);
	Person getPerson(String name);
}

JavaBean如下:

package org.Simple.API;
/*
	公共网络通信类,通过序列化该类,将客户端调用接口、方法、参数、参数类型封装,
	然后服务端反序列化,再通过反射,调取相应实现类的方法。
*/
public class NetModel implements Serializable{
	private static final long serialVersionUID = 1L;
	private String className;
	private String method;
	private  Object[] args ;
	private String[] types;
	// getter and setter ...
}
package org.Simple.API;
/*
	普通公共bean
*/
public class Person implements Serializable{
	private static final long serialVersionUID = 1L;
	private String name;
	private Integer age;
    // getter and setter...
}

通过JDK序列化和反序列化部分代码如下:

package org.Simple.API;
/**
 * JDK自带序列化
 */
public class SerializeUtils {
	/**
	 * 序列化
	 */
	public static byte[] serialize(Object object) throws IOException {
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		ObjectOutputStream outputStream = new ObjectOutputStream(os);
		outputStream.writeObject(object);
		outputStream.flush();
		byte[] byteArray = os.toByteArray();	
		outputStream.close();
		os.close();
		return byteArray;
	}
	/**
	 * 反序列化
	 */
	public static Object deSerialize(byte[] buf) throws IOException, ClassNotFoundException {
		ByteArrayInputStream is = new ByteArrayInputStream(buf);
		ObjectInputStream inputStream = new ObjectInputStream(is);
		Object object =  inputStream.readObject();
		inputStream.close();
		is.close();
		return object;
	}
}

2.RPCClient代码
Client端给服务端发送数据,并反序列化服务端返回的数据。作为RPC的核心部分,代码如下:

package org.Simple.C;
/*
	RPC客户端,也是用Socket与服务端进行通信
*/
public class RPCClient {
	
	 public static Object send(byte[] bs)  {
		try {
			Socket socket = new Socket("127.0.0.1", 9999);
			OutputStream outputStream = socket.getOutputStream();
			outputStream.write(bs);  
			InputStream in = socket.getInputStream();  
			byte[] buf = new byte[1024];
			in.read(buf);
			Object formatDate = SerializeUtils.deSerialize(buf);
			socket.close();  
			return formatDate;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	 
	public static void main(String[] args) {
		HelloService helloService = ProxyFactory.getInstance(HelloService.class);
		System.out.println("say:"+helloService.sayHello("whb"));
		System.out.println("Person:"+helloService.getPerson("whb"));
	}
}

其中,客户端执行的时候调用动态代理方法,代码如下:

package org.Simple.C;
public class ProxyFactory {
	
	private static InvocationHandler handler = new InvocationHandler() {
		
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			
			NetModel netModel = new NetModel();
			
			Class<?>[] classes = proxy.getClass().getInterfaces();
			String className = classes[0].getName();
			
			netModel.setClassName(className);
			netModel.setArgs(args);
			netModel.setMethod(method.getName());
			
			String [] types = null; 
			if(args!=null) {
				types = new String [args.length];
				for (int i = 0; i < types.length; i++) {
					types[i] = args[i].getClass().getName();
				}
			}
			netModel.setTypes(types);
			
			byte[] byteArray = SerializeUtils.serialize(netModel);
			Object send = RPCClient.send(byteArray);
			return send;
		}
	};
	@SuppressWarnings("unchecked")
	public static <T> T getInstance(Class<T> clazz) {
		return (T) Proxy.newProxyInstance(clazz.getClassLoader(), 
				new Class[]{clazz}, handler );
	}
}

主要是利用Proxy.newProxyInstance 实现一个代理对象,其中必须要实现InvocationHandler接口,这个接口中的invoke会拦截到你所调用的任何方法。在invoke方法中得到代理接口的全限定名、调用的方法、参数和参数类型,然后将这些参数封装在NetModel中,再将其序列化成byte数组,通过Client端的send方法将信息发送至Server端。

Server端对于收到的byte数组,经过一系列处理,通过method.invoke执行(具体见RPCServer部分),再将结果返回给Client。

至此,整个过程结束。

3.PRCServer代码

Server端主要实现对收到的数据进行网络传输结构化封装处理并返回给客户端。
核心代码中,处理接收到的byte数组数据,通过反序列化得到传过来的通信类NetModel,然后得到接口名、方法名、参数、参数类型,最后通过JDK反射来调取实现类的方法,并将调取结果序列化为byte数组,然后返回。

package org.Simple.S;
public class RPCServer {
	
	public static void main(String[] args) 
	{ 
		try {
			openServer();
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
	public static void openServer() throws IOException  {
		ServerSocket serverSocket =  new ServerSocket(9999); 
		try { 
			System.out.println("服务开启");
			while(true) {
				Socket socket = serverSocket.accept();
				System.out.println(socket.getInetAddress()+"-connected");  
				InputStream in = socket.getInputStream();
				byte[] buf = new byte[1024];  
				in.read(buf);  
				byte[] formatDate = formatData(buf);
				OutputStream out = socket.getOutputStream();  
				out.write(formatDate);  
				socket.close();  
			}
		} catch (Exception e) {
			e.printStackTrace();
			serverSocket.close();
		}
	}
	public static byte[] formatData(byte[] bs){
		try {
			 //将收到的byte数组反序列化为NetModel类型,然后通过反射调用HelloServiceImpl实现类的方法
            NetModel netModel = (NetModel)SerializeUtils.deSerialize(bs);
            String className = netModel.getClassName();
            String[] types = netModel.getTypes();
            Object[] args = netModel.getArgs();
            
            //获取实现方法中的详细信息
            Class<?> clazz = Class.forName(getPropertyValue("org.Simple.S.HelloServiceImpl"));
            Class<?> [] typeClazzs = null;
            if(types!=null) {
                typeClazzs = new Class[types.length];
                for (int i = 0; i < typeClazzs.length; i++) {
                    typeClazzs[i] = Class.forName(types[i]);
                }
            }
            Method method = clazz.getMethod(netModel.getMethod(),typeClazzs);
            Object object = method.invoke(clazz.newInstance(), args);
            byte[] byteArray = SerializeUtils.serialize(object);
            return byteArray;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

Server对接口实现如下:

package org.Simple.S;
public class HelloServiceImpl implements HelloService {
	public String sayHello(String name) {
		return "hello,"+name;
	}
	public Person getPerson(String name) {
		Person person = new Person();
		person.setName(name);
		person.setAge(18);
		return person;
	}
}

我们分别启动RPCServer、RPCClient,可以看到在Client端能正确得到方法返回结果。

say:hello,whb
Person:Person [name=whb, age=18]

附上该项目github:https://github.com/whbing/RPC 感谢WuBinBin提供博客参考。

上文通过简单的原生代码直观地感受了RPC运行过程。事实上,现有的用于生产的的RPC框架也有很多,如gRPC,及dubbospring cloud中对RPC的实现,它们底层使用的序列化及网络通信组件各有不同,但基本原理类似,在此不再赘述。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值