用Python構建、測試、部署和擴充套件微服務
本文為 Python Microservices Development 的選擇性翻譯,本書暫時無中文版,有能力請直接看原版
什麼是微服務?
A microservice is a lightweight application, which provides a narrowed list of features with a well-defined contract. It's a component with a single responsibility, which can be developed and deployed independently.
微服務是一個輕量級的應用程式,它通過定義良好的契約提供窄範圍的特性列表。 它是一個具有單一職責的元件,可以獨立地進行開發和部署。
本書內容:第一章 理解微服務,定義什麼是微服務,以及在現在web應用當中的角色.說明為什麼Python非常適合構建微服務。
第二章 瞭解Flask,介紹Flask的主要特性。用Flask搭建一個簡單的web應用微服務。
第三章 編碼,文件,和測試——良性迴圈,描述測試驅動和持續整合方法,Flask應用構建和打包實踐 第四章 設計Runnerly,通過應用的功能和使用者故事,解釋如何將他建成一個整體應用,然後把它分解成微服務,並解釋它們如何與資料進行互動。還將介紹Open API 2.0 規範(ex-Swagger),該規範可用於藐視HTTP APIs。
第五章 整合其他服務,解釋一個服務如何與後端服務整合,怎樣處理網路分割和其他互動問題,以及如何在隔離狀態下測試服務。
第六章 服務安全,解釋如何保護你的微服務,以及如何處理使用者身份嚴重「」服務到服務身份驗證和使用者管理。還將介紹欺詐和濫用,以及如何將他們降低。 第七章 監控你的服務,解釋如何在你的程式碼中新增日誌和指標(metrics),以及如何確保你對你的應用程式中正在發生的事情有一個清晰的全域性理解,從而跟蹤問題並瞭解你的服務使用情況。
第八章 整合起來,描述瞭如何設計和構建一個使用了微服務的JavaScript應用程式,給終端使用者介面。 第九章 打包執行Runnerly,描述如何打包、構建和執行整個Forrest應用程式。作為一名開發人員,能夠將構成應用程式的所有部分執行到單個開發包中是必不可少的。
第十章 集裝箱式服務,解釋什麼是虛擬化,如何使用Docker,以及如何將你的服務進行Dockerize。
第十一章 在AWS上部署,介紹現有的雲服務提供商,和AWS世界,展示瞭如何例項化伺服器,對於執行微服務的應用程式非常有用的AWS服務。本文還介紹了CoreOS,一個專門為在雲中部署Docker容器而建立的Linux發行版。
第十二章 下一步是什麼,在書的結尾處給出了一些關於如何獨立於特定的雲供應商和虛擬化技術構建微服務的建議,以避免將所有雞蛋放在同一個籃子裡。它強調了你在第九章中學到的東西,打包和執行Runnerly。
單體應用的利弊:
- 可能是開始一個專案最簡單的方式
- 集中式資料庫簡化了資料的設計和組織。
- 部署一個應用程式很簡單。
- 程式碼中的任何更改都可能影響不相關的特性。當某個功能出現問題時,整個應用程式都可能出現問題。
- 擴充套件應用程式的解決方案是有限的:你可以部署多個例項,但是如果應用程式中的一個特定功能佔用了所有資源,那麼它就會影響到所有方面。
- 隨著程式碼庫的增長,很難保持它的整潔和受控。
一個酒店預訂網站的結構
1. 單體應用結構
2. 微服務結構
- 在這個設計中,每個元件都使用 HTTP 協議進行通訊,並且特性可以通過 RESTful web 服務獲得。
- 沒有集中的資料庫,每個微服務在內部處理自己的資料結構,進出的資料使用語言無關的格式,如 JSON。
微服務優點
-
關注點分離
- 首先,每個微服務都可以由一個獨立的團隊獨立開發。 例如,構建一個預訂服務本身就是一個完整的專案。 負責團隊可以使用任何程式語言和資料庫,只要它有一個良好文件的 HTTP API。
- 這也意味著相比單體應用,程式的進化更好地受到控制。例如,如果支付系統改變了與銀行的底層互動,那麼影響就侷限在該服務內部,而應用程式的其餘部分保持穩定,可能不會受到影響。
- 這種鬆散耦合大大提高了整體專案的速度,因為我們在服務層面採用了類似於單一責任原則的理念
-
需要處理的是較小專案
- 第二個好處是打破了專案的複雜性。 當嚮應用程式新增一個特性(如 PDF 報告)時,即使做得乾淨利落,也會使基本程式碼變得更大、更復雜,有時還會變慢。
- 在單獨的應用程式中構建這個特性可以避免這個問題,並且可以使用任何你想要的工具更容易地編寫它。 你可以經常重構它,縮短髮布週期,能夠更好的掌控。程式的增長仍在控制之下。
- 在改進應用程式時,處理較小的專案也可以降低風險: 如果團隊想要嘗試最新的程式語言或框架,他們可以快速迭代一個原型,實現相同的微服務 API,嘗試它並決定是否仍然使用它
- 一個真實的例子是 Firefox Sync 儲存微服務。 目前有一些試驗從當前的 Python + MySQL 實現切換到基於 go 的實現,該實現將使用者的資料儲存在獨立的 SQLite 資料庫中。 這個原型是高度試驗性的,但是因為我們已經用一個定義良好的 HTTP API 將儲存特性分離到微服務中,所以很容易用一小部分使用者嘗試一下。(看來有時間還是要學習一下Go)
-
更多的擴充套件和部署選項
- 最後,將應用程式分割成元件,可以更容易地根據約束進行擴充套件。 假設你有很多客戶每天預定酒店,而生成PDF消耗大量cpu。這時可以將這個特定的微服務部署到擁有更大 cpu 的伺服器上。
- 另一個典型的例子是 RAM 消耗型的微服務,比如那些與記憶體資料庫(如 Redis 或 Memcache)互動的服務。 您可以調整部署,將其部署到具有更少 CPU 和更多 RAM 的伺服器上。
因此,我們可以將微服務的好處概括如下:
- 一個團隊可以獨立開發每個微服務,使用任何能使用的技術棧。 他們可以自定義一個釋出週期,只需要完成一個與語言無關的 HTTP API。
- 開發人員將應用程式的複雜性分解為邏輯元件。每個微服務都專注於做好一件事情。
- 由於微服務是獨立的應用程式,因此對部署有更好的控制,這使得擴充套件更加容易。
微服務體系結構有助於解決應用程式開始增長時可能出現的許多問題。 然而,我們需要意識到它帶來的一些新問題。
微服務隱患
- 不合邏輯的分割
- 微服務架構的第一個問題是如何設計它。一個團隊不可能在第一次就想出完美的微服務架構。 一些微服務(如 PDF 生成器)是顯而易見的用例。而只要是處理業務邏輯,你的程式碼就有很大的可能,在你理解如何將應用分割成正確的微服務集合之前,四處移動。
- 成熟的設計需要一些嘗試和失敗的迴圈。 新增和刪除微服務可能比重構單體應用程式更痛苦。
- 如果分隔不明顯的話,可以避免分割應用成微服務
- 如果有任何懷疑分割有無意義,就保持在一起。將一些程式碼分割成一個新的微服務,比在合併回兩個微服務要容易得多
- 例如,如果你總是必須將兩個微服務部署在一起,或者一個微服務中的一個更改影響到另一個微服務的資料模型,那麼您沒有正確地分割應用程式,並且這兩個服務應該重新組合。
- 更多的網路互動
- 資料儲存和共享
- 一個有效的微服務需要獨立於其他微服務,理想情況下不應該共享一個資料庫。 這對我們的酒店預訂應用程式意味著什麼?
- 同樣,這也引出了很多問題,比如:我們是在所有資料庫中使用相同的使用者 id,還是在每個服務中使用獨立的id並將其作為一個隱藏的實現細節?
- 一旦使用者新增到系統中,我們是通過資料抽取策略在其他服務資料庫中複製一些它的資訊,還是這樣做有點過了?
- 如何處理資料刪除?
- 儘可能避免資料重複,同時將微服務隔離開來,是設計基於微服務的應用程式的最大挑戰之一。
這些都是很難回答的問題,有很多不同的方法可以解決這些問題,我們將在書中學到這一點。
-
相容性問題
- 另一個問題發生在功能更改影響多個微服務時。如果更改以向後不相容的方式影響在服務之間傳輸的資料,那麼就會遇到麻煩。
- 你部署的新服務是否可以與其他服務的舊版本一起使用?還是需要同時更改和部署多個服務?這是否意味著你發現了一些服務應該被合併回來?
-
測試
- 最後,當你想要進行一些端到端測試並部署整個應用程式時,您現在必須處理許多應用。你需要一個健壯的、敏捷的部署流程來提高效率。你需要能夠在開發整個應用程式時使用它。你不可能僅僅用幾個用例就完全測試出來。
- 介紹一些促進微服務的工具
WSGI標準
WSGI 最大的問題在於它的同步性。你在前面的程式碼中看到的應用程式函式對每個傳入請求只呼叫一次,當函式返回時,它必須返回響應。 這意味著每次呼叫函式時,它都會阻塞,直到響應準備好為止。
WSGI 伺服器將允許你執行一個執行緒池來同時處理多個請求。 但是你不能執行成千上萬個這樣的服務,一旦這個池用完了,下一個請求就會阻止客戶的訪問,而你的微服務什麼也不做,只是空閒地等待後端服務的響應。
這就是為什麼 Twisted 和 Tornado 這樣的非 wsgi 框架( 在JavaScript 領域中是node.js),非常成功的原因——它完全是非同步的。
在編寫 Twisted 應用程式時,可以使用回撥來暫停和恢復生成響應的工作。 這意味著你可以接受新的請求並開始處理它們。 這種模式顯著地減少了程式中的空閒時間。 它可以處理成千上萬的併發請求。 當然,這並不意味著應用程式會更快地返回每個響應。 它僅僅意味著一個程式可以接受更多的併發請求,並且在資料準備發回時在這些請求之間進行切換。
WSGI 標準沒有簡單的方法來引入類似的東西,社群已經爭論了多年來達成共識---- 但失敗了。 可能的情況是,社群最終會放棄 WSGI 標準。
與此同時,如果你考慮到WSGI標準,一個請求等於一個執行緒,那麼構建具有同步框架的微服務仍然是可能的並且完全沒問題。
但是,有一個增強同步 web 應用程式的技巧—— Greenlet,將在下一節解釋。
Gevent
Gevent提供了 socket 模組的合作版本,該模組使用 greenlets 來在socket中有資料可用時自動暫停和恢復執行。 甚至還有一個 monkey 補丁功能,可以用 Gevent 的版本自動替換標準庫socket。 這使你的標準同步程式碼在每次使用 socket時都神奇地非同步——只需多加一行:
from gevent import monkey
monkey.patch_all()
def application(environ, start_response):
headers = [('Content-type', 'application/json')]
start_response('200 OK', headers)
# ...do something with sockets here...
return result
複製程式碼
不過,這種隱式的魔力是有代價的。 為了讓 Gevent 能夠正常工作,所有的底層程式碼都需要與 Gevent 所做的修補相容。 一些來自社群的軟體包會因為這個原因而繼續阻塞甚至產生意外的結果---- 特別是,如果他們使用 c 擴充套件,並繞過了標準庫 Gevent 補丁的一些特性。 但在大多數情況下效果都很好。 與 Gevent 相容的專案被稱為綠色專案。
Twisted and Tornado
如果你正在構建微服務,而且併發請求的數量很重要,那麼放棄WSGI標準是很誘人的,你可以使用非同步框架Twisted 或 Tornado
import time
import json
from twisted.web import server, resource
from twisted.internet import reactor, endpoints
class Simple(resource.Resource):
isLeaf = True
def render_GET(self, request):
request.responseHeaders.addRawHeader(b"content-type",
b"application/json")
return bytes(json.dumps({'time': time.time()}), 'utf8')
site = server.Site(Simple())
endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
endpoint.listen(site)
reactor.run()
複製程式碼
雖然 Twisted 是一個非常健壯和高效的框架,但是它在構建 HTTP 微服務時遇到了以下幾個問題:
- 您需要使用從Resource類派生的類來實現微服務中的每個端點,並實現每個受支援的方法。對於一些簡單的API,它新增了許多樣板程式碼。
- 由於其非同步性質,扭曲的程式碼很難理解和除錯。
- 當你連結太多連續依次觸發的函式時,很容易陷入回撥地獄 - 程式碼可能變得混亂。
- 正確測試Twisted應用程式很困難,您必須使用特定於Twisted的單元測試模型。
Tornado 基於類似的模型,但在某些領域做得更好。它有一個更輕的路由系統,並盡一切可能使程式碼更接近普通的Python。 Tornado也使用回撥模型,因此除錯很困難。 依賴 Python 3中引入的新的非同步特性。兩個框架都在努力彌合這一問題。
asyncio
from aiohttp import web
import time
async def handle(request):
return web.json_response({'time': time.time()})
if __name__ == '__main__':
app = web.Application()
app.router.add_get('/', handle)
web.run_app(app)
複製程式碼
但是,基於Python 3的非同步框架和庫仍然在不斷湧現,如果使用非同步或aiohttp之類的框架,則需要針對每個需要的特性堅持使用特定的非同步實現。 如果需要在程式碼中使用非非同步的庫,則從非同步程式碼使用它意味著如果要防止阻塞事件迴圈,則需要執行一些額外的、具有挑戰性的工作。
如果你的微服務處理的資源數量有限,這可能是可控的。 但是在寫這篇文章的時候,堅持使用已經存在了一段時間的同步框架而不是非同步框架可能是一個更安全的選擇。 讓我們享受成熟軟體包的現有生態系統,並等待非同步生態系統變得更加完善。
這本書的第二版很有可能使用非同步框架。 但是對於這個版本,我們將在整本書中使用 Flask 框架。
語言效能
當然,每個人都知道Python比Java或GO慢,但是執行速度並不總是最優先考慮的。微服務通常是一層很薄的程式碼,其生命週期的大部分時間都在等待來自其他服務的一些網路響應。與從 Postgres 伺服器返回 SQL 查詢的速度相比,它的核心速度通常不那麼重要,因為後者佔用了響應的大部分時間。
但是想要一個儘可能快的應用程式是合理的
Python 社群中關於加速語言的一個有爭議的話題是,GIL如何破壞效能,因為多執行緒應用程式不能使用多個程式。
GIL 有存在的充分理由。它保護CPython直譯器中非執行緒安全的部分,並且存在於其他語言中,如 Ruby。到目前為止,所有試圖刪除它的嘗試都未能生成更快的 CPython 實現。
對於微服務,除了防止在同一程式中使用多個核心之外,GIL 在高負載時會稍微降低效能,因為互斥鎖引入了系統呼叫開銷。
然而,圍繞 GIL 的所有審查都是有益的: 在過去幾年中已經完成了減少直譯器中 GIL 爭論的工作,並且在某些方面,Python 的效能有了很大的提高。
請記住,即使核心團隊刪除 GIL,Python 也是一種解釋語言和垃圾收集語言,並且會因為這些屬性而遭受效能損失。
在靜態編譯語言中編寫一個類似的函式將大大減少產生相同結果所需的運算元量。
不過,有一些方法可以提高 Python 的執行速度。
一種方法是通過構建 c 擴充套件,或者使用語言的靜態擴充套件(如 Cython (http: / / Cython. org /) ,將部分程式碼編寫到已編譯的程式碼中,但這會使程式碼更加複雜。
另一個解決方案是最有希望的,那就是使用 PyPy 直譯器(http: / / PyPy. org /)簡單地執行應用程式。
Pypy 實現一個實時(JIT)編譯器。 這個編譯器在執行時直接用 CPU 可以直接使用的機器程式碼替換部分 Python 程式碼。 對於 JIT 來說,整個技巧就是要在執行之前提前檢測到什麼時候以及如何去做。
即使PyPy總是CPython之後的幾個Python版本,但它已經達到了可以在生產中使用的程度,而且它的效能相當驚人。 我們在 Mozilla 的一個專案需要快速執行,PyPy 版本幾乎和 Go 版本一樣快,所以我們決定在那裡使用 Python。 ... 無論如何,對於大多數專案來說,Python 及其生態系統的好處大大超過了本節描述的效能問題,因為微服務的開銷很少成為問題。 如果效能有問題,微服務方法允許你重寫效能關鍵元件,而不會影響系統的其餘部分。
在本章中,我們比較了單體應用和微服務的方法來構建 web 應用程式,很明顯,這不是一個二元世界,你不是必須在第一天就選擇一種方法並一直使用它。
你應該將微服務視為一個單體應用程式的改進。 隨著專案的成熟,服務邏輯的一部分應該遷移到微服務中。 正如我們在本章學到的,這是一個有用的方法,但是要小心謹慎,以免落入一些常見的陷阱。
Flask
略
-
不同型別的測試
- 單元測試: 確保一個類或一個函式獨立地工作
- 功能測試: 從使用者的角度驗證微服務是否言行一致,即使對於錯誤請求,微服務也能正確執行
- 整合測試: 驗證微服務如何與其所有網路依賴項整合
- 負載測試: 測量微服務效能
- 當我的服務承受壓力時,它是 RAM 還是主要受 cpu 限制?
- 是否可以新增相同服務的其他例項並橫向擴充套件?
- 如果我的微服務呼叫其他服務,可以使用連線池,還是必須通過一個連線序列化所有的互動?
- 服務能一次執行多天而不降級嗎?
- 服務在使用高峰期之後是否正常工作?
- 端到端測試: 驗證整個系統是否與端到端測試一起工作
- 總結:
- 功能測試是要編寫的最重要的測試,並且通過在測試中例項化應用程式並與之互動,很容易在 Flask 中完成這項工作
- 單元測試是一個很好的補充,但是不要濫用模擬
- 整合測試類似於功能測試,但是與真正的部署相對立
- 負載測試對於瞭解微服務瓶頸和規劃下一步非常有用
- 端到端測試需要使用客戶端通常使用的相同 UI
-
使用 WebTest
-
使用 pytest 和 Tox
- pytest自動發現和執行專案中的所有測試
- Tox python多版本測試
-
開發者文件
-
持續整合