•12 min read

Tối ưu hóa Python FastAPI cho khả năng đồng thời cao

Tối ưu hóa Python FastAPI cho khả năng đồng thời cao

FastAPI đã tạo nên một cơn sốt trong hệ sinh thái phát triển web Python. Với trải nghiệm tuyệt vời dành cho nhà phát triển, khả năng tự động tạo Swagger UI và xác thực dữ liệu mạnh mẽ được hỗ trợ bởi Pydantic, nó ngày càng trở thành framework được lựa chọn cho các API Python hiện đại. Tuy nhiên, tính năng được ca ngợi nhất của nó có lẽ là tốc độ. Các bài kiểm tra hiệu năng thường xuyên xếp FastAPI vào nhóm các framework Python nhanh nhất hiện có, cạnh tranh với NodeJS và Go trong một số trường hợp.

Nhưng hiệu suất mặc định có thể gây hiểu lầm. Mặc dù một bài kiểm tra "Hello World" đơn giản sẽ chạy cực nhanh, các ứng dụng thực tế xử lý khối lượng công việc đồng thời cao—hàng nghìn người dùng cùng lúc, các truy vấn cơ sở dữ liệu nặng và các cuộc gọi API bên ngoài—đòi hỏi cấu hình cẩn thận và tư duy kiến trúc. Nếu bạn triển khai một ứng dụng FastAPI đơn giản vào một môi trường có tính đồng thời cao, bạn có thể nhanh chóng thấy nó bị quá tải.

Trong hướng dẫn chuyên sâu này, chúng ta sẽ khám phá các chiến lược quan trọng để tối ưu hóa Python FastAPI cho môi trường có tính đồng thời cao. Chúng ta sẽ đề cập đến quản lý tiến trình, các sắc thái của vòng lặp sự kiện của Python, nhóm kết nối cơ sở dữ liệu và các kỹ thuật tuần tự hóa nâng cao để đảm bảo backend của bạn mở rộng một cách linh hoạt.

Audio Briefing
0:00 / 0:00

1. Nền tảng: ASGI và Uvicorn

Để hiểu cách tối ưu hóa FastAPI, trước tiên bạn cần hiểu cách nó hoạt động. Các framework web Python truyền thống như Django và Flask được xây dựng trên WSGI (Web Server Gateway Interface), vốn có tính đồng bộ. FastAPI, mặt khác, được xây dựng trên ASGI (Asynchronous Server Gateway Interface).

ASGI cho phép xử lý yêu cầu không đồng bộ, nghĩa là một tiến trình máy chủ duy nhất có thể xử lý nhiều yêu cầu đồng thời mà không cần chờ các hoạt động I/O chậm (như yêu cầu mạng hoặc đọc cơ sở dữ liệu) hoàn thành.

Máy chủ ASGI tiêu chuẩn được khuyến nghị cho FastAPI là Uvicorn. Uvicorn được xây dựng dựa trên uvloop (một sự thay thế trực tiếp cho các vòng lặp sự kiện asyncio tiêu chuẩn được viết bằng Cython) và httptools. Sự kết hợp này là điều mang lại cho Uvicorn tốc độ vượt trội.

Thông thường, bạn chạy một ứng dụng FastAPI trong quá trình phát triển như sau:

uvicorn main:app --reload

Mặc dù Uvicorn cực kỳ nhanh, việc chạy nó như thế này trong môi trường sản xuất sẽ tạo ra một nút thắt cổ chai lớn: một tiến trình Uvicorn duy nhất là đơn luồng. Do Global Interpreter Lock (GIL) của Python, một tiến trình Uvicorn duy nhất chỉ có thể sử dụng một lõi CPU. Trên một máy chủ hiện đại với 16 hoặc 32 lõi, việc chạy một phiên bản Uvicorn duy nhất khiến phần lớn sức mạnh tính toán của máy chủ hoàn toàn không hoạt động.

Advertisement

2. Mở rộng trên các lõi: Gunicorn với Uvicorn Workers

Để khai thác toàn bộ sức mạnh của một máy đa lõi, chúng ta cần một trình quản lý tiến trình có thể tạo và quản lý nhiều phiên bản Uvicorn cùng lúc. Điều này cho phép song song hóa thực sự (thực hiện nhiều hoạt động cùng một lúc) bổ sung cho tính đồng thời của chúng ta (quản lý nhiều hoạt động đồng thời).

Trình quản lý tiến trình tiêu chuẩn trong ngành cho các ứng dụng web Python là Gunicorn. Mặc dù Gunicorn theo lịch sử là một máy chủ WSGI, nó có thể được cấu hình để sử dụng Uvicorn làm lớp worker của nó, mang lại khả năng quản lý tiến trình cho ứng dụng ASGI của chúng ta.

Để triển khai FastAPI cho tính đồng thời cao, bạn nên chạy Gunicorn với các worker Uvicorn:

gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app

Xác định số lượng Worker

Bạn nên cấu hình bao nhiêu worker? Khuyến nghị Gunicorn cổ điển cho các ứng dụng WSGI là (2 * number_of_cpu_cores) + 1. Tuy nhiên, vì các ứng dụng ASGI là không đồng bộ và không chặn luồng trong khi chờ I/O, bạn có thể không cần nhiều như vậy.

Một điểm khởi đầu tốt cho FastAPI là một worker cho mỗi lõi CPU, có thể thêm một hoặc hai worker nữa. Ví dụ, trên một máy 8 lõi, bạn có thể bắt đầu với -w 8. Số lượng chính xác phụ thuộc rất nhiều vào khối lượng công việc cụ thể của bạn (CPU-bound so với I/O-bound). Nếu ứng dụng của bạn thực hiện các tác vụ tính toán nặng, ít worker hơn có thể tốt hơn để tránh chi phí chuyển đổi ngữ cảnh. Luôn kiểm tra tải để tìm ra điểm tối ưu cho cơ sở hạ tầng của bạn.

3. Vấn đề Async/Await: async def vs def

Đây có lẽ là phần quan trọng nhất của hướng dẫn này. Việc hiểu sai cách FastAPI xử lý async def so với def tiêu chuẩn là nguyên nhân phổ biến nhất gây ra các lỗi hiệu suất thảm khốc trong môi trường sản xuất.

Khi bạn định nghĩa một route bằng async def, FastAPI sẽ chạy nó trực tiếp trên vòng lặp sự kiện không đồng bộ chính. Điều này hoàn hảo cho các hoạt động không chặn—chẳng hạn như các truy vấn cơ sở dữ liệu không đồng bộ hoặc các yêu cầu HTTP không đồng bộ (ví dụ: sử dụng httpx).

# GOOD: Using an async HTTP client inside async def
@app.get("/data")
async def get_data():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com")
        return response.json()

Tuy nhiên, nếu bạn đặt một hoạt động đồng bộ, chặn bên trong một route async def, bạn sẽ chặn toàn bộ vòng lặp sự kiện. Trong khi hoạt động đồng bộ đó đang chạy, máy chủ của bạn không thể chấp nhận hoặc xử lý bất kỳ yêu cầu nào khác. Tính đồng thời giảm xuống bằng không.

import time

# TERRIBLE: Blocking the event loop
@app.get("/slow")
async def slow_operation():
    time.sleep(5)  # The entire server stops for 5 seconds
    return {"status": "done"}

Điều gì sẽ xảy ra nếu bạn phải sử dụng một thư viện đồng bộ, như requests tiêu chuẩn, SQLAlchemy tiêu chuẩn (không có tiện ích mở rộng async), hoặc một tác vụ xử lý hình ảnh nặng về CPU?

FastAPI cung cấp một giải pháp thanh lịch: sử dụng def tiêu chuẩn thay vì async def. Khi bạn sử dụng def tiêu chuẩn, FastAPI đủ thông minh để chạy hàm đó trong một threadpool bên ngoài. Điều này giữ cho vòng lặp sự kiện chính không bị chặn và phản hồi nhanh, cho phép các yêu cầu khác được xử lý đồng thời.

# GOOD: FastAPI runs this in a threadpool, protecting the event loop
@app.get("/sync-slow")
def slow_sync_operation():
    time.sleep(5) 
    return {"status": "done"}

Quy tắc chung:

  • Chỉ sử dụng async def nếu mọi hoạt động bên trong hàm đều có thể await và không chặn.
  • Sử dụng def nếu bạn đang sử dụng bất kỳ trình điều khiển cơ sở dữ liệu đồng bộ, client API đồng bộ hoặc thực hiện các tác vụ nặng về CPU.

4. Nhóm kết nối cơ sở dữ liệu

Trong môi trường có tính đồng thời cao, ứng dụng của bạn sẽ cố gắng giao tiếp với cơ sở dữ liệu hàng trăm hoặc hàng nghìn lần mỗi giây. Mở một kết nối TCP mới đến cơ sở dữ liệu là một hoạt động chậm và tốn kém. Hơn nữa, các cơ sở dữ liệu như PostgreSQL có giới hạn cứng về số lượng kết nối đồng thời tối đa (max_connections).

Nếu ứng dụng FastAPI của bạn tạo một kết nối mới cho mỗi yêu cầu dưới tải nặng, bạn sẽ nhanh chóng làm cạn kiệt các kết nối cơ sở dữ liệu và làm sập hệ thống.

Giải pháp là Nhóm kết nối (Connection Pooling). Một nhóm kết nối duy trì một tập hợp các kết nối cơ sở dữ liệu đang hoạt động trong bộ nhớ. Khi một yêu cầu cần truy vấn cơ sở dữ liệu, nó sẽ mượn một kết nối từ nhóm, chạy truy vấn và trả lại kết nối cho nhóm để được sử dụng lại bởi yêu cầu tiếp theo.

