时间记录:2019-7-24
在分布式系统中有很多的子系统,而子系统之间进行数据的交互会通过远程过程调用**(RPC)**来进行数据的交互的,那么在远程过程调用中需要注意到什么呢?其实最主要的就是传输的速度和使用的速度。我们传输的数据的过程和拿到数据进行使用的过程。既然是远程调用那么离不开远程的对象,那我们的客户机是怎么知道远程的对象呢?远程的对象怎么进行调用呢?客户机怎么知道这个远程对象在内存中是什么样子呢?如何使用这个远程对象呢?
以下基于java的RMI来描述RPC的实现【个人对rpc的设计有很多疑问,未得到验证很难受】
基本实现思路
首先rmi分为服务端和客户端,也就是服务端提供远程对象,而客户端根据远程对象的一个ID来获取到这个对象的数据,然后在客户端进行执行。客户端进行调用的请求,服务端将执行的结果返回给客户端。这里的过程是直接通过某类进行执行的,比reset方式跟简单,且对数据的转换跟方便。这里我们主要注意到几点。
1:客户端和服务端都存在实际的类(即需要被调用的)
2:服务端注册了对应的服务且被暴露出来
3:客户端对服务的访问正确,且服务端存在实例
4:客户端和服务端的代理,即进行序列化数据传输的代理
5:数据的格式【对象序列化?全部都是如此?】
先看简单例子然后具体分析代码如何实现的
服务端
package com.huo.rmi;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RmiServer {
public static void main(String[] args) {
try {
HelloInterface helloInterface = new HelloInterfaceImpl();
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
registry.bind("hello", helloInterface);
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端
package com.huo.rmi;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RmiClient {
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.getRegistry("localhost");
HelloInterface remoteMath = (HelloInterface)registry.lookup("hello");
remoteMath.sayHello();
}catch(Exception e) {
e.printStackTrace();
}
}
}
实际的Remote
package com.huo.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloInterface extends Remote {
public void sayHello() throws RemoteException;;
}
package com.huo.rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloInterfaceImpl extends UnicastRemoteObject implements HelloInterface{
protected HelloInterfaceImpl() throws RemoteException {
super();
// TODO Auto-generated constructor stub
}
@Override
public void sayHello() throws RemoteException{
// TODO Auto-generated method stub
System.out.println("Hello world");
}
}
执行结果显示
服务端:
Hello world
客户端:
Hello world toll
由这里可以知道,具体的方法是在服务端进行执行的,然后将return的数据返回到客户端
注:在进行远程调用的时候会判断Method的返回类型是什么,如果是Void类型那么不进行通信【这里
这种方式我们是不是可以做成通知类的东西?】
具体的执行过程分析
我们知道在服务端保存了一个具体的对象,那么这个对象是怎么存储在服务端的呢【如何去发现这个对象】,我们之前提到了Remote的接口,这里会通过这个接口对实例进行一个代理,不然如果需要处理所有的类多么痛苦。对象存储在服务端了之后,那么客户端如何发现这个呢?这里就有另外一个关键的点,对象给予一个ID,客户端在使用这个ID发现就可以。那么发现了这个对象后是怎么调用具体的方法的呢?这是就需要知道Method和具体的对象,然后进行调用,将具体的结果返回给客户端。我们说到服务端存在一个代理对象,同样的客户端也存在有个代理将具体的执行数据返回。这样就感觉是客户端在进行执行,让人方便理解。
具体的代码分析
我们的HelloInterfaceImpl是继承自UnicastRemoteObject,在这里就已经将服务进行暴露出来了,我们看具体的代码。在HelloInterfaceImpl的构造函数中会调用父类的构造函数,在父类的构造函数中会调用**exportObject((Remote) this, port)**的一个方法
protected UnicastRemoteObject(int port) throws RemoteException
{
this.port = port;
exportObject((Remote) this, port); //这里就是将具体的Remote暴露出来的
}
|到下面的方法
public static Remote exportObject(Remote obj, int port)
throws RemoteException
{
return exportObject(obj, new UnicastServerRef(port));
}
|到下面
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}
|到下面
public Remote exportObject(Remote impl, Object data,
boolean permanent)
throws RemoteException
{
Class<?> implClass = impl.getClass();
Remote stub;
try {
stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
} catch (IllegalArgumentException e) {
throw new ExportException(
"remote object implements illegal remote interface", e);
}
if (stub instanceof RemoteStub) {
setSkeleton(impl);
}
//注意看这里,将remote保存在Target中
Target target =
new Target(impl, this, stub, ref.getObjID(), permanent);
ref.exportObject(target);//暴露
hashToMethod_Map = hashToMethod_Maps.get(implClass);//这里将方法保存成Method对象,后面在收到客户端的方法名然后进行查找,返回Method对象
return stub;
}
|到下面
public void exportObject(Target target) throws RemoteException {
ep.exportObject(target);
}
|到下面
public void exportObject(Target target) throws RemoteException {
/*
* Ensure that a server socket is listening, and count this
* export while synchronized to prevent the server socket from
* being closed due to concurrent unexports.
*/
synchronized (this) {
listen(); //这里的listener就是将这个对象服务暴露出去
exportCount++;
}
/*
* Try to add the Target to the exported object table; keep
* counting this export (to keep server socket open) only if
* that succeeds.
*/
boolean ok = false;
try {
//这里是将target保留在服务端的对象表中,供查询找到具体的对象
super.exportObject(target);
ok = true;
} finally {
if (!ok) {
synchronized (this) {
decrementExportCount();
}
}
}
}
|到下面 看监听部分
private void listen() throws RemoteException {
assert Thread.holdsLock(this);
TCPEndpoint ep = getEndpoint();
int port = ep.getPort();
if (server == null) {
if (tcpLog.isLoggable(Log.BRIEF)) {
tcpLog.log(Log.BRIEF,
"(port " + port + ") create server socket");
}
try {
server = ep.newServerSocket();
/*
* Don't retry ServerSocket if creation fails since
* "port in use" will cause export to hang if an
* RMIFailureHandler is not installed.
*/
//给定一个Runnable【AcceptLoop】,执行任务
Thread t = AccessController.doPrivileged(
new NewThreadAction(new AcceptLoop(server),
"TCP Accept-" + port, true));
t.start();
} catch (java.net.BindException e) {
throw new ExportException("Port already in use: " + port, e);
} catch (IOException e) {
throw new ExportException("Listen failed on port: " + port, e);
}
} else {
// otherwise verify security access to existing server socket
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkListen(port);
}
}
}
|到下面,看具体的任务
public void run() {
try {
executeAcceptLoop();
} finally {
try {
/*
* Only one accept loop is started per server
* socket, so after no more connections will be
* accepted, ensure that the server socket is no
* longer listening.
*/
serverSocket.close();
} catch (IOException e) {
}
}
}
|继续看executeAcceptLoop方法
private void executeAcceptLoop() {
if (tcpLog.isLoggable(Log.BRIEF)) {
tcpLog.log(Log.BRIEF, "listening on port " +
getEndpoint().getPort());
}
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
/*
* Find client ho