我要學好分佈式-RMI通訊框架


title: 我要學好分佈式-RMI通訊框架
date: 2018-07-26 19:28:30java

tags: [技術,我要學好分佈式]

分佈式框架是最近幾年的熱門。但是要想理解分佈式框架着實不易,爲了努力跟上時代潮流,特此開了一個專題,起名「我要學好分佈式」,經過博客來分享一下個人學習過程,加深我對分佈式總體框架的理解。git

想要解鎖更多新姿式?請訪問個人博客github

什麼是RPC

英文就不說了。中文名遠程進程調用協議。顧名思義,客戶端在不知道細節的狀況下,能夠調用遠程計算機的api,就像是調用本地方法同樣。segmentfault

RPC協議是一個規範。主流的PRC協議有DubboThrifRMIWebserviceHessainapi

他又一個很是大的特色,網絡協議和網絡IO對於調用端和服務端來講是透明的(動態代理)安全

一個RPC框架包含的要素:服務器

1532605493344

RMI

RMI(remote method invocation)  , 能夠認爲是RPC的java版本 網絡

RMI使用的是JRMP(Java Remote Messageing Protocol), JRMP是專門爲java定製的通訊協議,因此他是純java的分佈式解決方案 。注意,這個RMI已經老舊過期了。框架

RMI Demo

  1. 先寫個測試用的遠程接口,注意接口要拋異常
public interface ISayHello extends Remote {
    public String satHello(String name) throws RemoteException;
}

​ 2.實現遠程接口,而且繼承:UnicastRemoteObjectsocket

public class SayHelloImpl extends UnicastRemoteObject implements ISayHello{

    protected SayHelloImpl() throws RemoteException {
    }

    public String satHello(String name) throws RemoteException {
        return "hello," + name;
    }
}

