深入Protobuf源碼-Descriptor、Message、RPC框架

Descriptor框架

對非optimize_forLITE_RUNTIMEproto文件,protobuf編譯器會在編譯出的Java代碼文件末尾添加一個FileDescriptor靜態字段以描述該proto文件定義時的所有元數據信息、爲每個message對象定義一個Descriptor靜態字段以描述該message定義時的元數據信息、爲每個message對象定義一個FieldAccessorTable靜態字段用於使用反射讀取/設置某個字段的值等(以提供GeneratedMessage中方法的反射實現):    
private static Descriptor inter-nal_static_levin_protobuf_Result_descriptor;
private static FieldAccessorTable inter-nal_static_levin_protobuf_Result_fieldAccessorTable;
private static Descriptor inter-nal_static_levin_protobuf_SearchResponse_descriptor;
private static FieldAccessorTable inter-nal_static_levin_protobuf_SearchResponse_fieldAccessorTable;
private static FileDescriptor descriptor;

protobuf中存在多種類型的元數據描述類:

1.     FileDescriptor:對一個proto文件的描述,它包含文件名、包名、選項(如java_packagejava_outer_classname等)、文件中定義的所有message、文件中定義的所有enum、文件中定義的所有service、文件中所有定義的extension、文件中定義的所有依賴文件(import)等。在FileDescriptor中還存在一個DescriptorPool實例,它保存了所有的dependencies(依賴文件的FileDescriptor)nameGenericDescriptor的映射、字段到FieldDescriptor的映射、枚舉項到EnumValueDescriptor的映射,從而可以從該DescriptorPool中查找相關的信息,因而可以通過名字從FileDescriptor中查找MessageEnumServiceExtensions等。

2.   Descriptor:對一個message定義的描述,它包含該message定義的名字、所有字段、內嵌message、內嵌enum、關聯的FileDescriptor等。可以使用字段名或字段號查找FieldDescriptor

3.   FieldDescriptor:對一個字段或擴展字段定義的描述,它包含字段名、字段號、字段類型、字段定義(required/optional/repeated/packed)、默認值、是否是擴展字段以及和它關聯的Descriptor/FileDescriptor等。

4.   EnumDescriptor:對一個enum定義的描述,它包含enum名、全名、和它關聯的FileDescriptor。可以使用枚舉項或枚舉值查找EnumValueDescriptor

5.   EnumValueDescriptor:對一個枚舉項定義的描述,它包含枚舉名、枚舉值、關聯的EnumDescriptor/FileDescriptor等。

6.   ServiceDescriptor:對一個service定義的描述,它包含service名、全名、關聯的FileDescriptor等。

7.   MethodDescriptor:對一個在service中的method的描述,它包含method名、全名、參數類型、返回類型、關聯的FileDescriptor/ServiceDescriptor等。

最後,protobuf編譯生成的代碼末尾還有一個descriptorData字符串數組,它是序列化後的FileDescriptorProto數據,在靜態初始化塊中可以調用FileDescriptor.internalBuildGeneratedFileFrom()方法構造整個FileDescriptor實例,在完成FileDescriptor的構造後,還會回調傳入的InternalDescriptorAssigner實例以初始化其他的靜態字段,如以上提到的所有的靜態字段。

protobufDescriptor的類圖:


Message、MessageLite框架

序列化和反序列化是protobuf最基礎的框架,它使用MessageLite/Message接口來抽象一個可序列化的實例,並且使用Builder從字節數組或輸入字節流中構建MessageLite/Message實例,MessageLiteMessage內部都定義了自己的Builder類,他們個字繼承自MessageLiteOrBuilder以及MessageOrBuiler,它們定義了MessageLite/Message和它們各自Builder類的共同接口。

MessageLiteOrBuilder接口只定義了MessageLiteMessageLite.Builder兩個接口共有的兩個方法:getDefaultInstanceForType()方法獲取一個當前還未初始化的當前Message實例(沒有字段被賦值,因而所有字段返回默認值,對repeat字段返回空,在當前protobuf 2.5.0的實現中,它返回的是一個單例,和每個生成的靜態方法getDefaultInstance()返回相同的實例)isInitialized()方法用來判斷是否所有required字段已經被賦值。MessageLite接口中定義了兩個writeTo()方法分別將當前實例序列化並寫入輸出字節流中,而另一個writeDelimitedTo()方法則在寫入之前將當前實例的總長度寫入輸出字節流中(以可變長32Int編碼方式),從而可以同時向一個輸出字節流中寫入多個Message實例;MessageLite中還定義了獲取當前MessageLite在序列化成字節流後的總字節數的方法getSerializedSize(),兩個直接返回字節數組的toByteArray()/toByteString()方法,以及獲取它的Parser實例(getParserForType())和返回它的Builder實例(toBuilder()-創建一個新的Builder實例/newBuilderForType()-用當前MessageLite類初始化一個新的Builder實例並返回)方法。其中Builder接口用於從字節流或字節數組中解析並構造MessageLite對象(各種版本的mergeFrom()方法,如果發送端寫入了MessageLite字節長度,則使用mergeDelimitedFrom()方法),最後Builder使用build()方法構造MessageLite對象,此時如果有required字段還未被設置,會拋出UninitializedMessageException,爲了避免拋出異常,可以使用buildPartial()方法;另外Builder還定義了clone()clear()方法;在生成的每個Message對象中都定義了一個newBuilder()靜態方法,一般使用該靜態方法初始化一個Builder實例。Parser接口也定義了各個版本的parseFrom()/parsePartialFrom()/parseDelimitedFrom()/parsePartialDelimitedFrom()方法用來從字節數組或字節流中解析出Message實例,在生成的代碼中,Builder的實現直接調用Parser實現類中的方法。

在大部分情況下,MessageLite已經能完成所有的序列化和反序列化操作了,特別是一些資源有限額手持設備,它如果運行整個protobuf庫會顯得太耗資源;可以在.proto文件中加入一下指令來告訴protobuf編譯器只需要生成實現MessageLite的類:

option optimize_for = LITE_RUNTIME

然而對一般的Server程序來說,我們並不在乎這點資源的損耗,因而會選擇實現Message接口,它相比MessageLite,添加了Descriptors相關的支持,即支持使用FieldDescriptor來構建Message.Builder實例並最終構建Message實例。

MessageOrBuilder接口繼承自MessageLiteOrBuilder接口,它定義了MessageMessage.Builder共有的接口,即添加了DescriptorFieldDescriptor等相關的擴展。由於實現MessageMessage.Builder接口的類保存了所有Message定義時具有的信息(文件名、包名、字段列表等,使用各種Descriptor類來抽象),因而我們可以使用Message/Message.Builder類獲取到更多的信息,如一個Message/Message.Builder沒有賦值所有required的字段,可以使用findInitializationErrors()方法來獲取所有未賦值的字段列表(字段的全路徑名,getInitializationErrorString()是這個列表的字符串形式表達,爲了提升性能,建議使用isInitialized()方法先做初步判斷,因爲它更快);另外在MessageOrBuilder中還定義了當前Message對應的Descriptor實例:getDescriptorForType()方法,獲取所有已經賦值的FieldDescriptor到其值的一個MapgetAllFields(),通過FieldDescriptor取得其值:getField(),判斷一個字段是否已經被賦值:hasField(),獲取repeated字段的countgetRepeatedFieldCount(),通過FieldDescriptor以及index獲取repeated字段在index處的值:getRepeatedField(),獲取未知的字段:getUnknownFields()Message接口除了繼承自MessageOrBuilder接口的方法,並沒有定義多餘的方法,只是添加了equalshashCodetoString方法的定義。而Message.Builder接口除了繼承自MessageOrBuilder接口以外,它還定義了基於FieldDescriptor的方法,如通過FieldDescriptor創建/獲取Builder實例:newBuilderForFileld()/getFieldBuilder(),通過FieldDescriptor設置/清除字段的值:setField()/clearField()/setRepeatedField()/addRepeatedField(),以及設置UnknownFieldssetUnknownFields()/mergeUnknownFields()

 

MessageLite/Message類圖如下:



RPC框架

除了序列化框架,protobuf還定義了一套簡單的RPC框架。之所以說簡單是因爲它定義的Service層接口的協議,而沒有具體和傳輸相關的實現,而只是將傳輸相關的邏輯抽象成RpcChannelBlockingRpcChannel分別用於表示同步和一步方式的Service方法調用,而至於底層用什麼樣的協議和框架,由用戶自己決定並實現。

所謂RPC框架,從用戶角度上最基本的就是定義客戶端和服務器端的協議,即服務器端暴露出什麼樣的接口供客戶端調用,這個接口定義了服務器在一個Host的某個(些)端口上接收某些請求數據,並期望能返回的響應。其中服務器和端口號屬於傳輸實現的範疇,protobuf只是用RpcChannel/BlockingRpcChannel的概念做了抽象,而沒有給出具體實現;而接收某個請求數據以及期待的響應數據,在protobuf使用Service/BlockingService抽象來定義,並且這也是protobufRPC框架的定義部分,其中ServiceRpcChannel共同構成異步方式的RPC框架,而BlockingServiceBlockingRpcChannel共同構成了同步(阻塞)方式的RPC框架。

從底層實現的角度,一個RPC調用就是客戶端發送一些請求數據給服務器,服務器解析並處理這些請求數據,然後將響應數據返回給客戶端。爲了隱藏內部實現細節,提升寫代碼的效率,RPC將這一過程封裝成方法調用,即不同的請求用不同的方法表達,這就是protobufRPC的定義。在protobuf中,定義一個PRC接口比較簡單:首先開啓RPC功能,然後用service關鍵字定義一個接口,在接口中使用rpc關鍵字定義一個方法,方法包含方法名、方法參數、返回值,其中方法參數和返回值都必須是一個message類型,並且只能有一個:

option java_generic_services = true;

service MyService {
    rpc request(SearchRequest) returns(SearchResponse);
}

protobuf編譯生成的代碼中,它會生成一個MyService抽象類實現了Service接口,一般它只是作爲一個命名空間,它內部定義了兩個接口:InterfaceBlockingInterface本別繼承自Service接口和BlockingService接口,用於抽象異步和同步方式的RPC方法調用;這兩個接口有兩個實現類:StubBlockingStub,他們分別接收RpcChannelBlockingRpcChannel實例作爲構造函數參數,可以使用MyService中的靜態方法newStub()newBlockingStub()方法獲取他們各自實例,他們主要用於客戶端的調用。在生成的request方法中,除了request本身的參數,還有一個RpcController參數,它用於處理在RpcChannel/BlockingRpcChannel調用中的狀態處理,如錯誤處理等,使用它可以獲知此次調用是否出錯,錯誤信息是什麼等。在MyService中還定義了兩個靜態方法newReflectiveService/newReflectiveBlockingService,他們接收Interface/BlockingInterface實例,並返回Service/BlockingService的實現實例(暫時還沒有想到使用他們的場景)。



MyServiceRPC框架實現中,在服務器端,實現MyService.Interface/MyService.BlockingInterface接口,然後將它註冊到對RpcChannel/BlockingRpcChannel框架的實現中;在客戶端則創建一個RpcChannel/BlockingRpcChannel實例,傳入MyService.newStub()/MyService.newBlockingStub()方法獲取對應的實例,然後使用這個Stub/BlockingStub實例調用相應的方法即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章