企業神奇中間件-RPC之RMI(上)
說了那么久,到底什么是 RMI (Java Remote Method Invocation)呢?我們來看看維基百科的解釋。
In computing, the Java Remote Method Invocation (Java RMI) is a Java API that performs remote method invocation, the object-oriented equivalent of remote procedure calls (RPC), with support for direct transfer of serialized Java classes and distributed garbage-collection.
emmm....,就是一個專門給 Java 提供的一個遠程方法調用,以及進行實現 RPC 的一個 Java API,可以用來遠程傳輸序列化類,以及實現分布式垃圾回收。完全obbqk,咱今天也分成幾個階段來看看RMI,Java Remote Method Invocation,Java 遠程方法調用。具體是怎么玩的,怎么去使用它,又是什么原理。
說走咱就走啊,僅需體驗三番鐘,你揍會甘我一樣,愛上介款 RPC 。 首先,看到這個鏈接。https://github.com/CallMeDJ/BigBanana-rpc.git
小心翼翼打開命令行終端,就那么git clone 一下。
貼心的我已經為你準備好了,你拷貝一下就行了,當然你要是沒有 git 那就下載一個。
git clone https://github.com/CallMeDJ/BigBanana-rpc.git
然后用 JetBrains 的 IntelliJ IDEA import 一下,喏就這個Import Project,瘋狂點下一步就好了。
等 IDEA 的界面打開后,然后右鍵點一下 Provider 的 Run 'Provider.main()'。
再右鍵點一下 Consumer 的 Run 'Consumer.main()'.
然后在 console 控制臺,瞎打一些東西。然后用全身的力氣按一下 Enter ,你會發現,咦,發現有一些數字回顯出來。喔雪特你這么這么厲害??恭喜恭喜,你學會了RMI了。
emmm...好了,歡迎大家來到 RMI 第二層,上套路,講代碼,這上邊這玩意怎么寫出來的呢?
- Service
首先定義一個服務 HelloService ,有一個名字叫 hello 的方法,返回一個 Integer 。
- ServiceImpl
實現它!HelloServiceImpl 實現了 HelloService 的 hello 方法,直接返回字符串的 hashCode。
至于為什么要 繼承 UnicastRemoteObject 呢。文檔上的注釋是這樣的。用來曝露一個擁有對象引用,然后用 JRMP 協議溝通的遠程對象。當然看不懂也沒關系,就當沒看見繼續往下走。
- /**
- * Used for exporting a remote object with JRMP and obtaining a stub
- * that communicates to the remote object.
- *
- Provider
定義一個 Provider,用來對外暴露服務。
先初始化一個 HelloService 服務。
- HelloService helloService = new HelloServiceImpl();
生成一個注冊表中心,用來將服務注冊到這個地方,注意這里一定要先寫,不然后邊的服務就沒法注冊上來了。
- Registry registry = LocateRegistry.createRegistry(8888);
將初始化完的服務,注冊到注冊中心中,這樣,其他地方就可以通過注冊中心獲取到這個服務啦。
- Naming.bind("rmi://127.0.0.1:8888/hello",helloService);
線程池的作用是避免應用被關掉,關鍵代碼其實就上邊那三行。
- Consumer
好了,輪到 Consumer 了。
啦。
尋找一下注冊中心。
- Registry registry = LocateRegistry.getRegistry(8888);
把那個名字叫 hello 的服務找出來
- HelloService masterService = (HelloService)registry.lookup("hello");
把當前 console 的那行值取出來,然后調用一下 HelloService 的服務,打印到 console。
- String command = scanner.nextLine();
- System.out.println(masterService.hello(command));
完美~你可以寫自己基于 RMI 的服務了。隨便找個地方搞一個注冊中心,注冊一大堆服務上去,其他地方就可以通過這個注冊中心調用了了。
好了,歡迎大家來到 RMI 第三層,上原理,講設計。好了,能耐心看到這里的,估計還是對這個技術有點想了解,那么我們繼續講,關于這個 RMI 技術的原理。
還是回到這個圖,我們可以看到其實這個架構還是蠻簡單的。首先 RMI 是基于 socket 技術進行網絡通訊的。其次本地調用的是一個叫 Client stub (存根),而這個存根跟客戶端是處于同一 JVM 的。第三 Server 端其實也有一個一模一樣的存根,從技術上來講,這兩個存根里邊的內容是一摸一樣的,只是從 Server 端下載到了 Client 端。
RMI技術有兩種使用方式,分別是繼承 UnicastRemoteObject 的 由服務端直接提供服務對象類,以及繼承 Activatable 的由注冊中心提供激活的服務對象的方式。
第一種:
1、啟動一個注冊中心,這個注冊中心是用 rmi 協議的,可以啟動在本地的JVM也可以啟動在其他地方。都是使用這個。這就是 RMI 強大的地方所在了,不僅僅 Consumer 和 Provider 可以遠程,連注冊中心都既可以運行在本地,也可以運行在遠程。
- LocateRegistry.createRegistry(8888);
2、Provider 本地啟動一個 SocketServer ,用來進行服務提供。找到注冊中心,然后向注冊中心,以某個服務名字(比如 hello) 將自己注冊到注冊中心中,本地保留一個 stub 存根。
3、Consumer 找到注冊中心,找到后尋求某個服務名字(比如 hello),得到服務端 Provider 的 socket 地址,其實也就是 ip 和 端口。
4、Consumer 用 socket 調用 Provider ,請求某個服務,Provider 用 JRMP(Java Remote Method Protocol)把本地服務的整個 Java 對象進行序列化打個大包,然后寫到 socket 中。
5、Consumer 拿到這個對象流后呢,用 JRMP 進行反序列化,然后創建一個代理對象,交給調用方。
6、調用方拿著這個代理,進行本地調用。
好了,這就結束了第一種了。我們看到上邊的第6點,其實這些遠程調用并不是遠程調用,只是遠程把整個對象下載到本地,然后在本地跑這些方法。所以呢,你如果覺得每次都 hold 住這個代理,就可以了,那你就錯了,一旦 Provider 有任何變更,你這樣干的話,是獲取不到最新的服務的。
雖然我感覺你的聽得云里霧里,但是我不管,我就繼續來說說第二種吧。。第二種會直接把類文件放到注冊中心,然后在恰當的實際進行"激活"。什么叫激活呢,我的理解就是對類進行初始化,然后用序列化的方式交給調用方。
1、啟動一個注冊中心,這個注冊中心是用 rmi 協議的,可以啟動在本地的JVM也可以啟動在其他地方。都是使用這個。這就是 RMI 強大的地方所在了,不僅僅 Consumer 和 Provider 可以遠程,連注冊中心都既可以運行在本地,也可以運行在遠程。
- LocateRegistry.createRegistry(8888);
2、Provider 初始化一個對象,然后類文件和自己的激活ID,激活組ID都放到注冊中心。
3、Consumer 找到注冊中心,找到后尋求某個激活ID,激活組ID的服務,請求進行存根請求。
4、Consumer 用 socket 從請求注冊中心取得存根,注冊中心對類進行初始化(激活),然后 用 JRMP(Java Remote Method Protocol)把激活后的的整個 Java 對象進行序列化打個大包,然后寫到 socket 中。
5、Consumer 拿到來自注冊中心的這個對象流后呢,用 JRMP 進行反序列化,然后創建一個代理對象,交給調用方。
6、調用方拿著這個代理,進行本地調用。
綜上所述,RMI 就是把類下載到本地跑一波。
歡迎大家來到 RMI 第四層,講源碼。好了,下面很枯燥很枯燥,都是源碼,不開心的可以退出了,抓緊時間。要是不想退出,來都來了,看看唄反正也不吃虧。
算了,下次再說了,我要去吃飯了。有人問我既然不寫了為什么不刪掉上邊那段話,我覺得既然字被打出來了那就是有生命的,把它刪掉好像把它殺了似的,不忍心。。。
【本文為51CTO專欄作者“大蕉”的原創稿件,轉載請通過作者微信公眾號“一名叫大蕉的程序員”獲取授權】