​ 3.建立服務器程序: createRegistry方法註冊遠程對象

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class HelloServer {
    public static void main(String[] args) {
        try {
            ISayHello sayHello =new SayHelloImpl();
            LocateRegistry.createRegistry(8888);
            Naming.bind("rmi://localhost:8888/sayhello",sayHello);
            System.out.println("server start success");
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }

​ 4.建立客戶端程序

public class HelloClient {
    public static void main(String[] args) {
        try {
            ISayHello iSayHello = (ISayHello) Naming.lookup("rmi://localhost:8888/sayhello");
            System.out.println("hello");
        } catch (NotBoundException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

RMI調用過程

1532610569381

流程:

1.去註冊中心註冊,server端啓動服務。

2.註冊中心聯繫stub(存根)。stub用於客戶端 ,在j2ee中是這麼說的:爲屏蔽客戶調用遠程主機上的對象,必須提供某種方式來模擬本地對象,這種本地對象稱爲存根(stub),存根負責接收本地方法調用,並將它們委派給各自的具體實現對象

3.server註冊對象,而後返回註冊對象

4.客戶端訪問註冊中心,(動態代理)返回stub對象

5.stub(存根)遠程調用skeleton (骨架 )

6.skeleton 調用相應接口

1532610883598

源碼

讓我看看核心的註冊服務的源碼實現

1532612686999

public RegistryImpl(final int var1) throws RemoteException {
        this.bindings = new Hashtable(101);
        //安全認證
        if (var1 == 1099 && System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                    public Void run() throws RemoteException {
                        LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
                        RegistryImpl.this.setup(new UnicastServerRef(var1x, (var0) -> {
                            return RegistryImpl.registryFilter(var0);
                        }));
                        return null;
                    }
                }, (AccessControlContext)null, new SocketPermission("localhost:" + var1, "listen,accept"));
            } catch (PrivilegedActionException var3) {
                throw (RemoteException)var3.getException();
            }
        } else {
            //初始化遠程引用UnicastServerRef對象
            LiveRef var2 = new LiveRef(id, var1);//《--------------------------
            this.setup(new UnicastServerRef(var2, RegistryImpl::registryFilter));
        }

    }

點進UnicastServerRef,找出實現的關係~

1532613044628

點進setup方法,用idea反編碼

public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {
        Class var4 = var1.getClass();

        Remote var5;
        try {
            var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);//《--------------------
        } catch (IllegalArgumentException var7) {
            throw new ExportException("remote object implements illegal remote interface", var7);
        }

        if (var5 instanceof RemoteStub) {//《--------------------------
            this.setSkeleton(var1);
        }

        Target var6 = new Target(var1, this, var5, this.ref.getObjID(), var3);//《------------------------
        this.ref.exportObject(var6);
        this.hashToMethod_Map = (Map)hashToMethod_Maps.get(var4);
        return var5;
    }

發如今建立代理,判斷當前的var是否是遠程stub,若是是就設置骨架。若是不是,就構建target對象。點開代理

public static Remote createProxy(Class<?> var0, RemoteRef var1, boolean var2) throws StubNotFoundException {
        Class var3;
        try {
            var3 = getRemoteClass(var0);//《--------------------------
        } catch (ClassNotFoundException var9) {
            throw new StubNotFoundException("object does not implement a remote interface: " + var0.getName());
        }

        if (var2 || !ignoreStubClasses && stubClassExists(var3)) {
            return createStub(var3, var1);//《--------------------------
        } else {
            final ClassLoader var4 = var0.getClassLoader();
            final Class[] var5 = getRemoteInterfaces(var0);
            final RemoteObjectInvocationHandler var6 = new RemoteObjectInvocationHandler(var1);

            try {
                return (Remote)AccessController.doPrivileged(new PrivilegedAction<Remote>() {
                    public Remote run() {
                        return (Remote)Proxy.newProxyInstance(var4, var5, var6);
                    }
                });
            } catch (IllegalArgumentException var8) {
                throw new StubNotFoundException("unable to create proxy", var8);
            }
        }
    }

發如今調用遠程服務,而後建立了stub。繼續點開getRemoteClass()方法

private static Class<?> getRemoteClass(Class<?> var0) throws ClassNotFoundException {
        while(var0 != null) {
            Class[] var1 = var0.getInterfaces();//《--------------------------

            for(int var2 = var1.length - 1; var2 >= 0; --var2) {
                if (Remote.class.isAssignableFrom(var1[var2])) {
                    return var0;
                }
            }

            var0 = var0.getSuperclass();
        }

        throw new ClassNotFoundException("class does not implement java.rmi.Remote");
    }

發現如今在建立實例

好吧,回到createProxy方法,再看看順着往下走,看看Target var6 = new Target(var1, this, var5, this.ref.getObjID(), var3);

`this.ref.exportObject(var6);`的出口對象方法
public void exportObject(Target var1) throws RemoteException {
        this.ep.exportObject(var1);
    }
public interface Endpoint {
    Channel getChannel();

    void exportObject(Target var1) throws RemoteException;

    Transport getInboundTransport();

    Transport getOutboundTransport();
}
public void exportObject(Target var1) throws RemoteException {
        this.transport.exportObject(var1);
    }

一路點下去,找到了tcp出口的方法。這是屬於協議層的玩意。

public void exportObject(Target var1) throws RemoteException {
        synchronized(this) {
            this.listen();
            ++this.exportCount;
        }

一路點下去,發現listen。

private void listen() throws RemoteException {
        assert Thread.holdsLock(this);

        TCPEndpoint var1 = this.getEndpoint();
        int var2 = var1.getPort();
        if (this.server == null) {
            if (tcpLog.isLoggable(Log.BRIEF)) {
                tcpLog.log(Log.BRIEF, "(port " + var2 + ") create server socket");
            }

            try {
                this.server = var1.newServerSocket();//《--------------------------
                Thread var3 = (Thread)AccessController.doPrivileged(new NewThreadAction(new TCPTransport.AcceptLoop(this.server), "TCP Accept-" + var2, true));
                var3.start();
            } catch (BindException var4) {
                throw new ExportException("Port already in use: " + var2, var4);
            } catch (IOException var5) {
                throw new ExportException("Listen failed on port: " + var2, var5);
            }
        } else {
            SecurityManager var6 = System.getSecurityManager();
            if (var6 != null) {
                var6.checkListen(var2);
            }
        }

發現newServerSocket!!!

綜上,整體流程和上圖同樣。

RMI缺陷

1.基於java,支持語言單一

2.服務註冊只能註冊到我上面分析的那個源碼。註冊中心掛了之後就完了

3.序列化是用java原生那個方法,效率很差

4.服務端底層是bio方式,性能很差

手寫RMI

步驟:

  1. 編寫服務器程序,暴露一個監聽, 可使用socket
  2. 編寫客戶端程序,經過ip和端口鏈接到指定的服務器,而且將數據作封裝(序列化)
  3. 服務器端收到請求,先反序列化。再進行業務邏輯處理。把返回結果序列化返回

源碼:https://github.com/tengshe789...

把源碼發佈到GitHub了,在把源碼粘貼太麻煩了。

結束

此片完了~ 想要了解更多精彩新姿式?請訪問個人我的博客 .

本篇爲原創內容,已在我的博客率先發表,隨後CSDN,segmentfault,juejin同步發出。若有雷同,緣分呢兄弟。趕快加個好友~

相關文章
相關標籤/搜索