天天看點

簡單軟體架構的一些好處

作者 | Dan Luu

譯者 | Sambodhi

策劃 | Tina

本文最初發表于 Wave 官網,經原作者 Dan Luu 授權,InfoQ 中文站翻譯并分享。

Wave 是一家價值 17 億美元的公司,擁有 70 名工程師,該公司的産品是一款加減數字的 CRUD 應用程式。為了與此保持一緻,我們的架構是一種标準的 CRUD 應用架構,基于 Postgres 的 Python 單體架構。先從一個簡單的架構入手,然後盡量用最簡單的方式來解決問題,這使得我們的業務範圍能夠擴大到這種規模,而工程師們大多專注于為使用者提供價值的工作。

Stackoverflow 擴大了單體的規模,取得了良好的效果(2013 年的架構 /2016 年的架構),最後以 18 億美元的價格被收購。如果我們關注的是流量而非市場市值,那麼 Stackoverflow 就是網際網路上流量最高的前 100 個網站之一(關于其他許多建立在單體之上的有價值的公司的案例,請參考這條 Twitter 主題的回複(https://twitter.com/danluu/status/1462607028585525249)。我們沒有很多網絡流量,因為我們是一個移動應用,但 Alexa 還是将我們的網站列在了前 75000 名,盡管我們的網站基本上隻是人們查找 APP 的一種途徑,而大部分人并沒有從我們的網站中獲得這些 APP)。

有些應用的要求,使得在一個枯燥的資料庫中建構出一個簡單的單體應用是不可能的,但對大部分應用來說,即便是在前 100 個網站的流量水準上,計算機的運作速度也足以滿足使用簡單的架構來提供服務,通常建立簡單的架構比複雜的架構更便宜、更容易。

盡管簡單的架構具有不合理的有效性,但是大部分的新聞報道都是圍繞着複雜的架構展開的。舉例來說,在最新的通用技術會議上,就有六場演講讨論了怎樣建構或處理基于微服務的複雜結構的負面影響,卻沒有一場演講讨論如何建構簡單的單體。甚至關于量子計算的演講也有一場。更大規模的會議也一樣;最近舊金山的一次以企業為導向的會議上,關于處理複雜架構的演講,場次就高達兩位數;卻沒有一場關于如何建構簡單的單體的演講。我上次去的那次會議給我留下了很深的印象,就是許多公司的員工,他們的應用程式規模很小,本來可以用簡單的架構就能完成,但是他們使用的都是會議圈子和網絡上流行的最新、最複雜的技術。

我們的架構是如此簡單,以至于我都懶得去做一個架構圖。我會讨論我們所做的使一切乏味的事。

我們目前使用的是乏味的、同步的 Python,這意味着,當我們的伺服器程序在等待 I/O 時被阻塞,比如網絡請求。我們之前嘗試過 Eventlet,這是一種理論上能使我們從 Python 中獲得更高效率的異步架構,但是我們碰到了大量的 Bug,我們覺得,等待事件的 CPU 和延遲成本,都不值得我們為處理 Eventlent 問題而承擔操作上的痛苦。其他知名的 Python 架構也有類似的情況,但是大規模使用它們的使用者往往也會報告大規模使用這些架構帶來的嚴重後果。使用同步的 Python 代價很高,因為我們需要支付 CPU 的費用,而在網絡請求期間,CPU 除了等待之外什麼都不做,但是,現在,我們每個月隻能處理幾十億個請求,是以,即便是使用 Python 這種緩慢的語言,也要支付公共雲的零售費用,這樣的成本也很低。我們工程團隊的成本完全決定了我們所營運的系統的成本。

我們将長時間運作的任務(我們不想讓響應阻塞)配置設定到一個隊列中,而不是承擔使我們的單體異步的複雜性。

我們不能像我們想的那樣無聊的地方,就是我們的内部資料中心。當我們隻在塞内加爾和科特迪瓦營運時,我們完全是在雲端中營運,但是,随着我們的業務範圍擴大到烏幹達(以及未來更多的國家 / 地區),我們不得不拆分後端,部署到當地的内部資料中心,以遵守當地的資料存儲法律和法規。這并非一項簡單的操作,但正如那些在面向服務的複雜架構中做過相同工作的人所知道的那樣,這種操作要比使用複雜的服務導向的架構要簡單得多。

另外一個方面是我們必須研發的軟體,而非購買。剛起步時,我們強烈地傾向于購買軟體,而非研發軟體,因為一個由少數工程師組成的團隊無法承擔研發軟體的時間成本。雖然“購買”這一選項,通常會給你提供一些無效的工具(https://danluu.com/nothing-works/),但這在那個時候是正确的選擇。如果我們不能說服供應商修複 Showstopper 錯誤,而這個錯誤對我們至關重要,那麼在這種情況下,建構更多的自己的工具,并且在更多的方面保留内部的專業知識,這的确是很有意義的(https://danluu.com/in-house/),但這與公司應隻選擇“建構”其核心能力的标準建議相悖。這種複雜性的大部分都是我們不願意承擔的,但是對于某些類别的産品,即便是進行了相當廣泛的研究,我們仍然找不到供應商能夠提供适合我們的産品。公平地說,我們的供應商需要解決的問題比我們需要解決的問題複雜得多,因為我們的供應商承擔着為每個客戶解決問題的複雜性,而我們隻需要為一個客戶解決問題,那就是我們自己。

譯注:Showtopper 錯誤是導緻執行停止并基本上變得無用的硬體或軟體錯誤。必須修複此嚴重錯誤,以使開發過程進一步進行。

我們在營運的頭幾個月裡,就犯了一個錯誤,就是沒有仔細地界定資料庫事務的邊界,這在今天已經付出了一定的代價。在 Wave 的代碼庫中,SQLAlchemy 資料庫會話是一個請求全局變量;在任何時候通路 DB 對象的屬性時,它都隐含地開始一個新的資料庫事務,并且 Wave 代碼庫中的任何函數都可以在會話上調用 commit,使其送出所有挂起、的更新。這使得我們很難控制資料庫更新發生的時間,進而增加了出現微妙的資料完整性錯誤的機率,并且很難依靠資料庫來建構類似于幂等鍵(idempotency key)或事務性暫存的作業流失。這樣做還會增加我們意外地持有打開的長時間運作的資料庫事務的風險,這可能使模式遷移操作變得困難。

一些我們不确定的選擇(因為我們在考慮更改,或建議其他從零起步的團隊考慮另一種方式)有:使用 RabbitMQ(就我們的目的而言,Redis 可能同樣适用于任務隊列,隻需要 Redis 就可以減輕操作負擔);使用 Celery(這對于我們的用例來說過于複雜,并且已經出現了好幾次故障,比如在版本更新過程中出現了向後相容性問題);使用 SQLAlchemy(它使開發人員難以了解自己的代碼将會産生怎樣的資料庫查詢,進而導緻各種難以調試的情況,同時也帶來了不必要的操作痛苦,尤其是與上面提到的資料庫事務邊界的觀點有關);以及使用 Python(由于我們的創始 CTO 的技術背景,這是最初的正确選擇,但其并發支援、性能和廣泛的動态性使我們質疑它是否是大規模後端代碼庫的正确選擇)。以上所有這些都不是主要的錯誤,而且對于一些(例如 Python) 來說,缺陷已經很少了,是以,與投資到理論上更好的遷移相比,我們将花費更少的費用去進行更多的維護,但如果我們現在就從頭編寫一套類似的代碼庫,那麼我們就會認真考慮,它們是否正确的選擇。

在某些方面,我們很滿意能做出這樣的選擇,雖然這些聽上去并不像是最簡單可行的解決方案,比如我們的 API,我們使用 GraphQL;我們的傳輸協定,我們有一段時間使用自定義協定;還有我們的主機管理,我們使用 Kubernetes。對于我們的傳輸協定,我們曾經使用了一種基于 UDP 的自定義協定,并帶有 SMS 和 USSD 後備功能,這也是這場講座所提到的性能理由。在 HTTP/3 釋出後,我們已經能夠用 HTTP/3 來替代我們的自定義協定,通常我們隻需要 USSD 就可以解決像最近在馬裡發生的網際網路關閉這樣的事件)。

對于 GraphQL 的使用,我們相信其優點多于缺點:

優點:

精确傳回類型的自文檔化;

精确傳回類型的代碼生成使得用戶端更加安全;

GraphiQL 互動式探索器是生産力的一個勝利;

我們的各種應用(使用者應用、支援應用、Wave agent 應用等)大多可以共享一個 API,進而減少複雜性。

可組合的查詢語言允許用戶端在一次資料包往返中準确擷取它們需要的資料,而無需建立大量的特殊用途的端點;

避免了對什麼算作 RESTful API 的無意義争論。

缺點:

當我們采用 GraphQL 時,GraphQL 庫并不是很好(基本的 Python 庫是從 JavaScript 庫中移植過來的,是以不是 Python 化的,Graphene 需要大量的模闆,Apollo-Android 生成的優化代碼非常爛)。

預設的 GQL 編碼是備援的,而且由于很多用戶端的帶寬較低,是以我們非常關心限制大小。

對于 Kubernetes,我們之是以選擇 Kubernetes,是因為我們清楚,如果業務成功(确實如此),而且我們不斷擴張,我們最終會擴張到那些要求我們在該國内營運服務的國家。各國之間的具體規定各不相同,但是我們在非洲的主要市場上拓展了業務,這就要求我們在該國營運我們的 “主要資料中心”,還有其他一些規定,例如,要求我們能夠将故障轉移到該國的資料中心。

電信內建是我們無法回避的複雜性的一個方面。從理論上講,我們将使用 SaaS SMS 提供商來完成這一切,但是,非洲各大 SaaS SMS 提供商的業務并沒有遍及整個非洲(https://youtu.be/6tb8ALAvodM?t=196),是以在那裡使用這些服務的費用都讓人望而卻步。如果我們利用 SaaS SMS 提供商來解決我們所有的短信需求,那麼以前的那些說工程師的薪酬成本如何主導我們系統成本的說法是站不住腳的;提供電信內建服務的團隊為此付出了數倍的代價。

通過将應用架構盡量簡化,我們就可以将複雜性(以及人力)預算用于有利于業務發展的領域。如果沒有足夠的理由去提高複雜性,那麼我們就可以基于盡可能簡單地做事的這一想法,用少量的工程師,建立一個規模不小的業務,雖然我們所經營的非洲金融業務通常被視為難以涉足的業務,我們會在以後的文章中談到(我們的早期和最有幫助的咨詢顧問之一,他給我們提出的建議,對于 Wave 的成功非常關鍵,起初提出 Wave 是一個糟糕的商業點子,而創始人應該選擇另一個,因為他預見到了許多潛在的困難)。

https://www.wave.com/en/blog/simple-architecture/index.html

繼續閱讀