RPC框架基本原理(三):調用鏈路分析


本文主要闡述下RPC調用過程中的尋址,序列化,以及服務端調用問題。

尋址

隨機尋址

從可用列表中,隨機選擇地址

一致性尋址

可用服務地址一致性hash管理:根據可服務的地址,構造treemap,計算crc16 ccitt碼時,加入虛擬節點數量,指向同一個可用地址。

   for (String addr : list) {
        for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) {
            ketamaMap.put(CRC16.getSlot(addr + VIRTUAL_NODE_SPLITER + i), addr);
        }
    }

當新加入服務時,確保之前服務的key是一致的,新加的key離散到treemap中。

調用方生成種子編碼:CRC16 CCITT計算出種子。

尋址,采用treemap的tailmap(有序map的特性)。
找出比種子大的區間map,返回區間map的第一個,如果區間map為空,返回整個map的第一個。

CRC16 CCITT編碼

 //地址表
 private static final int LOOKUP_TABLE[] = {0x0000, 0x1021, 0x2042, 0x3063,
        0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B,
        0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252,
        0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A,
        0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
        0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509,
        0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630,
        0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738,
        0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7,
        0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF,
        0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96,
        0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E,
        0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5,
        0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
        0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4,
        0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC,
        0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB,
        0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3,
        0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA,
        0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2,
        0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589,
        0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481,
        0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
        0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0,
        0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F,
        0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827,
        0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E,
        0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16,
        0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D,
        0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45,
        0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C,
        0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
        0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,};
  
 //計算CRC碼
 /**
 * Create a CRC16 checksum from the bytes. implementation is from
 * mp911de/lettuce, modified with some more optimizations
 *
 * @param bytes
 * @return CRC16 as integer value
 */
public static int getCRC16(byte[] bytes) {
    int crc = 0x0000;

    for (byte b : bytes) {
        crc = ((crc << 8) ^ LOOKUP_TABLE[((crc >>> 8) ^ (b & 0xFF)) & 0xFF]);
    }
    return crc & 0xFFFF;
}

序列化

序列化和反序列化在我們軟件設計中有很多的應該場景,例如:

1、對象的持久化,以及從儲存媒介還原對象。如果需要將一個對象保持到儲存媒介中,我們需要先將這個對象進行序列化編碼,然后將編碼后的數據寫入到儲存媒介中。

2、進程之間的數據交互。在典型分布式服務架構中,面向服務的架構之間通常是通過RPC的方式來進行服務交互,在使用RPC的過程中,會將遠程方法的參數等信息進行序列化,編碼成字節序列,在網絡中傳輸。服務方收到這個數據包后會對數據進行解碼,進行反序列化。即將字節序列轉換成參數對象;

3、網絡中傳輸對象;使用分布式的緩存系統,緩存對象的獲取以及對象放入緩存,都會使用序列化及反序列化;

Java序列化

1 簡介

Java 對象序列化是 JDK 1.1 中引入的一組開創性特性之一,用於作為一種將 Java 對象的狀態轉換為字節數組,以便存儲或傳輸的機制,以后,仍可以將字節數組轉換回 Java 對象原有的狀態。

實際上,序列化的思想是 “凍結” 對象狀態,傳輸對象狀態(寫到磁盤、通過網絡傳輸等等),然后 “解凍” 狀態,重新獲得可用的 Java 對象。所有這些事情的發生有點像是魔術,這要歸功於 ObjectInputStream/ObjectOutputStream 類、完全保真的元數據以及程序員願意用Serializable 標識接口標記他們的類,從而 “參與” 這個過程。

2 優點

使用非常方便,對象只要實現接口Serializable ,就可以被序列化;如果交互雙方都是Java環境,那么使用Java自帶的序列化機制還是最方便的了;

3 缺點

1、不能跨語言。Java自帶的序列化,只適合Java語言使用。如果存在誇語言的應用場景,那就行不通了;	
2、從序列化及反序列化的性能來看,Java自帶的序列化性能是比較差的。無論是從時間,還是空間上看,性能都已經低的;

Protocol Buffers

1 簡介

Protocol buffers是Google開源的一個序列化框架;是一個用來序列化結構化數據的技術,支持多種語言諸如C++、Java以及Python語言,可以使用該技術來持久化數據或者序列化成網絡傳輸的數據。在Google 幾乎所有它內部的RPC協議和文件格式都是采用PB。它具有靈活、高效、自動化的特點;相比較一些其他的XML技術而言,該技術的一個明顯特點就是更加節省空間(以二進制流存儲)、速度更快以及更加靈活。

通常,編寫一個protocol buffers應用需要經歷如下三步:

1、定義消息格式文件,最好以proto作為后綴名
2、使用Google提供的protocol buffers編譯器來生成代碼文件,一般為.h和.cc文件,主要是對消息格式以特定的語言方式描述
3、使用protocol buffers庫提供的API來編寫應用程序

2 優點

1、平台無關、語言無關
2、高性能 比XML塊20-100倍
3、體積小 比XML小3-10倍
4、使用簡單
5、兼容性好

3 缺點

1、使用時首先需要定義消息格式文件,然后生成代碼,代碼不是純pojo,對於代碼有一定的侵入性; 
2、使用方便些上,不適合那種動態類型的數據。因為使用他之前必須先定義好格式文件;

XML類

1 簡介

XML 序列化用處很多,包括對象持久化和數據傳輸。但是一些 XML 序列化技術實現起來可能很復雜。XStream 是一個輕量級的、簡單易用的開放源代碼 Java™ 庫,用於將 Java 對象序列化為 XML 或者再轉換回來;

使用 XStream 不用任何映射就能實現多數 Java 對象的序列化。在生成的 XML 中對象名變成了元素名,類中的字符串組成了 XML 中的元素內容。使用 XStream 序列化的類不需要實現 Serializable 接口。XStream 是一種序列化工具而不是數據綁定工具,就是說不能從 XML 或者 XML Schema Definition (XSD) 文件生成類。

2 優點

Xml最大的優點是可閱讀性;
1、XStream 不關心序列化/反序列化的類的字段的可見性。
2、序列化/反序列化類的字段不需要 getter 和 setter 方法。
3、序列化/反序列化的類不需要有默認構造函數。
4、不需要修改類,使用 XStream 就能直接序列化/逆序列化任何第三方類。

3 缺點

1、xml序列化性能差,無論是空間還是時間,性能都不高;

JSON類

1 簡介

JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。 易於人閱讀和編寫。同時也易於機器解析和生成。 它基於JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一個子集。

JSON采用完全獨立於語言的文本格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 這些特性使JSON成為理想的數據交換語言。

JSON 已經是 JavaScript 標准的一部分。目前,主流的瀏覽器對 JSON 支持都非常完善。應用 JSON,我們可以從 XML 的解析中擺脫出來,對那些應用 Ajax 的 Web 2.0 網站來說,JSON 確實是目前最靈活的輕量級方案
JSON非常適合Web瀏覽器端和服務端的數據交換;

2 優點

1、跨平台,跨語言實現;
2、輕量級,非常容易閱讀與編寫;

3 缺點

1、貌似除了FastJSON的性能比較好外,其他JSON庫的性能不是很高;

Hessian

1 簡介

Hessian是Resin的開源的,一個基於二進制的RPC服務框架。他內部有一套完善的序列化及反序列化組件,采用二進制的序列化協議。Hessian使用簡單。協議性能比較高效,有各種語言的實現版本;

2 優點

1、跨平台,跨語言實現;
2、使用簡單,性能相對來說,比較高效;

3 缺點

1、 各版本之間好像不太兼容。有些版本有BUG;

Avro

1 簡介

Avro是Hadoop中的一個子項目,也是Apache中一個獨立的項目,Avro是一個基於二進制數據傳輸高性能的中間件。在Hadoop的其他項目中例如HBase(Ref)和Hive(Ref)的Client端與服務端的數據傳輸也采用了這個工具,Avro可以做到將數據進行序列化,適用於遠程或本地大批量數據交互。

在傳輸的過程中Avro對數據二進制序列化后 節約數據存儲空間 和 網絡傳輸帶寬。做個比方:有一個100平方的房子,本來能放100件東西,現在期望借助某種手段能讓原有面積的房子能存放比原來多150件以上或者更多的東西,就好比數據存放在緩存中,緩存是精貴的,需要充分的利用緩存有限的空間,存放更多的數據。再例如網絡帶寬的資源是有限的,希望原有的帶寬范圍能傳輸比原來高大的數據量流量,特別是針對結構化的數據傳輸和存儲,這就是Avro存在的意義和價值。

Avro還可以做到在同一系統中支持多種不同語言,也有點類似Apache的另一個產品:Thrift(Ref),對於Thrift不同的是Avro更加具有靈活性,Avro可以支持對定義的數據結構(Schema)動態加載,利於系統擴展。

2 優點

1、跨平台,跨語言實現;
2、Avro最大的特點是序列化數據不要帶字段標簽,這樣他序列化的后的數據非常精簡,在空間上站很大的優勢;
3、性能優秀;無論是空間還是時間,都比較好;

3 缺點

1、 依賴Schema文件;使用前必須定義數據的Schema格式文件,編碼及解碼都依賴於Schema文件
2、 各版本之間好像不太兼容。有些版本有BUG;

Apache Thrift

Apache Thrift 是 Facebook 實現的一種高效的、支持多種編程語言的遠程服務調用的框架 ,同時也提供了序列化的功能;

Thrift源於大名鼎鼎的Facebook之手,在2007年facebook提交Apache基金會將Thrift作為一個開源項目,對於當時的facebook來說創造thrift是為了解決Facebook系統中各系統間大數據量的傳 輸通信以及系統之間語言環境不同需要跨平台的特性。所以thrift可以支持多種程序語言,例如: C++, C#, Cocoa, Erlang, Haskell, Java, Ocami, Perl, PHP, Python, Ruby, Smalltalk. 在多種不同的語言之間通信thrift可以作為二進制的高性能的通訊中間件,支持數據(對象)序列化和多種類型的RPC服務。Thrift適用於程序對程 序靜態的數據交換,需要先確定好他的數據結構,他是完全靜態化的,當數據結構發生變化時,必須重新編輯IDL文件,代碼生成,再編譯載入的流程,跟其他IDL工具相比較可以視為是Thrift的弱項,Thrift適用於搭建大型數據交換及存儲的通用工具,對於大型系統中的內部數據傳輸相對於JSON和xml無論在性能、傳輸大小上有明顯的優勢;

Kryo

Kryo 是一個快速高效的Java對象圖形序列化框架,它原生支持java,且在java的序列化上甚至優於google著名的序列化框架protobuf。

Kryo只適用於Java平台,如果只考慮Java平台,Kryo是一個不錯的選擇。期性能非常的好。

MessagePack

MessagePack是一個基於二進制高效的對象序列化Library用於跨語言通信。 它可以像JSON那樣,在許多種語言之間交換結構對象;但是它比JSON更快速也更輕巧。 支持Python、Ruby、Java、C/C++、Javascript等眾多語言。

性能比較

服務端調用

同步模型只能在調用方法中,將所有的邏輯順序執行,如下圖所示:

從時序圖可以看出,如果用戶執行邏輯比較耗時(紅色部分)很容易造成HSFBizProcessor線程全部卡在這些邏輯上,造成線程池滿的問題。

使用HSF服務端異步處理的方式,將耗時的部分從HSF的處理線程中移到用戶的服務端,使得HSF服務端線程能夠快速釋放,這樣可以 保證 整個機器的服務吞吐量處於較高水平,進而提升穩定性(假設耗時的方法調用頻度不高)。如下圖所示:

想一下之前提到的ListenableFuture,它的含義就是異步計算結束后,幫我做一些事情。我們把這個邏輯套用過來,當ClientThread完成計算之后,讓框架幫忙把數據寫出去,這樣就解決了客戶端寫數據的問題了。

是不是感覺使用一個ListenableFuture作為處理的依據還是很不習慣?其實我們只需要識別三個條件因素就可以了,第一,誰負責計算(調用Future.set(Object obj)),第二,計算成功之后誰來做什么,第三,計算結果是什么。在這個例子里面,計算結果是RPCResult,負責計算的是客戶端ClientThread,而計算成功之后是框架負責將數據寫出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM