0

Bài 1: Tổng quan về Protobuf & Cú pháp cơ bản (proto3)

Trong kiến trúc Microservices, việc truyền tải dữ liệu giữa các service đòi hỏi tốc độ cao, tiết kiệm băng thông mạng và tính an toàn về kiểu dữ liệu (type safety). Trước đây, JSON và XML là lựa chọn mặc định. Tuy nhiên, khi hệ thống phải xử lý hàng triệu request mỗi giây, các định dạng dựa trên văn bản (text-based) này bắt đầu bộc lộ hạn chế.

Đó là lúc Protocol Buffers (Protobuf) bước lên sân khấu. Trong bài học mở đầu này, chúng ta sẽ mổ xẻ lý do tại sao Protobuf lại vượt trội và làm quen với cú pháp cốt lõi của chuẩn proto3.


1. Tại sao Protobuf vượt trội hơn JSON / XML?

JSON và XML cực kỳ thân thiện với con người vì chúng là dạng văn bản thuần túy (human-readable), dễ đọc và dễ debug bằng mắt thường trên Postman hay Terminal. Tuy nhiên, cái giá phải trả cho sự "dễ đọc" đó là hiệu năng:

  • Dung lượng truyền tải: JSON phải lặp lại tên của các trường (field names) trong mọi request (ví dụ: {"user_id": 123, "username": "hieu"} lặp lại từ khóa user_id và username). Protobuf chuyển đổi toàn bộ cấu trúc này thành định dạng nhị phân (binary format), sử dụng các mã định danh dạng số (tag numbers) thay cho tên trường, giúp kích thước gói tin nhỏ hơn từ 3 đến 10 lần so với JSON.
  • Tốc độ xử lý (Serialization/Deserialization): Để đọc được JSON, máy tính phải phân tích cú pháp từng ký tự chuỗi (string parsing). Với Protobuf, dữ liệu nhị phân được ánh xạ trực tiếp vào bộ nhớ, giúp tốc độ đóng gói và giải mã nhanh hơn từ 20 đến 100 lần.
  • An toàn kiểu dữ liệu (Strong Typing): JSON không ép buộc kiểu nghiêm ngặt (một biến có lúc là string, lúc là number). Protobuf bắt buộc bạn phải định nghĩa schema tường minh qua file .proto, loại bỏ hoàn toàn các lỗi kiểu dữ liệu ngầm ẩn trước khi biên dịch sang code Go.

2. Cấu trúc cơ bản của một file .proto (Phiên bản proto3)

Hiện nay, phiên bản tiêu chuẩn và được khuyên dùng rộng rãi nhất là proto3. Một file định nghĩa .proto thường có cấu trúc chuẩn như sau:

syntax = "proto3"; // Khai báo sử dụng phiên bản proto3

package user;      // Định nghĩa namespace để tránh trùng lặp tên

// Cấu hình tùy chọn dành riêng cho Golang (đường dẫn package sinh ra code)
option go_package = "github.com/your-username/your-repo/pb;pb";

// Khai báo một Message (tương đương với Struct trong Go)
message UserRequest {
    int64 id = 1;
    string email = 2;
}

3. Các kiểu dữ liệu cơ bản (Scalar Types) trong proto3

Khi khai báo các trường bên trong một message, bạn sử dụng các kiểu dữ liệu scalar cơ bản của Protobuf. Dưới đây là bảng ánh xạ tương đương sang kiểu dữ liệu trong Golang:

Kiểu dữ liệu trong Protobuf Kiểu dữ liệu tương đương trong Golang Mô tả
int32, int64 int32, int64 Số nguyên có dấu (hỗ trợ số âm).
uint32, uint64 uint32, uint64 Số nguyên không dấu (chỉ số dương).
bool bool Giá trị logic true hoặc false.
string string Chuỗi ký tự định dạng UTF-8 hoặc ASCII.
bytes []byte Dữ liệu nhị phân thô (raw byte array).
float, double float32, float64 Số thực dấu chấm động.

4. Quy tắc đánh số trường (Field Numbers) - Cực kỳ quan trọng!

Nếu bạn nhìn vào cú pháp int64 id = 1;, con số 1 ở cuối không phải là giá trị mặc định của biến, mà là Field Number (Số định danh trường). Đây là phần quan trọng nhất trong cơ chế mã hóa nhị phân của Protobuf.

Các quy tắc vàng khi đánh số trường:

  1. Định danh bất biến: Trong định dạng nhị phân của Protobuf, tên trường (id, email) hoàn toàn bị vứt bỏ. Trình biên dịch chỉ dùng Field Number để nhận diện trường đó ở phía bên nhận. Do đó, một khi đã đưa code lên Production, tuyệt đối không được đổi số thứ tự của trường đã có.
  2. Tối ưu hóa băng thông (Tag Optimization):
    • Các số từ 1 đến 15 chỉ tốn 1 byte để mã hóa trong định dạng nhị phân (bao gồm cả số thẻ và kiểu dữ liệu). Hãy dành các số này cho các trường xuất hiện thường xuyên nhất trong message của bạn.
    • Các số từ 16 đến 2047 sẽ tốn 2 bytes để mã hóa.
  3. Vùng số bị cấm: Không được sử dụng các số trong khoảng từ 19000 đến 19999 (FieldDescriptor.ReservedRange). Đây là dải số được giữ riêng cho các mã nguồn nội bộ của Google.

Ví dụ minh họa:

message Product {
    // Đánh số từ 1-15 cho các trường truy vấn thường xuyên
    int64 id = 1;         
    string name = 2;      
    double price = 3;     

    // Các trường ít dùng hơn có thể đánh số lớn hơn 16
    string description = 100; 
}

💡 Tổng kết

Protobuf giúp chúng ta định nghĩa schema dữ liệu một cách chặt chẽ, tối ưu hóa kích thước gói tin và tốc độ xử lý dưới dạng nhị phân thay vì văn bản. Việc tuân thủ quy tắc đánh số trường (Field Numbers) chính là chìa khóa giúp hệ thống duy trì tính tương thích ngược (Backward Compatibility) khi cấu trúc dữ liệu thay đổi theo thời gian.

Trong bài học tiếp theo, chúng ta sẽ tiến một bước sâu hơn với các cấu trúc phức tạp như mảng (repeated), bản đồ (map), kiểu liệt kê (enum) và cách xử lý thời gian chuẩn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí