同一類框架,后出現的總會吸收之前框架的優點,然后加以改進,avro在序列化方面相對thrift就是一個很好的例子。借用Apache Avro 與 Thrift 比較 一文中的幾張圖來說明一下,avro在序列化方面的改進:
1、無需強制生成目標語言代碼
avro提供了二種使用方式,一種稱之為Sepcific方式,這跟thrift基本一致,都是寫定義IDL文件,然后用編譯器(或插件)生成目標class,另一種方式是Generic,這種方式下,不用生成目標代碼,而是采用動態加載定義文件的方式,將 FieldName - FieldValue,以Map<K,V>的方式存儲。
2、scheme/tag信息不重復寫入二進制數據,存儲及傳輸更高效
上圖是thrift的存儲格式,每塊數據前都有一個tag用于標識數據域的類型及編號(這部分tag信息可以理解為數據域的meta信息),如果傳輸一個List集合,集合中的每條記錄,這部分meta信息實際是重復存儲的,多少有些浪費。
這是avro的改進,avro拋棄了對Filed編號的做法,而是直接在class的頭部,把所有schema元數據信息包含在內(見下面的java代碼),這樣,client與server二端其實都已經知道數據的schema(架構模式)信息,僅僅在client與server通訊初始化,首次傳輸即可,以后無需再傳遞這部分信息,提升了網絡傳輸效率。類似剛才的List集合這種情況,這部分信息也需要重復存儲到2進制數據中,反序列化時,也不需再關注schema的信息,存儲空間更小。
package yjmyzz.avro.study.dto; @SuppressWarnings("all") @org.apache.avro.specific.AvroGenerated public class QueryParameter extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"QueryParameter\",\"namespace\":\"yjmyzz.avro.study.dto\",\"fields\":[{\"name\":\"ageStart\",\"type\":\"int\"},{\"name\":\"ageEnd\",\"type\":\"int\"}]}"); public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } //... }
這是avro生成的java代碼,從源代碼可以印證Schema確實已經包含在java代碼內。
關于avro的序列化,可以用下面的代碼測試一下:
package yjmyzz.avro.test; import org.apache.avro.Schema; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericDatumReader; import org.apache.avro.generic.GenericDatumWriter; import org.apache.avro.generic.GenericRecord; import org.apache.avro.io.*; import org.apache.avro.specific.SpecificDatumReader; import org.apache.avro.specific.SpecificDatumWriter; import org.junit.Assert; import org.junit.Test; import yjmyzz.avro.study.dto.QueryParameter; import java.io.ByteArrayOutputStream; import java.io.IOException; public class SerializeTest { @Test public void test() throws IOException { QueryParameter queryParameter = getQueryParameter(); //****** 1 Specific 方式-序列化*******// ByteArrayOutputStream out1 = new ByteArrayOutputStream(); DatumWriter<QueryParameter> writer1 = new SpecificDatumWriter<QueryParameter>(QueryParameter.class); BinaryEncoder encoder1 = EncoderFactory.get().binaryEncoder(out1, null); writer1.write(queryParameter, encoder1); encoder1.flush(); out1.close(); byte[] byte1 = out1.toByteArray(); System.out.println("Avro Specific二進制序列后的byte數組長度:" + byte1.length); //反序列化 DatumReader<QueryParameter> reader1 = new SpecificDatumReader<QueryParameter>(QueryParameter.class); Decoder decoder1 = DecoderFactory.get().binaryDecoder(out1.toByteArray(), null); QueryParameter result1 = reader1.read(null, decoder1); Assert.assertEquals(queryParameter.getAgeStart(), result1.getAgeStart()); Assert.assertEquals(queryParameter.getAgeEnd(), result1.getAgeEnd()); //**我是萬惡的分割線***// //****** 2 Genericy 方式-序列化*******// Schema.Parser parser = new Schema.Parser(); //Schema schema = parser.parse(new File("/Users/jimmy/Work/Code/avro/avro-contract/src/main/avro/QueryParameter.avsc")); Schema schema = parser.parse(getClass().getResourceAsStream("/QueryParameter.avsc")); //根據schema創建一個record示例(跟反射的思想有點類似) GenericRecord datum = new GenericData.Record(schema); datum.put("ageStart", 1); datum.put("ageEnd", 5); //序列化 ByteArrayOutputStream out2 = new ByteArrayOutputStream(); DatumWriter<GenericRecord> writer2 = new GenericDatumWriter<GenericRecord>(schema); Encoder encoder2 = EncoderFactory.get().binaryEncoder(out2, null); writer2.write(datum, encoder2); encoder2.flush(); out2.close(); byte[] byte2 = out2.toByteArray(); System.out.println("Avro Generic二進制序列后的byte數組長度:" + byte2.length); //反序列化 DatumReader<GenericRecord> reader2 = new GenericDatumReader<GenericRecord>(schema); Decoder decoder2 = DecoderFactory.get().binaryDecoder(out2.toByteArray(), null); GenericRecord result2 = reader2.read(null, decoder2); Assert.assertEquals(datum.get("ageStart"), result2.get("ageStart")); Assert.assertEquals(datum.get("ageEnd"), result2.get("ageEnd")); } private QueryParameter getQueryParameter() { QueryParameter query = new QueryParameter(); query.setAgeStart(1); query.setAgeEnd(5); return query; } }
Avro Specific二進制序列后的byte數組長度:2
Avro Generic二進制序列后的byte數組長度:2
與前一篇thrift中的序列化結果相比,存儲占用的空間比thrift的TCompactProtocol還要小,確實在序列化方面avro做得更好。
但是,凡事總有二面性,雖然avro在序列化方面做了不少改進,但是其RPC的實現并沒有做出太多的創新,默認提供的HttpServer、NettyServer都是直接用的其它開源產品實現,不象Thrift自己提供了全新的實現,所以在RPC的性能方面,avro仍有很多可以優化的空間,默認情況下,從我自己測試的情況下,avro是不敵thrift的。但具體能優化到什么程度,就看使用的人在網絡通訊、網絡協議方面的功底了,有朋友說avro使用c#語言開發Server與Client端,對源代碼優化后,可達到每秒20~30萬的處理數。
文章列表