應puppet大拿劉宇的邀請,我去西山居運維團隊作了一個簡短分享,談談爲何我要將咱們的項目從python轉向go。php
坦白的講,在一幫python用戶面前講爲何放棄python轉而用go實際上是一件壓力蠻大的事情,語言之爭就跟vim和emacs之爭同樣,是一個永恆的無解話題,稍微不注意就可能致使粉絲強烈地反擊。因此我只會從咱們項目實際狀況出發,來說講爲何我最終選擇了go。java
首先,我其實得說說爲何咱們會選擇python。在我加入企業快盤團隊以前,整個項目包括更早的金山快盤都是採用python進行開發的。至於爲何這麼選擇,當時的架構師蔥頭告訴我,主要是由於python上手簡單,開發迅速。對於團隊裏面大部分徹底沒服務端開發經驗的同窗來講,python真的是一個很好的選擇。python
python的簡單高效,我是深有體會的。當時私有云項目也就幾個程序員,可是咱們要服務多家大型企業,進行定製化的開發,多虧了python,咱們才能快速出活。後來企業快盤掛掉以後,咱們啓動輕辦公項目,天然也使用python進行了原始版本的構建。mysql
python雖然很強大,但咱們在使用的時候也碰到了一些問題,主要由以下幾個方面:nginx
動態語言c++
python是一門動態強類型語言。可是,仍然可能出現int + string這樣的運行時錯誤,由於對於一個變量,在寫代碼的時候,咱們有時候很容易就忘記這個變量究竟是啥類型的了。程序員
在python裏面,能夠容許同名函數的出現,後一個函數會覆蓋前一個函數,有一次咱們系統一個很嚴重的錯誤就是由於這個致使的。golang
上面說到的這些,靜態語言在編譯的時候就能幫咱們檢測出來,而不須要等到運行時出問題才知道。雖然咱們有很完善的測試用例,但總有case遺漏的狀況。因此每次出現運行時錯誤,我內心都想着若是能在編譯的時候就發現該多好。sql
性能docker
其實這個一直是不少人吐槽python的地方,但python有它適合乾的事情,硬是要用python進行一些高性能模塊的開發,那也有點難爲它了。
python的GIL致使沒法真正的多線程,你們可能會說我用多進程不就完了。但若是一些計算須要涉及到多進程交互,進程之間的通信開銷也是不得不考慮的。
無狀態的分佈式處理使用多進程很方便,譬如處理http請求,咱們就是在nginx後面掛載了200多個django server來處理http的,但這麼多個進程天然致使總體機器負載偏高。
但即便咱們使用了多個django進程來處理http請求,對於一些超大量請求,python仍然處理不過來。因此咱們使用openresty,將高頻次的http請求使用lua來實現。可這樣又致使使用兩種開發語言,並且一些邏輯還得寫兩份不一樣的代碼。
同步網絡模型
django的網絡是同步阻塞的,也就是說,若是咱們須要訪問外部的一個服務,在等待結果返回這段時間,django不能處理任何其餘的邏輯(固然,多線程的除外)。若是訪問外部服務須要很長時間,那就意味着咱們的整個服務幾乎在很長一段時間徹底不可用。
爲了解決這個問題,咱們只能不斷的多開django進程,同時須要保證全部服務都能快速的處理響應,但想一想這實際上是一件很不靠譜的事情。
異步網絡模型
tornado的網絡模型是異步的,這意味着它不會出現django那樣由於外部服務不可用致使這個服務沒法響應的問題。話說,比起django,我但是很是喜歡tornado的,小巧簡單,之前還寫過幾篇深刻剖析tornado的文章了。
雖然tornado是異步的,可是python的mysql庫都不支持異步,這也就意味着若是咱們在tornado裏面訪問數據庫,咱們仍然可能面臨由於數據庫問題形成的整個服務不可用。
其實異步模型最大的問題在於代碼邏輯的割裂,由於是事件觸發的,因此咱們都是經過callback進行相關處理,因而代碼裏面就常常出現幹一件事情,傳一個callback,而後callback裏面又傳callback的狀況,這樣的結果就是整個代碼邏輯很是混亂。
python沒有原生的協程支持,雖然能夠經過gevent,greenlet這種的上patch方式來支持協程,但畢竟更改了python源碼。另外,python的yield也能夠進行簡單的協程模擬,但畢竟不能跨堆棧,侷限性很大,不知道3.x的版本有沒有改進。
開發運維部署
當我第一次使用python開發項目,我是沒成功安裝上項目須要的包的,光安裝成功mysql庫就弄了好久。後來,是一位同事將他整個python目錄打包給我用,我才能正常的將項目跑起來。話說,如今有了docker,是多麼讓人幸福的一件事情。
而部署python服務的時候,咱們須要在服務器上面安裝一堆的包,光是這一點就讓人很麻煩,雖然能夠經過puppet,salt這些自動化工具解決部署問題,但相比而言,靜態編譯語言只用扔一個二進制文件,可就方便太多了。
代碼失控
python很是靈活簡單,寫c幾十行代碼才能搞定的功能,python一行代碼沒準就能解決。可是太簡單,反而致使不少同窗沒法對代碼進行深層次的思考,對整個架構進行細緻的考量。來了一個需求,啪啪啪,鍵盤敲完開速實現,結果就是代碼愈來愈混亂,最終致使了整個項目代碼失控。
雖然這也有咱們自身的緣由,譬如沒好的代碼review機制,沒有好的項目規範,但我的感受,若是一個程序員沒通過良好的編碼訓練,用python很容易就寫出爛的代碼,由於太自由了。
固然,我這裏並非說用python沒法進行大型項目的開發,豆瓣,dropbox都是很好的例子,只是在咱們項目中,咱們的python代碼失控了。
上面提到的都是咱們在實際項目中使用python遇到的問題,雖然最終都解決了,可是讓我愈發的以爲,隨着項目複雜度的增大,流量性能壓力的增大,python並非一個很好的選擇。
說完了python,如今來講說爲何咱們選擇go。其實除了python,咱們也有其餘的選擇,java,php,lua(openresty),但最終咱們選擇了go。
雖然java和php都是最好的編程語言(你們都這麼爭的),但我更傾向一門更簡單的語言。而openresty,雖然性能強悍,但lua仍然是動態語言,也會碰到前面說的動態語言一些問題。最後,前金山許式偉用的go,前快盤架構師蔥頭也用的go,因此咱們很天然地選擇了go。
go並非完美,一堆值得咱們吐槽的地方。
error,好吧,若是有語言潔癖的同窗可能真的受不了go的語法,尤爲是約定的最後一個返回值是error。項目裏面常常會充斥這樣的代碼:
if _, err := w.Write(data1); err != nil { returun err } if _, err := w.Write(data2); err != nil { returun err }
難怪有個梗是對於一個需求,java的程序員在寫配置的時候,go程序員已經寫了大部分代碼,可是當java的程序員寫完的時候,go程序員還在寫err != nil
。
這方面,errors-are-values卻是推薦了一個不錯的解決方案。
包管理,go的包管理太弱了,只有一個go get,也就是若是不當心更新了一個外部庫,頗有可能就致使現有的代碼編譯不過了。雖然已經有不少開源方案,譬如godep以及如今纔出來的gb等,但畢竟不是官方的。貌似google也是經過vendor機制來管理第三方庫的。但願go 1.5或者以後的版本能好好處理下這個問題。
GC,java的GC發展20年了,go才這麼點時間,gc鐵定不完善。因此咱們仍然不能爲所欲爲的寫代碼,否則在大請求量下面gc可能會卡頓整個服務。因此有時候,該用對象池,內存池的必定要用,雖然代碼醜了點,但好歹性能上去了。
泛型,雖然go有inteface,但泛型的缺失會讓咱們在實現一個功能的時候寫大量的重複代碼,譬如int32和int64類型的sort,咱們得爲分別寫兩套代碼,好冗餘。go 1.4以後有了go generate的支持,但這種的仍然須要本身根據go的AST庫來手動寫相關的parser,難度也挺大的。雖然也有不少開源的generate實現,但畢竟不是官方的。
固然還有不少值得吐槽的地方,就不一一列舉了,可是go仍舊有它的優點。
到如今爲止,咱們幾乎全部的服務端項目都已經轉向go,固然在使用的時候也遇到了一些問題,列出來算是經驗分享吧。
雖然我如今選擇了go,可是並不表示我之後不會嘗試其餘的語言。語言沒有好壞,能幫我解決問題的就是好語言。但至少在很長的一段時間,我都會用go來進行開發。Let' go!!!