從零開始學(xué)Python | 使用 gRPC 的 Python 微服務(wù) I 從零開始學(xué)python | 使用 gRPC 的 Python 微服務(wù) II
目錄
為什么是微服務(wù)?
模塊化
靈活性
穩(wěn)健性
所有權(quán)
“微”有多小?
微服務(wù)與單體的權(quán)衡
示例微服務(wù)
為什么是 RPC 和協(xié)議緩沖區(qū)?
文檔
驗(yàn)證
表現(xiàn)
開發(fā)人員友好
示例實(shí)現(xiàn)
RPC 客戶端
RPC 服務(wù)器
捆綁在一起
生產(chǎn)就緒的 Python 微服務(wù)
碼頭工人
聯(lián)網(wǎng)
Docker 撰寫
測(cè)試
部署到 Kubernetes
使用-進(jìn)行 Python 微服務(wù)監(jiān)控
為什么不是裝飾器
-
最佳實(shí)踐
Protobuf 組織
Protobuf 版本控制
Protobuf Linting
類型檢查 Protobuf 生成的代碼
優(yōu)雅地關(guān)閉
保護(hù)通道
異步IO和gRPC
結(jié)論
微服務(wù)是組織復(fù)雜軟件系統(tǒng)的一種方式。您不是將所有代碼都放在一個(gè)應(yīng)用程序中,而是將應(yīng)用程序分解為獨(dú)立部署并相互通信的微服務(wù)。本教程將教您如何使用最流行的框架之一 gRPC 啟動(dòng)和運(yùn)行 Python 微服務(wù)。
很好地實(shí)現(xiàn)微服務(wù)框架很重要。當(dāng)您構(gòu)建一個(gè)支持關(guān)鍵應(yīng)用程序的框架時(shí),您必須確保它的健壯性和對(duì)開發(fā)人員的友好性。在本教程中,您將學(xué)習(xí)如何做到這一點(diǎn)。這些知識(shí)將使您對(duì)成長中的公司更有價(jià)值。
為了從本教程中獲益最多,您應(yīng)該了解Python和Web 應(yīng)用程序的基礎(chǔ)知識(shí)。如果您想復(fù)習(xí)這些內(nèi)容,請(qǐng)先通讀提供的鏈接。
在本教程結(jié)束時(shí),您將能夠:
在 Python 中實(shí)現(xiàn)通過 gRPC 相互通信的微服務(wù)
實(shí)現(xiàn)中間件來監(jiān)控微服務(wù)
單元測(cè)試和集成測(cè)試您的微服務(wù)和中間件
使用Kubernetes 將微服務(wù)部署到 Python 生產(chǎn)環(huán)境
您可以通過單擊以下鏈接下載本教程中使用的所有源代碼:
為什么是微服務(wù)?
想象一下,您在 Online Books For You 工作,這是一個(gè)在線銷售圖書的流行電子商務(wù)網(wǎng)站。該公司擁有數(shù)百名開發(fā)人員。每個(gè)開發(fā)人員都在為某些產(chǎn)品或后端功能編寫代碼,例如管理用戶的購物車、生成推薦、處理付款交易或處理倉庫庫存。
現(xiàn)在問問自己,您是否希望在一個(gè)巨大的應(yīng)用程序中包含所有這些代碼?這有多難理解?測(cè)試需要多長時(shí)間?您將如何保持代碼和數(shù)據(jù)庫模式的健全?這肯定會(huì)很困難,尤其是當(dāng)企業(yè)試圖快速發(fā)展時(shí)。
難道您不希望與模塊化產(chǎn)品功能相對(duì)應(yīng)的代碼是模塊化的嗎?用于管理購物車的購物車微服務(wù)。用于管理庫存的庫存微服務(wù)。
在下面的部分中,您將更深入地了解將 Python 代碼分離為微服務(wù)的一些原因。
模塊化
代碼更改通常采用阻力最小的路徑。您心愛的 Online Books For You CEO 希望添加新的買兩本書送一功能。您是被要求盡快啟動(dòng)它的團(tuán)隊(duì)的一員。看看當(dāng)所有代碼都在一個(gè)應(yīng)用程序中時(shí)會(huì)發(fā)生什么。
作為團(tuán)隊(duì)中最聰明的工程師,您提到可以向購物車邏輯添加一些代碼來檢查購物車中是否有兩本書以上。如果是這樣,您可以簡單地從購物車總數(shù)中減去最便宜的書的成本。不費(fèi)吹灰之力——你提出一個(gè)拉取請(qǐng)求。
然后你的產(chǎn)品經(jīng)理說你需要跟蹤這個(gè)活動(dòng)對(duì)圖書銷售的影響。這也很簡單。由于實(shí)現(xiàn)買二送一功能的邏輯在購物車代碼中,因此您將在結(jié)帳流程中添加一行,更新交易數(shù)據(jù)庫中的新列以指示銷售是促銷活動(dòng)的一部分:buy_two_get_one_free_promo = true。完畢。
接下來,您的產(chǎn)品經(jīng)理會(huì)提醒您,該交易僅對(duì)每位客戶使用一次有效。您需要添加一些邏輯來檢查是否有任何先前的事務(wù)buy_two_get_one_free_promo設(shè)置了該標(biāo)志。哦,您需要隱藏主頁上的促銷橫幅,因此您也添加了該支票。哦,您需要向未使用促銷的人發(fā)送電子郵件。加上那個(gè)。
幾年后,事務(wù)數(shù)據(jù)庫變得太大,需要用新的共享數(shù)據(jù)庫替換。所有這些引用都需要更改。不幸的是,此時(shí)數(shù)據(jù)庫在整個(gè)代碼庫中都被引用。您認(rèn)為添加所有這些引用實(shí)際上有點(diǎn)太容易了。
這就是為什么將所有代碼放在一個(gè)應(yīng)用程序中從長遠(yuǎn)來看會(huì)很危險(xiǎn)的原因。有時(shí),有界限是件好事。
交易數(shù)據(jù)庫應(yīng)該只能由交易微服務(wù)訪問。然后,如果您需要擴(kuò)展它,那還不錯(cuò)。代碼的其他部分可以通過隱藏實(shí)現(xiàn)細(xì)節(jié)的抽象 API 與事務(wù)交互。您可以在單個(gè)應(yīng)用程序中執(zhí)行此操作 — 只是不太可能。代碼更改通常采用阻力最小的路徑。
靈活性
將 Python 代碼拆分為微服務(wù)可為您提供更大的靈活性。一方面,您可以用不同的語言編寫微服務(wù)。通常,公司的第一個(gè) Web 應(yīng)用程序?qū)⑹褂肦uby或PHP 編寫。這并不意味著其他一切也必須如此!
您還可以獨(dú)立擴(kuò)展每個(gè)微服務(wù)。在本教程中,您將使用一個(gè) Web 應(yīng)用程序和一個(gè) Recommendations 微服務(wù)作為運(yùn)行示例。
您的 Web 應(yīng)用程序可能會(huì)受I/O 限制,從數(shù)據(jù)庫中獲取數(shù)據(jù),并可能從磁盤加載模板或其他文件。推薦微服務(wù)可能會(huì)進(jìn)行大量的數(shù)字運(yùn)算,使其受 CPU 限制。在不同的硬件上運(yùn)行這兩個(gè) Python 微服務(wù)是有意義的。
穩(wěn)健性
如果您的所有代碼都在一個(gè)應(yīng)用程序中,那么您必須一次部署所有代碼。這是一個(gè)很大的風(fēng)險(xiǎn)!這意味著對(duì)一小部分代碼的更改可能會(huì)導(dǎo)致整個(gè)站點(diǎn)癱瘓。
所有權(quán)
當(dāng)一個(gè)代碼庫被很多人共享時(shí),代碼的架構(gòu)通常沒有清晰的愿景。在員工來來去去的大公司中尤其如此。可能有人對(duì)代碼的外觀有遠(yuǎn)見,但是當(dāng)任何人都可以修改它并且每個(gè)人都在快速移動(dòng)時(shí),很難強(qiáng)制執(zhí)行。
微服務(wù)的好處之一是團(tuán)隊(duì)可以清楚地?fù)碛凶约旱拇a。這使得更有可能對(duì)代碼有一個(gè)清晰的愿景,并且代碼將保持干凈和有條理。它還明確了誰負(fù)責(zé)向代碼添加功能或在出現(xiàn)問題時(shí)進(jìn)行更改。
“微”有多小?
微服務(wù)應(yīng)該有多小是可以在工程師之間引發(fā)激烈爭論的話題之一。這是我的兩分錢:微型是用詞不當(dāng)。我們應(yīng)該只說服務(wù)。但是,在本教程中,您將看到用于一致性的微服務(wù)。
使微服務(wù)太小會(huì)導(dǎo)致問題。首先,它實(shí)際上違背了使代碼模塊化的目的。在微服務(wù)的代碼應(yīng)該是有意義在一起,就像在數(shù)據(jù)和方法類有意義起來。
要使用類作為類比,請(qǐng)考慮filePython 中的對(duì)象。該file對(duì)象具有您需要的所有方法。你可以.read()和.write()它,或者你可以,.readlines()如果你想。你不應(yīng)該需要一個(gè)FileReader和一個(gè)FileWriter類。也許你熟悉這樣做的語言,也許你一直認(rèn)為這有點(diǎn)麻煩和混亂。
微服務(wù)是一樣的。代碼的范圍應(yīng)該是正確的。不要太大,也不要太小。
其次,微服務(wù)比單體代碼更難測(cè)試。如果開發(fā)人員想要測(cè)試跨越多個(gè)微服務(wù)的功能,那么他們需要在他們的開發(fā)環(huán)境中啟動(dòng)并運(yùn)行所有這些功能。這增加了摩擦。有幾個(gè)微服務(wù)還不錯(cuò),但如果有幾十個(gè),那么這將是一個(gè)重大問題。
調(diào)整微服務(wù)規(guī)模是一門藝術(shù)。需要注意的一件事是每個(gè)團(tuán)隊(duì)都應(yīng)該擁有合理數(shù)量的微服務(wù)。如果您的團(tuán)隊(duì)有 5 個(gè)人但有 20 個(gè)微服務(wù),那么這是一個(gè)危險(xiǎn)信號(hào)。另一方面,如果您的團(tuán)隊(duì)只處理一個(gè)由其他五個(gè)團(tuán)隊(duì)共享的微服務(wù),那么這也可能是一個(gè)問題。
不要僅僅為了它而使微服務(wù)盡可能小。一些微服務(wù)可能很大。但是當(dāng)一個(gè)微服務(wù)在做兩個(gè)或更多完全不相關(guān)的事情時(shí)要小心。這通常是因?yàn)橄颥F(xiàn)有微服務(wù)添加不相關(guān)的功能是阻力最小的路徑,而不是因?yàn)樗鼘儆谀抢铩?/p>
以下是您可以將假設(shè)的在線書店分解為微服務(wù)的一些方法:
Marketplace為用戶提供導(dǎo)航站點(diǎn)的邏輯。
購物車跟蹤用戶放入購物車的內(nèi)容和結(jié)帳流程。
交易處理付款處理和發(fā)送收據(jù)。
庫存提供有關(guān)哪些圖書有庫存的數(shù)據(jù)。
用戶帳戶管理用戶注冊(cè)和帳戶詳細(xì)信息,例如更改他們的密碼。
評(píng)論存儲(chǔ)用戶輸入的圖書評(píng)分和評(píng)論。
這些只是幾個(gè)例子,并不是詳盡的清單。但是,您可以看到其中的每一個(gè)都可能由自己的團(tuán)隊(duì)擁有,并且每個(gè)的邏輯都是相對(duì)獨(dú)立的。此外,如果 Reviews 微服務(wù)的部署存在導(dǎo)致其崩潰的錯(cuò)誤,那么盡管無法加載評(píng)論,用戶仍然可以使用該站點(diǎn)并進(jìn)行購買。
微服務(wù)與單體的權(quán)衡
微服務(wù)并不總是比將所有代碼保存在一個(gè)應(yīng)用程序中的單體應(yīng)用更好。一般來說,尤其是在軟件開發(fā)生命周期的開始階段,單體應(yīng)用會(huì)讓你更快地行動(dòng)。它們使共享代碼和添加功能變得不那么復(fù)雜,并且只需部署一項(xiàng)服務(wù),您就可以快速將您的應(yīng)用程序提供給用戶。
權(quán)衡是,隨著復(fù)雜性的增加,所有這些都會(huì)逐漸使單體應(yīng)用更難開發(fā)、部署更慢、更脆弱。實(shí)施單體應(yīng)用可能會(huì)在前期為您節(jié)省時(shí)間和精力,但稍后它可能會(huì)再次困擾您。
在 Python 中實(shí)現(xiàn)微服務(wù)可能會(huì)在短期內(nèi)花費(fèi)你的時(shí)間和精力,但如果做得好,從長遠(yuǎn)來看,它可以讓你更好地?cái)U(kuò)展。當(dāng)然,當(dāng)速度最有價(jià)值時(shí),過早實(shí)施微服務(wù)可能會(huì)減慢您的速度。
典型的硅谷啟動(dòng)周期是從一個(gè)整體開始,以便在企業(yè)找到適合客戶的產(chǎn)品時(shí)實(shí)現(xiàn)快速迭代。在公司有了成功的產(chǎn)品并雇傭了更多的工程師之后,是時(shí)候開始考慮微服務(wù)了。不要太早實(shí)施它們,但不要等待太久。
有關(guān)微服務(wù)與單體的權(quán)衡的更多信息,請(qǐng)觀看 Sam Newman 和 Martin Fowler 的精彩討論,何時(shí)使用微服務(wù)(以及何時(shí)不使用!)。
示例微服務(wù)
在本節(jié)中,您將為您的 Online Books For You 網(wǎng)站定義一些微服務(wù)。你會(huì)定義一個(gè)API,他們和編寫Python代碼實(shí)現(xiàn)他們的微服務(wù),當(dāng)您瀏覽本教程。
為了使事情易于管理,您將只定義兩個(gè)微服務(wù):
Marketplace將是一個(gè)非常小的網(wǎng)絡(luò)應(yīng)用程序,它向用戶顯示書籍列表。
推薦將是一個(gè)微服務(wù),它提供用戶可能感興趣的書籍列表。
這是一個(gè)圖表,顯示了您的用戶如何與微服務(wù)交互:
可以看到,用戶將通過瀏覽器與 Marketplace 微服務(wù)進(jìn)行交互,Marketplace 微服務(wù)將與 Recommendations 微服務(wù)進(jìn)行交互。
考慮一下 Recommendations API。您希望推薦請(qǐng)求具有一些功能:
用戶 ID:您可以使用它來個(gè)性化推薦。但是,為簡單起見,本教程中的所有建議都是隨機(jī)的。
圖書類別:為了使 API 更有趣,您將添加圖書類別,例如神秘、自助等。
最大結(jié)果:您不想退回所有庫存圖書,因此您需要為請(qǐng)求添加一個(gè)限制。
響應(yīng)將是書籍列表。每本書都會(huì)有以下數(shù)據(jù):
圖書 ID:圖書的唯一數(shù)字 ID。
書名:您可以向用戶顯示的書名。
一個(gè)真正的網(wǎng)站會(huì)有更多的數(shù)據(jù),但為了這個(gè)例子,你將限制功能的數(shù)量。
現(xiàn)在你可以更正式地定義這個(gè) API,使用協(xié)議緩沖區(qū)的語法:
syntax = "proto3"; 2 3enum BookCategory { 4 MYSTERY = 0; 5 SCIENCE_FICTION = 1; 6 SELF_HELP = 2; 7} 8 9message RecommendationRequest { 10 int32 user_id = 1; 11 BookCategory category = 2; 12 int32 max_results = 3; 13} 14 15message BookRecommendation { 16 int32 id = 1; 17 string title = 2; 18} 19 20message RecommendationResponse { 21 repeated BookRecommendation recommendations = 1; 22} 23 24service Recommendations { 25 rpc Recommend (RecommendationRequest) returns (RecommendationResponse); 26}
此協(xié)議緩沖區(qū)文件聲明您的 API。協(xié)議緩沖區(qū)由 Google 開發(fā),提供了一種正式指定 API 的方法。起初這可能看起來有點(diǎn)神秘,所以這里是逐行細(xì)分:
第 1 行指定文件使用proto3語法而不是舊proto2版本。
第 3 行到第 7 行定義您的圖書類別,并且每個(gè)類別還分配有一個(gè)數(shù)字 ID。
第 9 到 13 行定義了您的 API 請(qǐng)求。Amessage包含字段,每個(gè)字段都具有特定類型。您正在使用int32,它是一個(gè) 32 位整數(shù),用于user_ID和max_results字段。您還使用BookCategory上面定義的枚舉作為category類型。除了每個(gè)字段都有一個(gè)名稱之外,它還分配了一個(gè)數(shù)字字段 ID。你可以暫時(shí)忽略這個(gè)。
第 15 到 18 行定義了一種新類型,您可以將其用于圖書推薦。它有一個(gè) 32 位整數(shù) ID 和一個(gè)基于字符串的標(biāo)題。
第 20 到 22 行定義了您的 Recommendations 微服務(wù)響應(yīng)。注意repeated關(guān)鍵字,它表示響應(yīng)實(shí)際上有一個(gè)BookRecommendation對(duì)象列表。
第 24 到 26 行定義了 API的方法。您可以將其視為類上的函數(shù)或方法。它需要 aRecommendationRequest并返回 a?RecommendationResponse。
rpc代表遠(yuǎn)程過程調(diào)用。您很快就會(huì)看到,您可以像在 Python 中調(diào)用普通函數(shù)一樣調(diào)用 RPC。但是 RPC 的實(shí)現(xiàn)是在另一臺(tái)服務(wù)器上執(zhí)行的,這就是遠(yuǎn)程過程調(diào)用的原因。
為什么是 RPC 和協(xié)議緩沖區(qū)?
好的,那么為什么要使用這種正式語法來定義 API?如果你想從一個(gè)微服務(wù)向另一個(gè)微服務(wù)發(fā)出請(qǐng)求,難道你不能只發(fā)出一個(gè)HTTP 請(qǐng)求并得到一個(gè) JSON 響應(yīng)嗎?好吧,你可以這樣做,但使用協(xié)議緩沖區(qū)有好處。
文檔
使用協(xié)議緩沖區(qū)的第一個(gè)好處是它們?yōu)槟?API 提供了一個(gè)定義良好且自文檔化的架構(gòu)。如果您使用 JSON,那么您必須記錄它包含的字段及其類型。與任何文檔一樣,您面臨文檔不準(zhǔn)確、不完整或過時(shí)的風(fēng)險(xiǎn)。
當(dāng)您使用協(xié)議緩沖區(qū)語言編寫 API 時(shí),您可以從中生成 Python 代碼。您的代碼永遠(yuǎn)不會(huì)與您的文檔不同步。文檔很好,但自文檔化的代碼更好。
驗(yàn)證
第二個(gè)好處是,當(dāng)您從協(xié)議緩沖區(qū)生成 Python 代碼時(shí),您可以免費(fèi)獲得一些基本驗(yàn)證。例如,生成的代碼不會(huì)接受錯(cuò)誤類型的字段。生成的代碼還內(nèi)置了所有 RPC 樣板。
如果您的 API 使用 HTTP 和 JSON,那么您需要編寫一些代碼來構(gòu)造請(qǐng)求、發(fā)送請(qǐng)求、等待響應(yīng)、檢查狀態(tài)代碼以及解析和驗(yàn)證響應(yīng)。使用協(xié)議緩沖區(qū),您可以生成看起來像常規(guī)函數(shù)調(diào)用但在后臺(tái)執(zhí)行網(wǎng)絡(luò)請(qǐng)求的代碼。
您可以使用 HTTP 和 JSON 框架(例如Swagger和RAML)獲得這些相同的好處。有關(guān) Swagger 運(yùn)行的示例,請(qǐng)查看Python REST APIs With Flask、Connexion 和 SQLAlchemy。
那么是否有理由使用 gRPC 而不是其中一種替代方案?答案仍然是肯定的。
表現(xiàn)
gRPC 框架通常比使用典型的 HTTP 請(qǐng)求更有效。gRPC 建立在HTTP/2之上,它可以以線程安全的方式在長期連接上并行發(fā)出多個(gè)請(qǐng)求。連接設(shè)置相對(duì)較慢,因此執(zhí)行一次并在多個(gè)請(qǐng)求之間共享連接可以節(jié)省時(shí)間。gRPC 消息也是二進(jìn)制的,比 JSON 小。此外,HTTP/2 具有內(nèi)置的標(biāo)頭壓縮。
gRPC 內(nèi)置了對(duì)流式請(qǐng)求和響應(yīng)的支持。它將比基本的 HTTP 連接更優(yōu)雅地管理網(wǎng)絡(luò)問題,即使在長時(shí)間斷開連接后也會(huì)自動(dòng)重新連接。它還具有-,您將在本教程的后面部分了解。您甚至可以為生成的代碼實(shí)現(xiàn)插件,人們已經(jīng)這樣做以輸出Python 類型提示。基本上,您可以免費(fèi)獲得許多出色的基礎(chǔ)設(shè)施!
開發(fā)人員友好
許多人更喜歡 gRPC 而不是 REST 的最有趣的原因可能是您可以根據(jù)函數(shù)而不是 HTTP 動(dòng)詞和資源來定義您的 API?。作為一名工程師,您習(xí)慣于從函數(shù)調(diào)用的角度進(jìn)行思考,這正是 gRPC API 的樣子。
將功能映射到 REST API 通常很尷尬。您必須決定您的資源是什么、如何構(gòu)建路徑以及使用哪些動(dòng)詞。通常有多種選擇,例如如何嵌套資源或是否使用 POST 或其他一些動(dòng)詞。REST 與 gRPC 可能會(huì)變成關(guān)于偏好的爭論。一種并不總是比另一種更好,因此請(qǐng)使用最適合您的用例的方法。
嚴(yán)格來說,protocol buffers是指兩個(gè)微服務(wù)之間發(fā)送的數(shù)據(jù)的序列化格式。因此協(xié)議緩沖區(qū)類似于 JSON 或 XML,因?yàn)樗鼈兪歉袷交瘮?shù)據(jù)的方式。與 JSON 不同,協(xié)議緩沖區(qū)具有嚴(yán)格的架構(gòu),并且在通過網(wǎng)絡(luò)發(fā)送時(shí)更加緊湊。
另一方面,RPC 基礎(chǔ)設(shè)施實(shí)際上稱為gRPC或 Google RPC。這更類似于 HTTP。事實(shí)上,如上所述,gRPC 是建立在 HTTP/2 之上的。
示例實(shí)現(xiàn)
在討論了協(xié)議緩沖區(qū)之后,是時(shí)候看看它們能做什么了。術(shù)語協(xié)議緩沖區(qū)是一口口水,因此您將看到本教程中使用的常見速記protobuf。
正如多次提到的,您可以從 protobufs 生成 Python 代碼。該工具作為grpcio-tools軟件包的一部分安裝。
首先,定義您的初始目錄結(jié)構(gòu):
. ├── protobufs/ │ └── recommendations.proto | └── recommendations/
該protobufs/目錄將包含一個(gè)名為recommendations.proto.?這個(gè)文件的內(nèi)容就是上面的protobuf代碼。為方便起見,您可以通過展開下面的可折疊部分來查看代碼:
完整recommendations.proto代碼顯示隱藏
您將生成 Python 代碼以在recommendations/目錄中與它進(jìn)行交互。首先,您必須安裝grpcio-tools.?創(chuàng)建文件recommendations/requirements.txt并添加以下內(nèi)容:
grpcio-tools ~= 1.30
要在本地運(yùn)行代碼,您需要將依賴項(xiàng)安裝到虛擬環(huán)境中。以下命令將在 Windows 上安裝依賴項(xiàng):
C:\ python -m venv venv C:\ venv\Scripts\activate.bat (venv) C:\ python -m pip install -r requirements.txt
在 Linux 和 macOS 上,使用以下命令創(chuàng)建虛擬環(huán)境并安裝依賴項(xiàng):
$ python3 -m venv venv $ source venv/bin/activate # Linux/macOS only (venv) $ python -m pip install -r requirements.txt
現(xiàn)在,要從 protobufs 生成 Python 代碼,請(qǐng)運(yùn)行以下命令:
$ cd recommendations $ python -m grpc_tools.protoc -I ../protobufs --python_out=. \ --grpc_python_out=. ../protobufs/recommendations.proto
這將從.proto文件中生成多個(gè) Python文件。這是一個(gè)細(xì)分:
python -m grpc_tools.protoc?運(yùn)行 protobuf 編譯器,它將從 protobuf 代碼生成 Python 代碼。
-I ../protobufs告訴編譯器在哪里可以找到您的 protobuf 代碼導(dǎo)入的文件。您實(shí)際上并沒有使用導(dǎo)入功能,但-I仍然需要該標(biāo)志。
--python_out=. --grpc_python_out=.告訴編譯器在哪里輸出 Python 文件。您很快就會(huì)看到,它將生成兩個(gè)文件,如果您愿意,您可以將每個(gè)文件放在帶有這些選項(xiàng)的單獨(dú)目錄中。
../protobufs/recommendations.proto?是 protobuf 文件的路徑,它將用于生成 Python 代碼。
如果您查看生成的內(nèi)容,您將看到兩個(gè)文件:
$ ls recommendations_pb2.py recommendations_pb2_grpc.py
這些文件包括與 API 交互的 Python 類型和函數(shù)。編譯器將生成調(diào)用 RPC 的客戶端代碼和實(shí)現(xiàn) RPC 的服務(wù)器代碼。您將首先查看客戶端。
RPC 客戶端
生成的代碼只有主板才會(huì)喜歡。也就是說,它不是很漂亮的Python。這是因?yàn)樗⒉徽嬲m合人類閱讀。打開 Python shell 以查看如何與之交互:
>>>
>>> from recommendations_pb2 import BookCategory, RecommendationRequest >>> request = RecommendationRequest( ... user_id=1, category=BookCategory.SCIENCE_FICTION, max_results=3 ... ) >>> request.category 1
可以看到 protobuf 編譯器生成了與你的 protobuf 類型對(duì)應(yīng)的 Python 類型。到現(xiàn)在為止還挺好。您還可以看到對(duì)字段進(jìn)行了一些類型檢查:
>>> request = RecommendationRequest( ... user_id="oops", category=BookCategory.SCIENCE_FICTION, max_results=3 ... ) Traceback (most recent call last): File "
這表明如果將錯(cuò)誤的類型傳遞給 protobuf 字段之一,則會(huì)收到TypeError。
一個(gè)重要的注意事項(xiàng)是所有字段proto3都是可選的,因此您需要驗(yàn)證它們是否都已設(shè)置。如果未設(shè)置,則數(shù)字類型默認(rèn)為零,字符串默認(rèn)為空字符串:
>>>
>>> request = RecommendationRequest( ... user_id=1, category=BookCategory.SCIENCE_FICTION ... ) >>> request.max_results 0
這0是因?yàn)檫@是未設(shè)置int字段的默認(rèn)值。
雖然 protobuf 會(huì)為您進(jìn)行類型檢查,但您仍然需要驗(yàn)證實(shí)際值。因此,當(dāng)您實(shí)現(xiàn)您的 Recommendations 微服務(wù)時(shí),您應(yīng)該驗(yàn)證所有字段是否具有良好的數(shù)據(jù)。無論您使用 protobufs、JSON 還是其他任何東西,這對(duì)于任何服務(wù)器都是如此。始終驗(yàn)證輸入。
recommendations_pb2.py為您生成的文件包含類型定義。該recommendations_pb2_grpc.py文件包含客戶端和服務(wù)器的框架。看一下創(chuàng)建客戶端所需的導(dǎo)入:
>>>
>>> import grpc >>> from recommendations_pb2_grpc import RecommendationsStub
您導(dǎo)入該grpc模塊,該模塊提供了一些用于設(shè)置與遠(yuǎn)程服務(wù)器的連接的功能。然后導(dǎo)入 RPC 客戶端存根。之所以稱為存根,是因?yàn)榭蛻舳吮旧頉]有任何功能。它調(diào)用遠(yuǎn)程服務(wù)器并將結(jié)果傳回。
如果你回顧一下你的 protobuf 定義,那么你會(huì)看到最后的service Recommendations {...}部分。protobuf 編譯器采用此微服務(wù)名稱 ,Recommendations并將Stub其附加以形成客戶端名稱RecommendationsStub。
現(xiàn)在您可以發(fā)出 RPC 請(qǐng)求:
>>> channel = grpc.insecure_channel("localhost:50051") >>> client = RecommendationsStub(channel) >>> request = RecommendationRequest( ... user_id=1, category=BookCategory.SCIENCE_FICTION, max_results=3 ... ) >>> client.Recommend(request) Traceback (most recent call last): ... grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with: status = StatusCode.UNAVAILABLE details = "failed to connect to all addresses" ...
您localhost在端口 上創(chuàng)建到您自己的機(jī)器的連接50051。此端口是 gRPC 的標(biāo)準(zhǔn)端口,但您可以根據(jù)需要更改它。您現(xiàn)在將使用一個(gè)不安全的通道,它是未經(jīng)身份驗(yàn)證和未加密的,但您將在本教程后面學(xué)習(xí)如何使用安全通道。然后您將此通道傳遞給您的存根以實(shí)例化您的客戶端。
您現(xiàn)在可以調(diào)用Recommend您在Recommendations微服務(wù)上定義的方法。回想線25在protobuf的定義:rpc Recommend (...) returns (...)。這就是Recommend方法的來源。你會(huì)得到一個(gè)異常,因?yàn)閷?shí)際上沒有微服務(wù)在運(yùn)行l(wèi)ocalhost:50051,所以你接下來要實(shí)現(xiàn)它!
現(xiàn)在您已經(jīng)整理好了客戶端,您將查看服務(wù)器端。
RPC 服務(wù)器
在控制臺(tái)中測(cè)試客戶端是一回事,但在那里實(shí)現(xiàn)服務(wù)器有點(diǎn)多。您可以讓控制臺(tái)保持打開狀態(tài),但您將在文件中實(shí)現(xiàn)微服務(wù)。
從導(dǎo)入和一些數(shù)據(jù)開始:
# recommendations/recommendations.py 2from concurrent import futures 3import random 4 5import grpc 6 7from recommendations_pb2 import ( 8 BookCategory, 9 BookRecommendation, 10 RecommendationResponse, 11) 12import recommendations_pb2_grpc 13 14books_by_category = { 15 BookCategory.MYSTERY: [ 16 BookRecommendation(id=1, title="The Maltese Falcon"), 17 BookRecommendation(id=2, title="Murder on the Orient Express"), 18 BookRecommendation(id=3, title="The Hound of the Baskervilles"), 19 ], 20 BookCategory.SCIENCE_FICTION: [ 21 BookRecommendation( 22 id=4, title="The Hitchhiker's Guide to the Galaxy" 23 ), 24 BookRecommendation(id=5, title="Ender's Game"), 25 BookRecommendation(id=6, title="The Dune Chronicles"), 26 ], 27 BookCategory.SELF_HELP: [ 28 BookRecommendation( 29 id=7, title="The 7 Habits of Highly Effective People" 30 ), 31 BookRecommendation( 32 id=8, title="How to Win Friends and Influence People" 33 ), 34 BookRecommendation(id=9, title="Man's Search for Meaning"), 35 ], 36}
此代碼導(dǎo)入您的依賴項(xiàng)并創(chuàng)建一些示例數(shù)據(jù)。這是一個(gè)細(xì)分:
第 2 行導(dǎo)入,futures因?yàn)?gRPC 需要一個(gè)線程池。稍后你會(huì)談到這一點(diǎn)。
第 3 行導(dǎo)入,random因?yàn)槟鷮㈦S機(jī)選擇書籍進(jìn)行推薦。
第 14 行創(chuàng)建books_by_category?字典,其中鍵是書籍類別,值是該類別中的書籍列表。在真正的推薦微服務(wù)中,書籍將存儲(chǔ)在數(shù)據(jù)庫中。
接下來,您將創(chuàng)建一個(gè)實(shí)現(xiàn)微服務(wù)功能的類:
class RecommendationService( 30 recommendations_pb2_grpc.RecommendationsServicer 31): 32 def Recommend(self, request, context): 33 if request.category not in books_by_category: 34 context.abort(grpc.StatusCode.NOT_FOUND, "Category not found") 35 36 books_for_category = books_by_category[request.category] 37 num_results = min(request.max_results, len(books_for_category)) 38 books_to_recommend = random.sample( 39 books_for_category, num_results 40 ) 41 42 return RecommendationResponse(recommendations=books_to_recommend)
您已經(jīng)創(chuàng)建了一個(gè)具有實(shí)現(xiàn)RecommendRPC的方法的類。以下是詳細(xì)信息:
第 29 行定義了RecommendationService類。這是您的微服務(wù)的實(shí)現(xiàn)。請(qǐng)注意,您將RecommendationsServicer.?這是您需要做的與 gRPC 集成的一部分。
第 32行在Recommend()您的類上定義了一個(gè)方法。這必須與您在 protobuf 文件中定義的 RPC 具有相同的名稱。它也接受 aRecommendationRequest并返回 aRecommendationResponse就像在 protobuf 定義中一樣。它還需要一個(gè)context參數(shù)。該上下文允許您設(shè)置的狀態(tài)代碼的響應(yīng)。
線33和34的使用abort()結(jié)束請(qǐng)求和狀態(tài)代碼設(shè)置NOT_FOUND,如果你得到意想不到的類別。由于 gRPC 建立在 HTTP/2 之上,因此狀態(tài)碼類似于標(biāo)準(zhǔn)的 HTTP 狀態(tài)碼。設(shè)置它允許客戶端根據(jù)它收到的代碼采取不同的行動(dòng)。它還允許中間件,如監(jiān)控系統(tǒng),記錄有多少請(qǐng)求有錯(cuò)誤。
第 36 到 40 行從給定類別中隨機(jī)挑選一些書籍進(jìn)行推薦。您確保將推薦數(shù)量限制為max_results。你min()用來確保你不要求更多的書,否則random.sample會(huì)出錯(cuò)。
第 38 行?返回一個(gè)RecommendationResponse包含您的書籍推薦列表的對(duì)象。
請(qǐng)注意,在錯(cuò)誤條件下引發(fā)異常會(huì)更好,而不是abort()像您在此示例中那樣使用,但是響應(yīng)不會(huì)正確設(shè)置狀態(tài)代碼。有一種方法可以解決這個(gè)問題,您將在本教程后面查看-時(shí)進(jìn)行介紹。
在RecommendationService類定義你的微服務(wù)實(shí)現(xiàn),但你仍然需要運(yùn)行它。這就是serve()它的作用:
def serve(): 42 server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 43 recommendations_pb2_grpc.add_RecommendationsServicer_to_server( 44 RecommendationService(), server 45 ) 46 server.add_insecure_port("[::]:50051") 47 server.start() 48 server.wait_for_termination() 49 50 51if __name__ == "__main__": 52 serve()
serve()?啟動(dòng)網(wǎng)絡(luò)服務(wù)器并使用您的微服務(wù)類來處理請(qǐng)求:
第 42 行創(chuàng)建了一個(gè) gRPC 服務(wù)器。你告訴它使用10線程來處理請(qǐng)求,這對(duì)于這個(gè)演示來說完全是矯枉過正,但對(duì)于實(shí)際的 Python 微服務(wù)來說是一個(gè)很好的默認(rèn)設(shè)置。
第 43行將您的類與服務(wù)器相關(guān)聯(lián)。這就像為請(qǐng)求添加一個(gè)處理程序。
第 46 行告訴服務(wù)器在端口上運(yùn)行50051。如前所述,這是 gRPC 的標(biāo)準(zhǔn)端口,但您可以使用任何您喜歡的端口。
第 47 和 48 行調(diào)用server.start()和server.wait_for_termination()啟動(dòng)微服務(wù)并等待它停止。在這種情況下停止它的唯一方法是在終端中鍵入Ctrl+C。在生產(chǎn)環(huán)境中,有更好的關(guān)閉方法,您將在稍后介紹。
在不關(guān)閉用于測(cè)試客戶端的終端的情況下,打開一個(gè)新終端并運(yùn)行以下命令:
$ python recommendations.py
這將運(yùn)行 Recommendations 微服務(wù),以便您可以在一些實(shí)際數(shù)據(jù)上測(cè)試客戶端。現(xiàn)在返回到您用來測(cè)試客戶端的終端,以便您可以創(chuàng)建通道存根。如果您將控制臺(tái)保持打開狀態(tài),那么您可以跳過導(dǎo)入,但在此重復(fù)它們作為復(fù)習(xí):
>>>
>>> import grpc >>> from recommendations_pb2_grpc import RecommendationsStub >>> channel = grpc.insecure_channel("localhost:50051") >>> client = RecommendationsStub(channel)
現(xiàn)在您有了一個(gè)客戶端對(duì)象,您可以發(fā)出請(qǐng)求:
>>> request = RecommendationRequest( ... user_id=1, category=BookCategory.SCIENCE_FICTION, max_results=3) >>> client.Recommend(request) recommendations { id: 6 title: "The Dune Chronicles" } recommendations { id: 4 title: "The Hitchhiker\'s Guide To The Galaxy" } recommendations { id: 5 title: "Ender\'s Game" }
有用!您向您的微服務(wù)發(fā)出了 RPC 請(qǐng)求并得到了響應(yīng)!請(qǐng)注意,您看到的輸出可能會(huì)有所不同,因?yàn)橥扑]是隨機(jī)選擇的。
現(xiàn)在您已經(jīng)實(shí)現(xiàn)了服務(wù)器,您可以實(shí)現(xiàn) Marketplace 微服務(wù)并讓它調(diào)用 Recommendations 微服務(wù)。如果您愿意,現(xiàn)在可以關(guān)閉 Python 控制臺(tái),但讓 Recommendations 微服務(wù)保持運(yùn)行。
捆綁在一起
為您的 Marketplace 微服務(wù)創(chuàng)建一個(gè)新marketplace/目錄并marketplace.py在其中放置一個(gè)文件。您的目錄樹現(xiàn)在應(yīng)如下所示:
. ├── marketplace/ │ ├── marketplace.py │ ├── requirements.txt │ └── templates/ │ └── homepage.html | ├── protobufs/ │ └── recommendations.proto | └── recommendations/ ├── recommendations.py ├── recommendations_pb2.py ├── recommendations_pb2_grpc.py └── requirements.txt
請(qǐng)注意marketplace/微服務(wù)代碼的新目錄requirements.txt和主頁。所有將在下面描述。您可以暫時(shí)為它們創(chuàng)建空文件,稍后再填充它們。
您可以從微服務(wù)代碼開始。Marketplace 微服務(wù)將是一個(gè)Flask應(yīng)用程序,用于向用戶顯示網(wǎng)頁。它將調(diào)用 Recommendations 微服務(wù)以獲取要在頁面上顯示的圖書推薦。
打開marketplace/marketplace.py文件并添加以下內(nèi)容:
# marketplace/marketplace.py 2import os 3 4from flask import Flask, render_template 5import grpc 6 7from recommendations_pb2 import BookCategory, RecommendationRequest 8from recommendations_pb2_grpc import RecommendationsStub 9 10app = Flask(__name__) 11 12recommendations_host = os.getenv("RECOMMENDATIONS_HOST", "localhost") 13recommendations_channel = grpc.insecure_channel( 14 f"{recommendations_host}:50051" 15) 16recommendations_client = RecommendationsStub(recommendations_channel) 17 18 19@app.route("/") 20def render_homepage(): 21 recommendations_request = RecommendationRequest( 22 user_id=1, category=BookCategory.MYSTERY, max_results=3 23 ) 24 recommendations_response = recommendations_client.Recommend( 25 recommendations_request 26 ) 27 return render_template( 28 "homepage.html", 29 recommendations=recommendations_response.recommendations, 30 )
您設(shè)置 Flask,創(chuàng)建一個(gè) gRPC 客戶端,并添加一個(gè)函數(shù)來呈現(xiàn)主頁。這是一個(gè)細(xì)分:
第 10 行創(chuàng)建了一個(gè) Flask 應(yīng)用程序來為用戶呈現(xiàn)網(wǎng)頁。
第 12 到 16 行創(chuàng)建您的 gRPC 通道和存根。
第 20 到 30 行創(chuàng)建render_homepage()在用戶訪問您的應(yīng)用程序主頁時(shí)調(diào)用。它返回一個(gè)從模板加載的 HTML 頁面,其中包含三本科幻小說推薦。
注意:在此示例中,您將 gRPC 通道和存根創(chuàng)建為globals。通常全局變量是不可以的,但在這種情況下,例外是有保證的。
gRPC 通道保持與服務(wù)器的持久連接,以避免必須重復(fù)連接的開銷。它可以處理許多并發(fā)請(qǐng)求,并會(huì)重新建立斷開的連接。但是,如果您在每個(gè)請(qǐng)求之前創(chuàng)建一個(gè)新通道,那么 Python 將對(duì)其進(jìn)行垃圾回收,并且您將無法獲得持久連接的大部分好處。
您希望頻道保持打開狀態(tài),這樣您就無需為每個(gè)請(qǐng)求重新連接到推薦微服務(wù)。您可以將通道隱藏在另一個(gè)模塊中,但由于在這種情況下您只有一個(gè)文件,因此您可以通過使用全局變量使事情變得更簡單。
打開目錄中的homepage.html文件marketplace/templates/并添加以下 HTML:
2 3 4
5Mystery books you may like
9- 10 {% for book in recommendations %} 11
- {{ book.title }} 12 {% endfor %} 13
這只是一個(gè)演示主頁。完成后,它應(yīng)該顯示書籍推薦列表。
要運(yùn)行此代碼,您需要以下依賴項(xiàng),您可以將其添加到marketplace/requirements.txt:
flask ~= 1.1 grpcio-tools ~= 1.30 Jinja2 ~= 2.11 pytest ~= 5.4
Recommendations 和 Marketplace 微服務(wù)各有其自己的requirements.txt.?運(yùn)行以下命令來更新您的虛擬環(huán)境:
$ python -m pip install -r marketplace/requirements.txt
現(xiàn)在您已經(jīng)安裝了依賴項(xiàng),您還需要在marketplace/目錄中為您的 protobuf 生成代碼。為此,請(qǐng)?jiān)诳刂婆_(tái)中運(yùn)行以下命令:
$ cd marketplace $ python -m grpc_tools.protoc -I ../protobufs --python_out=. \ --grpc_python_out=. ../protobufs/recommendations.proto
這與您之前運(yùn)行的命令相同,因此這里沒有任何新內(nèi)容。在marketplace/和recommendations/目錄中擁有相同的文件可能會(huì)讓人感到奇怪,但稍后您將看到如何在部署過程中自動(dòng)生成這些文件。您通常不會(huì)將它們存儲(chǔ)在 Git 等版本控制系統(tǒng)中。
要運(yùn)行您的 Marketplace 微服務(wù),請(qǐng)?jiān)谀目刂婆_(tái)中輸入以下內(nèi)容:
$ FLASK_APP=marketplace.py flask run
您現(xiàn)在應(yīng)該在兩個(gè)單獨(dú)的控制臺(tái)中運(yùn)行 Recommendations 和 Marketplace 微服務(wù)。如果您關(guān)閉了 Recommendations 微服務(wù),請(qǐng)?jiān)诹硪粋€(gè)控制臺(tái)中使用以下命令重新啟動(dòng)它:
$ cd recommendations $ python recommendations.py
這將運(yùn)行您的 Flask 應(yīng)用程序,該應(yīng)用程序默認(rèn)在端口 上運(yùn)行5000。繼續(xù)并在瀏覽器中打開它并檢查它:
您現(xiàn)在有兩個(gè)微服務(wù)在互相通信!但它們?nèi)匀恢皇窃谀拈_發(fā)機(jī)器上。接下來,您將學(xué)習(xí)如何將這些應(yīng)用到生產(chǎn)環(huán)境中。
您可以通過在運(yùn)行Python 微服務(wù)的終端中鍵入Ctrl+C來停止它們。接下來您將在Docker 中運(yùn)行它們,這就是它們?cè)谏a(chǎn)環(huán)境中的運(yùn)行方式。
從零開始學(xué)python | 使用 gRPC 的 Python 微服務(wù) II
Python 容器 微服務(wù)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。