Protocol Buffers (Protobuf) là một định dạng tuần tự hóa nhị phân cho dữ liệu có cấu trúc, được Google phát triển để trao đổi thông tin hiệu quả giữa các dịch vụ. Định dạng yêu cầu định nghĩa lược đồ trước trong các tệp .proto, từ đó mã được tạo cho nhiều ngôn ngữ. Theo tài liệu chính thức của Google, Protobuf cung cấp kích thước thông điệp nhỏ hơn JSON 3-10 lần. Protobuf được sử dụng trong gRPC, Google Maps và hàng ngàn dịch vụ nội bộ.
Các điểm chính
Protocol Buffers là một cơ chế tuần tự hóa dữ liệu có cấu trúc do Google phát triển, tương tự như JSON và XML, nhưng có một sự khác biệt cơ bản: dữ liệu được mã hóa ở định dạng nhị phân. Điều này có nghĩa là thông điệp Protobuf không thể đọc bằng mắt thường, nhưng nó chiếm ít không gian hơn đáng kể và xử lý nhanh hơn các đối tác dựa trên văn bản.
Protobuf được tạo ra nội bộ tại Google để giải quyết các vấn đề hiệu suất trong trao đổi dữ liệu giữa các dịch vụ. Năm 2008, công nghệ này trở thành một dự án mã nguồn mở với hỗ trợ cho nhiều ngôn ngữ: C++, Java, Python, Go, JavaScript, Kotlin, Swift, Dart và các ngôn ngữ khác. Phiên bản proto3, phát hành năm 2016, đã đơn giản hóa cú pháp và thêm hỗ trợ cho nhiều ngôn ngữ hơn.
Sự khác biệt chính giữa Protobuf và JSON là sự cần thiết phải xác định lược đồ trước khi bắt đầu trao đổi dữ liệu. Tệp lược đồ (.proto) mô tả cấu trúc thông điệp: các trường, kiểu và số thứ tự trường duy nhất. Từ lược đồ này, trình biên dịch protoc tạo ra các lớp trong ngôn ngữ đích để tuần tự hóa và giải tuần tự hóa.
Kiến trúc Protobuf bao gồm ba thành phần chính: ngôn ngữ định nghĩa lược đồ (.proto), trình biên dịch protoc và thư viện thời gian chạy cho các ngôn ngữ cụ thể. Nhà phát triển mô tả cấu trúc dữ liệu trong tệp .proto, chạy biên dịch và nhận được các lớp có sẵn để làm việc với dữ liệu này.
Mỗi trường trong thông điệp Protobuf có một số duy nhất (số trường) — đây không phải là số thứ tự mà là một thẻ được sử dụng trong định dạng nhị phân để nhận dạng trường. Các số từ 1 đến 15 được mã hóa trong một byte, các số từ 16 đến 2047 trong hai byte. Do đó, các trường quan trọng có tần suất sử dụng cao nên được đánh số từ 1 đến 15 để tối ưu kích thước.
Protobuf hỗ trợ nhiều loại kiểu: vô hướng (int32, int64, float, double, bool, string, bytes), liệt kê (enum), tổng hợp (message) và các kiểu đặc biệt (oneof, map). Mỗi kiểu có một biểu diễn nhị phân cụ thể được tối ưu hóa cho trường hợp sử dụng tương ứng.
| Kiểu .proto | Kiểu C++ | Java/Kotlin | Mô tả |
|---|---|---|---|
| double | double | double | Số dấu phẩy động 64 bit |
| float | float | float | Số dấu phẩy động 32 bit |
| int32 | int32 | int | 32 bit, mã hóa độ dài thay đổi |
| int64 | int64 | long | 64 bit, mã hóa độ dài thay đổi |
| string | string | String | Chuỗi UTF-8 |
| bytes | string | ByteString | Byte tùy ý |
| bool | bool | boolean | true / false |
Protobuf cung cấp ba lợi ích chính so với các định dạng dựa trên văn bản: kích thước thông điệp, tốc độ tuần tự hóa và phân loại chặt chẽ. Trong các hệ thống có tải cao và ứng dụng di động có lưu lượng giới hạn, những lợi ích này trở nên quan trọng.
Biểu diễn nhị phân của Protobuf sử dụng mã hóa độ dài thay đổi (Varint) cho các số: số nhỏ chiếm 1 byte, số lớn tối đa 10 byte. Điều này cho phép mã hóa hiệu quả các định danh, cờ và bộ đếm mà trong JSON hoặc XML sẽ chiếm hàng chục byte dưới dạng chuỗi văn bản. Ví dụ, số 150 trong Protobuf chiếm 2 byte, trong JSON — 3 byte (dưới dạng văn bản “150”), trong XML — 3 byte + thẻ.
Một lợi ích bổ sung là tương thích ngược. Thêm một trường mới vào lược đồ không làm hỏng các client cũ: chúng chỉ đơn giản bỏ qua các trường không xác định. Xóa một trường chỉ cần dành riêng số của nó để tránh xung đột trong tương lai.
Lược đồ .proto mô tả cấu trúc dữ liệu bằng một ngôn ngữ đặc biệt. Tệp bắt đầu bằng việc chỉ định cú pháp (proto3), gói và các phụ thuộc nhập. Mỗi thông điệp được định nghĩa với từ khóa message cùng các trường, trong đó mỗi trường có một kiểu, tên và số duy nhất.
Các thực hành tốt về lược đồ: số trường từ 1 đến 15 cho các trường thường xuyên sử dụng, tên có ý nghĩa, nhóm các trường liên quan vào một message riêng, sử dụng oneof cho các trường chỉ có thể là một trong nhiều tùy chọn.
syntax = "proto3";
package mobileapp;
message User {
string user_id = 1;
string name = 2;
string email = 3;
int32 age = 4;
UserRole role = 5;
repeated string tags = 6;
map<string, string> metadata = 7;
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_USER = 1;
USER_ROLE_ADMIN = 2;
USER_ROLE_MODERATOR = 3;
}
Các thông điệp có thể được lồng nhau: một trường profile kiểu Profile sẽ chứa dữ liệu người dùng bên trong. Hỗ trợ lồng nhau cho phép mô tả các cấu trúc phân cấp phức tạp mà không trùng lặp định nghĩa.
syntax = "proto3";
package mobileapp;
message Order {
string order_id = 1;
repeated OrderItem items = 2;
double total_price = 3;
PaymentInfo payment = 4;
}
message OrderItem {
string product_id = 1;
string title = 2;
int32 quantity = 3;
double price = 4;
}
message PaymentInfo {
string method = 1;
string transaction_id = 2;
double amount = 3;
}
Trình biên dịch protoc chuyển đổi các tệp .proto thành mã nguồn trong ngôn ngữ đích. Đối với Kotlin/Java, tham số --java_out được sử dụng, đối với Swift --swift_out (thông qua plugin Apple Swift Protobuf), đối với Dart --dart_out. Các lớp được tạo chứa các phương thức builder để xây dựng thông điệp và các phương thức tuần tự hóa/giải tuần tự hóa.
Trên Android, để làm việc với Protobuf, sử dụng plugin com.google.protobuf phiên bản 0.9+ trong Gradle. Sau khi thêm plugin và chỉ định các tệp .proto, quá trình xây dựng sẽ tự động tạo ra các lớp Kotlin sẵn sàng để sử dụng trong ứng dụng.
import com.google.protobuf.kotlin.toByteString
import com.example.mobileapp.UserOuterClass.User
fun createUser(): User {
return User.newBuilder()
.setUserId("usr_001")
.setName("IT Sectr")
.setEmail("team@itsectr.com")
.setAge(5)
.setRole(UserOuterClass.UserRole.USER_ROLE_ADMIN)
.addTags("mobile")
.addTags("backend")
.build()
}
fun serializeAndDeserialize(user: User): User {
// Tuần tự hóa sang định dạng nhị phân
val bytes = user.toByteArray()
// Giải tuần tự hóa từ định dạng nhị phân
return User.parseFrom(bytes)
}
Protobuf đặc biệt hiệu quả trong kiến trúc microservice và ứng dụng di động. Hãy xem xét một kịch bản điển hình: một ứng dụng di động nhận danh sách sản phẩm từ máy chủ qua gRPC. Thông điệp Protobuf chứa thông tin sản phẩm bao gồm mã định danh, tên, giá và danh mục. Định dạng nhị phân giảm kích thước phản hồi từ 5 đến 8 lần so với JSON.
Ví dụ về dịch vụ gRPC với Protobuf ở phía máy chủ. Dịch vụ khai báo phương thức RPC GetProducts, chấp nhận yêu cầu với các tham số phân trang và trả về danh sách sản phẩm. Việc triển khai Kotlin sử dụng các lớp được tạo để làm việc với các yêu cầu và phản hồi.
import com.example.mobileapp.ProductServiceGrpcKt
import com.example.mobileapp.ProductOuterClass.Product
import com.example.mobileapp.ProductOuterClass.GetProductsRequest
import com.example.mobileapp.ProductOuterClass.GetProductsResponse
class ProductService : ProductServiceGrpcKt.ProductServiceCoroutineImplBase() {
override suspend fun getProducts(
request: GetProductsRequest
): GetProductsResponse {
val products = fetchProductsFromDb(
page = request.page,
limit = request.limit
)
return GetProductsResponse.newBuilder()
.addAllProducts(products)
.setTotalCount(products.size)
.build()
}
}
Thiết lập Protobuf trong dự án Android được thực hiện thông qua plugin Gradle com.google.protobuf. Plugin tự động chạy trình biên dịch protoc trong quá trình xây dựng và tạo các lớp Kotlin từ các tệp .proto. Để hoạt động, bạn cần thêm plugin vào build.gradle cấp dự án và áp dụng nó trong mô-đun ứng dụng.
Sau khi cấu hình Gradle, các tệp .proto được đặt trong thư mục src/main/proto. Trình biên dịch protoc xử lý chúng trong mỗi lần xây dựng, tạo ra các lớp Kotlin có thể được sử dụng trong mã ứng dụng. Điều quan trọng là phải chỉ định chính xác phiên bản protobuf và protoc để tránh xung đột phụ thuộc với các thư viện khác của dự án.
Đối với các dự án iOS, Protobuf được kết nối qua CocoaPods hoặc Swift Package Manager. Plugin Swift Protobuf tự động tạo các cấu trúc Swift tuân thủ giao thức Codable. Trong các dự án Dart cho Flutter, gói protobuf được sử dụng và việc biên dịch được thực hiện qua dart run protoc_plugin.
Câu hỏi thường gặp
Protobuf là định dạng nhị phân với lược đồ bắt buộc, nhỏ gọn hơn JSON 3-10 lần. JSON dựa trên văn bản, có thể đọc được bởi con người và không yêu cầu lược đồ. Protobuf tuần tự hóa và giải tuần tự hóa nhanh hơn nhưng yêu cầu biên dịch tệp .proto. JSON dễ gỡ lỗi hơn và không yêu cầu thiết lập trước.
Tải protoc từ bản phát hành GitHub của protobuf cho nền tảng của bạn. Đối với macOS, cài đặt qua brew install protobuf. Đối với Windows, tải xuống tệp zip và thêm protoc.exe vào PATH. Đối với Android/Kotlin, sử dụng plugin Gradle com.google.protobuf, plugin này tự động chạy biên dịch trong quá trình xây dựng.
gRPC là một framework RPC hiệu suất cao của Google sử dụng Protobuf làm ngôn ngữ định nghĩa giao diện (IDL) và định dạng tuần tự hóa. gRPC định nghĩa các dịch vụ và phương thức RPC trong các tệp .proto, tạo mã client và server, đồng thời hỗ trợ phát trực tiếp và các giao thức nhị phân.
Được Google hỗ trợ chính thức: C++, Java, Kotlin, Python, Go, Ruby, C#, PHP, JavaScript, Objective-C, Swift và Dart. Cộng đồng đã phát triển hỗ trợ cho Rust, TypeScript, Scala, Lua và các ngôn ngữ khác. Đối với phát triển di động, có sẵn Kotlin/Java (Android) và Swift/Objective-C (iOS).
Protobuf hỗ trợ tương thích ngược thông qua các quy tắc sau: không thay đổi số trường, không xóa trường (sử dụng reserved), thêm trường mới với số mới. Các client cũ bỏ qua các trường không xác định, các client mới nhận được giá trị mặc định cho các trường cũ bị thiếu.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm