Tăng tốc Microservices: Tại sao mình chuyển từ REST sang gRPC với Python?

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Tại sao mình từ bỏ REST API để sang gRPC?

Hồi hệ thống mới chỉ có 3-4 service, mình cứ “táng” REST API (JSON qua HTTP/1.1) cho tiện. Mọi thứ vẫn ổn cho đến khi con số này tăng lên 20 service. Lúc này, tổng lượng request nội bộ chạm mốc 1.5 triệu mỗi phút và bắt đầu nảy sinh hai vấn đề nhức nhối.

Đầu tiên là độ trễ (Latency) tăng vọt do overhead của HTTP/1.1. Thứ hai là Payload quá nặng. Đống text JSON dư thừa chiếm quá nhiều băng thông vô ích. Sau 6 tháng chuyển sang gRPC cho các giao tiếp nội bộ, hiệu năng hệ thống thay đổi rõ rệt. Payload giảm tới 60% nhờ cơ chế binary. Đặc biệt, mình không còn phải hì hục viết tài liệu API thủ công vì mọi thứ đã được định nghĩa chặt chẽ trong Protocol Buffers.

Quick Start: Chạy gRPC trong vòng 5 phút

Lý thuyết suông rất dễ quên, hãy bắt tay vào code ngay để thấy sự khác biệt. Mình sẽ dựng một service đơn giản: Gửi tên và nhận về lời chào.

Bước 1: Cài đặt thư viện

pip install grpcio grpcio-tools

Bước 2: Định nghĩa file .proto

Tạo file hello.proto. Đây là bản thiết kế (contract) duy nhất giữa các service.

syntax = "proto3";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Bước 3: Generate code Python

Thay vì tự viết class, bạn hãy để Python tự sinh code từ file proto:

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. hello.proto

Lệnh này tạo ra hai file: hello_pb2.py (chứa message) và hello_pb2_grpc.py (chứa logic kết nối).

Bước 4: Viết Server và Client

File server.py:

import grpc
from concurrent import futures
import hello_pb2
import hello_pb2_grpc

class Greeter(hello_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return hello_pb2.HelloReply(message=f'Chào {request.name}, mình là gRPC server!')

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    hello_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    print("Server đang chạy tại port 50051...")
    server.start()
    server.wait_for_termination()

if __name__ == '__main__':
    serve()

File client.py:

import grpc
import hello_pb2
import hello_pb2_grpc

def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = hello_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(hello_pb2.HelloRequest(name='itfromzero'))
    print("Client nhận được: " + response.message)

if __name__ == '__main__':
    run()

Giải mã sức mạnh: Tại sao gRPC lại nhanh vượt trội?

Nếu bạn thấy bước compile file proto hơi phiền, thì đây là những lý do khiến nỗ lực đó cực kỳ đáng giá.

1. Cơ chế nén của Protocol Buffers (Protobuf)

REST gửi {"user_id": 123} dưới dạng text. Protobuf thì khác. Nó lược bỏ tên field, chỉ gửi tag số và giá trị dưới dạng nhị phân. Kết quả là gói tin nhỏ hơn từ 3 đến 10 lần so với JSON truyền thống.

2. Tận dụng sức mạnh HTTP/2

REST thường bị giới hạn bởi cơ chế tuần tự của HTTP/1.1. Ngược lại, gRPC chạy trên HTTP/2 với khả năng Multiplexing. Bạn có thể gửi hàng trăm request cùng lúc trên một kết nối duy nhất. Điều này loại bỏ hoàn toàn tình trạng nghẽn cổ chai khi số lượng service tăng lên.

3. Rào chắn lỗi nhờ Strong Typing

Lỗi “Service A gửi thiếu field mà Service B cần” là nỗi ám ảnh của dân backend. Với gRPC, nếu bạn truyền sai kiểu dữ liệu, hệ thống sẽ báo lỗi ngay lập tức. Bạn không còn phải tốn hàng giờ debug chỉ vì một giá trị null chết tiệt trong JSON.

Kỹ thuật nâng cao: Streaming dữ liệu thực tế

Streaming là tính năng mình yêu thích nhất. Hãy tưởng tượng bạn cần đẩy 100.000 dòng log từ server về client. Thay vì bắt client đợi tải xong một file khổng lồ, gRPC cho phép đẩy từng dòng một.

Chỉ cần thêm từ khóa stream vào file proto:

rpc ListLogs (LogRequest) returns (stream LogResponse) {}

Phía Server sẽ dùng lệnh yield để đẩy dữ liệu về liên tục. Cách làm này giúp giảm tải RAM đáng kể cho cả hai đầu thiết bị.

4 bài học xương máu sau 6 tháng chạy Production

Triển khai thực tế phức tạp hơn ví dụ Hello World rất nhiều. Đây là kinh nghiệm của mình:

  • Quản lý Proto tập trung: Đừng để file proto nằm rải rác. Hãy tạo một repo Git riêng chứa toàn bộ file .proto và dùng Git Submodule để các service khác cùng trỏ vào.
  • Middleware (Interceptors): Đừng viết code Logging hay Auth vào từng hàm. Hãy dùng Interceptors để xử lý tập trung, giúp code gọn gàng hơn hẳn.
  • Health Checks là bắt buộc: Hãy triển khai giao thức health check chuẩn của gRPC. Nếu không, Kubernetes sẽ không biết service của bạn đang sống hay đã treo để tự động restart.
  • Nguyên tắc số Tag: Khi cập nhật file proto, tuyệt đối không đổi số thứ tự (tag). Nếu cần bỏ field, hãy dùng reserved. Việc dùng lại số cũ sẽ khiến dữ liệu cũ và mới xung đột, gây crash hệ thống.

Lời kết

gRPC không phải là giải pháp cho mọi bài toán. Nếu làm Web Frontend cho người dùng, REST vẫn là chân ái. Nhưng nếu bạn đang đau đầu với giao tiếp nội bộ giữa các Microservices, gRPC chính là chìa khóa để nâng cấp hiệu năng lên một tầm cao mới.

Hãy thử áp dụng cho một service nhỏ nhất trong hệ thống. Bạn sẽ thấy việc quay lại viết JSON thủ công thật sự rất… mệt mỏi.

Share: