高效數(shù)據(jù)傳輸?shù)拿孛芪淦鱌rotobuf的使用教程_第1頁(yè)
高效數(shù)據(jù)傳輸?shù)拿孛芪淦鱌rotobuf的使用教程_第2頁(yè)
高效數(shù)據(jù)傳輸?shù)拿孛芪淦鱌rotobuf的使用教程_第3頁(yè)
高效數(shù)據(jù)傳輸?shù)拿孛芪淦鱌rotobuf的使用教程_第4頁(yè)
高效數(shù)據(jù)傳輸?shù)拿孛芪淦鱌rotobuf的使用教程_第5頁(yè)
已閱讀5頁(yè),還剩15頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第高效數(shù)據(jù)傳輸?shù)拿孛芪淦鱌rotobuf的使用教程目錄Protobuf介紹編寫Protobuf頭部全局定義消息結(jié)構(gòu)具體定義字段類型定義編譯Protobuf使用Protobuf構(gòu)造消息對(duì)象序列化、反序列化Protobuf為什么高效序列化大小對(duì)比序列化速度對(duì)比為什么高效?總結(jié)當(dāng)涉及到網(wǎng)絡(luò)通信和數(shù)據(jù)存儲(chǔ)時(shí),數(shù)據(jù)序列化一直都是一個(gè)重要的話題;特別是現(xiàn)在很多公司都在推行微服務(wù),數(shù)據(jù)序列化更是重中之重,通常會(huì)選擇使用JSON作為數(shù)據(jù)交換格式,且JSON已經(jīng)成為業(yè)界的主流。但是Google這么大的公司使用的卻是一種被稱為Protobuf的數(shù)據(jù)交換格式,它是有什么優(yōu)勢(shì)嗎?這篇文章介紹Protobuf的相關(guān)知識(shí)。

GitHub:/protocolbuffers/protobuf

官方文檔:https://protobuf.dev/overview/

Protobuf介紹

Protobuf(ProtocolBuffers)是由Google開(kāi)發(fā)的一種輕量級(jí)、高效的數(shù)據(jù)交換格式,它被用于結(jié)構(gòu)化數(shù)據(jù)的序列化、反序列化和傳輸。相比于XML和JSON等文本格式,Protobuf具有更小的數(shù)據(jù)體積、更快的解析速度和更強(qiáng)的可擴(kuò)展性。

Protobuf的核心思想是使用協(xié)議(Protocol)來(lái)定義數(shù)據(jù)的結(jié)構(gòu)和編碼方式。使用Protobuf,可以先定義數(shù)據(jù)的結(jié)構(gòu)和各字段的類型、字段等信息,然后使用Protobuf提供的編譯器生成對(duì)應(yīng)的代碼,用于序列化和反序列化數(shù)據(jù)。由于Protobuf是基于二進(jìn)制編碼的,因此可以在數(shù)據(jù)傳輸和存儲(chǔ)中實(shí)現(xiàn)更高效的數(shù)據(jù)交換,同時(shí)也可以跨語(yǔ)言使用。

相比于XML和JSON,Protobuf有以下幾個(gè)優(yōu)勢(shì):

更小的數(shù)據(jù)量:Protobuf的二進(jìn)制編碼通常比XML和JSON小3-10倍,因此在網(wǎng)絡(luò)傳輸和存儲(chǔ)數(shù)據(jù)時(shí)可以節(jié)省帶寬和存儲(chǔ)空間。更快的序列化和反序列化速度:由于Protobuf使用二進(jìn)制格式,所以序列化和反序列化速度比XML和JSON快得多??缯Z(yǔ)言:Protobuf支持多種編程語(yǔ)言,可以使用不同的編程語(yǔ)言來(lái)編寫客戶端和服務(wù)端。這種跨語(yǔ)言的特性使得Protobuf受到很多開(kāi)發(fā)者的歡迎(JSON也是如此)。易于維護(hù)可擴(kuò)展:Protobuf使用.proto文件定義數(shù)據(jù)模型和數(shù)據(jù)格式,這種文件比XML和JSON更容易閱讀和維護(hù),且可以在不破壞原有協(xié)議的基礎(chǔ)上,輕松添加或刪除字段,實(shí)現(xiàn)版本升級(jí)和兼容性。

編寫Protobuf

