天天看點

牛逼哄哄的 RPC 架構,底層到底什麼原理?

1. RPC架構的概念

RPC(Remote Procedure Call)–遠端過程調用,通過網絡通信調用不同的服務,共同支撐一個軟體系統,微服務實作的基石技術。

使用RPC可以解耦系統,友善維護,同時增加系統處理請求的能力。

牛逼哄哄的 RPC 架構,底層到底什麼原理?

上面是一個簡單的軟體系統結構,我們拆分出來使用者系統和訂單系統做為服務存在,讓不同的站點去調用。

隻需要引入各個服務的接口包,在代碼中調用RPC服務就跟調用本地方法一樣,我剛接觸到這種調用方式的時候頗為驚奇,我明明調用的就是java語言方法啊(已java為例,現在RPC架構一般都支援多語言),怎麼就調用了遠端的服務了呢??

2. RPC架構的原了解析

最近自己寫了一個簡單的RPC架構KRPC,本文原理分析結合中代碼,均為該架構源碼,RPC與RMI的差別看這篇文章《Java RMI 和 RPC 的差別》。

2.1 流程縱覽

牛逼哄哄的 RPC 架構,底層到底什麼原理?

如上圖所示,我将一個RPC調用流程概括為上圖中5個流程,左邊3個為用戶端流程,右邊兩個為服務端流程。

下面就各流程進行解析

2.2 用戶端調用

服務調用方在調用服務時,一般進行相關初始化,通過配置檔案/配置中心 擷取服務端位址使用者調用。

// 使用者服務接口
public interface UserService {
 public User genericUser(Integer id,String name,Long phone);
}

//調用方
//服務初始化
KRPC.init("D:\\krpc\\service\\demo\\conf\\client.xml");
UserService service = ProxyFactory.create(UserService.class, "demo","demoService");
User user = service.genericUser(1, "yasin", 1888888888L);      

一開始接觸RPC調用方法肯定就有疑惑,它不是一個接口嗎,直接調用應該沒啥效果啊,我也沒有引入實作包。

帶着這個疑惑,我們就進入下一個知識點,動态代理。

2.3 動态代理

動态代理這東西意如其名,它代理你幫你做事情,動态代理看這篇文章《詳解 Java 中的三種代理模式》。

上面我們不說道直接調用一個接口中的方法,并且沒有用該接口的實作類調用,那麼方法是怎麼生效的呢?

可以看到這個使用者服務這個service是由ProxyFactory代理工程創造的,在該ProxyFactory#create()方法中就跟一個代理處理器綁定在一起了。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    //構造請求request
    Request request = new Request();
    ....

    return RequestHandler.request(serviceName, request,returnClass);
}      

這個類實作了InvocationHandler接口(JDK提供的動态代理技術),每次去調用接口方法,最終都交由該handler進行處理。

這個環節一般會擷取方法的一些資訊,例如方法名,方法參數類型,方法參數值,傳回對象類型。

同時這個環節會提供序列化功能,一般的RPC網絡傳輸使用TCP(哪怕使用HTTP)傳輸,這裡也要将這些參數進行封裝成我們定義的資料接口進行傳輸。

2.4 網絡傳輸

我們通過将方法參數進行處理後,就要使用發起網絡請求,使用tcp傳輸的就利用socket通信進行傳輸,這一塊我開源項目中使用的同步堵塞的方案進行請求,也可以使用一些非堵塞方案進行請求,效率會更高一些。

2.5 服務端資料接受

這一塊使用netty,可以快速一個高性能、高可靠的一個服務端。

public class ServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf)msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);

        byte[] responseBytes = RequestHandler.handler(bytes);
        ByteBuf resbuf = ctx.alloc().buffer(responseBytes.length);
        resbuf.writeBytes(responseBytes);
        ctx.writeAndFlush(resbuf);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}      

上面代碼是我項目中使用的服務端代碼。關于netty網上學習的資料很多,這裡也隻是宏觀的講解RPC原理,就不展開。

2.6 真實調用

服務端擷取用戶端請求的資料後, 調用請求中的方法,方法參數值,通過反射調用真實的方法,擷取其傳回值,将其序列化封裝,通過netty進行資料傳回,用戶端在接受資料并解析,這就完成了一次rpc請求調用的全過程。

method = clazz.getMethod(request.getMethodName(),requestParamTypes);
method.setAccessible(true);
result = method.invoke(service, requestParmsValues)      

上面代碼片段為通過反射調用真實方法

2.7 服務端的動态加載

通過2.2到2.6的說明,一次RPC請求過程大緻如此,但是一個RPC架構會有很多細節需要處理。

其實在一次請求調用前,服務端肯定要先啟動。

服務端作為一個容器,跟我們熟知的tomcat一樣,它可以動态的加載任何項目。是以在服務端啟動的時候,必須要進行一個動态加載的過程。

在KRPC中,我使用了URLClassLoader動态加載一個指定路徑的jar包,任何業務服務的實作所依賴的jar包都可以放入該路徑中。

3. 總結

一個RPC架構大緻需要動态代理、序列化、網絡請求、網絡請求接受(netty實作)、動态加載、反射這些知識點。

現在開源及各公司自己造的RPC架構層出不窮,唯有掌握原理是一勞永逸的。

掌握原理最好的方法莫不是閱讀源碼,自己動手寫是最快的。