Python FastAPI 构建可扩展的微服务:设计与最佳实践

54 阅读7分钟

使用 FastAPI 构建可扩展的微服务:设计与最佳实践

随着业务复杂度的提升,单体应用暴露出越来越多的问题。微服务架构应运而生,它通过将大型应用拆分为一组小而独立的服务,彻底改变了应用的开发和维护方式。每个服务都聚焦于一项独立的业务能力,可以独立开发、部署和扩展。

在众多技术选型中,Python 的 FastAPI** 框架凭借其轻量级和高性能的特点,成为了构建微服务的理想选择。

本文将从项目结构、Docker化实践到最终的部署策略,带你一步步探索如何用 FastAPI 构建一套优雅、可扩展的微服务系统。

本文将重点探讨以下内容:

  1. 一个高可维护性的 FastAPI 微服务项目结构。
  2. 将服务 Docker 化的最佳实践。
  3. 基于 Docker 与容器的现代部署策略。

1. FastAPI 微服务的项目结构

一个设计良好的微服务架构,首先体现在清晰的项目结构上。通常,我们会为每个微服务创建独立的目录,甚至是独立的代码仓库,以保证它们能被独立地开发和部署。

下面是一个推荐的项目结构范例:

project-root/
├── service_one/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── main.py
│   │   ├── api/
│   │   │   ├── __init__.py
│   │   │   └── endpoints.py
│   │   └── models/
│   │       ├── __init__.py
│   │       └── item.py
│   ├── Dockerfile
│   └── requirements.txt
├── service_two/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── main.py
│   │   ├── api/
│   │   │   ├── __init__.py
│   │   │   └── endpoints.py
│   │   └── database/
│   │       └── database.py
│   ├── Dockerfile
│   └── requirements.txt
├── common/            
│   ├── __init__.py
│   └── utils.py
└── docker-compose.yml 
文件夹结构解析
  • project-root/ : 项目的根目录,统领所有微服务。
  • service_one/, service_two/ : 每个子目录代表一个独立的微服务。这种物理隔离是实现独立开发和部署的基础。
  • app/ : 存放每个微服务的核心应用代码。
  • init.py: 一个空文件,但它的存在告诉 Python 这是一个包,使其内部的模块可以被导入。
  • main.py: FastAPI 应用的入口文件,负责初始化 FastAPI 实例和加载路由。
  • api/ : 存放 API 相关的定义,如路由、端点和请求响应处理逻辑。
  • endpoints.py: 使用 @app.get()@app.post() 等装饰器来定义具体的 API 端点。
  • models/ 或 database/ : 用于定义数据模型(如 Pydantic 模型)或数据库交互逻辑。具体命名取决于服务的职责。
  • Dockerfile: 定义了如何为该服务构建一个独立的 Docker 镜像。
  • requirements.txt: 清晰地列出了该服务所需的所有 Python 依赖。
  • common/ (可选) : 一个共享目录,可以存放多个服务共用的工具函数、基础模型或配置。但请注意,过度依赖共享目录会增加服务间的耦合,削弱微服务的独立性,违背其设计初衷,因此请谨慎使用。
  • docker-compose.yml: 主要用于本地开发环境,通过它来定义和管理多个 Docker 容器的启动与协作。

服务代码示例

为了让你有更直观的感受,我们以一个“产品服务”和一个“订单服务”为例,展示其内部代码的组织方式。

示例 1: service_one (产品服务)

service_one/app/models/item.py (定义产品数据模型):

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None

service_one/app/api/endpoints.py (定义产品相关的 API 端点):

from fastapi import APIRouter
from app.models.item import Item

router = APIRouter()

@router.get("/items/{item_id}")
async def get_item(item_id: int):
    return {"item_id": item_id, "name""示例产品""price"29.99}

@router.post("/items/")
async def create_item(item: Item):
    return {"message"f"产品 '{item.name}' 创建成功""item": item}
示例 2: service_two (订单服务)

service_two/app/database/database.py (模拟订单数据库操作):

# 这是一个简化的内存数据库示例
orders_db = {}
order_id_counter = 1

async def create_order(user_id: int, items: list):
    global order_id_counter
    order_id = order_id_counter
    orders_db[order_id] = {"user_id": user_id, "items": items}
    order_id_counter += 1
    return order_id

async def get_order(order_id: int):
    return orders_db.get(order_id)

service_two/app/api/endpoints.py (定义订单相关的 API 端点):

from fastapi import APIRouter, HTTPException
from typing import List
from pydantic import BaseModel
from app.database import database

router = APIRouter()

class OrderRequest(BaseModel):
    user_id: int
    item_ids: List[int]

class OrderResponse(BaseModel):
    order_id: int
    user_id: int
    items: List[int]

@router.post("/orders/")
async def create_new_order(order_request: OrderRequest):
    order_id = await database.create_order(order_request.user_id, order_request.item_ids)
    return {"message"f"订单创建成功,ID: {order_id}""order_id": order_id}

@router.get("/orders/{order_id}", response_model=OrderResponse)
async def get_existing_order(order_id: int):
    order = await database.get_order(order_id)
    if not order:
        raise HTTPException(status_code=404, detail="订单未找到")
    return OrderResponse(order_id=order_id, **order)

service_two/app/main.py (订单服务的应用入口):

from fastapi import FastAPI
from app.api import endpoints

app = FastAPI()
app.include_router(endpoints.router)

@app.get("/")
async def read_root():
    return {"message""欢迎使用订单服务"}

2. 服务的 Docker 化最佳实践

将服务容器化是微服务架构的关键一步。如何管理 Dockerfile 和 requirements.txt,直接关系到隔离性和可维护性。

方案一:每个服务独立配置(强烈推荐)

这是最符合微服务理念的做法。每个服务都有自己专属的 Dockerfile 和 requirements.txt

  • 独立的 Dockerfile: 每个服务可能有不同的系统依赖或构建步骤。独立的 Dockerfile 确保了构建过程的自包含和定制化。
  • 独立的 requirements.txt: 每个服务只声明自己需要的 Python 库。这不仅能避免版本冲突,还能构建出更小、更安全的镜像。
project-root/
├── service_one/
│   ├── app/
│   ├── Dockerfile         # 服务一的构建文件
│   └── requirements.txt   # 服务一的依赖
├── service_two/
│   ├── app/
│   ├── Dockerfile         # 服务二的构建文件
│   └── requirements.txt   # 服务二的依赖
└── docker-compose.yml

方案二:共享配置(不推荐)

这种方式是将所有服务的代码打包进一个巨大的 Docker 镜像,并共享一份 requirements.txt

  • 单一 Dockerfile: 镜像会变得异常臃肿,并且缺乏针对性。任何一个服务的代码变更都可能需要重新构建整个大镜像。
  • 单一 requirements.txt: 所有服务的依赖都混在一起,极易引发“依赖地狱”,即不同服务需要同一库的不同版本而导致冲突。

小结:要想真正发挥微服务的优势,方案一是你的不二之选。它通过最大化服务的独立性,构建出更小、更高效的镜像,完美契合了微服务架构的核心理念。


3. 基于 Docker 和容器的部署策略

当服务被 Docker 化之后,我们还需要选择合适的策略将它们部署到生产环境。

方案一:单服务单容器,集群化管理(推荐)

这是业界公认的最佳实践:将每个微服务打包成一个独立的 Docker 容器。然后,利用像 Kubernetes 或 Docker Swarm 这样的容器编排平台,对这些成百上千的容器进行自动化部署、管理和扩展。

这种模式的好处是显而易见的:

  • 高度隔离: 服务之间互不干扰,一个服务的崩溃不会影响其他服务。
  • 独立伸缩: 可以根据每个服务的实际负载(如产品服务的流量远大于用户服务),进行独立的、精细化的扩缩容。
  • 独立部署: 可以随时更新单个服务,而无需中断整个应用,实现真正的敏捷开发和持续交付。
  • 故障隔离: 错误会被限制在单个服务内部,防止故障扩散。
  • 精细化资源管理: 可以为每个服务按需分配 CPU 和内存,提高资源利用率。

方案二:多服务单容器(仅限测试,切勿用于生产)

理论上,你可以用 Supervisor** 这样的进程管理工具,在同一个容器里运行多个 FastAPI 应用。

但这种做法完全违背了微服务的初衷:

  • 耦合度过高: 服务之间失去了隔离,一个服务的资源泄露或崩溃会直接影响到其他服务。
  • 无法独立扩展: 你无法单独扩展某个服务,只能对这个臃肿的容器进行整体扩展,造成资源浪费。
  • 伪微服务: 这本质上又回到了“单体应用”的老路,只是外面套了一个 Docker 的壳。

最终总结

要构建一套高质量、可扩展的 FastAPI 微服务系统,请遵循以下核心原则:

  • 结构上要独立: 每个服务都应该拥有自己独立的项目目录和代码库。
  • 构建时要隔离: 每个服务都应该有自己独立的 Dockerfile 和依赖定义,构建出小而美的镜像。
  • 部署时要分离: 每个服务都应该运行在独立的容器中,并交由专业的容器编排平台管理。

总而言之,拥抱微服务架构意味着拥抱独立性与自动化。通过 FastAPI 构建微服务时,只有坚持“独立打包、独立部署”的原则,并借助容器编排平台,我们才能最大限度地发挥其在灵活性、弹性和可维护性上的巨大优势,从容应对未来业务的任何挑战。

参考:mp.weixin.qq.com/s/G_ynafNvO…