系統架構札記

什麼是高內聚、低耦合?


    起因:模塊獨立性指每個模塊只完成系統要求的獨立子功能,並且與其他模塊的聯繫最少且接口簡單,兩個定性的度量標準――耦合性和內聚性。


    耦合性也稱塊間聯繫。指軟件系統結構中各模塊間相互聯繫緊密程度的一種度量。模塊之間聯繫越緊密,其耦合性就越強,模塊的獨立性則越差。模塊間耦合高低取決於模塊間接口的複雜性、調用的方式及傳遞的信息。

    耦合性分類(――): 無直接耦合;數據耦合;標記耦合;控制耦合;公共耦合;內容耦合;

1 無直接耦合:

2 數據耦合: 指兩個模塊之間有調用關係,傳遞的是簡單的數據值,相當於高級語言的值傳遞;

3 標記耦合: 指兩個模塊之間傳遞的是數據結構,如高級語言中的數組名、記錄名、文件名等這些名字即標記,其實傳遞的是這個數據結構的地址;

4 控制耦合: 指一個模塊調用另一個模塊時,傳遞的是控制變量(如開關、標誌等),被調模塊通過該控制變量的值有選擇地執行塊內某一功能;

5 公共耦合: 指通過一個公共數據環境相互作用的那些模塊間的耦合。公共耦合的複雜程序隨耦合模塊的個數增加而增加。

6 內容耦合: 這是最高程度的耦合,也是最差的耦合。當一個模塊直接使用另一個模塊的內部數據,或通過非正常入口而轉入另一個模塊內部。

 

    內聚性又稱塊內聯繫。指模塊的功能強度的度量,即一個模塊內部各個元素彼此結合的緊密程度的度量。若一個模塊內各元素(語名之間、程序段之間)聯繫的越緊密,則它的內聚性就越高。

    內聚性匪類(――): 偶然內聚;邏輯內聚;時間內聚;通信內聚;順序內聚;功能內聚;

1 偶然內聚: 指一個模塊內的各處理元素之間沒有任何聯繫。

2 邏輯內聚: 指模塊內執行幾個邏輯上相似的功能,通過參數確定該模塊完成哪一個功能。

3 時間內聚: 把需要同時執行的動作組合在一起形成的模塊爲時間內聚模塊。

4 通信內聚: 指模塊內所有處理元素都在同一個數據結構上操作(有時稱之爲信息內聚),或者指各處理使用相同的輸入數據或者產生相同的輸出數據。

5 順序內聚: 指一個模塊中各個處理元素都密切相關於同一功能且必須順序執行,前一功能元素輸出就是下一功能元素的輸入。

6 功能內聚: 這是最強的內聚,指模塊內所有元素共同完成一個功能,缺一不可。與其他模塊的耦合是最弱的。

 

    耦合性與內聚性是模塊獨立性的兩個定性標準,將軟件系統劃分模塊時,儘量做到高內聚低耦合,提高模塊的獨立性,爲設計高質量的軟件結構奠定基礎。

 

    有個例子很容易明白:一個程序有50個函數,這個程序執行得非常好;然而一旦你修改其中一個函數,其他49個函數都需要做修改,這就是高耦合的後果。

一旦你理解了它,你編寫概要設計的時候設計類或者模塊自然會考慮到高內聚,低耦合


序列化和反序列化

 摘要

  序列化和反序列化幾乎是工程師們每天都要面對的事情,但是要精確掌握這兩個概念並不容易:一方面,它們往往作爲框架的一部分出現而湮沒在框架之中;另一方面,它們會以其他更容易理解的概念出現,例如加密、持久化。然而,序列化和反序列化的選型卻是系統設計或重構一個重要的環節,在分佈式、大數據量系統設計裏面更爲顯著。恰當的序列化協議不僅可以提高系統的通用性、強健性、安全性、優化系統性能,而且會讓系統更加易於調試、便於擴展。本文從多個角度去分析和講解序列化和反序列化,並對比了當前流行的幾種序列化協議,期望對讀者做序列化選型有所幫助。

  簡介

  文章作者服務於美團推薦與個性化組,該組致力於爲美團用戶提供每天billion級別的高質量個性化推薦以及排序服務。從Terabyte級別的用戶行爲數據,到Gigabyte級別的Deal/Poi數據;從對實時性要求毫秒以內的用戶實時地理位置數據,到定期後臺job數據,推薦與重排序系統需要多種類型的數據服務。推薦與重排序系統客戶包括各種內部服務、美團客戶端、美團網站。爲了提供高質量的數據服務,爲了實現與上下游各系統進行良好的對接,序列化和反序列化的選型往往是我們做系統設計的一個重要考慮因素。

  本文內容按如下方式組織:

  • 第一部分給出了序列化和反序列化的定義,以及其在通訊協議中所處的位置。
  • 第二部分從使用者的角度探討了序列化協議的一些特性。
  • 第三部分描述在具體的實施過程中典型的序列化組件,並與數據庫組建進行了類比。
  • 第四部分分別講解了目前常見的幾種序列化協議的特性,應用場景,並對相關組件進行舉例。
  • 最後一部分,基於各種協議的特性,以及相關benchmark數據,給出了作者的技術選型建議。

  一、定義以及相關概念

  互聯網的產生帶來了機器間通訊的需求,而互聯通訊的雙方需要採用約定的協議,序列化和反序列化屬於通訊協議的一部分。通訊協議往往採用分層模型,不同模型每層的功能定義以及顆粒度不同,例如:TCP/IP協議是一個四層協議,而OSI模型卻是七層協議模型。在OSI七層協議模型中展現層(Presentation Layer)的主要功能是把應用層的對象轉換成一段連續的二進制串,或者反過來,把二進制串轉換成應用層的對象--這兩個功能就是序列化和反序列化。一般而言,TCP/IP協議的應用層對應與OSI七層協議模型的應用層,展示層和會話層,所以序列化協議屬於TCP/IP協議應用層的一部分。本文對序列化協議的講解主要基於OSI七層協議模型。

  • 序列化: 將數據結構或對象轉換成二進制串的過程
  • 反序列化:將在序列化過程中所生成的二進制串轉換成數據結構或者對象的過程

  數據結構、對象與二進制串

  不同的計算機語言中,數據結構,對象以及二進制串的表示方式並不相同。

  數據結構和對象:對於類似Java這種完全面向對象的語言,工程師所操作的一切都是對象(Object),來自於類的實例化。在Java語言中最接近數據結構的概念,就是POJOPlain Old Java Object)或者Javabean--那些只有setter/getter方法的類。而在C++這種半面向對象的語言中,數據結構和struct對應,對象和class對應。

  二進制串:序列化所生成的二進制串指的是存儲在內存中的一塊數據。C++語言具有內存操作符,所以二進制串的概念容易理解,例如,C++語言的字符串可以直接被傳輸層使用,因爲其本質上就是以'\0'結尾的存儲在內存中的二進制串。在Java語言裏面,二進制串的概念容易和String混淆。實際上String Java的一等公民,是一種特殊對象(Object)。對於跨語言間的通訊,序列化後的數據當然不能是某種語言的特殊數據類型。二進制串在Java裏面所指的是byte[]byteJava8中原生數據類型之一(Primitive data types)。

  二、序列化協議特性

  每種序列化協議都有優點和缺點,它們在設計之初有自己獨特的應用場景。在系統設計的過程中,需要考慮序列化需求的方方面面,綜合對比各種序列化協議的特性,最終給出一個折衷的方案。

  通用性

  通用性有兩個層面的意義:

  第一、技術層面,序列化協議是否支持跨平臺、跨語言。如果不支持,在技術層面上的通用性就大大降低了。

  第二、流行程度,序列化和反序列化需要多方參與,很少人使用的協議往往意味着昂貴的學習成本;另一方面,流行度低的協議,往往缺乏穩定而成熟的跨語言、跨平臺的公共包。

  強健性/魯棒性

  以下兩個方面的原因會導致協議不夠強健:

  第一、成熟度不夠,一個協議從制定到實施,到最後成熟往往是一個漫長的階段。協議的強健性依賴於大量而全面的測試,對於致力於提供高質量服務的系統,採用處於測試階段的序列化協議會帶來很高的風險。

  第二、語言/平臺的不公平性。爲了支持跨語言、跨平臺的功能,序列化協議的制定者需要做大量的工作;但是,當所支持的語言或者平臺之間存在難以調和的特性的時候,協議制定者需要做一個艱難的決定--支持更多人使用的語言/平臺,亦或支持更多的語言/平臺而放棄某個特性。當協議的制定者決定爲某種語言或平臺提供更多支持的時候,對於使用者而言,協議的強健性就被犧牲了。

  可調試性/可讀性

  序列化和反序列化的數據正確性和業務正確性的調試往往需要很長的時間,良好的調試機制會大大提高開發效率。序列化後的二進制串往往不具備人眼可讀性,爲了驗證序列化結果的正確性,寫入方不得同時撰寫反序列化程序,或提供一個查詢平臺--這比較費時;另一方面,如果讀取方未能成功實現反序列化,這將給問題查找帶來了很大的挑戰--難以定位是由於自身的反序列化程序的bug所導致還是由於寫入方序列化後的錯誤數據所導致。對於跨公司間的調試,由於以下原因,問題會顯得更嚴重:

  第一、支持不到位,跨公司調試在問題出現後可能得不到及時的支持,這大大延長了調試周期。

  第二、訪問限制,調試階段的查詢平臺未必對外公開,這增加了讀取方的驗證難度。

  如果序列化後的數據人眼可讀,這將大大提高調試效率, XMLJSON就具有人眼可讀的優點。

  性能

  性能包括兩個方面,時間複雜度和空間複雜度:

  第一、空間開銷(Verbosity), 序列化需要在原有的數據上加上描述字段,以爲反序列化解析之用。如果序列化過程引入的額外開銷過高,可能會導致過大的網絡,磁盤等各方面的壓力。對於海量分佈式存儲系統,數據量往往以TB爲單位,巨大的的額外空間開銷意味着高昂的成本。

  第二、時間開銷(Complexity),複雜的序列化協議會導致較長的解析時間,這可能會使得序列化和反序列化階段成爲整個系統的瓶頸。

  可擴展性/兼容性

  移動互聯時代,業務系統需求的更新週期變得更快,新的需求不斷湧現,而老的系統還是需要繼續維護。如果序列化協議具有良好的可擴展性,支持自動增加新的業務字段,而不影響老的服務,這將大大提供系統的靈活度。

  安全性/訪問限制

  在序列化選型的過程中,安全性的考慮往往發生在跨局域網訪問的場景。當通訊發生在公司之間或者跨機房的時候,出於安全的考慮,對於跨局域網的訪問往往被限制爲基於HTTP/HTTPS80443端口。如果使用的序列化協議沒有兼容而成熟的HTTP傳輸層框架支持,可能會導致以下三種結果之一:

  第一、因爲訪問限制而降低服務可用性。

  第二、被迫重新實現安全協議而導致實施成本大大提高。

  第三、開放更多的防火牆端口和協議訪問,而犧牲安全性。

  三、序列化和反序列化的組件

  典型的序列化和反序列化過程往往需要如下組件:

  • IDLInterface description language)文件:參與通訊的各方需要對通訊的內容需要做相關的約定(Specifications)。爲了建立一個與語言和平臺無關的約定,這個約定需要採用與具體開發語言、平臺無關的語言來進行描述。這種語言被稱爲接口描述語言(IDL),採用IDL撰寫的協議約定稱之爲IDL文件。
  • IDL CompilerIDL文件中約定的內容爲了在各語言和平臺可見,需要有一個編譯器,將IDL文件轉換成各語言對應的動態庫。
  • Stub/Skeleton Lib:負責序列化和反序列化的工作代碼。Stub是一段部署在分佈式系統客戶端的代碼,一方面接收應用層的參數,並對其序列化後通過底層協議棧發送到服務端,另一方面接收服務端序列化後的結果數據,反序列化後交給客戶端應用層;Skeleton部署在服務端,其功能與Stub相反,從傳輸層接收序列化參數,反序列化後交給服務端應用層,並將應用層的執行結果序列化後最終傳送給客戶端Stub
  • Client/Server:指的是應用層程序代碼,他們面對的是IDL所生存的特定語言的classstruct
  • 底層協議棧和互聯網:序列化之後的數據通過底層的傳輸層、網絡層、鏈路層以及物理層協議轉換成數字信號在互聯網中傳遞。


  序列化組件與數據庫訪問組件的對比

  數據庫訪問對於很多工程師來說相對熟悉,所用到的組件也相對容易理解。下表類比了序列化過程中用到的部分組件和數據庫訪問組件的對應關係,以便於大家更好的把握序列化相關組件的概念。

序列化組件 數據庫組件 說明

IDL DDL 用於建表或者模型的語言

DL file DB Schema 表創建文件或模型文件

Stub/Skeleton lib O/R mapping classTable或者數據模型進行映射

  四、幾種常見的序列化和反序列化協議

  互聯網早期的序列化協議主要有COMCORBA

  COM主要用於Windows平臺,並沒有真正實現跨平臺,另外COM的序列化的原理利用了編譯器中虛表,使得其學習成本巨大(想一下這個場景, 工程師需要是簡單的序列化協議,但卻要先掌握語言編譯器)。由於序列化的數據與編譯器緊耦合,擴展屬性非常麻煩。

  CORBA是早期比較好的實現了跨平臺,跨語言的序列化協議。COBRA的主要問題是參與方過多帶來的版本過多,版本之間兼容性較差,以及使用複雜晦澀。這些政治經濟,技術實現以及早期設計不成熟的問題,最終導致COBRA的漸漸消亡。J2SE 1.3之後的版本提供了基於CORBA協議的RMI-IIOP技術,這使得Java開發者可以採用純粹的Java語言進行CORBA的開發。

  這裏主要介紹和對比幾種當下比較流行的序列化協議,包括XMLJSONProtobufThriftAvro

  一個例子

  如前所述,序列化和反序列化的出現往往晦澀而隱蔽,與其他概念之間往往相互包容。爲了更好了讓大家理解序列化和反序列化的相關概念在每種協議裏面的具體實現,我們將一個例子穿插在各種序列化協議講解中。在該例子中,我們希望將一個用戶信息在多個系統裏面進行傳遞;在應用層,如果採用Java語言,所面對的類對象如下所示:

class Address

{

    private String city;

    private String postcode;

    private String street;

}

public class UserInfo

{

    private Integer userid;

    private String name;

    private List<Address> address;

}

  XML&SOAP

  XML是一種常用的序列化和反序列化協議,具有跨機器,跨語言等優點。 XML歷史悠久,其1.0版本早在1998年就形成標準,並被廣泛使用至今。XML的最初產生目標是對互聯網文檔(Document)進行標記,所以它的設計理念中就包含了對於人和機器都具備可讀性。 但是,當這種標記文檔的設計被用來序列化對象的時候,就顯得冗長而複雜(Verbose and Complex)。 XML本質上是一種描述語言,並且具有自我描述(Self-describing)的屬性,所以XML自身就被用於XML序列化的IDL 標準的XML描述格式有兩種:DTDDocument Type Definition)和XSDXML Schema Definition)。作爲一種人眼可讀(Human-readable)的描述語言,XML被廣泛使用在配置文件中,例如O/R mapping Spring Bean Configuration File 等。

  SOAPSimple Object Access protocol 是一種被廣泛應用的,基於XML爲序列化和反序列化協議的結構化消息傳遞協議。SOAP在互聯網影響如此大,以至於我們給基於SOAP的解決方案一個特定的名稱--Web serviceSOAP雖然可以支持多種傳輸層協議,不過SOAP最常見的使用方式還是XML+HTTPSOAP協議的主要接口描述語言(IDL)是WSDLWeb Service Description Language)。SOAP具有安全、可擴展、跨語言、跨平臺並支持多種傳輸層協議。如果不考慮跨平臺和跨語言的需求,XML的在某些語言裏面具有非常簡單易用的序列化使用方法,無需IDL文件和第三方編譯器, 例如JavaXStream

  自我描述與遞歸

  SOAP是一種採用XML進行序列化和反序列化的協議,它的IDLWSDL. WSDL的描述文件是XSD,而XSD自身是一種XML文件。 這裏產生了一種有趣的在數學上稱之爲遞歸的問題,這種現象往往發生在一些具有自我屬性(Self-description)的事物上。

  IDL文件舉例

  採用WSDL描述上述用戶基本信息的例子如下:

<xsd:complexType name='Address'>

     <xsd:attribute name='city' type='xsd:string' />

     <xsd:attribute name='postcode' type='xsd:string' />

     <xsd:attribute name='street' type='xsd:string' />

</xsd:complexType>

<xsd:complexType name='UserInfo'>

     <xsd:sequence>

     <xsd:element name='address' type='tns:Address'/>

     <xsd:element name='address1' type='tns:Address'/> 

     </xsd:sequence>

     <xsd:attribute name='userid' type='xsd:int' />

     <xsd:attribute name='name' type='xsd:string' /> 

</xsd:complexType>

  典型應用場景和非應用場景

  SOAP協議具有廣泛的羣衆基礎,基於HTTP的傳輸協議使得其在穿越防火牆時具有良好安全特性,XML所具有的人眼可讀(Human-readable)特性使得其具有出衆的可調試性,互聯網帶寬的日益劇增也大大彌補了其空間開銷大(Verbose)的缺點。對於在公司之間傳輸數據量相對小或者實時性要求相對低(例如秒級別)的服務是一個好的選擇。

  由於XML的額外空間開銷大,序列化之後的數據量劇增,對於數據量巨大序列持久化應用常景,這意味着巨大的內存和磁盤開銷,不太適合XML。另外,XML的序列化和反序列化的空間和時間開銷都比較大,對於對性能要求在ms級別的服務,不推薦使用。WSDL雖然具備了描述對象的能力,SOAPS代表的也是simple,但是SOAP的使用絕對不簡單。對於習慣於面向對象編程的用戶,WSDL文件不直觀。

  JSONJavascript Object Notation

  JSON起源於弱類型語言Javascript 它的產生來自於一種稱之爲"Associative array"的概念,其本質是就是採用"Attributevalue"的方式來描述對象。實際上在JavascriptPHP等弱類型語言中,類的描述方式就是Associative arrayJSON的如下優點,使得它快速成爲最廣泛使用的序列化協議之一:

  1、這種Associative array格式非常符合工程師對對象的理解。

  2、它保持了XML的人眼可讀(Human-readable)的優點。

  3、相對於XML而言,序列化後的數據更加簡潔。 來自於的以下鏈接的研究表明:XML所產生序列化之後文件的大小接近JSON的兩倍。http://www.codeproject.com/Articles/604720/JSON-vs-XML-Some-hard-numbers-about-verbosity

  4、它具備Javascript的先天性支持,所以被廣泛應用於Web browser的應用常景中,是Ajax的事實標準協議。

  5、與XML相比,其協議比較簡單,解析速度比較快。

  6、鬆散的Associative array使得其具有良好的可擴展性和兼容性。

  IDL悖論

  JSON實在是太簡單了,或者說太像各種語言裏面的類了,所以採用JSON進行序列化不需要IDL。這實在是太神奇了,存在一種天然的序列化協議,自身就實現了跨語言和跨平臺。然而事實沒有那麼神奇,之所以產生這種假象,來自於兩個原因:

  第一、Associative array在弱類型語言裏面就是類的概念,在PHPJavascript裏面Associative array就是其class的實際實現方式,所以在這些弱類型語言裏面,JSON得到了非常良好的支持。

  第二、IDL的目的是撰寫IDL文件,而IDL文件被IDL Compiler編譯後能夠產生一些代碼(Stub/Skeleton),而這些代碼是真正負責相應的序列化和反序列化工作的組件。 但是由於Associative array和一般語言裏面的class太像了,他們之間形成了一一對應關係,這就使得我們可以採用一套標準的代碼進行相應的轉化。對於自身支持Associative array的弱類型語言,語言自身就具備操作JSON序列化後的數據的能力;對於Java這強類型語言,可以採用反射的方式統一解決,例如Google提供的Gson

  典型應用場景和非應用場景

  JSON在很多應用場景中可以替代XML,更簡潔並且解析速度更快。典型應用場景包括:

  1、公司之間傳輸數據量相對小,實時性要求相對低(例如秒級別)的服務。

  2、基於Web browserAjax請求。

  3、由於JSON具有非常強的前後兼容性,對於接口經常發生變化,並對可調式性要求高的場景,例如Mobile app與服務端的通訊。

  4、由於JSON的典型應用場景是JSONHTTP,適合跨防火牆訪問。

  總的來說,採用JSON進行序列化的額外空間開銷比較大,對於大數據量服務或持久化,這意味着巨大的內存和磁盤開銷,這種場景不適合。沒有統一可用的IDL降低了對參與方的約束,實際操作中往往只能採用文檔方式來進行約定,這可能會給調試帶來一些不便,延長開發週期。 由於JSON在一些語言中的序列化和反序列化需要採用反射機制,所以在性能要求爲ms級別,不建議使用。

  IDL文件舉例

  以下是UserInfo序列化之後的一個例子:

{"userid":1,"name":"messi","address":[{"city":"北京","postcode":"1000000","street":"wangjingdonglu"}]}

  Thrift

  ThriftFacebook開源提供的一個高性能,輕量級RPC服務框架,其產生正是爲了滿足當前大數據量、分佈式、跨語言、跨平臺數據通訊的需求。 但是,Thrift並不僅僅是序列化協議,而是一個RPC框架。相對於JSONXML而言,Thrift在空間開銷和解析性能上有了比較大的提升,對於對性能要求比較高的分佈式系統,它是一個優秀的RPC解決方案;但是由於Thrift的序列化被嵌入到Thrift框架裏面,Thrift框架本身並沒有透出序列化和反序列化接口,這導致其很難和其他傳輸層協議共同使用(例如HTTP)。

  典型應用場景和非應用場景

  對於需求爲高性能,分佈式的RPC服務,Thrift是一個優秀的解決方案。它支持衆多語言和豐富的數據類型,並對於數據字段的增刪具有較強的兼容性。所以非常適用於作爲公司內部的面向服務構建(SOA)的標準RPC框架。

  不過Thrift的文檔相對比較缺乏,目前使用的羣衆基礎相對較少。另外由於其Server是基於自身的Socket服務,所以在跨防火牆訪問時,安全是一個顧慮,所以在公司間進行通訊時需要謹慎。 另外Thrift序列化之後的數據是Binary數組,不具有可讀性,調試代碼時相對困難。最後,由於Thrift的序列化和框架緊耦合,無法支持向持久層直接讀寫數據,所以不適合做數據持久化序列化協議。

  IDL文件舉例

struct Address

    1: required string city;

    2: optional string postcode;

    3: optional string street;

struct UserInfo

    1: required string userid;

    2: required i32 name;

    3: optional list<Address> address;

}

  Protobuf

  Protobuf具備了優秀的序列化協議的所需的衆多典型特徵:

  1、標準的IDLIDL編譯器,這使得其對工程師非常友好。

  2、序列化數據非常簡潔,緊湊,與XML相比,其序列化之後的數據量約爲1/31/10

  3、解析速度非常快,比對應的XML快約20-100倍。

  4、提供了非常友好的動態庫,使用非常簡介,反序列化只需要一行代碼。

  Protobuf是一個純粹的展示層協議,可以和各種傳輸層協議一起使用;Protobuf的文檔也非常完善。 但是由於Protobuf產生於Google,所以目前其僅僅支持JavaC++Python三種語言。另外Protobuf支持的數據類型相對較少,不支持常量類型。由於其設計的理念是純粹的展現層協議(Presentation Layer),目前並沒有一個專門支持ProtobufRPC框架。

  典型應用場景和非應用場景

  Protobuf具有廣泛的用戶基礎,空間開銷小以及高解析性能是其亮點,非常適合於公司內部的對性能要求高的RPC調用。由於Protobuf提供了標準的IDL以及對應的編譯器,其IDL文件是參與各方的非常強的業務約束,另外,Protobuf與傳輸層無關,採用HTTP具有良好的跨防火牆的訪問屬性,所以Protobuf也適用於公司間對性能要求比較高的場景。由於其解析性能高,序列化後數據量相對少,非常適合應用層對象的持久化場景。

  它的主要問題在於其所支持的語言相對較少,另外由於沒有綁定的標準底層傳輸層協議,在公司間進行傳輸層協議的調試工作相對麻煩。

  IDL文件舉例

message Address

{

    required string city=1;

        optional string postcode=2;

        optional string street=3;

}

message UserInfo

{

    required string userid=1;

    required string name=2;

    repeated Address address=3;

}

  Avro

  Avro的產生解決了JSON的冗長和沒有IDL的問題,Avro屬於Apache Hadoop的一個子項目。 Avro提供兩種序列化格式:JSON格式或者Binary格式。Binary格式在空間開銷和解析性能方面可以和Protobuf媲美,JSON格式方便測試階段的調試。 Avro支持的數據類型非常豐富,包括C++語言裏面的union類型。Avro支持JSON格式的IDL和類似於ThriftProtobufIDL(實驗階段),這兩者之間可以互轉。Schema可以在傳輸數據的同時發送,加上JSON的自我描述屬性,這使得Avro非常適合動態類型語言。 Avro在做文件持久化的時候,一般會和Schema一起存儲,所以Avro序列化文件自身具有自我描述屬性,所以非常適合於做HivePigMapReduce的持久化數據格式。對於不同版本的Schema,在進行RPC調用的時候,服務端和客戶端可以在握手階段對Schema進行互相確認,大大提高了最終的數據解析速度。

  典型應用場景和非應用場景

  Avro解析性能高並且序列化之後的數據非常簡潔,比較適合於高性能的序列化服務。

  由於Avro目前非JSON格式的IDL處於實驗階段,而JSON格式的IDL對於習慣於靜態類型語言的工程師來說不直觀。

  IDL文件舉例

protocol Userservice {

  record Address {

   string city;

   string postcode;

   string street;

  }  

  record UserInfo {

   string name;

   int userid;

   array<Address> address = [];

  }

}

  所對應的JSON Schema格式如下:

{

  "protocol" : "Userservice",

  "namespace" : "org.apache.avro.ipc.specific",

  "version" : "1.0.5",

  "types" : [ {

    "type" : "record",

    "name" : "Address",

    "fields" : [ {

      "name" : "city",

      "type" : "string"

    }, {

      "name" : "postcode",

      "type" : "string"

    }, {

      "name" : "street",

      "type" : "string"

    } ]

  }, {

    "type" : "record",

    "name" : "UserInfo",

    "fields" : [ {

      "name" : "name",

      "type" : "string"

    }, {

      "name" : "userid",

      "type" : "int"

    }, {

      "name" : "address",

      "type" : {

        "type" : "array",

        "items" : "Address"

      },

      "default" : [ ]

    } ]

  } ],

  "messages" : { }

}

  五、Benchmark以及選型建議

  Benchmark

  以下數據來自https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking

  解析性能


  序列化之空間開銷


  從上圖可得出如下結論:

  1XML序列化(Xstream)無論在性能和簡潔性上比較差。

  2ThriftProtobuf相比在時空開銷方面都有一定的劣勢。

  3ProtobufAvro在兩方面表現都非常優越。

  選型建議

  以上描述的五種序列化和反序列化協議都各自具有相應的特點,適用於不同的場景:

  1、對於公司間的系統調用,如果性能要求在100ms以上的服務,基於XMLSOAP協議是一個值得考慮的方案。

  2、基於Web browserAjax,以及Mobile app與服務端之間的通訊,JSON協議是首選。對於性能要求不太高,或者以動態類型語言爲主,或者傳輸數據載荷很小的的運用場景,JSON也是非常不錯的選擇。

  3、對於調試環境比較惡劣的場景,採用JSONXML能夠極大的提高調試效率,降低系統開發成本。

  4、當對性能和簡潔性有極高要求的場景,ProtobufThriftAvro之間具有一定的競爭關係。

  5、對於T級別的數據的持久化應用場景,ProtobufAvro是首要選擇。如果持久化後的數據存儲在Hadoop子項目裏,Avro會是更好的選擇。

  6、由於Avro的設計理念偏向於動態類型語言,對於動態語言爲主的應用場景,Avro是更好的選擇。

  7、對於持久層非Hadoop項目,以靜態類型語言爲主的應用場景,Protobuf會更符合靜態類型語言工程師的開發習慣。

  8、如果需要提供一個完整的RPC解決方案,Thrift是一個好的選擇。

  9、如果序列化之後需要支持不同的傳輸層協議,或者需要跨防火牆訪問的高性能場景,Protobuf可以優先考慮。

  參考文獻:

  http://www.codeproject.com/Articles/604720/JSON-vs-XML-Some-hard-numbers-about-verbosity

  https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking

  http://en.wikipedia.org/wiki/Serialization

  http://en.wikipedia.org/wiki/Soap

  http://en.wikipedia.org/wiki/XML

  http://en.wikipedia.org/wiki/JSON

  http://avro.apache.org/

  http://www.oracle.com/technetwork/java/rmi-iiop-139743.html




持久化

持久化是將程序數據在持久狀態和瞬時狀態間轉換的機制。通俗的講,就是瞬時數據(比如內存中的數據,是不能永久保存的)持久化爲持久數據(比如持久化至數據庫中,能夠長久保存)。

中文名

持久化

外文名

Persistence

    

通過邏輯處理接口儲存數據

    

持久化是針對時間來說的

目錄

  • 1 定義
  • 2 理解
  • 3 二個層面
  • 應用層
  • 系統層
  • 4 特點
  • 對象
  • 市場
  • 序列化
  • 5 意義
  • 興起原因
  • 運用
  • 6 相關實現
  • Hibernate
  • JPA




定義

編輯

持久化(Persistence),即把數據(如內存中的對象)保存到可永久保存的存儲設備中(如磁盤)。持久化的主要應用是將內存中的對象存儲在的數據庫中,或者存儲在磁盤文件中、XML數據文件中等等。

持久化是將程序數據在持久狀態和瞬時狀態間轉換的機制。

JDBC就是一種持久化機制。文件IO也是一種持久化機制。

日常持久化的方法

將鮮肉冷藏,喫的時候再解凍的方法也是。

將水果做成罐頭的方法也是。

將人的臟器迅速冷凍,運輸,然後解凍給人移植的技術也是。[1] 




理解

編輯

我們這樣理解:

在一定週期內保持不變就是持久化,持久化是針對時間來說的。

數據庫中的數據就是持久化了的數據,只要你不去刪除或修改。

比如在IE瀏覽器中一次Session會話中Session對象變量也是不變的,Session容器中持久化。

對象持久化的方式有很多種,根據週期不同有,page,Session,Application

對象序列化機制對於需要將對象的狀態保存到文件中,而後能夠通過讀入對象狀態來重新構造對象,恢復程序狀態,

對象序列化的過程是對象持久化的方法之一,把對象保存到文件中。




二個層面

編輯

簡單的理解持久化可以在二個層面:應用層和系統層、




應用層

如果關閉(shutdown)你的應用然後重新啓動則先前的數據依然存在。




系統層

如果關閉(shutdown)你的系統(電腦)然後重新啓動則先前的數據依然存在。




特點

編輯




對象

持久化是一種對象服務,就是把內存中的對象保存到外存中,讓以後能夠取回。需要實現至少3個接口:

void Save(object o) 把一個對象保存到外存中

Object Load(object oid) 通過對象標識從外存中取回對象

boolExists(object oid) 檢查外存中是否存在某個對象

爲什麼需要持久化服務呢?那是由於內存本身的缺陷引起的:

內存掉電後數據會丟失,但有一些對象是無論如何都不能丟失的,比如銀行賬號,遺憾的是,人們還無法保證內存永不掉電。

內存過於昂貴,與硬盤、磁帶、光盤等外存相比,內存的價格要高2~3個數量級,而且維持成本也高,至少需要一直供電吧。所以即使對象不需要永久保存,也會因爲內存的容量限制不能一直呆在內存中,需要持久化來緩存到外存。




市場

既然持久化服務在看得到的未來還有市場,我們就來看看如何構建一個好的持久化框架,框架是否真的好在於如何在擴展性、縮放性、重用性上取得良好的平衡:

擴展性,如果一個持久性框架不能支持用戶定義的類型,顯然不是一個好的框架。

縮放性,保存和取回對象都需要耗費cpu帶寬、時間資源,哪一個消耗太多都不能接受。

重用性是我們建立框架的初衷,就是通過框架能夠減少一些編碼和測試的工作量。

這幾個需求往往是互相沖突的,所以關鍵是平衡。




序列化

我們先跳開一下,看看另一個類似的有用概念:序列化也是一種對象服務,就是把內存中的對象序列化成流、或者把流反序列化成對象。需要實現2個接口:

void Serialize(Stream stream,object o) 把對象序列化到流中

object Deserialize(Stream stream) 把流反序列化成對象

序列化和持久化很相似,有些人甚至混爲一談,其實還是有區別的,序列化是爲了解決對象的傳輸問題,傳輸可以在線程之間、進程之間、內存外存之間、主機之間進行。我之所以在這裏提到序列化,是因爲我們可以利用序列化來輔助持久化,可以說凡是可以持久化的對象都可以序列化,因爲序列化相對容易一些(也不是很容易),所以主流的軟件基礎設施,比如.netjava,已經把序列化的框架完成了。

持久化方案可以分爲關係數據庫方案、文件方案、對象數據庫方案、xml數據庫方案,現今主流的持久化方案是關係數據庫方案,關係數據庫方案不僅解決了併發的問題,更重要的是,關係數據庫還提供了持久化服務之外的價值:統計分析功能。剛纔我說到,凡是可以序列化的對象都可以持久化,極端的說,我們可以只建立一個表Object(OID,Bytes),但基本上沒有人這麼做,因爲一旦這樣,我們就失去了關係數據庫額外的統計分析功能。

關係數據庫和面向對象之間有一條鴻溝,因爲二者模式不匹配,所以就存在一個OR映射問題。




意義

編輯




興起原因

當持久化興起的時候,逐漸形成了實體層這個概念了。hibernate[2]  ,jdo,以及博客園的nbear都可謂是大名鼎鼎!有的公司不使用這種ORM框架,他們使用一些自動生成工具生成實體(例如用Codesmith生成),並生成和該表對應的業務邏輯,於是乎感覺我們的程序好像一下子全都寫好了,下一步就輕鬆了,我們只要擴展業務即可了!莫非這樣真是那麼方便了?在維護上真的是最便捷嗎? 其它的持久層解決方案不敢說,但至少我覺得像orm的鼻祖hibernate那種開發機制,在維護還是相當之麻煩呀!一個實體還得對應一個xml文件(雖說這些都可以自動生成),但是你深入項目的時候去想想,我們的業務真能一切都可以定下來嗎?人的思想總是在變的,客戶的需求就更難以琢磨了!哪天我們要給數據庫加個字段,你想想你必須要走幾步改動?首先我們必須重新生成xml和實體,然後我們必須還得在業務邏輯中增加代碼,還得在視圖層加一個界面(如加一個input輸入框等)!講實話,加一個字段對這種orm框架的改動還是最少的,哪天假如說我們修改了哪個字段的名稱、修改了字段類型,你想想,天吶!很難想像,和這個字段關聯的程序都得改動!如果名稱改了,ok,你可以全部替換它的原先名稱,改成你新的名稱。那類型改了呢?沒辦法只能手工一個個改掉所有的賦值的類型吧?視圖層、控制層中的驗證(js驗證,業務驗證)、邏輯層、實體層,xml配置等等都必須動。搞啥個hsq,這和sql不差不多了嗎(雖然說hsq,抽象了數據庫模型)?不過我想沒有程序員不懂sql的吧?況且hsq對複雜的語句還是會力不從心的吧!




運用

運用ORM框架勢必會運用大量的反射,代價是犧牲性能。當然現今的各種ORM框架都在嘗試使用各種方法來減輕這塊(LazyLoadCache),效果還是很顯著的。可是我們犧牲了這麼大的性能,而且我是覺得在維護上ORM還是最便捷。

真不知道爲啥像hibernate這樣的框架還有一個xml配置文件?如果我真ORM的話,我不能把這些數據關係緩存起來,動態取關係不就行了嗎?這樣我不更靈活了嗎?

當然使用ORM也有它的活的活之處,在維護上那種自動生成的方式(petshop模式)比使用ORM框架維護量上更大一些,那種構架如果是每個數據操作對應一個存儲過程的改動會更會讓人暈頭轉向的。其構架大致如以下描述:

主要由BLL,MODEL,DAL三層構架方式實現,BLL存放的是相關業務,MODEL是相關的數據庫表格實體,DAL業務的SQL語句(或存儲過程參數).爲了鬆散耦合,在BLL層和DAL層中間加入了工廠層(Factory),其作用是方便DAL層的載體變動(如把Sqlserver改成Mysql),DAL層有一個setObject數據庫字段到實體屬性設置,便於數據庫表格映射成實體。

程序編寫的最大問題就是耦合高,怎麼降耦也是開發的一個重中之重。以上述的程序構架來看,如果我改動了數據庫中的其中一個表格的某個字段,程序改動的至少就有三層。如果再按照自動生成方式那種看,DAL中的update,insert,select, setObject都需要改動,如果存在存儲過程的話,像get,getAll,update,insert都必須改動,想象一下這裏改動地方有幾處了?而且還需改動Model層,修改量之大可見一斑。當然我們這裏可以用自動生成工具生成並替換,可又有誰知道這裏面的替換工作量多少?

總之,提倡"高內聚,低耦合"是構架永恆的話題,尋找便捷亦是構架的終級目標。




相關實現

編輯




Hibernate

hibernate爲應用程序提供了高效的O/R關係映射和查詢服務,爲面向對象的領域模型到傳統的關係型數據庫的映射,提供了一個使用方便的框架。




JPA

JPAJava Persistense API)是EJB3.0的一部分,爲其提供了一套O/R關係映射的API,但不僅限於EJB中使用,它也可以在web應用或者應用程序客戶端中被使用,甚至在Java桌面程序中被使用。





1.什麼是持久化?

本人找了好多文章都沒有找到滿意的答案,最後是從孫衛琴寫的《精通HibernateJava對象持久化技術詳解》中,看到如下的解釋,感覺還是比較完整的。摘抄如下:

狹義的理解: “持久化僅僅指把域對象永久保存到數據庫中;廣義的理解,“持久化包括和數據庫相關的各種操作(持久化就是將有用的數據以某種技術保存起來,將來可以再次取出來應用,數據庫技術,將內存數據一文件的形式保存在永久介質中(磁盤等)都是持久化的例子.)

●     保存:把域對象永久保存到數據庫。

●     更新:更新數據庫中域對象的狀態。

●     刪除:從數據庫中刪除一個域對象。

●     加載:根據特定的OID,把一個域對象從數據庫加載到內存。

●     查詢:根據特定的查詢條件,把符合查詢條件的一個或多個域對象從數據庫加載內在存中。

2.爲什麼要持久化?

持久化技術封裝了數據訪問細節,爲大部分業務邏輯提供面向對象的API

通過持久化技術可以減少訪問數據庫數據次數,增加應用程序執行速度;

代碼重用性高,能夠完成大部分數據庫操作;

鬆散耦合,使持久化不依賴於底層數據庫和上層業務邏輯實現,更換數據庫時只需修改配置文件而不用修改代碼。


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