Trong những năm gần đây Microservices không còn xa lạ với các kiến trúc sư phần mềm, các nhà phát triển ứng dụng. Bên cạnh các kiến trúc hoạt động ổn định, để đáp ứng những yêu cầu mới ngày càng phức tạp của các ứng dụng, các công nghệ mới, framework mới không ngừng được ra đời. Trong bài viết này, tác giả sẽ chia sẻ đến các bạn một công nghệ được phát triển bởi đội ngũ chuyên gia của Google cách đây vài năm. Mời các bạn cùng tìm hiểu.

gRPC là gì?

Nếu bạn đã từng làm việc với RPC thì bạn có thể hiểu gRPC là một sự cải tiến của RPC. Cũng giống như RPC, gRPC cung cấp cho bạn một cách thức để bạn có thể gọi và thực thi các method của một service nào đó từ xa ở các server từ phía client, giống như bạn đang thực hiện ở trên server đó.

gRPC mặc định sử dụng Protocol Buffers như là một ngôn ngữ để định nghĩa các service và là định dạng để giao tiếp giữa các service với nhau.

gRPC cấp cho người dùng cơ chế cho phép các service sử dụng những platform khác nhau như Java, NodeJS, Go, Python, Ruby… có thể tương tác với nhau một cách dễ dàng mà không cần thêm công sức để viết các thư viện trung gian.

Protocol Buffers là gì?

Protocol Buffers là ngôn ngữ để định nghĩa các service và định dạng, kiểu dữ liệu để giao tiếp giữa các service với nhau. Mỗi định nghĩa Protocol Buffers được đặt trong một file với đuôi là proto.

Chúng ta cùng xem các ví dụ sau để có cái nhìn tổng quát về Protocol Buffers.

Định nghĩa cấu trúc 1 message

Như ở trên chúng ta đang định nghĩa một message với tên là SearchRequest, trong đó gồm có 3 trường dữ liệu với các kiểu tương ứng:

  • query: là một chuỗi truy vấn dữ liệu với kiểu là string.
  • page_number: để xác định page cần lấy dữ liệu với kiểu là integer.
  • result_per_page: là số trường dữ liệu cho mỗi page với kiểu là integer.

Định nghĩa nhiều cấu trúc message

Mỗi file proto có thể chứa nhiều định nghĩa message khác nhau, như ví dụ này chúng ta cùng định nghĩa cấu trúc của SearchRequest và SearchResponse trong cùng một file.

Kiểu dữ liệu với cấu trúc phức tạp

Ngoài các dữ liệu cơ bản như byte, integer, string,… thì Protocol Buffers hỗ trợ chúng ta định nghĩa các message có cấu trúc phức tạp, các message lồng nhau, các kiểu dữ liệu enum, mảng,…

Ở ví dụ này chúng ta định nghĩa trường results của SearchResponse là một trường dữ liệu có cấu trúc là Result message.

Định nghĩa service

Để thực hiện được một lệnh gọi RPC chúng ta phải có 2 đối tượng là Server và Client. Như trong định nghĩa ở trên chúng ta đang mong muốn là có 1 service với tên là SearchService, trong đó có 1 method là search với tham số đầu vào là SearchRequest và đầu ra là SearchResponse.

Chúng ta có thể đặt các định nghĩa message và service trong cùng một file proto cùng nhau.

Sinh code

Sau khi chúng ta đã định nghĩa cấu trúc dữ liệu và service trong file proto, chúng ta cần sinh code các class tương ứng với ngôn ngữ mà chúng ta đang sử dụng như: C#, Java, C++,…

Google đã cung cấp cho chúng ta một công cụ để thực hiện việc đó gọi là protoc.

Protoc giúp chúng ta biên dịch file proto ra các file code tương ứng với các tham số như sau:

  • IMPORT_PATH: là thư mục chứa các file proto liên quan được sử dụng trong quá trình biên dịch.
  • Chúng ta có thể cung cấp các tham số khác để chỉ dẫn quá trình biên dịch:
    • –cpp_out: sinh code C++ vào thư mục DST_DIR.
    • –java_out: sinh code Java vào thư mục DST_DIR.
    • –python_out: sinh code Python vào thư mục DST_DIR.
    • –go_out: sinh code Go vào thư mục DST_DIR.
    • –ruby_out: sinh code Ruby vào thư mục DST_DIR.
    • –objc_out: sinh code Objective-C vào thư mục DST_DIR.
    • –csharp_out: sinh code C# vào thư mục DST_DIR.
    • –php_out: sinh code PHP vào thư mục DST_DIR.

Các bạn có thể tìm hiểu chi tiết hơn ở: https://developers.google.com/protocol-buffers/docs/proto3

Ưu điểm của Protocol Buffers

Một số thông số khi so sánh với XML được đưa ra bởi Google như sau:

  • Đơn giản hơn.
  • Nhỏ hơn từ 3 đến 10 lần.
  • Nhanh hơn từ 20 đến 100 lần.
  • Rõ ràng hơn.
  • Dễ dàng sinh các class để sử dụng trực tiếp cho các ngôn ngữ lập trình.

