設想我們在一家很大的互聯網公司做IT方面的規劃、開發和維護,有以下這樣的應用場景: 公司里有若幹個不同的開發團隊,開發語言有Java、.net、Python、C++....十來種,還有很多外包團隊對項目進行開發,大中小系統已經多的數不過來;並且各個團隊、系統間都需要進行海量數據的交換(比如搜索引擎實 ...
設想我們在一家很大的互聯網公司做IT方面的規劃、開發和維護,有以下這樣的應用場景:
- 公司里有若幹個不同的開發團隊,開發語言有Java、.net、Python、C++....十來種,還有很多外包團隊對項目進行開發,大中小系統已經多的數不過來;並且各個團隊、系統間都需要進行海量數據的交換(比如搜索引擎實時的數據,GPS物聯網實時數據,電站的實時監控數據等),如何定義一種數據格式,使得各種平臺和語言都能夠相容,交換的成本最低?
- 如此多的結構化、非結構化數據都需要進行存儲,有幾百個哈杜集群、數十萬台、百萬台伺服器,數據存儲在Hbase、RocksDb或者其他自己開發的資料庫結構中,對查詢的實時性很高(記憶體表個位數毫秒、SSD十幾毫秒等)。如何用一種較為統一的格式存放這些數據?
相信在大公司或者在大公司做過外包的童鞋,都接觸過這樣一種數據對象,那就是Bond格式,目前Bond由M$維護,官方網站:https://github.com/microsoft/bond/,上面提供了各種語言的示例、編譯工具等。
一個基本的Bond文件如下所示:
namespace School
struct Student
{
0: string Name;
1: uint8 Age;
2: bool IsBoy;
3: optional vector<string> Interests;
}
這裡定義了一個學校的命名空間,裡面有個學生類,學生類裡面有四個欄位,依次是姓名、年齡、是否為男孩、興趣愛好的列表(可選)。
很容易看出Bond結構實際是與平臺和語言無關的,它是一個DSL,在不同的平臺上,利用Bond編譯工具gbc,可以把Bond文件編譯成不同的類,然後就可以賦值、存儲和傳輸了,編譯好的Bond原生支持RPC調用。
Bond支持的數據類型有:
- 基本類型:int8, int16, int32, int64, uint8, uint16, uint32, uint64, float, double, bool, string, blob等,需要註意java平臺沒有uint類型,會編譯成帶有符號的同類型,數據會丟失精度(正數變成負數);
- 列表 vector、字典 map
- 枚舉 enum
- 預設值
- 可選欄位 optional,必須欄位 required
- 可空欄位 nullable
- 支持類的繼承
- 支持欄位的修飾 Attribute,這對前端驗證和資料庫存儲比較有用,能定義欄位長度、範圍、列族等
這些類型能很好的滿足數據交換和存儲的需要;除此以外,Bond是一種非常高效的數據存儲格式,它的二進位序列化最大程度去除了元數據的影響,極其緊湊,我們來看一個示例:
ListingItem是一個Bond類型,它的結構定義如下:
struct ListingItem
{
1: required uint64 xxxxxxxxx;
2: required uint8 xxxxxxxxx;
3: optional uint16 score;
4: optional vector<xxxx> xxxxxxxxxxx;
5: optional map<xxxxxx, uint16> xxxxxxxxx;
6: optional xxxxxx xxxxxxxxxxx = Exxxxx;
7: optional bool IsDeleted;
8: optional vector<xxxxxxxx> xxxxxxxxList = nothing;
}
由於牽涉到生產環境的真實數據,所以一些欄位和引用使用xxxxx來代替了,這個類的大小中等,有各種欄位,還有對其它類的引用和集合等等。
我們用隨機化的方式生成一百萬個類,類裡面的欄位和引用都不一樣,數值都是隨機生成的,然後用Bond序列化和Java中帶的Gson序列化方式進行序列化後的二進位長度比較,渣代碼如下:
@Test
public void ListingItemTest() throws IOException {
int cycleLength = 1000000;
Random random = new Random();
// Create 1000000 listing item
List<ListingItem> items = new ArrayList<>();
for(int i = 0; i < cycleLength; i ++){
ListingItem item = new ListingItem();
// ...
//賦值省略,利用random.nextLong() nextInt()等給欄位賦值
// ...
items.add(item);
}
StopWatch stopWatch = new StopWatch();
int length = 0;
stopWatch.start();
//Serialization Bond Object for 1000000 times
for(int i = 0; i < cycleLength; i ++){
byte[] bytes = BondSerializationUtils.serializeBondToBytes(items.get(i), ProtocolType.MARSHALED_PROTOCOL);
length += bytes.length;
}
stopWatch.stop();
System.out.println(String.format("Bond Serialization %d objects cost %d ms, avg length in bytes is %d", cycleLength, stopWatch.getTime(), length / cycleLength));
//Serialization as Json Object
length = 0;
stopWatch.reset();
stopWatch.start();
for(int i = 0; i < cycleLength; i ++){
String json = gson.toJson(items.get(i));
length += json.length();
}
stopWatch.stop();
System.out.println(String.format("Json Serialization %d objects cost %d ms, avg length in string is %d", cycleLength, stopWatch.getTime(), length / cycleLength));
}
在我的破筆記本(10代i5低功耗u)運行結果如下:
Bond Serialization 1000000 objects cost 1392 ms, avg length in bytes is 60
Json Serialization 1000000 objects cost 8837 ms, avg length in string is 310
由於Java字元串getBytes()後和原長度一樣,所以我們可以把字元串長度看作二進位數組長度。
多運行幾遍代碼,可以看到,Bond序列化的速度比Gson序列化的速度快4到5倍,序列化後的大小也只有json的1/5。(使用不同的序列化協議,比如COMPACT_PROTOCOL可以進一步壓縮結果大小和序列化時間,速度能比Json序列化快10倍以上)
這是個了不起的成績,如果我們生產環境中每天產生上百億條數據,這些數據用於各種轉換、分析與統計,使用Bond結構存儲只有使用字元串存儲空間的1/5,能夠省下4/5以EB、PB計的存儲成本;而且由於數據量的減少,傳輸和計算的成本也進一步壓縮,每年在IT基礎設施上的投入能節約上百億上千億美元,這些節省的成本最後都是利潤。
最後,由於Java平臺沒有自帶二進位序列化框架,我們用.net自帶的序列化框架測試下二進位序列化和Json序列化,序列化的類如下:
[Serializable]
public class TAListings
{
public string LxxxxxxxxList { get; set; }
public string Titles { get; set; }
public string CxxxxxxxxxxxxxxList { get; set; }
public string CxxxxxxxxxxxxxxxxList { get; set; }
}
代碼如下:
TAListings listings = new TAListings() { CxxxxxxxxxxxxxxxxList= "5033333309:-:73333333333334,34444444442:-:744444444442,54444444449:-:744444444444444448,544444443:-:744444444444444" };
var binSerilization = BinaryHelper.Serialize(listings);
var jsonSerilization = JsonHelper.Serialize(listings);
Console.WriteLine(string.Join(" ", binSerilization.Select(f => f.ToString("x2"))));
Console.WriteLine("Binary Serilization Length: " + binSerilization.Length);
Console.WriteLine();
Console.WriteLine(jsonSerilization);
Console.WriteLine("Json Serilization UTF8 Length: " + Encoding.UTF8.GetByteCount(jsonSerilization));
Console.ReadLine();
結果如截圖所示:
可以看到,如果只是普通的類,在.net使用二進位序列化後,反而比json序列化大了不少,增加的長度在二到四倍左右不等,這很反常識,是因為.net二進位序列化需要存儲更多的元數據嗎?
大家對我的文章有什麼問題和建議,都希望能夠參與討論,謝謝大家!
作者:thanks 微信:- QQ:305380844 出處:https://thanks.cnblogs.com/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。