為什麼他們最後決定改用 Go 重寫了?
(同步發表於 Cepave)
現今這個時代的網站已經很少單純只用 HTML 完成,為了處理動態資料,除了前端的展現,我們還需要由後端幫忙處理資料,目前主流的網頁語言與框架有:
- PHP – Laravel
- Python – Django/Flask/Falcon
- Ruby – RoR
- Node.js – Express/Salt
- Go – Beego
沒有一個框架是完美的。但是我們可以發現有越來越多的人/公司逐漸選擇向 Go 靠攏:
- From Python to Go: migrating our entire API
- How We Moved Our API From Ruby to Go and Saved Our Sanity
- Farewell Node.js
Go 是 Cepave 主要的開發語言,我們在之前的文章也透過範例體現了 Go 的好。這些文章都寫得非常好,而我們為什麼選擇 Go 的原因也在本文的整理中不證自明。
從 Python 到 Go 性能大幅提升
Repustate 是做提供語意分析服務的公司。每日處理 5 ~ 10 億的文字片段,文字片段來源為 Tweets、新聞標題、網站的留言評論、與使用者回饋等等。
文字分析要水平擴展沒有辦法跟一般 API 一樣採用 Cache,因為文字分析的特性就是很少會兩個請求分析的文字內容完全相同。所以關鍵就是你必須了解你在分析文字的過程有多少動作可以平行處理。以 Speech Tagging 為例,很大部份演算法使用機率模型來決定 Tag,在 這種情況下句子與句子並不互相影響,句子就可以被拆出來做平行分析。
動機
主因是 Django 處理 request/response 的週期太長了。當 API 的使用量增加後,不但帶來許多可靠度問題也提高機器成本。在選擇 Go 之前,嘗試過 Flask 以及 Falcon,相對 Django 快速又輕量化,在框架強制限制下可以設計出乾淨的 RESTful APIs,又可快速地從現有的 Django 程式碼移植過去。
而最終還是決定棄用了 Falcon,因為速度提升還是無法滿足 Repustate 的需求,畢竟平行處理還是 Python 的弱項(Repustate 使用的是 Python2.7)。另一個原因是 Python 部署太不方便。因為隱私與安全性考量,很多公司會要求讓他們自己部署 Repustate 的服務,用 VM 可以解決這問題,但是整個 Stacks 用 VM 打包實在太肥了。
缺點
當你習慣用 Python, Ruby 這類的語言開發後, 很容易讓你忽略了背後複雜的記憶體處理細節。所以用 Go 要特別留意空間的分配、避免不必要的浪費、以及垃圾回收機制介入的時間。
移植後得到的好處
- 容易實現並發,並且效能大幅提升: - API平均響應時間由100ms降至10ms;
- 所需EC2實例的數量減少了85%;
- 由於Go可以編譯成一個單獨的 Binary,只需要讓客戶下載最新的 Binary 就能夠提供一個Repustate自託管版本。
- Go 1.5 讓交叉編譯變得很容易,可以部署到任何操作系統
- 由於Python和Go相似,所以他們能夠快速重建單元測試
Parse 評估多種方案後選擇 Go
Parse 平台提供了完整的解決方案給移動端的開發者,讓開發者可以完全不需要煩惱 Server 端的撰寫與維護,專注在 APP 的開發。Parse 在 2013 年被 Facebook 併購。
動機
創立初期為了開發速度而選用了 RoR,但隨著 Parse 的 API 流量越來越大,背後處理資料庫的機器也需要跟著增加以應付新增的請求。Rails 的處理模式是 “One process per request"(在典型的 RoR 配置中你必須有固定的 Pool of worker processes,其中一個 worker 只可以處理一個請求),這使得 Rails 很難水平擴展。
選擇 Go 的原因
- 具有語言內建低階的非同步操作,這讓很多人可以很容易地一起開發非同步的程式碼。
- MongoDB Driver 中最好的 Driver 就是用 Go 語言寫的。而在 Parse 需要跟 MongoDB 複雜的互動,所以這點很關鍵。
- Goroutines 比執行緒更加輕量化,這降低了開發入門非同步應用的門檻。
移植遇到的最大困難
Rails 的設計哲學是 Be liberal in what you accept 所以有很多 HTTP 請求就算不符合 RFC 標準 Rails 都會處理得很好:
So we had to port a lot of delightful behavior from the Ruby API to the Go API, to make sure we kept handling the weird requests that Rails handled. Stuff like doubly encoded URLs, weird content-length requirements, bodies in HTTP requests that shouldn’t have bodies, horrible oauth misuse, horrible mis-encoded Unicode.
移植後得到的好處
- 穩定性高了一個數量級
- 更簡潔的程式碼
- 整合測試從 25 分鐘減少到 2 分鐘,部署時間從 30 分鐘減少到 3 分鐘
- 更容易部署以及調校、輕量化、並且擁有豐富的開發資源
- 重啟速度非常快,所以 Load-Balancer 不需要 Pre-warming 了
對於細節有興趣的可以看看 GopherCon 2015 的議程:Rebuilding Parse.com in Go,以及 Facebook 在 github 上的 go 項目。
再見了 Node.js
TJ Holowaychuk 是 Node.js 大神級的人物,是 Express, Jade, Stylus, Luna, Cluster, and Mocha 等等 300 多個 Github 項目的作者。Quora 上還可以找到大家對他驚人生產力的問卦。
我們就來看一下這個對於 Node.js 貢獻許多的大神怎麼決定由 Go 跳到 Node.js 的:
優點
- Go 官方版本推進速度夠快(目前已經到了 1.5),可以感受到官方想要儘速推到 2.0 的決心
- 不滿意 Node.js 在分散式系統上的發展方向:在意性能更甚於易用性與穩定性。個人觀點認為新創公司必須關注在可靠性勝過產品的性能,否則會破壞與客戶之間的關係;相較之下 Go 穩定、性能更好、易於維護、並且有較好的測試覆蓋。
- 透過 Type 來重構程式碼相對來說輕鬆愉快。
- 有完善的性能分析、文件、格式化、除錯、等工具。
- 內建的函式庫非常的完善,幾乎只需要透過 stdlib 就可以輕易的完成一整個程式:compression, json, IO, buffered IO, string manipulation 等等。
- 在分散式處理中,Go 的平行運算語法易讀又好用
- 錯誤處理比 Node.js 還好,因為 Node.js 必須設法重現 callbacks,但是你可能因為 lost in limbo 而永遠無法重現這行為,而且很容易不知道哪裡些地方需要 error handler。 Callback hell 令人困擾。
- 在 Go 裡面當程式碼執行結束後該敘述就真的結束了;而 Node.js 不一樣,程式碼可能因為 callback 被反覆執行,導致你永遠不知道什麼時候會結束?所以這讓你想要了解產品程式碼變得非常地困難。
缺點
- 沒有統一的模組管理,而且名稱可以亂取導致很多東西看起來都一樣,也缺乏版本控管。在 Node.js 中你只需要 npm 你所熟悉的名稱那麼應該就會是選到正確的套件了(如:redis, mongodb-native, zeromq,…)