RPC框架原理

RPC調(diào)用是面向服務(wù)架構(gòu)場景下進行服務(wù)間調(diào)用的常用組件销钝,一個完整的RPC調(diào)用的流程如圖1所示:


圖1 RPC調(diào)用流程

為了方便RPC調(diào)用者和服務(wù)者的開發(fā),開發(fā)者們開發(fā)了很多RPC框架。比較有名的RPC框架有Google的gRPC边苹、Facebook的Thrift 和 阿里的 Dubbo 等狱窘。這些框架在具體實現(xiàn)上雖然各不相同杜顺,但其工作原理基本上是一致的。

一個設(shè)計良好的RPC框架的愿景是簡化服務(wù)提供者和調(diào)用者的開發(fā)蘸炸,將圖1中 2~8 步驟的所有操作全部隱藏躬络,讓開發(fā)者可以像開發(fā)和調(diào)用本地方法一樣開發(fā)和調(diào)用遠程方法。為了隱藏這些實現(xiàn)細節(jié)搭儒,RPC框架需要關(guān)注如下幾點:

  1. 方法調(diào)用:方法調(diào)用的過程如何轉(zhuǎn)換為可以傳輸?shù)南?nèi)容穷当;
  2. 服務(wù)發(fā)現(xiàn):如何發(fā)布服務(wù)并告知調(diào)用者服務(wù)地址;
  3. 數(shù)據(jù)傳輸:服務(wù)調(diào)用者和提供者之間如何進行數(shù)據(jù)傳輸淹禾。

下面我們以Java語言為例馁菜,就以上幾點分別做出分析。

1. 方法調(diào)用

1.1 “協(xié)議”設(shè)定

要完成一個完整的方法調(diào)用铃岔,需要在調(diào)用者和被調(diào)用者之間設(shè)計一套“協(xié)議”對被調(diào)接口汪疮、被調(diào)方法、參數(shù)類型、參數(shù)值智嚷、返回類型卖丸、返回值等進行定義,如下是一段比較合理的通信“協(xié)議”:

public class Request implements Serializable {
    private long sid;
    private String serviceName;
    private String methodName;
    private Class<?>[] paramTypes;
    private Object[] params;
}

public class Response implements Serializable {
    private long sid;
    private Class<?> resultType;
    private Object result;
}

Request里面盏道,需要指定具體的服務(wù)稍浆、方法名,以及調(diào)用方法時需要傳入的參數(shù)類型和參數(shù)值摇天;Response里面直接指定返回值及其類型(因為返回值最多只有一個)粹湃。特別地,在Request 和 Response里面分別有一個 sid 字段泉坐,是為了在異步處理時能正確地對應(yīng)請求和返回的關(guān)系为鳄。

1.2 請求、返回與“協(xié)議”對象的轉(zhuǎn)化

為了簡化服務(wù)方和調(diào)用方的實現(xiàn)腕让,RPC框架里面需要完成方法請求與request對象之間的轉(zhuǎn)換 和 處理結(jié)果與reponse對象之間的轉(zhuǎn)換孤钦。對于Java,可以采用JDK的動態(tài)代理來實現(xiàn)這個轉(zhuǎn)換纯丸;如果采用Spring框架偏形,還可以通過Spring的動態(tài)代理類 -- FactoryBean進行實現(xiàn)。下面一段代碼是通過JDK的動態(tài)代理實現(xiàn)本地調(diào)用轉(zhuǎn)化為消息的一個demo:

// 動態(tài)代理類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class RpcProxyInvoker implements InvocationHandler {
    private Class<?> clazz;

    public RpcProxyInvoker(Class<?> clazz) {
        this.clazz = clazz;
    }

    public static Object getProxy(Class<?> clazz) {
        RpcProxyInvoker invoker = new RpcProxyInvoker(clazz);
        return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, invoker);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?>[] paramTypes = new Class[] {};
        for (int idx = 0; idx < args.length; ++idx) {
            paramTypes[idx] = args[idx].getClass();
        }

        Request req = new Request();
        req.setServiceName(clazz.getClass().getName());
        req.setMethodName(method.getName());
        req.setParameterTypes(paramTypes);
        req.setParameters(args);

        /// ... 后續(xù)操作:序列化觉鼻、網(wǎng)絡(luò)傳輸?shù)?        return null;
    }
}

// 暴露給調(diào)用方的接口
public interface UserService {
    UserDTO get(int userId);
}

// 調(diào)用方調(diào)用簡單示例
public class Main {
    public static void main(String[] args) {
        UserService userService = (UserService) RpcProxyInvoker.getProxy(UserService.class);
        userService.get(1);
    }
}

為了方便傳輸俊扭,需要對生成的Request對象進行序列化。最簡單的序列化方法是采用Java的ObjectInput/OutputStream實現(xiàn)坠陈,當然也可以通過JSON等格式進行對象的序列化萨惑。
序列化結(jié)束后,交給網(wǎng)絡(luò)模塊進行網(wǎng)絡(luò)傳輸并等待返回結(jié)果進行后續(xù)處理仇矾。

2. 服務(wù)發(fā)現(xiàn)

當RPC框架應(yīng)用于大型分布式系統(tǒng)時庸蔼,不可能將服務(wù)的地址人為地告知到每個調(diào)用方,應(yīng)該自動化地完成這個“告知”工作贮匕。這個工作的流程如圖2所示:


圖2 服務(wù)發(fā)現(xiàn)

注冊中心可以采用redis或者zookeeper等方式進行實現(xiàn)姐仅。
從服務(wù)提供者的角度看:當提供者服務(wù)啟動時,需要自動向注冊中心注冊服務(wù)刻盐;當提供者服務(wù)停止時掏膏,需要向注冊中心注銷服務(wù);提供者需要定時向注冊中心發(fā)送心跳敦锌,一段時間未收到來自提供者的心跳后壤追,認為提供者已經(jīng)停止服務(wù),從注冊中心上摘取掉對應(yīng)的服務(wù)供屉。從調(diào)用者的角度看:調(diào)用者啟動時訂閱注冊中心的消息并從注冊中心獲取提供者的地址行冰;當有提供者上線或者下線時溺蕉,注冊中心會告知到調(diào)用者;調(diào)用者下線時悼做,取消訂閱疯特。

3. 數(shù)據(jù)傳輸

服務(wù)提供者和調(diào)用者之間進行數(shù)據(jù)傳輸,可以借助基于端口的協(xié)議和基于HTTP的協(xié)議進行肛走,如使用TCP協(xié)議通信 或 使用HTTP協(xié)議通信漓雅。通信方式上,可以使用傳統(tǒng)的BIO(Blocked I/O) 或 NIO(New I/O)方式進行朽色,在此不再一一贅述邻吞。

雖然RPC框架的原理比較簡單,但要實現(xiàn)一個功能齊全葫男、適應(yīng)性強抱冷、對代碼無侵入的RPC框架并非易事,需要在網(wǎng)絡(luò)傳輸梢褐、序列化旺遮、Stub和Skeleton功能等方面下功夫。當然盈咳,在了解原理的情況下耿眉,使用一個RPC框架,對業(yè)務(wù)代碼的編寫和優(yōu)化也有一定的促進作用鱼响。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸣剪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子丈积,更是在濱河造成了極大的恐慌筐骇,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桶癣,死亡現(xiàn)場離奇詭異,居然都是意外死亡娘锁,警方通過查閱死者的電腦和手機牙寞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莫秆,“玉大人间雀,你說我怎么就攤上這事∧魇海” “怎么了惹挟?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缝驳。 經(jīng)常有香客問我连锯,道長归苍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任运怖,我火速辦了婚禮拼弃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摇展。我一直安慰自己吻氧,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布咏连。 她就那樣靜靜地躺著盯孙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祟滴。 梳的紋絲不亂的頭發(fā)上振惰,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音踱启,去河邊找鬼报账。 笑死,一個胖子當著我的面吹牛埠偿,可吹牛的內(nèi)容都是我干的透罢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼冠蒋,長吁一口氣:“原來是場噩夢啊……” “哼羽圃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抖剿,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤朽寞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后斩郎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脑融,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年缩宜,在試婚紗的時候發(fā)現(xiàn)自己被綠了肘迎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡锻煌,死狀恐怖妓布,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宋梧,我是刑警寧澤匣沼,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站捂龄,受9級特大地震影響释涛,放射性物質(zhì)發(fā)生泄漏加叁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一枢贿、第九天 我趴在偏房一處隱蔽的房頂上張望殉农。 院中可真熱鬧,春花似錦局荚、人聲如沸超凳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轮傍。三九已至,卻和暖如春首装,著一層夾襖步出監(jiān)牢的瞬間创夜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工仙逻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驰吓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓系奉,卻偏偏與公主長得像檬贰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缺亮,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容