Triển khai Pooling với SQLAlchemy Async

Nếu bạn đang sử dụng SQLAlchemy 2.0 với các tiện ích mở rộng async của nó, nhóm kết nối đã được tích hợp sẵn. Bạn cấu hình nhóm khi tạo engine:

from sqlalchemy.ext.asyncio import create_async_engine

DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname"

engine = create_async_engine(
    DATABASE_URL,
    pool_size=20,          # The number of persistent connections to keep open
    max_overflow=10,       # How many additional connections to allow during spikes
    pool_timeout=30,       # How long to wait for a connection before failing
    pool_recycle=1800      # Recycle connections after 30 minutes to prevent staleness
)

Đối với quy mô doanh nghiệp thực sự, chỉ dựa vào nhóm cấp ứng dụng là không đủ, đặc biệt khi chạy nhiều worker Gunicorn trên nhiều máy chủ (vì mỗi worker duy trì nhóm riêng của nó). Trong những trường hợp này, bạn nên triển khai một trình quản lý nhóm kết nối cấp cơ sở hạ tầng như PgBouncer. PgBouncer nằm giữa ứng dụng FastAPI của bạn và PostgreSQL, ghép kênh hàng nghìn kết nối ứng dụng thành một vài kết nối cơ sở dữ liệu thực tế.

Advertisement

5. Tối đa hóa thông lượng: Tuần tự hóa JSON và các tác vụ nền

Khi việc quản lý tiến trình và các nút thắt cổ chai I/O của bạn đã được giải quyết, bạn có thể tăng thêm hiệu suất với các tối ưu hóa cấp ứng dụng.

Tuần tự hóa JSON nhanh hơn

Theo mặc định, FastAPI sử dụng thư viện json tiêu chuẩn của Python để tuần tự hóa. Mặc dù đáng tin cậy, nó không phải là nhanh nhất. Trong các API có thông lượng cao trả về các payload JSON lớn, tuần tự hóa có thể trở thành một nút thắt cổ chai CPU đáng kể.

FastAPI hỗ trợ các lớp phản hồi tùy chỉnh. Bạn có thể thay thế phản hồi JSON mặc định bằng ORJSONResponse, sử dụng orjson—một thư viện JSON cực kỳ nhanh, dựa trên Rust.

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)

@app.get("/fast-json")
async def get_fast_json():
    return {"message": "This is serialized at the speed of light"}

Chỉ cần nhớ pip install orjson.

Giảm tải công việc với các tác vụ nền

Đừng bao giờ buộc người dùng phải chờ đợi các hoạt động có thể xảy ra không đồng bộ trong nền. Gửi email chào mừng, xử lý các tệp đã tải lên hoặc ping webhook không nên chặn phản hồi HTTP.

Đối với các tác vụ đơn giản, hãy sử dụng BackgroundTasks tích hợp sẵn của FastAPI. Nó thực thi hàm sau khi phản hồi HTTP đã được gửi đến client.

from fastapi import BackgroundTasks

def send_email_notification(email: str):
    # Simulated email sending
    pass

@app.post("/register")
async def register_user(email: str, background_tasks: BackgroundTasks):
    # Register user in DB...
    background_tasks.add_task(send_email_notification, email)
    return {"message": "User registered successfully"}

Đối với các khối lượng công việc nặng, phân tán, hãy tích hợp một hàng đợi tin nhắn chuyên dụng và trình chạy tác vụ như Celery hoặc RQ được hỗ trợ bởi Redis.

Kết luận

FastAPI là một công cụ đáng kinh ngạc mang lại lợi thế lớn trong cuộc đua về hiệu suất backend. Tuy nhiên, tính đồng thời cao sẽ phơi bày một cách tàn nhẫn mọi lỗi kiến trúc.

Bằng cách chạy FastAPI với các worker Gunicorn để mở rộng trên các lõi, tuân thủ nghiêm ngặt các quy tắc của vòng lặp sự kiện của Python (kết hợp async def và def một cách chính xác), triển khai nhóm kết nối cơ sở dữ liệu mạnh mẽ và tối ưu hóa tuần tự hóa, bạn có thể xây dựng các hệ thống backend có khả năng xử lý hàng triệu yêu cầu một cách dễ dàng. Nhanh theo mặc định là tốt; nhanh theo thiết kế thì tốt hơn.

Nếu bạn đã tinh chỉnh ngăn xếp FastAPI của mình và đang đánh giá xem một kiến trúc framework khác có thể mang lại thông lượng cao hơn hoặc ít bộ nhớ hơn cho mỗi worker hay không, hãy xem so sánh điểm chuẩn FastAPI vs Litestar (2026) của chúng tôi.

Bạn cũng có thể thích

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement