一、概述
RPC(Remote Procedure Call Protocol)-远程过程调用协议。通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。它假定某种传输协议的存在,如TCP,UDP,为通信程序之间携带信息数据,RPC是hadoop框架运行的基础,如果想精通Hadoop源码,RPC通信机制肯定是回避不了的,这里就先讲解下原理,然后通过一个简单实例来帮助你理解。
其实简单点来理解,就是比如有一个客户端,通过网络调用了服务端的服务,而不用关心服务端的协议。下图展示RPC通信机制的工作原理:
大体的通信流程:
步骤一:客户端调用自己手里的 Stub 程序上相应的接口;
步骤二:客户端侧的 Stub 程序将调用请求信息按照网络通信模块的要求封装成消息包,并交给通信模块以发送到远程服务端;
步骤三:远程服务端的通信模块在接收到消息包以后会将此消息包转发给调度程序;
![](/qrcode.jpg)
步骤四:调度程序会根据消息包中的标识符,再将消息转发给相关的服务端侧的 Stub 程序;
步骤五:服务端侧的 Stub 程序会拆封消息包,形成被调过程的形式,并去调用对应的方法函数;
步骤六:被调用的函数会按照所获参数执行,并将最终结果返回给 Stub 程序;
步骤七:服务端侧的 Stub 程序会将返回的结果打包成消息包,通过网络通信模块逐级地传送给客户端程序。
二、RPC通信实例
这里写了个RPC通信的简单实例,以便帮助大家更好的理解RPC通信,模拟实现用户登录。
实例主要分了三个部分:
1).Service处理端LoginService类,提供服务接口定义与服务实现,真正处理f服务端反射过来的请求,然后将处理结果返回服务端,代码如下:
接口类:LoginService
package com.hadoop.ljs.rpcTest.service;/** * @author: Created By lujisen * @company ChinaUnicom Software JiNan * @date: 2020-04-01 16:05 * @version: v1.0 * @description: com.hadoop.ljs.rpcTest */public interface LoginService { String loginProcess(String name);}
实现类:LoginServiceImpl
package com.hadoop.ljs.rpcTest.service;
/**
* @author: Created By lujisen
* @company ChinaUnicom Software JiNan
* @date: 2020-04-01 16:05
* @version: v1.0
* @description: com.hadoop.ljs.rpcTest
*/
public class LoginServiceImpl implements LoginService {
@Override
public String loginProcess(String name) {
return "logged in sucessfully,hello "+name;
}
}
2).RPC服务端RPCServer类,发布成远程服务,启动监听接收用户请求,转发Service进行处理,并将处理结果返回客户端,中间的数据传输都需要进行序列化,Service转发用到了JDK的动态代理机制。
接口类:RPCServer
package com.hadoop.ljs.rpcTest.server;import java.io.IOException;/** * @author: Created By lujisen * @company ChinaUnicom Software JiNan * @date: 2020-04-01 16:08 * @version: v1.0 * @description: com.hadoop.ljs.rpcTest.server */public interface RPCServer { public void stop(); public void start() throws IOException; public void register(Class serviceInterface, Class impl); public boolean isRunning(); public int getRpcPort();}
实现类:RPCServerImpl
package com.hadoop.ljs.rpcTest.server;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author: Created By lujisen
* @company ChinaUnicom Software JiNan
* @date: 2020-04-01 16:08
* @version: v1.0
* @description: com.hadoop.ljs.rpcTest.server
*/
public class RPCServermpl implements RPCServer {
/*初始化任务调度线程池*/
private static ExecutorService executorPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static final HashMap<String, Class> serviceRegistry = new HashMap<String, Class>();
private static boolean isRunning = false;
private static int rpcPort;
public RPCServermpl(int rpcPort) {
this.rpcPort = rpcPort;
}
public void stop() {
isRunning = false;
executorPool.shutdown();
}
public void start() throws IOException {
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(rpcPort));
System.out.println("SayHelloServer started!!!");
try {
while (true) {
//接收客户端连接,交由TaskRunner去调度具体的函数
executorPool.execute(new TaskRunner(server.accept()));
}
} finally {
server.close();
}
}
public void register(Class serviceInterface, Class impl) {
serviceRegistry.put(serviceInterface.getName(), impl);
}
public boolean isRunning() {
return isRunning;
}
public int getRpcPort() {
return rpcPort;
}
private static class TaskRunner implements Runnable {
Socket clent = null;
public TaskRunner(Socket client) {
this.clent = client;
}
public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
//反序列化客户端请求信息,通过反射调用服务对应的方法,返回执行结果到客户端
input = new ObjectInputStream(clent.getInputStream());
String serviceName = input.readUTF();
String methodName = input.readUTF();
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
Class serviceClass = serviceRegistry.get(serviceName);
if (serviceClass == null) {
throw new ClassNotFoundException(serviceName + " can not found");
}
Method method = serviceClass.getMethod(methodName, parameterTypes);
Object result = method.invoke(serviceClass.newInstance(), arguments);
// 反序列化执行结果,并发送客户端
output = new ObjectOutputStream(clent.getOutputStream());
output.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (clent != null) {
try {
clent.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
3).RC客户端RPCClient类,模拟发送用户登录请求,代码如下:
package com.hadoop.ljs.rpcTest.client;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.net.InetSocketAddress;import java.net.Socket;/** * @author: Created By lujisen * @company ChinaUnicom Software JiNan * @date: 2020-04-01 16:19 * @version: v1.0 * @description: com.hadoop.ljs.rpcTest.client */public class RPCClient { public static <T> T getRemoteProxyObj(final Class<?> serviceInterface, final InetSocketAddress addr) { //这里使用JDk的动态代理,该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 //相关知识: //JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的; // 但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理, // 这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。 //要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。 // 在java中如何用程序去生成一个对象的代理对象呢,java在JDK1.5之后提供了一个"java.lang.reflect.Proxy"类, // 通过"Proxy"类提供的一个newProxyInstance方法用来创建一个对象的代理对象 return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface}, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = null; ObjectOutputStream output = null; ObjectInputStream input = null; try { //实例化socket客户端,通过IP地址和端口连接RPCServer端 socket = new Socket(); socket.connect(addr); output = new ObjectOutputStream(socket.getOutputStream()); //指定远程调用的接口类 output.writeUTF(serviceInterface.getName()); //指定远程调用的接口方法 output.writeUTF(method.getName()); //指定远程调用的参数类型 output.writeObject(method.getParameterTypes()); //指定远程调用的参数列表 output.writeObject(args); // 同步阻塞等待服务器返回应答,获取应答后返回 //等待服务器返回处理结果,这里会阻塞 input = new ObjectInputStream(socket.getInputStream()); return input.readObject(); } finally { if (socket != null) socket.close(); if (output != null) output.close(); if (input != null) input.close(); } } }); }}
4).主函数测试类:RPCTestMain
package com.hadoop.ljs.rpcTest;
import com.hadoop.ljs.rpcTest.client.RPCClient;
import com.hadoop.ljs.rpcTest.server.RPCServermpl;
import com.hadoop.ljs.rpcTest.service.LoginService;
import com.hadoop.ljs.rpcTest.service.LoginServiceImpl;
import java.io.IOException;
import java.net.InetSocketAddress;
/**
* @author: Created By lujisen
* @company ChinaUnicom Software JiNan
* @date: 2020-04-01 16:25
* @version: v1.0
* @description: com.hadoop.ljs.rpcTest
*/
public class RPCTestMain {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try {
RPCServermpl serviceServer = new RPCServermpl(8088);
serviceServer.register(LoginService.class, LoginServiceImpl.class);
serviceServer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
LoginService service = RPCClient.getRemoteProxyObj(LoginService.class, new InetSocketAddress("localhost", 8088));
System.out.println(service.loginProcess("JasonLu1986"));
}
}