-
[Protocol Buffer] 프로토콜 버퍼 소개 및 Go에서의 사용법OpenSource 2017. 5. 18. 15:14
Google Protocol Buffer
프로토콜 버퍼는 구글에서 내놓은 오픈소스 직렬화 라이브러리이다. 메시지를 연속된 비트로 만들고(직렬화-Serialize) 반대로 비트들에서 원래의 메시지로 만들어 낼 수 있다. (역직렬화-Deserialize) 네트워크 통신과 데이터 저장에 사용된다. 데이터 구조(Message)와 서비스를 Proto 스키마 파일에 저장하고, 다양한 언어(Java, C++, Python, Ruby, C, Go, Erlang, Javascript, Lua, Perl, PHP, R, Rust, Scala, Swift )로 인/디코딩하여 멀티 플랫폼 대응을 가능하게 한다. 즉, Big endian과 Little endian을 사용하는 언어사이에 이기종간 통신을 할 수 있다. 웹 애플리케이션에서는 보통 JSON을 사용한다. 네이티브 클라이언트라면 JSON 보다 Protobuf가 데이터의 크기를 줄이고 전반적인 성능 향상을 기대할 수 있다. 특히 전력, 네트워크 컴퓨팅 파워가 부족한 임베디드 기기의 경우 도움이 된다. 유사한 기술로는 Apache thrift와 Avro가 있다.
JSON, XML, Protocol buf에 대한 비교
JSON- 인간이 읽고 쓸 수 있다. : 개발과 디버깅이 편하다.
- 스키마에 대한 정보 없이, 사용할 수 있다.
- 웹 브라우저에서 사용하기 좋다.
- XML에 비해서 가볍다.
- 딱히 표준이라 할 만한 것들이 없다.
XML- 인간이 읽고 쓸 수 있다. : JSON 보다는 복잡하긴 하지만..
- 스키마에 대한 정보 없이, 사용할 수 있다.
- SOAP등의 표준이 있다.
- xsd, xslt, sax, dom 과 같은 좋은 툴들이 많다.
- JSON에 비해서 좀 장황하다.
Protocol Buffer- 데이터 압축률이 좋다.
- 정보를 얻기 위해서는 스키마에 대한 정확한 지식이 필요하다.
- 처리속도가 빠르다.
- 이진 데이터라서 사람의 눈으로 확인할 수 없다.
Protocol Buffer Language (Proto2)
(google의 language guide : https://developers.google.com/protocol-buffers/docs/proto): 프로토콜 버퍼는 Proto파일에 메세지 타입을 정의함으로써 직렬화할 데이터의 구조를 지정한다.
protocol buffer 언어를 사용하여 확장자가 " .proto "인 Proto 파일(데이터 스키마)을 작성한다.
- Message : 하나 이상의 필드, enum, nested message로 구성
- Field : 고유 번호, 이름, 데이터 타입을 가지며, 한정자 조건(required, optional, repeated) 명시
- required : 1개만 존재 가능
- optional : 0개 혹은 1개 존재 가능
- repeated : 여러 개 존재 가능 (0개 포함)고유번호
: 고유번호는 각각의 {}로 묶여진 범위내에서 고유한 번호를 가져야 하며, 1~15까지는 1바이트를 사용하기 때문에 최적화를 위해서는 required, repeated 필드는 1~15의 번호를 사용하고, 16이상 부터 optional 필드에게 할당하는 것이 좋다.
Golang에서 사용하기 (Proto2)
필드 데이터 타입 (Scalar Value Types)
.proto Type Notes Go Type double *float64 float *float32 int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. *int32 int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. *int64 uint32 Uses variable-length encoding. *uint32 uint64 Uses variable-length encoding. *uint64 sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. *int32 sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. *int64 fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. *uint32 fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. *uint64 sfixed32 Always four bytes. *int32 sfixed64 Always eight bytes. *int64 bool *bool string A string must always contain UTF-8 encoded or 7-bit ASCII text. *string bytes May contain any arbitrary sequence of bytes. []byte 컴파일링1. C++로 구현된 Protocol Buffer 컴파일러 다운로드 -> "src/README.md"에 따라서 설치
(https://github.com/google/protobuf/releases)
$ https://github.com/google/protobuf/releases/download/v3.3.0/protobuf-cpp-3.3.0.tar.gz
$ tar xvf protobuf-cpp-3.3.0.tar.gz
$ cd protobuf-3.3.0
$ ./configure
$ make
$ make check
$ sudo make install
$ sudo ldconfig2. Go protocol buffer 플러그인 설치
$ go get -u github.com/golang/protobuf/protoc-gen-go
3. 컴파일링$ protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/[.proto 파일 이름]Go API
addressbook.proto
package tutorial;
message Person {
required string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}위와 같은 proto 파일을 컴파일 했을때, (protoc -I=. --go_out=. addressbook.proto)
go 코드가 생성된다.
addressbook.pb.go
....
type Person_PhoneType int32
const (
Person_MOBILE Person_PhoneType = 0
Person_HOME Person_PhoneType = 1
Person_WORK Person_PhoneType = 2
)var Person_PhoneType_name = map[int32]string{
0: "MOBILE",
1: "HOME",
2: "WORK",
}
var Person_PhoneType_value = map[string]int32{
"MOBILE": 0,
"HOME": 1,
"WORK": 2,
}....
type Person struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
Id *int32 `protobuf:"varint,2,req,name=id" json:"id,omitempty"`
Email *string `protobuf:"bytes,3,opt,name=email" json:"email,omitempty"`
Phone []*Person_PhoneNumber `protobuf:"bytes,4,rep,name=phone" json:"phone,omitempty"`
XXX_unrecognized []byte `json:"-"`
}.....
위와 같이 생성된 golang 코드를 바탕으로 인스턴스 생성, 직렬화, 역직렬화 하기
example.go
package main
import (
pb "beji/proto2/tutorial"
"fmt"
"io/ioutil"
"log""github.com/golang/protobuf/proto"
)func NewPerson(id *int32, name, email, num *string) *pb.Person {
mobile := pb.Person_MOBILE
p := &pb.Person{
Id: id,
Name: name,
Email: email,
Phone: []*pb.Person_PhoneNumber{
{Number: num, Type: &mobile},
},
}
return p
}func Write(ab *pb.AddressBook, fname string) {
out, err := proto.Marshal(ab)
if err != nil {
log.Fatalln("Failed to encode address book:", err)
}if err := ioutil.WriteFile(fname, out, 0633); err != nil {
log.Fatalln("Failed to write address book:", err)
}
}
func Read(fname string) *pb.AddressBook {
in, err := ioutil.ReadFile(fname)
if err != nil {
log.Fatalln("Error reading file:", err)
}book := &pb.AddressBook{}
if err := proto.Unmarshal(in, book); err != nil {
log.Fatalln("Failed to parse address book:", err)
}return book
}func main() {
id := int32(1234)
name := "beji"
email := "beji@example.com"
number := "010-123-4567"book := &pb.AddressBook{
Person: []*pb.Person{
NewPerson(&id, &name, &email, &number),
},
}fmt.Println("book:", book)
Write(book, "output.txt")book2 := Read("output.txt")
fmt.Println("book2:", book2.String())
fmt.Println("book2 Person Email:", book2.GetPerson()[0].GetName())
fmt.Println("book2 Person Phone:", book2.GetPerson()[0].GetPhone())
}output :
book: person:<name:"beji" id:1234 email:"beji@example.com" phone:<number:"010-123-4567" type:MOBILE > >
book2: person:<name:"beji" id:1234 email:"beji@example.com" phone:<number:"010-123-4567" type:MOBILE > >
book2 Person Email: beji
book2 Person Phone: [number:"010-123-4567" type:MOBILE ]proto2 버전이 필요해서 이 버전으로 공부를 해봤는데,이 예제에서는 proto3 버전이 사용하기 쉬웠을 것이다. 데이터 타입이 포인터가 아니기 때문에 !'OpenSource' 카테고리의 다른 글
[GRPC] gRPC 소개 및 Go 기반 gRPC 설치 (0) 2017.05.31 [oVirt] 소개 (1) 2017.05.31 [Kibana] 설치 및 사용 (0) 2017.04.13 [Elasticsearch] 개요 및 API (0) 2017.04.13 [kafka] kafka 버전 업그레이드 (Rolling Upgrade) 0.8.x -> 0.10.2.0 (0) 2017.03.31