使用 FastAPI 整合 gRPC 構(gòu)建 Python 微服務(wù)

為何選擇了 FastAPI 和 gRPC?


我們團(tuán)隊(duì)內(nèi)部早期使用的 Django 開(kāi)發(fā)的海外金融產(chǎn)品,后續(xù)考慮轉(zhuǎn)型到微服務(wù)架構(gòu),做了一些調(diào)研之后,決定選擇 FastAPI 和 gRPC。

FastAPI

完全從異步IO思維整合出來(lái)的框架,在 Web 領(lǐng)域異步IO的意義比較大。基于 Encode 團(tuán)隊(duì)(開(kāi)發(fā)過(guò)大名鼎鼎的 Django REST Framework)的新作品:Starlette。

gRPC

在 RPC 之塊,Python 還有一個(gè)有名的框架 nameko,團(tuán)隊(duì)之前使用過(guò),由于國(guó)內(nèi)使用率不高,遇到問(wèn)題時(shí),難以解決方案,雖然 nameko 的 API 非常優(yōu)雅,最終還是求穩(wěn)地選擇了 gRPC。gRPC 在云原生時(shí)代也是一個(gè)標(biāo)配,流行度上有保證。

落地實(shí)踐


簡(jiǎn)單地了解了一些 DDD 思維后,團(tuán)隊(duì)就拍著腦袋開(kāi)始拆分服務(wù)啦。總共拆分了 8 個(gè)服務(wù),每個(gè)服務(wù)同時(shí)提供 HTTP/RPC 服務(wù)。

類 Flask 框架的 FastAPI,擁有微框架的靈活性,但也是這種靈活性,讓團(tuán)隊(duì)技術(shù)水平并不統(tǒng)一的開(kāi)發(fā)者寫出了各式各式的項(xiàng)目結(jié)構(gòu),交叉維護(hù)項(xiàng)目的時(shí)候,非常頭痛。于是團(tuán)隊(duì)內(nèi)部為了統(tǒng)一及簡(jiǎn)化 FastAPI & gRPC 的開(kāi)發(fā),迭代出了一個(gè)整合的框架:bali,業(yè)務(wù)代碼分布為在兩個(gè)層次:Model 層和 Resource 層。Model 層是一個(gè) Fat Model 模型,除了字段定義,還有與 Model 相關(guān)的一些邏輯;Resource 層提供同時(shí)兼容 HTTP/RPC 的資源服務(wù)。遵循 RESTful 及 gRPC 的開(kāi)發(fā)指南定義了幾個(gè)標(biāo)準(zhǔn)方法。

# 注意 bali 框架在 pipy 倉(cāng)庫(kù)里面的名稱是:bali-core 
pip install bali-core

項(xiàng)目結(jié)構(gòu)

這里只涂?jī)蓚€(gè)服務(wù)作為實(shí)踐例子,用戶服務(wù)(user)和訂單服務(wù)(order),每個(gè)服務(wù)都是一個(gè)獨(dú)立的 repo,有個(gè)獨(dú)立的 proto repo 專門用來(lái)存放 gRPC 需要使用的 protobuf。

# user repo
├── user
│   ├── Dockerfile
│   ├── README.md
│   ├── clients
│   │   ├── __init__.py
│   │   ├── _config.py
│   │   ├── _utils.py
│   │   ├── intermediates
│   ├── core
│   │   ├── config.py
│   │   ├── constants
│   │   ├── environment.py
│   │   ├── logging.py
│   ├── main.py
│   ├── models
│   │   ├── __init__.py
│   │   ├── user.py
│   ├── requirements.txt
│   ├── requirements_dev.txt
│   ├── resources
│   │   ├── __init__.py
│   │   ├── user.py
│   ├── schemas
│   │   ├── __init__.py
│   │   ├── user.py
│   ├── services
│   │   ├── __init__.py
│   │   ├── db.py
│   │   ├── http
│   │   │   ├── users.py
│   │   └── rpc
│   │       ├── service.py
│   ├── tests
│   │   ├── __init__.py
│   │   ├── test_models
│   │   ├── test_resources
│   │   └── test_services

# order repo
├── user
│   ├── Dockerfile
│   ├── README.md
│   ├── clients
│   │   ├── __init__.py
│   │   ├── _config.py
│   │   ├── _utils.py
│   │   ├── intermediates
│   ├── core
│   │   ├── config.py
│   │   ├── constants
│   │   ├── environment.py
│   │   ├── logging.py
│   ├── main.py
│   ├── models
│   │   ├── __init__.py
│   │   ├── user.py
│   ├── requirements.txt
│   ├── requirements_dev.txt
│   ├── resources
│   │   ├── __init__.py
│   │   ├── user.py
│   ├── schemas
│   │   ├── __init__.py
│   │   ├── user.py
│   ├── services
│   │   ├── __init__.py
│   │   ├── db.py
│   │   ├── http
│   │   │   ├── users.py
│   │   └── rpc
│   │       ├── service.py
│   ├── tests
│   │   ├── __init__.py
│   │   ├── test_models
│   │   ├── test_resources
│   │   └── test_services

# proto repo
proto
├── order
│   └── order.proto
└── user
    └── user.proto

可以看出,微服務(wù)的結(jié)構(gòu)是基本一致的,user 服務(wù)和 order 服務(wù)的業(yè)務(wù)主要存放在 Models 和 Resources。

HTTP 服務(wù)

# user/services/http/users.py

@router.get("/users/", response_model=UserSchema)
def get_users(
    *,
    channel : str = Header(None),
) -> Any:
    return User.get_users()

這是 FastAPI 的自帶的 route 定義方式,bali 是兼容的。

# user/resources/user.py

class UserResource(Resource):
    schema = UserSchema

    @action()
    def custome_users(self, schema_in):
        User.get_customer_users()

Resouce 的方式,同時(shí)支持 HTTP、RPC。通用的 action 比如 GetUsers 不需要在 Resource 里面定義,已經(jīng)在 Resource 基類里面實(shí)現(xiàn)了。

啟動(dòng) HTTP 服務(wù):

python main.py --http

RPC 服務(wù)

  1. 定義 protobuf 文件
# user/services/rpc/user.proto

編譯 proto 文件,按 gRPC 官方提供的方法:

$ python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/user.proto

而使用 bali-cli(bali 框架配套的 cli 工具),只需要在項(xiàng)目根目錄執(zhí)行:

pip install bali-cli
bali build 

再配合 CI/CD,user 服務(wù)的 user.proto 會(huì)自動(dòng)添加到 proto repo 里面。

  1. gRPC 實(shí)現(xiàn)服務(wù)
# user/services/rpc/service.py

class UserService(user_pb2_grpc.UserServiceServicer):
    def GetUsers(self, request, context):
        return UserResource(request, context, pb2.UserResponse).get_users()

def serve():
    port = 5000
    server = grpc.server(
        futures.ThreadPoolExecutor(max_workers=50),
        interceptors=[ProcessInterceptor()],
    )
    user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
    server.add_insecure_port(f'[::]:{port}')
    server.start()
    logger.info("Service started on port: %s (env: %s)", port, ENV)
    server.wait_for_termination()

由于 get_users 是一個(gè)標(biāo)準(zhǔn)通用的 action,所以在 Resource 里面都不用自己實(shí)現(xiàn),定義好 Model、Schema 就可以直接使用了。

啟動(dòng) RPC 服務(wù):

python main.py --rpc
  1. gRPC 實(shí)現(xiàn)服務(wù)

在 order 服務(wù)里面調(diào)用 user 服務(wù),只需要使用 bali add {service} 命令即可在 order 項(xiàng)目,創(chuàng)建一個(gè) clients 需要的所有文件。

bali add user

在代碼里面調(diào)用

from clients import UserClient
users = UserClient().get_users()

項(xiàng)目總結(jié)

gRPC 確實(shí)是未來(lái)一個(gè)方向,簡(jiǎn)單、跨語(yǔ)言。而且云原生的 service mesh 都默認(rèn)支持 gRPC,比如 Linkerd。微服務(wù)場(chǎng)景下的網(wǎng)關(guān)產(chǎn)品也都對(duì) gRPC 默認(rèn)支持。

業(yè)務(wù)代碼放哪里這個(gè)問(wèn)題在所有項(xiàng)目里面都會(huì)成為一個(gè)話題,bali 框架只有兩層,Model 和 Resource,開(kāi)發(fā)微服務(wù)就不用思考太多東西,效率會(huì)高。一套代碼,同時(shí)兼容 HTTP 和 RPC 服務(wù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容