不少人和我同樣,在平時的工做中對於dubbo只是停留於使用階段。甚至不少時候,必需要新開放一個rpc接口,或者新引入一個rpc接口都是要抄以前的配置(全部參數照抄,只是把接口名改一下,而後把id改一下)。其實冷靜下來想一想,咱們正在慢慢朝着剛畢業時最鄙視的人(渾渾噩噩的碼農)轉變了。java
我最近跟公司的另一個小夥伴一塊兒在吃dubbo。他吃完了以後都作了筆記,而我可能看的源碼比較少,吃起來很累,並且可能也沒有那麼深入。不過畢竟這是我第一個吃的比較透的rpc框架,也但願可以留下一些文字,寫一下我吃的整個過程。沒準能給跟我同樣的小菜雞們帶來一點思路,哈哈哈哈。加油!git
你們都知道dubbo是個rpc框架,那麼rpc框架最基礎的實現是什麼呢?在這裏你們能夠先思考一下,如何搭建一個最最簡單的rpc框架?web
咱們使用過dubbo的同窗應該知道,若是A要調用B的方法,那麼必然是依賴了B的包(至少是api包,不然我怎麼知道要調用B的哪一個方法?_其實也能夠不依賴,這個在後面講_)。因此最最簡單的rpc框架應該是A告訴B,我要執行你的哪一個哪一個方法,而後你執行完以後把結果再告訴我。那麼這個方法執行的基礎信息是什麼呢?spring
若是從正向調用方法的邏輯去思考,可能說不清楚到底執行一個方法須要哪些東西。因而咱們能夠經過反射調用的方式來思考這個問題。apache
@Test public void test() { try { Class<?> greetingService = Class.forName("org.apache.dubbo.common.extension.ExtensionTest$GreetingService"); Class<?>[] parameterTypes = new Class[]{String.class, String.class}; Method sayHello = greetingService.getMethod("sayHello", parameterTypes); Object[] parameterObjects = new Object[]{"tom", "18"}; Object result = sayHello.invoke(greetingService.newInstance(), parameterObjects); System.out.println(result); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { e.printStackTrace(); } } static class GreetingService { public String sayHello(String name) { return "hello " + name; } public String sayHello(String name, String age) { return "hello " + name + ":" + age; } } }
經過類名,方法名,參數類型數組(區別方法重載),咱們能夠肯定惟一的一個方法。而當咱們須要調用這個方法的時候,就須要把具體的參數傳進去。後端
我上面寫的代碼顯示瞭如何用一個類(消費者)反射調用另外一個類(提供者)的方法。雖然用的是單元測試的代碼,相信你們也可以理解。不理解的,我以爲。。。這篇文章不適合你。。哈哈哈哈哈api
咱們回到rpc的環節。這時候咱們再繼續思考,最簡單的rpc框架就應該是對於上面代碼的擴展。而上面是單機版,咱們只要把它作成分佈式的,豈不是就成了?沒錯!咱們順着這個思路,很天然而然的就能獲得咱們下面一步要作的事情——把消費者和提供者分離開,兩邊經過網絡傳輸的方式進行交流。數組
說實話我這裏不知道應不該該大段貼代碼,貼太多代碼的話確實會影響觀感。。。由於socket這一塊並非本文要重點分析的,因此我考慮之後決定只貼一些關鍵代碼。本文最後會把個人git地址貼上,你們有興趣的話能夠直接把代碼clone下來看。tomcat
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = new Socket(host, port); OutputStream outputStream = socket.getOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); Class<?>[] paramTypes = new Class[args.length]; for (int i = 0; i < args.length; i++) { paramTypes[i] = args[i].getClass(); } //注意proxy已是代理類了 因此第一個參數要用method.getDeclaringClass().getName() ServiceMetadata serviceMetadata = new ServiceMetadata(method.getDeclaringClass().getName(), method.getName(), paramTypes, args); objectOutputStream.writeObject(serviceMetadata); return getResultFromRemote(socket); } /** * 從遠程服務獲取方法調用結果 * * @param socket 通訊socket * @return 調用結果 * @throws IOException * @throws ClassNotFoundException */ private Object getResultFromRemote(Socket socket) throws IOException, ClassNotFoundException { InputStream inputStream = socket.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); return objectInputStream.readObject(); }
/** * 開始監聽 */ public void doListen() throws IOException, ClassNotFoundException { Integer port = applicationRpcPort == null ? Integer.valueOf(20880) : applicationRpcPort; ServerSocket serverSocket = new ServerSocket(port); keepListening = new AtomicBoolean(true); while (keepListening.get()) { Socket accept = serverSocket.accept(); System.out.println("接收到消息"); InputStream inputStream = accept.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); Object readObject = objectInputStream.readObject(); if (readObject instanceof ServiceMetadata) { //當監聽到一個消費者請求 放到線程池裏面繼續操做 threadPoolExecutor.submit(() -> { Object result = RpcServiceInvoker.doInvoke((ServiceMetadata) readObject); try { OutputStream outputStream = accept.getOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(result); } catch (IOException e) { e.printStackTrace(); } }); } } threadPoolExecutor.shutdown(); }
講到這裏,我這邊會建議你們開始動手了! 真的很重要,若是不動手光看的話,感受是不同的。你們應該能寫出一個最簡單的rpc框架了。網絡
咱們在上一節寫好的框架,只能經過main()或者單元測試調用。咱們的下一步就是須要把咱們的框架嵌入到web應用中,作到服務啓動,咱們的項目就隨之啓動(跟dubbo同樣)。
考慮到咱們絕大多數的後端服務都會集成spring這個老大哥,並且引入spring以後也會方便咱們直接經過web請求驗證咱們的結果。因此咱們能夠經過引入spring-boot-starter-web把consumer作成一個controller,而後把provider就藏在另外一個tomcat項目裏面。
接下去咱們要作的事情,就是把從provider端引入的接口經過代理的方式,走遠程調用而不走接口直接調用(直接調用會報錯的,由於在consumer端只能拿到接口,拿不到實現)。
consumer端步驟以下:
provider端步驟以下:
什麼?你不知道項目啓動的時候調用本身的程序怎麼寫? spring的擴展千萬種,總有一種適合你~~
寫到這裏,咱們的rpc框架雛形已經出來了。並且這個也是真實可使用的框架。雖然在本文裏面沒有講關於dubbo的東西,可是相信若是你們能完整看下來而後寫下來的話,必定是會有啓發的。(若是你是大神,當我沒說過哈~)
最後附上我這個項目的地址https://gitee.com/hanochMa/rpc-study.git 。你們記得把分支切到v1.0。
在v1.0裏面的代碼和本文所分析的代碼,不一樣點在於v1.0裏面的代碼作了不少對象的封裝,因此裏面的類會比想象中的多那麼一點點。