使用Protobuf的語(yǔ)言定義文件(.proto)可以定義要傳輸?shù)男畔⒌臄?shù)據(jù)結(jié)構(gòu),可以包括各個(gè)字段的名稱、類型等信息。同時(shí)也可以相互嵌套組合,構(gòu)造出更加復(fù)雜的消息結(jié)構(gòu)。

比如想要構(gòu)造一個(gè)地址簿AddressBook信息結(jié)構(gòu)。一個(gè)AddressBook可以包含多個(gè)人員Person信息,每個(gè)Person信息可以包含id、name、email信息,同時(shí)一個(gè)Person也可以包含多個(gè)電話號(hào)碼信息PhoneNumber,每個(gè)電話號(hào)碼信息需要指定號(hào)碼種類,如手機(jī)、家庭電話、工作電話等。

如果使用Protobuf編寫定義文件如下:

//文件:to

syntax="proto3";

//指定protobuf包名,防止有相同類名的message定義

packagetobuf;

//是否生成多個(gè)文件

optionjava_multiple_files=true;

//生成的文件存放在哪個(gè)包下

optionjava_package="tos";

//生成的類名,如果沒(méi)有指定,會(huì)根據(jù)文件名自動(dòng)轉(zhuǎn)駝峰來(lái)命名

optionjava_outer_classname="AddressBookProtos";

messagePerson{

//=1,=2作為序列化后的二進(jìn)制編碼中的字段的唯一標(biāo)簽,也因此,1-15比16會(huì)少一個(gè)字節(jié),所以盡量使用1-15來(lái)指定常用字段。

optionalint32id=1;

optionalstringname=2;

optionalstringemail=3;

enumPhoneType{

MOBILE=0;

HOME=1;

WORK=2;

messagePhoneNumber{

optionalstringnumber=1;

optionalPhoneTypetype=2;

repeatedPhoneNumberphones=4;

messageAddressBook{

repeatedPersonpeople=1;

Protobuf文件中的語(yǔ)法解釋。

頭部全局定義

syntax=proto3指定Protobuf版本為版本3(最新版本)packagetobuf;指定Protobuf包名,防止有相同類名的message定義,這個(gè)包名是生成的類中所用到的一些信息的前綴,并非類所在包。optionjava_multiple_files=true;是否生成多個(gè)文件。若false,則只會(huì)生成一個(gè)類,其他類以內(nèi)部類形式提供。optionjava_package=生成的類所在包。optionjava_outer_classname生成的類名,若無(wú),自動(dòng)使用文件名進(jìn)行駝峰轉(zhuǎn)換來(lái)為類命名。

消息結(jié)構(gòu)具體定義

messagePerson定一個(gè)了一個(gè)Person類。

Person類中的字段被optional修飾,被optional修飾說(shuō)明字段可以不賦值。

修飾符optional表示可選字段,可以不賦值。修飾符repeated表示數(shù)據(jù)重復(fù)多個(gè),如數(shù)組,如List。修飾符required表示必要字段,必須給值,否則會(huì)報(bào)錯(cuò)RuntimeException,但是在Protobuf版本3中被移除。即使在版本2中也應(yīng)該慎用,因?yàn)橐坏┒x,很難更改。

字段類型定義

修飾符后面緊跟的是字段類型,如int32、string。常用的類型如下:

int32、int64、uint32、uint64:整數(shù)類型,包括有符號(hào)和無(wú)符號(hào)類型。float、double:浮點(diǎn)數(shù)類型。bool:布爾類型,只有兩個(gè)值,true和false。string:字符串類型。bytes:二進(jìn)制數(shù)據(jù)類型。enum:枚舉類型,枚舉值可以是整數(shù)或字符串。message:消息類型,可以嵌套其他消息類型,類似于結(jié)構(gòu)體。

字段后面的=1,=2是作為序列化后的二進(jìn)制編碼中的字段的對(duì)應(yīng)標(biāo)簽,因?yàn)镻rotobuf消息在序列化后是不包含字段信息的,只有對(duì)應(yīng)的字段序號(hào),所以節(jié)省了空間。也因此,1-15比16會(huì)少一個(gè)字節(jié),所以盡量使用1-15來(lái)指定常用字段。且一旦定義,不要隨意更改,否則可能會(huì)對(duì)不上序列化信息。

編譯Protobuf

使用Protobuf提供的編譯器,可以將.proto文件編譯成各種語(yǔ)言的代碼文件(如Java、C++、Python等)。

下載編譯器:/protocolbuffers/protobuf/releases/latest

安裝完成后可以使用protoc命令編譯proto文件,如編譯示例中的to.

protoc--java_out=./java./resources/to

生成后可以看到生產(chǎn)的類文件。

./

├──java

│└──com

│└──wdbyte

│└──tool

│├──protos

││├──AddressBook.java

││├──AddressBookOrBuilder.java

││├──AddressBookProtos.java

││├──Person.java

││├──PersonOrBuilder.java

└──resources

├──to

使用Protobuf

使用Java語(yǔ)言操作Protobuf,首先需要引入Protobuf依賴。

Maven依賴:

dependency

groupIdtobuf/groupId

artifactIdprotobuf-java/artifactId

version3.22.3/version

/dependency

構(gòu)造消息對(duì)象

//直接構(gòu)建

PhoneNumberphoneNumber1=PhoneNumber.newBuilder().setNumber().setType(PhoneType.HOME).build();

Personperson1=Person.newBuilder().setId(1).setName("").setEmail("xxx@").addPhones(phoneNumber1).build();

AddressBookaddressBook1=AddressBook.newBuilder().addPeople(person1).build();

System.out.println(addressBook1);

System.out.println("------------------");

//鏈?zhǔn)綐?gòu)建

AddressBookaddressBook2=AddressBook

.newBuilder()

.addPeople(Person.newBuilder()

.setId(2)

.setName("")

.setEmail("yyy@126.com")

.addPhones(PhoneNumber.newBuilder()

.setNumber()

.setType(PhoneType.HOME)

.build();

System.out.println(addressBook2);

輸出:

people{

id:1

name:

email:xxx@

phones{

number/p>

type:HOME

}

}

------------------

people{

id:2

name:

email:yyy@126.com

phones{

number/p>

type:HOME

}

}

序列化、反序列化

序列化:將內(nèi)存中的數(shù)據(jù)對(duì)象序列化為二進(jìn)制數(shù)據(jù),可以用于網(wǎng)絡(luò)傳輸或存儲(chǔ)等場(chǎng)景。

反序列化:將二進(jìn)制數(shù)據(jù)反序列化成內(nèi)存中的數(shù)據(jù)對(duì)象,可以用于數(shù)據(jù)處理和業(yè)務(wù)邏輯。

下面演示使用Protobuf進(jìn)行字符數(shù)組和文件的序列化及反序列化過(guò)程。

packagetos;

importjava.io.FileInputStream;

importjava.io.FileOutputStream;

importjava.io.IOException;

*@author

publicclassProtobufTest2{

publicstaticvoidmain(String[]args)throwsIOException{

PhoneNumberphoneNumber1=PhoneNumber.newBuilder().setNumber().setType(PhoneType.HOME).build();

Personperson1=Person.newBuilder().setId(1).setName("").setEmail("xxx@").addPhones(phoneNumber1).build();

AddressBookaddressBook1=AddressBook.newBuilder().addPeople(person1).build();

//序列化成字節(jié)數(shù)組

byte[]byteArray=addressBook1.toByteArray();

//反序列化-字節(jié)數(shù)組轉(zhuǎn)對(duì)象

AddressBookaddressBook2=AddressBook.parseFrom(byteArray);

System.out.println("字節(jié)數(shù)組反序列化:");

System.out.println(addressBook2);

//序列化到文件

addressBook1.writeTo(newFileOutputStream("AddressBook1.txt"));

//讀取文件反序列化

AddressBookaddressBook3=AddressBook.parseFrom(newFileInputStream("AddressBook1.txt"));

System.out.println("文件讀取反序列化:");

System.out.println(addressBook3);

輸出:

字節(jié)數(shù)組反序列化:

people{

id:1

name:

email:xxx@

phones{

number/p>

type:HOME

}

}

文件讀取反序列化:

people{

id:1

name:

email:xxx@

phones{

number/p>

type:HOME

}

}

Protobuf為什么高效

在分析Protobuf高效之前,我們先確認(rèn)一下Protobuf是否真的高效,下面將Protobuf與JSON進(jìn)行對(duì)比,分別對(duì)比序列化和反序列化速度以及序列化后的存儲(chǔ)占用大小。

測(cè)試工具:JMH,F(xiàn)astJSON,

測(cè)試對(duì)象:Protobuf的to,JSON的普通Java類。

Maven依賴:

dependency

groupIdcom.alibaba/groupId

artifactIdfastjson/artifactId

version2.0.7/version

/dependency

dependency

groupIdorg.openjdk.jmh/groupId

artifactIdjmh-core/artifactId

version1.33/version

/dependency

dependency

groupIdorg.openjdk.jmh/groupId

artifactIdjmh-generator-annprocess/artifactId

version1.33/version

scopeprovided/scope

/dependency

先編寫與to結(jié)構(gòu)相同的Java類AddressBookJava.java.

publicclassAddressBookJava{

ListPersonJavapersonJavaList;

publicstaticclassPersonJava{

privateintid;

privateStringname;

privateStringemail;

privatePhoneNumberJavaphones;

//get...set...

publicstaticclassPhoneNumberJava{

privateStringnumber;

privatePhoneTypeJavaphoneTypeJava;

//get....set....

publicenumPhoneTypeJava{

MOBILE,HOME,WORK;

publicListPersonJavagetPersonJavaList(){

returnpersonJavaList;

publicvoidsetPersonJavaList(ListPersonJavapersonJavaList){

this.personJavaList=personJavaList;

序列化大小對(duì)比

分別在地址簿中添加1000個(gè)人員信息,輸出序列化后的數(shù)組大小。

packagetos;

importjava.io.IOException;

importjava.util.ArrayList;

importcom.alibaba.fastjson.JSON;

importtos.AddressBook.Builder;

importtos.AddressBookJava.PersonJava;

importtos.AddressBookJava.PhoneNumberJava;

importtos.AddressBookJava.PhoneTypeJava;

importtos.Person.PhoneNumber;

importtos.Person.PhoneType;

*@author

publicclassProtobufTest3{

publicstaticvoidmain(String[]args)throwsIOException{

AddressBookJavaaddressBookJava=createAddressBookJava(1000);

StringjsonString=JSON.toJSONString(addressBookJava);

System.out.println("jsonstringsize:"+jsonString.length());

AddressBookaddressBook=createAddressBook(1000);

byte[]addressBookByteArray=addressBook.toByteArray();

System.out.println("protobufbytearraysize:"+addressBookByteArray.length);

publicstaticAddressBookcreateAddressBook(intpersonCount){

Builderbuilder=AddressBook.newBuilder();

for(inti=0;ipersonCount;i++){

builder.addPeople(Person.newBuilder()

.setId(i)

.setName("")

.setEmail("xxx@126.com")

.addPhones(PhoneNumber.newBuilder()

.setNumber()

.setType(PhoneType.HOME)

returnbuilder.build();

publicstaticAddressBookJavacreateAddressBookJava(intpersonCount){

AddressBookJavaaddressBookJava=newAddressBookJava();

addressBookJava.setPersonJavaList(newArrayList());

for(inti=0;ipersonCount;i++){

PersonJavapersonJava=newPersonJava();

personJava.setId(i);

personJava.setName("");

personJava.setEmail("xxx@126.com");

PhoneNumberJavanumberJava=newPhoneNumberJava();

numberJava.setNumber();

numberJava.setPhoneTypeJava(PhoneTypeJava.HOME);

personJava.setPhones(numberJava);

addressBookJava.getPersonJavaList().add(personJava);

returnaddressBookJava;

輸出:

jsonstringsize:108910

protobufbytearraysize:50872

可見(jiàn)測(cè)試中Protobuf的序列化結(jié)果比JSON小了將近一倍左右。

序列化速度對(duì)比

使用JMH進(jìn)行性能測(cè)試,分別測(cè)試JSON的序列化和反序列以及Protobuf的序列化和反序列化性能情況。每次測(cè)試前進(jìn)行3次預(yù)熱,每次3秒。接著進(jìn)行5次測(cè)試,每次3秒,收集測(cè)試情況。

packagetos;

importjava.util.ArrayList;

importjava.util.concurrent.TimeUnit;

importcom.alibaba.fastjson.JSON;

importtobuf.InvalidProtocolBufferException;

importtos.AddressBook.Builder;

importtos.AddressBookJava.PersonJava;

importtos.AddressBookJava.PhoneNumberJava;

importtos.AddressBookJava.PhoneTypeJava;

importtos.Person.PhoneNumber;

importtos.Person.PhoneType;

importorg.openjdk.jmh.annotations.Benchmark;

importorg.openjdk.jmh.annotations.BenchmarkMode;

importorg.openjdk.jmh.annotations.Fork;

importorg.openjdk.jmh.annotations.Measurement;

importorg.openjdk.jmh.annotations.Mode;

importorg.openjdk.jmh.annotations.OutputTimeUnit;

importorg.openjdk.jmh.annotations.Scope;

importorg.openjdk.jmh.annotations.Setup;

importorg.openjdk.jmh.annotations.State;

importorg.openjdk.jmh.annotations.Warmup;

*@author

@State(Scope.Thread)

@Fork(2)

@Warmup(iterations=3,time=3)

@Measurement(iterations=5,time=3)

@BenchmarkMode(Mode.Throughput)//Throughput:吞吐量,SampleTime:采樣時(shí)間

@OutputTimeUnit(TimeUnit.MILLISECONDS)

publicclassProtobufTest4{

privateAddressBookJavaaddressBookJava;

privateAddressBookaddressBook;

@Setup

publicvoidinit(){

addressBookJava=createAddressBookJava(1000);

addressBook=createAddressBook(1000);

@Benchmark

publicAddressBookJavatestJSON(){

//轉(zhuǎn)JSON

StringjsonString=JSON.toJSONString(addressBookJava);

//JSON轉(zhuǎn)對(duì)象

returnJSON.parseObject(jsonString,AddressBookJava.class);

@Benchmark

publicAddressBooktestProtobuf()throwsInvalidProtocolBufferException{

//轉(zhuǎn)JSON

byte[]addressBookByteArray=addressBook.toByteArray();

//JSON轉(zhuǎn)對(duì)象

returnAddressBook.parseFrom(addressBookByteArray);

publicstaticAddressBookcreateAddressBook(intpersonCount){

Builderbuilder=AddressBook.newBuilder();

for(inti=0;ipersonCount;i++){

builder.addPeople(Person.newBuilder()

.setId(i)

.setName("")

.setEmail("xxx@126.com")

.addPhones(PhoneNumber.newBuilder()

.setNumber()

.setType(PhoneType.HOME)

returnbuilder.build();

publicstaticAddressBookJavacreateAddressBookJava(intpersonCount){

AddressBookJavaaddressBookJava=newAddressBookJava();

addressBookJava.setPersonJavaList(newArrayList());

for(inti=0;ipersonCount;i++){

PersonJavapersonJava=newPersonJava();

personJava.setId(i);

personJava.setName("");

personJava.setEmail("xxx@126.com");

PhoneNumberJavanumberJava=newPhoneNumberJava();

numberJava.setNumber();

numberJava.setPhoneTypeJava(PhoneTypeJava.HOME);

personJava.setPhones(numberJava);

addressBookJava.getPersonJavaList().add(personJava);

returnaddressBookJava;

JMH吞吐量測(cè)試結(jié)果(Score值越大吞吐量越高,性能越好):

BenchmarkModeCntScoreErrorUnits

ProtobufTest3.testJSONthrpt101.8770.287ops/ms

ProtobufTest3.testProtobufthrpt102.8130.446ops/ms

JMH采樣時(shí)間測(cè)試結(jié)果(Score越小,采樣時(shí)間越小,性能越好):

BenchmarkModeCntScoreErrorUnits

ProtobufTest3.testJSONsample530280.5650.005ms/op

ProtobufTest3.testProtobufsample904130.3320.001ms/op

從測(cè)試結(jié)果看,不管是吞吐量測(cè)試,還是采樣時(shí)間測(cè)試,Protobuf都優(yōu)于JSON。

為什么高效?

Protobuf是如何實(shí)現(xiàn)這種高效緊湊的數(shù)據(jù)編碼和解碼的呢?

首先,Protobuf使用二進(jìn)制編碼,會(huì)提高性能;其次Protobuf在將數(shù)據(jù)轉(zhuǎn)換

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論