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

為何選擇了 FastAPI 和 gRPC贺纲?


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

FastAPI

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

gRPC

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

落地實(shí)踐


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

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

# 注意 bali 框架在 pipy 倉庫里面的名稱是:bali-core 
pip install bali-core

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

這里只涂兩個(gè)服務(wù)作為實(shí)踐例子性锭,用戶服務(wù)(user)和訂單服務(wù)(order)赠潦,每個(gè)服務(wù)都是一個(gè)獨(dú)立的 repo叫胖,有個(gè)獨(dú)立的 proto repo 專門用來存放 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í)是未來一個(gè)方向抡爹,簡單掩驱、跨語言。而且云原生的 service mesh 都默認(rèn)支持 gRPC,比如 Linkerd昙篙。微服務(wù)場景下的網(wǎng)關(guān)產(chǎn)品也都對(duì) gRPC 默認(rèn)支持腊状。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末同蜻,一起剝皮案震驚了整個(gè)濱河市棚点,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌湾蔓,老刑警劉巖瘫析,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異默责,居然都是意外死亡贬循,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門桃序,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杖虾,“玉大人,你說我怎么就攤上這事媒熊∑媸剩” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵芦鳍,是天一觀的道長嚷往。 經(jīng)常有香客問我,道長怜校,這世上最難降的妖魔是什么间影? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮茄茁,結(jié)果婚禮上魂贬,老公的妹妹穿的比我還像新娘。我一直安慰自己裙顽,他們只是感情好付燥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著愈犹,像睡著了一般键科。 火紅的嫁衣襯著肌膚如雪闻丑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天勋颖,我揣著相機(jī)與錄音嗦嗡,去河邊找鬼。 笑死饭玲,一個(gè)胖子當(dāng)著我的面吹牛侥祭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茄厘,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼矮冬,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了次哈?” 一聲冷哼從身側(cè)響起胎署,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窑滞,沒想到半個(gè)月后琼牧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葛假,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年障陶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聊训。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖恢氯,靈堂內(nèi)的尸體忽然破棺而出带斑,到底是詐尸還是另有隱情,我是刑警寧澤勋拟,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布勋磕,位于F島的核電站,受9級(jí)特大地震影響敢靡,放射性物質(zhì)發(fā)生泄漏挂滓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一啸胧、第九天 我趴在偏房一處隱蔽的房頂上張望赶站。 院中可真熱鬧,春花似錦纺念、人聲如沸贝椿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烙博。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渣窜,已是汗流浹背铺根。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乔宿,地道東北人夷都。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像予颤,于是被迫代替她去往敵國和親囤官。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容