Khi những người nói các ngôn ngữ khác nhau gặp nhau và nói chuyện, họ cố gắng sử dụng ngôn ngữ mà mọi người trong nhóm đều hiểu
Để đạt được điều này, mọi người phải dịch những suy nghĩ của họ, thường là bằng ngôn ngữ mẹ đẻ của họ, sang ngôn ngữ của nhóm. Tuy nhiên, việc “mã hóa và giải mã” ngôn ngữ này dẫn đến mất hiệu quả, tốc độ và độ chính xác
Khái niệm tương tự cũng có mặt trong các hệ thống máy tính và các thành phần của chúng. Tại sao chúng ta nên gửi dữ liệu bằng XML, JSON hoặc bất kỳ định dạng nào khác mà con người có thể đọc được nếu chúng ta không cần phải hiểu trực tiếp những gì họ đang nói?
Bộ đệm giao thức là một cách để mã hóa dữ liệu trước khi vận chuyển, giúp thu nhỏ các khối dữ liệu một cách hiệu quả và do đó tăng tốc độ khi gửi dữ liệu đó. Nó tóm tắt dữ liệu thành một định dạng ngôn ngữ và nền tảng trung lập
Mục lục
Tại sao lại là bộ đệm giao thức?
Mục đích ban đầu của Bộ đệm giao thức là đơn giản hóa công việc với các giao thức yêu cầu/phản hồi. Trước ProtoBuf, Google đã sử dụng một định dạng khác yêu cầu xử lý bổ sung đối với các thư được gửi
Ngoài ra, các phiên bản mới của định dạng trước đó yêu cầu các nhà phát triển đảm bảo rằng các phiên bản mới được hiểu trước khi thay thế các phiên bản cũ, khiến việc làm việc với nó trở nên phức tạp.
Chi phí này đã thúc đẩy Google thiết kế một giao diện giải quyết chính xác những vấn đề đó
ProtoBuf cho phép thay đổi giao thức được giới thiệu mà không vi phạm tính tương thích. Ngoài ra, các máy chủ có thể truyền dữ liệu và thực hiện các thao tác đọc trên dữ liệu mà không sửa đổi nội dung của nó
Do định dạng có phần tự mô tả, ProtoBuf được sử dụng làm cơ sở để tạo mã tự động cho Bộ nối tiếp và Bộ giải mã
Một trường hợp sử dụng thú vị khác là cách Google sử dụng nó cho các Lệnh gọi thủ tục từ xa [RPC] trong thời gian ngắn và để lưu trữ dữ liệu liên tục trong Bigtable. Do trường hợp sử dụng cụ thể của họ, họ đã tích hợp giao diện RPC vào ProtoBuf. Điều này cho phép tạo mã sơ khai nhanh chóng và đơn giản, có thể được sử dụng làm điểm khởi đầu cho việc triển khai thực tế. [Thông tin thêm về ProtoBuf RPC. ]
Các ví dụ khác về nơi ProtoBuf có thể hữu ích là dành cho các thiết bị IoT được kết nối qua mạng di động trong đó lượng dữ liệu được gửi phải được giữ ở mức nhỏ hoặc cho các ứng dụng ở các quốc gia vẫn còn hiếm băng thông cao. Gửi tải trọng ở định dạng nhị phân, được tối ưu hóa có thể dẫn đến sự khác biệt đáng chú ý về chi phí và tốc độ vận hành
Sử dụng tính năng nén gzip
trong giao tiếp HTTPS của bạn có thể cải thiện hơn nữa các số liệu đó
Bộ đệm giao thức là gì và chúng hoạt động như thế nào?
Nói chung, Bộ đệm giao thức là một giao diện được xác định để tuần tự hóa dữ liệu có cấu trúc. Nó xác định một cách giao tiếp chuẩn hóa, hoàn toàn độc lập với ngôn ngữ và nền tảng
Google quảng cáo ProtoBuf của mình như thế này
Bộ đệm giao thức là cơ chế có thể mở rộng, trung lập về ngôn ngữ, nền tảng và có thể mở rộng của Google để tuần tự hóa dữ liệu có cấu trúc – ví dụ như XML, nhưng nhỏ hơn, nhanh hơn và đơn giản hơn. Bạn xác định cách bạn muốn dữ liệu của mình được cấu trúc một lần…
Giao diện ProtoBuf mô tả cấu trúc của dữ liệu được gửi. Cấu trúc tải trọng được định nghĩa là "thông báo" trong cái được gọi là Proto-Files. Những tệp đó luôn kết thúc bằng phần mở rộng .proto
Ví dụ, cấu trúc cơ bản của một todolist. tập tin proto trông như thế này. Chúng ta cũng sẽ xem xét một ví dụ hoàn chỉnh trong phần tiếp theo
syntax = "proto3";
// Not necessary for Python, should still be declared to avoid name collisions
// in the Protocol Buffers namespace and non-Python languages
package protoblog;
message TodoList {
// Elements of the todo list will be defined here
...
}
Các tệp đó sau đó được sử dụng để tạo các lớp tích hợp hoặc sơ khai cho ngôn ngữ bạn chọn bằng cách sử dụng trình tạo mã trong trình biên dịch protoc. Phiên bản hiện tại, Proto3, đã hỗ trợ tất cả các ngôn ngữ lập trình chính. Cộng đồng hỗ trợ nhiều hơn nữa trong việc triển khai nguồn mở của bên thứ ba
Các lớp được tạo là các thành phần cốt lõi của Bộ đệm giao thức. Chúng cho phép tạo các phần tử bằng cách khởi tạo các thông báo mới, dựa trên các tệp .proto
, sau đó được sử dụng để tuần tự hóa. Chúng ta sẽ xem xét chi tiết điều này được thực hiện với Python như thế nào trong phần tiếp theo
Không phụ thuộc vào ngôn ngữ để tuần tự hóa, các thông báo được tuần tự hóa thành định dạng nhị phân, không tự mô tả, khá vô dụng nếu không có định nghĩa cấu trúc ban đầu
Sau đó, dữ liệu nhị phân có thể được lưu trữ, gửi qua mạng và sử dụng bất kỳ cách nào khác mà con người có thể đọc được dữ liệu như JSON hoặc XML. Sau khi truyền hoặc lưu trữ, luồng byte có thể được giải tuần tự hóa và khôi phục bằng cách sử dụng bất kỳ lớp protobuf được biên dịch, dành riêng cho ngôn ngữ nào mà chúng tôi tạo ra từ. tập tin proto
Sử dụng Python làm ví dụ, quy trình có thể trông giống như thế này
Đầu tiên, chúng tôi tạo một danh sách việc cần làm mới và điền vào đó một số nhiệm vụ. Danh sách việc cần làm này sau đó được đánh số thứ tự và gửi qua mạng, được lưu trong một tệp hoặc được lưu trữ liên tục trong cơ sở dữ liệu
Luồng byte đã gửi được giải tuần tự hóa bằng cách sử dụng phương thức phân tích cú pháp của lớp được biên dịch theo ngôn ngữ cụ thể của chúng tôi
Hầu hết các kiến trúc và cơ sở hạ tầng hiện tại, đặc biệt là microservice, đều dựa trên giao tiếp REST, WebSockets hoặc GraphQL. Tuy nhiên, khi tốc độ và hiệu quả là điều cần thiết, các RPC cấp thấp có thể tạo ra sự khác biệt lớn
Thay vì các giao thức chi phí cao, chúng ta có thể sử dụng một cách nhanh chóng và nhỏ gọn để di chuyển dữ liệu giữa các thực thể khác nhau vào dịch vụ của mình mà không lãng phí nhiều tài nguyên
Nhưng tại sao nó không được sử dụng ở mọi nơi?
Bộ đệm giao thức phức tạp hơn một chút so với các định dạng khác mà con người có thể đọc được. Điều này làm cho chúng khó gỡ lỗi và tích hợp vào các ứng dụng của bạn hơn.
Thời gian lặp lại trong kỹ thuật cũng có xu hướng tăng lên do các bản cập nhật trong dữ liệu yêu cầu cập nhật các tệp proto trước khi sử dụng
Cần phải cân nhắc cẩn thận vì ProtoBuf có thể là một giải pháp được thiết kế quá mức trong nhiều trường hợp
Tôi có những lựa chọn thay thế nào?
Một số dự án có cách tiếp cận tương tự với Bộ đệm giao thức của Google
Bộ đệm phẳng của Google và triển khai của bên thứ ba, được gọi là Cap'n Proto, tập trung hơn vào việc loại bỏ bước phân tích cú pháp và giải nén, điều cần thiết để truy cập dữ liệu thực tế khi sử dụng ProtoBufs. Chúng đã được thiết kế rõ ràng cho các ứng dụng quan trọng về hiệu suất, khiến chúng thậm chí còn nhanh hơn và tiết kiệm bộ nhớ hơn so với ProtoBuf
Khi tập trung vào khả năng RPC của ProtoBuf [được sử dụng với gRPC], có những dự án từ các công ty lớn khác như Facebook [Apache Thrift] hoặc Microsoft [giao thức Bond] có thể cung cấp các lựa chọn thay thế
Bộ đệm giao thức và Python
Python đã cung cấp một số cách duy trì dữ liệu bằng cách sử dụng phương pháp tẩy. Pickling rất hữu ích trong các ứng dụng chỉ dành cho Python. Nó không phù hợp lắm với các tình huống phức tạp hơn khi có liên quan đến việc chia sẻ dữ liệu với các ngôn ngữ khác hoặc thay đổi lược đồ
Ngược lại, Bộ đệm giao thức được phát triển cho chính xác các tình huống đó
Các tệp .proto
, chúng tôi đã nhanh chóng đề cập trước đây, cho phép người dùng tạo mã cho nhiều ngôn ngữ được hỗ trợ
Để biên dịch tệp .proto
sang lớp ngôn ngữ mà chúng tôi chọn, chúng tôi sử dụng protoc, trình biên dịch proto
Nếu bạn chưa cài đặt trình biên dịch protoc, có hướng dẫn tuyệt vời về cách thực hiện điều đó
- MacOS/Linux
Khi chúng tôi đã cài đặt protoc trên hệ thống của mình, chúng tôi có thể sử dụng một ví dụ mở rộng về cấu trúc danh sách việc cần làm của chúng tôi từ trước đó và tạo lớp tích hợp Python từ nó
syntax = "proto3";
// Not necessary for Python but should still be declared to avoid name collisions
// in the Protocol Buffers namespace and non-Python languages
package protoblog;
// Style guide prefers prefixing enum values instead of surrounding
// with an enclosing message
enum TaskState {
TASK_OPEN = 0;
TASK_IN_PROGRESS = 1;
TASK_POST_PONED = 2;
TASK_CLOSED = 3;
TASK_DONE = 4;
}
message TodoList {
int32 owner_id = 1;
string owner_name = 2;
message ListItems {
TaskState state = 1;
string task = 2;
string due_date = 3;
}
repeated ListItems todos = 3;
}
Hãy xem chi tiết hơn về cấu trúc của tệp .proto
để hiểu nó
Trong dòng đầu tiên của tệp proto, chúng tôi xác định xem chúng tôi đang sử dụng Proto2 hay 3. Trong trường hợp này, chúng tôi đang sử dụng Proto3
Các phần tử không phổ biến nhất của tệp proto là các số được gán cho từng thực thể của thư. Các số chuyên dụng đó làm cho mỗi thuộc tính trở thành duy nhất và được sử dụng để xác định các trường được chỉ định trong đầu ra được mã hóa nhị phân
Một khái niệm quan trọng cần nắm bắt là chỉ các giá trị 1-15 được mã hóa bằng một byte nhỏ hơn [Hex], điều này rất hữu ích để hiểu để chúng ta có thể gán các số cao hơn cho các thực thể ít được sử dụng hơn. Các số không xác định thứ tự mã hóa cũng như vị trí của thuộc tính đã cho trong thông báo được mã hóa
Định nghĩa gói giúp ngăn xung đột tên. Trong Python, các gói được xác định bởi thư mục của chúng. Do đó, việc cung cấp thuộc tính gói không có bất kỳ ảnh hưởng nào đến mã Python được tạo
Xin lưu ý rằng điều này vẫn nên được khai báo để tránh xung đột tên liên quan đến bộ đệm giao thức và cho các ngôn ngữ khác như Java
Phép liệt kê là danh sách đơn giản các giá trị có thể có cho một biến nhất định
Trong trường hợp này, chúng tôi xác định một Enum cho các trạng thái có thể có của từng tác vụ trong danh sách việc cần làm
Chúng ta sẽ xem cách sử dụng chúng một chút khi xem cách sử dụng trong Python
Như chúng ta có thể thấy trong ví dụ, chúng ta cũng có thể lồng các tin nhắn bên trong các tin nhắn
Ví dụ: nếu chúng ta muốn có một danh sách các việc cần làm được liên kết với một danh sách việc cần làm đã cho, chúng ta có thể sử dụng từ khóa lặp lại, có thể so sánh với các mảng có kích thước động
Để tạo mã tích hợp có thể sử dụng được, chúng tôi sử dụng trình biên dịch proto để biên dịch một mã đã cho. tệp proto vào các lớp tích hợp dành riêng cho ngôn ngữ. Trong trường hợp của chúng tôi, chúng tôi sử dụng đối số --python-out để tạo mã dành riêng cho Python
syntax = "proto3";
// Not necessary for Python but should still be declared to avoid name collisions
// in the Protocol Buffers namespace and non-Python languages
package protoblog;
// Style guide prefers prefixing enum values instead of surrounding
// with an enclosing message
enum TaskState {
TASK_OPEN = 0;
TASK_IN_PROGRESS = 1;
TASK_POST_PONED = 2;
TASK_CLOSED = 3;
TASK_DONE = 4;
}
message TodoList {
int32 owner_id = 1;
string owner_name = 2;
message ListItems {
TaskState state = 1;
string task = 2;
string due_date = 3;
}
repeated ListItems todos = 3;
}
4Trong thiết bị đầu cuối, chúng tôi gọi trình biên dịch giao thức với ba tham số
- -TÔI. xác định thư mục nơi chúng tôi tìm kiếm bất kỳ phụ thuộc nào [chúng tôi sử dụng. đó là thư mục hiện tại]
- --python_out. xác định vị trí chúng tôi muốn tạo lớp tích hợp Python [một lần nữa chúng tôi sử dụng. đó là thư mục hiện tại]
- Tham số không tên cuối cùng xác định. tệp proto sẽ được biên dịch [chúng tôi sử dụng todolist. tệp proto trong thư mục hiện tại]
This creates a new Python file called _pb2.py. In our case, it is todolist_pb2.py. When taking a closer look at this file, we won’t be able to understand much about its structure immediately.
Điều này là do trình tạo không tạo ra các phần tử truy cập dữ liệu trực tiếp, nhưng tiếp tục trừu tượng hóa sự phức tạp bằng cách sử dụng siêu dữ liệu và bộ mô tả cho từng thuộc tính. Chúng mô tả cách một lớp hành xử thay vì từng thể hiện của lớp đó
Phần thú vị hơn là cách sử dụng mã được tạo này để tạo, xây dựng và tuần tự hóa dữ liệu. Một tích hợp đơn giản được thực hiện với lớp được tạo gần đây của chúng tôi được thấy trong phần sau
import todolist_pb2 as TodoList
my_list = TodoList.TodoList[]
my_list.owner_id = 1234
my_list.owner_name = "Tim"
first_item = my_list.todos.add[]
first_item.state = TodoList.TaskState.Value["TASK_DONE"]
first_item.task = "Test ProtoBuf for Python"
first_item.due_date = "31.10.2019"
print[my_list]
Nó chỉ tạo một danh sách việc cần làm mới và thêm một mục vào đó. Sau đó, chúng tôi in chính thành phần danh sách việc cần làm và có thể thấy phiên bản dữ liệu không nhị phân, không tuần tự hóa mà chúng tôi vừa xác định trong tập lệnh của mình
owner_id: 1234
owner_name: "Tim"
todos {
state: TASK_DONE
task: "Test ProtoBuf for Python"
due_date: "31.10.2019"
}
Mỗi lớp Bộ đệm giao thức có các phương thức để đọc và viết tin nhắn bằng mã hóa dành riêng cho Bộ đệm giao thức, mã hóa tin nhắn thành định dạng nhị phân
Hai phương thức đó là
syntax = "proto3";
// Not necessary for Python but should still be declared to avoid name collisions
// in the Protocol Buffers namespace and non-Python languages
package protoblog;
// Style guide prefers prefixing enum values instead of surrounding
// with an enclosing message
enum TaskState {
TASK_OPEN = 0;
TASK_IN_PROGRESS = 1;
TASK_POST_PONED = 2;
TASK_CLOSED = 3;
TASK_DONE = 4;
}
message TodoList {
int32 owner_id = 1;
string owner_name = 2;
message ListItems {
TaskState state = 1;
string task = 2;
string due_date = 3;
}
repeated ListItems todos = 3;
}
5 và syntax = "proto3";
// Not necessary for Python but should still be declared to avoid name collisions
// in the Protocol Buffers namespace and non-Python languages
package protoblog;
// Style guide prefers prefixing enum values instead of surrounding
// with an enclosing message
enum TaskState {
TASK_OPEN = 0;
TASK_IN_PROGRESS = 1;
TASK_POST_PONED = 2;
TASK_CLOSED = 3;
TASK_DONE = 4;
}
message TodoList {
int32 owner_id = 1;
string owner_name = 2;
message ListItems {
TaskState state = 1;
string task = 2;
string due_date = 3;
}
repeated ListItems todos = 3;
}
6import todolist_pb2 as TodoList
my_list = TodoList.TodoList[]
my_list.owner_id = 1234
# ...
with open["./serializedFile", "wb"] as fd:
fd.write[my_list.SerializeToString[]]
my_list = TodoList.TodoList[]
with open["./serializedFile", "rb"] as fd:
my_list.ParseFromString[fd.read[]]
print[my_list]
Trong ví dụ mã ở trên, chúng tôi viết chuỗi byte được tuần tự hóa vào một tệp bằng cờ wb
Vì chúng tôi đã ghi tệp, chúng tôi có thể đọc lại nội dung và Phân tích cú pháp bằng ParseFromString. ParseFromString gọi một phiên bản mới của lớp Serialized của chúng tôi bằng cách sử dụng các cờ rb và phân tích cú pháp nó
Nếu chúng tôi tuần tự hóa thông báo này và in nó trong bảng điều khiển, chúng tôi sẽ nhận được biểu diễn byte giống như thế này
syntax = "proto3";
// Not necessary for Python but should still be declared to avoid name collisions
// in the Protocol Buffers namespace and non-Python languages
package protoblog;
// Style guide prefers prefixing enum values instead of surrounding
// with an enclosing message
enum TaskState {
TASK_OPEN = 0;
TASK_IN_PROGRESS = 1;
TASK_POST_PONED = 2;
TASK_CLOSED = 3;
TASK_DONE = 4;
}
message TodoList {
int32 owner_id = 1;
string owner_name = 2;
message ListItems {
TaskState state = 1;
string task = 2;
string due_date = 3;
}
repeated ListItems todos = 3;
}
7Lưu ý b trước dấu ngoặc kép. Điều này chỉ ra rằng chuỗi sau bao gồm các byte octet trong Python
Nếu chúng ta so sánh trực tiếp điều này với, e. g. , XML, chúng ta có thể thấy tác động của tuần tự hóa ProtoBuf đối với kích thước
1234
Tim
TASK_DONE
Test ProtoBuf for Python
31.10.2019
Biểu diễn JSON, không xấu xí, sẽ trông như thế này
{
"todoList": {
"ownerId": "1234",
"ownerName": "Tim",
"todos": [
{
"state": "TASK_DONE",
"task": "Test ProtoBuf for Python",
"dueDate": "31.10.2019"
}
]
}
}
Chỉ đánh giá các định dạng khác nhau bằng tổng số byte được sử dụng, bỏ qua bộ nhớ cần thiết cho chi phí định dạng, tất nhiên chúng ta có thể thấy sự khác biệt
Nhưng ngoài bộ nhớ được sử dụng cho dữ liệu, chúng tôi còn có thêm 12 byte trong ProtoBuf để định dạng dữ liệu tuần tự hóa. So sánh với XML, chúng tôi có thêm 171 byte trong XML để định dạng dữ liệu tuần tự hóa
Không có Schema, chúng tôi cần thêm 136 byte trong JSON để định dạng dữ liệu tuần tự hóa
Nếu chúng ta đang nói về hàng nghìn tin nhắn được gửi qua mạng hoặc được lưu trữ trên đĩa, thì ProtoBuf có thể tạo ra sự khác biệt
Tuy nhiên, có một nhược điểm. Nền tảng Auth0. com đã tạo ra một so sánh mở rộng giữa ProtoBuf và JSON. Nó cho thấy rằng, khi được nén, sự khác biệt về kích thước giữa hai loại có thể là không đáng kể [chỉ khoảng 9%]
Nếu bạn quan tâm đến những con số chính xác, vui lòng tham khảo toàn bộ bài viết, trong đó đưa ra phân tích chi tiết về một số yếu tố như kích thước và tốc độ
Một lưu ý thú vị là mỗi loại dữ liệu có một giá trị mặc định. Nếu các thuộc tính không được gán hoặc thay đổi, chúng sẽ duy trì các giá trị mặc định. Trong trường hợp của chúng tôi, nếu chúng tôi không thay đổi TaskState của một ListItem, nó sẽ có trạng thái là “TASK_OPEN” theo mặc định. Ưu điểm đáng kể của điều này là các giá trị không được đặt không được đánh số thứ tự, tiết kiệm thêm dung lượng
Ví dụ: nếu chúng tôi thay đổi trạng thái của tác vụ từ TASK_DONE thành TASK_OPEN, thì tác vụ đó sẽ không được tuần tự hóa
owner_id: 1234
owner_name: "Tim"
todos {
task: "Test ProtoBuf for Python"
due_date: "31.10.2019"
}
syntax = "proto3";
// Not necessary for Python but should still be declared to avoid name collisions
// in the Protocol Buffers namespace and non-Python languages
package protoblog;
// Style guide prefers prefixing enum values instead of surrounding
// with an enclosing message
enum TaskState {
TASK_OPEN = 0;
TASK_IN_PROGRESS = 1;
TASK_POST_PONED = 2;
TASK_CLOSED = 3;
TASK_DONE = 4;
}
message TodoList {
int32 owner_id = 1;
string owner_name = 2;
message ListItems {
TaskState state = 1;
string task = 2;
string due_date = 3;
}
repeated ListItems todos = 3;
}
8Ghi chú cuối cùng
Như chúng ta đã thấy, Bộ đệm giao thức khá tiện dụng khi nói đến tốc độ và hiệu quả khi làm việc với dữ liệu. Do tính chất mạnh mẽ của nó, có thể mất một thời gian để làm quen với hệ thống ProtoBuf, mặc dù cú pháp xác định thông báo mới rất đơn giản
Lưu ý cuối cùng, tôi muốn chỉ ra rằng đã có/đang có các cuộc thảo luận về việc liệu Bộ đệm giao thức có “hữu ích” cho các ứng dụng thông thường hay không. Chúng được phát triển rõ ràng cho các vấn đề mà Google đã lưu ý
Nếu bạn có bất kỳ câu hỏi hoặc phản hồi nào, vui lòng liên hệ với tôi trên bất kỳ phương tiện truyền thông xã hội nào như twitter hoặc email. ]
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
Người học đam mê và nhà phát triển quan tâm đến tự động hóa. Sử dụng Scratch để dạy viết mã cho trẻ em trong các hội thảo tại thư viện thành phố địa phương. Người đam mê mã nguồn mở. Người tạo InstaPy
Nếu bạn đọc đến đây, hãy tweet cho tác giả để cho họ thấy bạn quan tâm. Tweet một lời cảm ơn
Học cách viết mã miễn phí. Chương trình giảng dạy mã nguồn mở của freeCodeCamp đã giúp hơn 40.000 người có được việc làm với tư cách là nhà phát triển. Bắt đầu