HTTP/2

Tại sao gRPC hoạt động hiệu quả hơn RPC, có điểm gì đặc biệt ở công nghệ này tạo ra điều đó? Câu trả lời là: HTTP/2, công nghệ gRPC được xây dựng dựa trên HTTP/2. Những cải tiến của HTTP/2 so với các phiên bản tiền nhiệm cho phép các kết nối http hoạt động tốt hơn, hiệu quả hơn. Chúng ta cùng điểm qua một số tính năng HTTP/2 nổi trội:

  • Multiplexing (ghép kênh): một trong những tính năng quan trong nhất của HTTP/2 là ghép kênh, chúng ta có thể gửi và nhận nhiều kết quả trong cùng một kết nối duy nhất. Các bạn xem hình sau:

HTTP/1.1 cần nhiều kết nối cho các request khác nhau.

HTTP/2.0 cần 1 kết nối cho nhiều requests và responses.

  • Nén Header: khi thực hiện mỗi http request chúng ta cần cung cấp các thông tin cần thiết cho request đó như thông tin cookie, agent,… việc gửi các thông tin này lặp đi lặp lại sẽ tốn một lượng tài nguyên nhất định và làm giảm hiệu quả hoạt động của ứng dụng. Với HTTP/2 quá trình này sẽ được tối ưu hóa bằng cách nén thông tin đó lại, trong cơ chế này cả client và server đều lưu trữ thông tin của các lần request trước, các lần request sau có thể lược bỏ bớt các thông tin mà các request trước đã có.
  • Dữ liệu nhị phân: HTTP/2 truyền dữ liệu ở dạng nhị phân thay vì dạng text như các phiên bản trước. Nó giúp quá trình truyền tải hiệu quả hơn và tránh các lỗi thường gặp khi xử lý dữ liệu các request như khoảng trống, các ký tự đặc biệt,…

Và còn nhiều cải tiến khác các bạn xem thêm ở các link tham khảo cuối bài.

Những kiểu RPC method nào được hỗ trợ?

gRPC cung cấp cho chúng ta 4 kiểu method có thể thực hiện.

Unary RPCs

Kiểu này giống với các kiểu RPC truyền thống, client gửi một request đến server, đợi server xử lý rồi trả về kết quả cho client.

Cách định nghĩa method này trong file proto như sau:

Server streaming RPCs

Ở kiểu này, Client gửi một request đến server sau đó đợi server trả về một stream dữ liệu. Client sẽ đọc các message từ stream đó cho đến khi không còn message nào được trả về. Thứ tự các message của mỗi stream được đảm bảo giống nhau giữa Client và Server.

Cách định nghĩa method kiểu này trong file proto như sau:

Client streaming RPCs

Tương tự kiểu Server streaming RPCs, ở kiểu này Client sẽ là phía gửi stream dữ liệu lên server, server sẽ đọc stream dữ liệu đó và thực hiện các xử lý cần thiết, tiếp theo sẽ trả dữ liệu về cho phía Client.

Cách định nghĩa method kiểu này trong file proto như sau:

Bidirectional streaming RPCs

Đây là kiểu method mà dữ liệu được gửi đi theo stream từ cả 2 chiều Client và Server, dữ liệu stream cả 2 chiều là độc lập với nhau, Client và Server có thể xử lý dữ liệu stream đó độc lập. Nghĩa là khi Client gửi 1 message lên server thì server có thể xử lý để thực hiện một tác vụ nào đó (trong khi vẫn nhận các message khác) và gửi kết quả lại cho Client (trong khi Client vẫn đang gửi message khác).

Cách định nghĩa method kiểu này trong file proto như sau:

Tích hợp gRPC services giữa các nền tảng khác nhau

Như đã nói ở trên, một trong những điểm cải tiến quan trọng của gRPC là hỗ trợ đa nền tảng. Với RPC đơn thuần, để tích hợp các service được viết với các nền tảng với nhau rất khó khăn. gRPC hỗ trợ rất tốt để chúng ta có thể tích hợp các service với nhau mà không cần thêm 1 thư viện nào khác. Bạn có thể tạo ra một RPC service ở phía server với một ngôn ngữ bất kỳ (ví dụ Java), sau đó bạn tạo Client để gọi RPC service đó ở một ngôn ngữ hoàn toàn khác (ví dụ NodeJS).

Bạn có thể download source code ở đây để test thử:

Phía server được viết bằng Java, nhận input là firstName và lastName, kết quả trả về là: “Hello <firstName> <lastName>”

Phía Client viết bằng NodeJS như sau:

Kết quả test như hình sau:

Ngoài kiểu UnaryRPC, tác giả còn kèm theo source code mẫu cho các kiểu hỗ trợ khác của gRPC, bạn xem ở các branch tương ứng.

Nhược điểm

Song song các ưu điểm nổi trội đã được nói, gRPC cũng có những nhược điểm bạn nên cân nhắc khi sử dụng:

Tài liệu tham khảo

Trần Hữu Lập FPT Software

Tin liên quan: