Đua tốc độ với Protoco Buffer
Bài viết được sự cho phép của tác giả Nguyễn Hữu Đồng
Vốn là một thằng đam mê tốc độ, chạy xe 80,90km/h trên cao tốc như cơm bữa, trong quá trình học tập mình luôn giành thời gian tìm hiểu xem có giải pháp nào nhanh hơn giải pháp hiện tại không ? Và đương nhiên sẽ đánh đổi lại bằng nhiều như điển hình như độ phức tạp và thời gian triển khai, nhanh thì thực ra còn tuỳ thuộc vào bối cảnh, và bản chất của vấn đề cần giải quyết.
Sau tháng ngày dài làm việc cùng với JSON, chỉ biết JSON thì được ông anh giới thiệu cho thằng Protobuf, với một câu nói “nhanh lắm” mình liên bắt tay vào tìm hiểu và quả thực nó nhanh thât.
Đầu tiên nói về Protobuf, protobuf là một giao thức để tuần tự hoá dữ liệu có cấu trúc, tương tự với JSON hay XML, nhưng tốc độ thì JSON phải gọi bằng ông nội còn XML thì thôi không nói nữa, các bạn xem qua hình dưới nhé , sơ sơ thì tốc độ encode gấp 3 JSON và decode gấp 4–5 lần.
![Đua tốc độ với Protoco Buffer](https://topdev.vn/blog/wp-content/uploads/2020/12/0_EIy1OSvuMwTjfnXo.png)
Nhìn bề ngoài vậy thôi chứ bản chất Protobuf nó lưu trữ data dưới dạng Binary nên làm mất khả năng đọc hiểu của loài người.
Khi làm việc với Protobuf, bạn định nghĩa các mà data được cấu trúc như thế nào,sau đó thì Proto Complier sẽ biên dịch ra mã nguồn tuỳ theo ngôn ngữ mà các bạn sử dụng, không như JSON được sử dụng rộng rãi mà mọi ngôn ngữ đều có thể áp dụng một tiêu chuẩn chung còn Protobuf là hàng nội bộ của Google nên chỉ Google mới có thể tạo ra những driver cho từng ngôn ngữ.
Protobuf rất phù hợp để làm ngôn ngữ giao tiếp giữa các server hơn là server và browser-client đơn giản là vì hầu hết browser-client giao tiếp với server bằng style REST API +JSON cộng thêm vẫn cần khả năng readable ở browser.
Định nghĩa cách cấu trúc dữ liệu.
Mỗi file .proto gồm nhiều “message type” hiểu tương tự như struct trong go lang và class trong c++, mỗi message có thể embedded một hay nhiều message khác ví dụ trong file dứoi đây của mình, message user có thể embedded message contact.
syntax = "proto3"; package user; message ContactProtobuf { string phoneNumber = 5; string country = 6; } message UserProtobuf { string first_name = 1; string last_name = 2; string email = 3; repeated ContactProtobuf contact = 4; }
Hãy biên dịch ra file .go xem trong file đó có gì hay. Để biên dịch các bạn phải download proto compiler và xem hướng dẫn cài đặt trong file readme tại đây và plugin cho từng ngôn ngữ, với Golang để cài đặt compiler plugin thì chỉ cần
go get -u github.com/golang/protobuf/protoc-gen-go
Để compile thì chạy lệnh
protoc -I=<include-folder-path> --go_out=<out-put> path-to-file
Trong đó -I dùng khi các bạn cần dùng các file Proto của bên thứ 3, các bạn xem tại đây. Trong trường hợp của mình mình cần biên dịch ra cùng thư mục với thư mục chứa file proto nên mình chạy lênh
protoc --go_out= . *.proto
Và được file user.pb.go, sau đó mình sẽ copy file này vào trong thư mục trongproject nơi chứa package user và sử dụng, cùng xem file output có gì nào.
// Code generated by protoc-gen-go. DO NOT EDIT. // source: user.proto package user import ( fmt "fmt" proto "github.com/golang/protobuf/proto" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type ContactProtobuf struct { PhoneNumber string `protobuf:"bytes,5,opt,name=phoneNumber,proto3" json:"phoneNumber,omitempty"` Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *ContactProtobuf) Reset() { *m = ContactProtobuf{} } func (m *ContactProtobuf) String() string { return proto.CompactTextString(m) } func (*ContactProtobuf) ProtoMessage() {} func (*ContactProtobuf) Descriptor() ([]byte, []int) { return fileDescriptor_116e343673f7ffaf, []int{0} } func (m *ContactProtobuf) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ContactProtobuf.Unmarshal(m, b) } func (m *ContactProtobuf) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_ContactProtobuf.Marshal(b, m, deterministic) } func (m *ContactProtobuf) XXX_Merge(src proto.Message) { xxx_messageInfo_ContactProtobuf.Merge(m, src) } func (m *ContactProtobuf) XXX_Size() int { return xxx_messageInfo_ContactProtobuf.Size(m) } func (m *ContactProtobuf) XXX_DiscardUnknown() { xxx_messageInfo_ContactProtobuf.DiscardUnknown(m) } var xxx_messageInfo_ContactProtobuf proto.InternalMessageInfo func (m *ContactProtobuf) GetPhoneNumber() string { if m != nil { return m.PhoneNumber } return "" } func (m *ContactProtobuf) GetCountry() string { if m != nil { return m.Country } return "" } type UserProtobuf struct { FirstName string `protobuf:"bytes,1,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"` LastName string `protobuf:"bytes,2,opt,name=last_name,json=lastName,proto3" json:"last_name,omitempty"` Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` Contact []*ContactProtobuf `protobuf:"bytes,4,rep,name=contact,proto3" json:"contact,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *UserProtobuf) Reset() { *m = UserProtobuf{} } func (m *UserProtobuf) String() string { return proto.CompactTextString(m) } func (*UserProtobuf) ProtoMessage() {} func (*UserProtobuf) Descriptor() ([]byte, []int) { return fileDescriptor_116e343673f7ffaf, []int{1} } func (m *UserProtobuf) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_UserProtobuf.Unmarshal(m, b) } func (m *UserProtobuf) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_UserProtobuf.Marshal(b, m, deterministic) } func (m *UserProtobuf) XXX_Merge(src proto.Message) { xxx_messageInfo_UserProtobuf.Merge(m, src) } func (m *UserProtobuf) XXX_Size() int { return xxx_messageInfo_UserProtobuf.Size(m) } func (m *UserProtobuf) XXX_DiscardUnknown() { xxx_messageInfo_UserProtobuf.DiscardUnknown(m) } var xxx_messageInfo_UserProtobuf proto.InternalMessageInfo func (m *UserProtobuf) GetFirstName() string { if m != nil { return m.FirstName } return "" } func (m *UserProtobuf) GetLastName() string { if m != nil { return m.LastName } return "" } func (m *UserProtobuf) GetEmail() string { if m != nil { return m.Email } return "" } func (m *UserProtobuf) GetContact() []*ContactProtobuf { if m != nil { return m.Contact } return nil } func init() { proto.RegisterType((*ContactProtobuf)(nil), "user.ContactProtobuf") proto.RegisterType((*UserProtobuf)(nil), "user.UserProtobuf") } func init() { proto.RegisterFile("user.proto", fileDescriptor_116e343673f7ffaf) } var fileDescriptor_116e343673f7ffaf = []byte{ // 190 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x2d, 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0x7c, 0xb9, 0xf8, 0x9d, 0xf3, 0xf3, 0x4a, 0x12, 0x93, 0x4b, 0x02, 0x40, 0xa2, 0x49, 0xa5, 0x69, 0x42, 0x0a, 0x5c, 0xdc, 0x05, 0x19, 0xf9, 0x79, 0xa9, 0x7e, 0xa5, 0xb9, 0x49, 0xa9, 0x45, 0x12, 0xac, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0xc8, 0x42, 0x42, 0x12, 0x5c, 0xec, 0xc9, 0xf9, 0xa5, 0x79, 0x25, 0x45, 0x95, 0x12, 0x6c, 0x60, 0x59, 0x18, 0x57, 0x69, 0x22, 0x23, 0x17, 0x4f, 0x68, 0x71, 0x6a, 0x11, 0xdc, 0x30, 0x59, 0x2e, 0xae, 0xb4, 0xcc, 0xa2, 0xe2, 0x92, 0xf8, 0xbc, 0xc4, 0xdc, 0x54, 0x09, 0x46, 0xb0, 0x6a, 0x4e, 0xb0, 0x88, 0x5f, 0x62, 0x6e, 0xaa, 0x90, 0x34, 0x17, 0x67, 0x4e, 0x22, 0x4c, 0x96, 0x09, 0x2c, 0xcb, 0x01, 0x12, 0x00, 0x4b, 0x8a, 0x70, 0xb1, 0xa6, 0xe6, 0x26, 0x66, 0xe6, 0x48, 0x30, 0x83, 0x25, 0x20, 0x1c, 0x21, 0x7d, 0x90, 0xe5, 0x60, 0x17, 0x4b, 0xb0, 0x28, 0x30, 0x6b, 0x70, 0x1b, 0x89, 0xea, 0x81, 0x7d, 0x85, 0xe6, 0x8d, 0x20, 0x98, 0xaa, 0x24, 0x36, 0xb0, 0x7f, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xb2, 0x87, 0xe1, 0xdb, 0xfd, 0x00, 0x00, 0x00, }
Xem qua có thể thấy, compiler đã generate ra một struct và các method như Marshal , UnMarshal để encode message sang binary và decode to message struct từ binary. Và để tạo nên một message thì ta làm như sau.
Việc decode từ Binary sang ProtoBuf cũng rất nhanh so với JSON thuần để so sánh thì bài có làm một bài test, follow thực hiện ta tạo ra một message sau đó encode sang binary rồi lại decode ra message như ban đầu, cùng thực hiện trên cả JSON và Protobuf với N lần.
package user import ( "encoding/json" "fmt" "time" "github.com/golang/protobuf/proto" ) type Contact struct { PhoneNumber string `json:"phone_number` Country string `json:"country"` } type User struct { FirstName string `json:"first_name` LastName string `json:"last_name"` Contact []Contact `json:"contact"` } func Benchmark() { n := 5000000 fmt.Println("JSON - START") now := time.Now() for i := 1; i <= n; i++ { u := User{ FirstName: "Dong", LastName: "Nguyen", Contact: []Contact{ Contact{ PhoneNumber: "039 390 1228", Country: "Viet Nam", }, }, } binary, _ := json.Marshal(u) var v User json.Unmarshal(binary, &v) } fmt.Println("JSON - END :", time.Now().Sub(now)) fmt.Println("PROTOBUF - START") now = time.Now() for i := 1; i <= n; i++ { u := &UserProtobuf{ FirstName: "Dong", LastName: "Nguyen", Contact: []*ContactProtobuf{ &ContactProtobuf{ PhoneNumber: "039 390 1228", Country: "Viet Name", }, }, } binary, _ := proto.Marshal(u) var v UserProtobuf proto.Unmarshal(binary, &v) } fmt.Println("PROTOBUF - END :", time.Now().Sub(now)) } package main import "bench/user" func main() { user.Benchmark() }
Và đây là kết quả. Thực tế thì Protobuf nhanh hơn so với JSON khoảng 4 đến 5 lần.
Mặc dù hơi tốn sức trong việc define các message type rồi compile nhưng thành quả nhận được rất là xứng đáng.
Đến đây mình xin dừng bút trong bài sau mình sẽ giới thiệu gRPC cái thứ mà nếu dùng với Protobuf thì các các server của các bạn sẽ trở thành ma tốc độ.
Bye bye các bạn, cảm ơn đã đọc bài 😀
Bài viết gốc được đăng tải tại medium.com
Có thể bạn quan tâm:
- Buffer là gì? Hiểu về Buffer và Cache
- Giao tiếp hiệu quả giữa các Microservice
- Javascript Prototype là gì?
Xem thêm Việc làm Developer hấp dẫn trên TopDev
- C Cách tích hợp ChatGPT vào Google Search siêu dễ
- V VoiceGPT là gì? Giới thiệu tính năng và cách cài đặt sử dụng Voice GPT
- G GPT-4o Mini – Thông minh hơn và tiết kiệm hơn?
- C ChatGPT-4o là gì? Điểm mới của ChatGPT-4o vs ChatGPT-4
- C Chat GPT 4.0 là gì? Có gì vượt trội so với Chat GPT phiên bản cũ?
- C Cách tự học code web, tìm kiếm công việc dễ dàng và hạnh phúc mỗi ngày
- G Giới thiệu 15 website học và luyện hack hợp pháp
- T Tầm quan trọng của các chương trình đào tạo sau đại học trong kỷ nguyên 4.0
- i iOS 17.5 Beta 1 vừa được phát hành, những thay đổi nào đáng chú ý?
- i iOS 18 khi nào ra mắt? iOS 18 sẽ không hỗ trợ máy nào?