應puppet大拿劉宇的邀請锌历,我去西山居運維團隊做了一個簡短分享序无,談談為什么我要將我們的項目從python轉向go乘盼。
坦白的講螟凭,在一幫python用戶面前講為什么放棄python轉而用go其實是一件壓力蠻大的事情亿虽,語言之爭就跟vim和emacs之爭一樣菱涤,是一個永恒的無解話題,稍微不注意就可能導致粉絲強烈地反擊洛勉。所以我只會從我們項目實際情況出發(fā)粘秆,來講講為什么我最終選擇了go。
為什么放棄python
首先坯认,我其實得說說為什么我們會選擇python翻擒。在我加入企業(yè)快盤團隊之前,整個項目包括更早的金山快盤都是采用python進行開發(fā)的牛哺。至于為什么這么選擇陋气,當時的架構師蔥頭告訴我,主要是因為python上手簡單引润,開發(fā)迅速巩趁。對于團隊里面大部分完全沒服務端開發(fā)經驗的同學來說,python真的是一個很好的選擇淳附。
python的簡單高效议慰,我是深有體會的。當時私有云項目也就幾個程序員奴曙,但是我們要服務多家大型企業(yè)别凹,進行定制化的開發(fā),多虧了python洽糟,我們才能快速出活炉菲。后來企業(yè)快盤掛掉之后,我們啟動輕辦公項目坤溃,自然也使用python進行了原始版本的構建拍霜。
python雖然很強大,但我們在使用的時候也碰到了一些問題薪介,主要由如下幾個方面:
-
動態(tài)語言
python是一門動態(tài)強類型語言祠饺。但是,仍然可能出現(xiàn)int + string這樣的運行時錯誤汁政,因為對于一個變量道偷,在寫代碼的時候缀旁,我們有時候很容易就忘記這個變量到底是啥類型的了。
在python里面试疙,可以允許同名函數的出現(xiàn)诵棵,后一個函數會覆蓋前一個函數抠蚣,有一次我們系統(tǒng)一個很嚴重的錯誤就是因為這個導致的祝旷。
上面說到的這些,靜態(tài)語言在編譯的時候就能幫我們檢測出來嘶窄,而不需要等到運行時出問題才知道怀跛。雖然我們有很完善的測試用例,但總有case遺漏的情況柄冲。所以每次出現(xiàn)運行時錯誤吻谋,我心里都想著如果能在編譯的時候就發(fā)現(xiàn)該多好。
-
性能
其實這個一直是很多人吐槽python的地方现横,但python有它適合干的事情漓拾,硬是要用python進行一些高性能模塊的開發(fā),那也有點難為它了戒祠。
python的GIL導致無法真正的多線程骇两,大家可能會說我用多進程不就完了。但如果一些計算需要涉及到多進程交互姜盈,進程之間的通訊開銷也是不得不考慮的低千。
無狀態(tài)的分布式處理使用多進程很方便,譬如處理http請求馏颂,我們就是在nginx后面掛載了200多個django server來處理http的,但這么多個進程自然導致整體機器負載偏高示血。
但即使我們使用了多個django進程來處理http請求,對于一些超大量請求救拉,python仍然處理不過來难审。所以我們使用openresty,將高頻次的http請求使用lua來實現(xiàn)亿絮「婧埃可這樣又導致使用兩種開發(fā)語言,而且一些邏輯還得寫兩份不同的代碼壹无。
-
同步網絡模型
django的網絡是同步阻塞的葱绒,也就是說岖是,如果我們需要訪問外部的一個服務猾浦,在等待結果返回這段時間,django不能處理任何其他的邏輯(當然灯抛,多線程的除外)。如果訪問外部服務需要很長時間对嚼,那就意味著我們的整個服務幾乎在很長一段時間完全不可用夹抗。
為了解決這個問題,我們只能不斷的多開django進程摆舟,同時需要保證所有服務都能快速的處理響應,但想想這其實是一件很不靠譜的事情忆矛。
-
異步網絡模型
tornado的網絡模型是異步的,這意味著它不會出現(xiàn)django那樣因為外部服務不可用導致這個服務無法響應的問題请垛。話說催训,比起django洽议,我可是非常喜歡tornado的,小巧簡單漫拭,以前還寫過幾篇深入剖析tornado的文章了亚兄。
雖然tornado是異步的,但是python的mysql庫都不支持異步采驻,這也就意味著如果我們在tornado里面訪問數據庫审胚,我們仍然可能面臨因為數據庫問題造成的整個服務不可用。
其實異步模型最大的問題在于代碼邏輯的割裂挑宠,因為是事件觸發(fā)的菲盾,所以我們都是通過callback進行相關處理颓影,于是代碼里面就經常出現(xiàn)干一件事情各淀,傳一個callback,然后callback里面又傳callback的情況诡挂,這樣的結果就是整個代碼邏輯非乘榻剑混亂。
python沒有原生的協(xié)程支持璃俗,雖然可以通過gevent奴璃,greenlet這種的上patch方式來支持協(xié)程,但畢竟更改了python源碼城豁。另外苟穆,python的yield也可以進行簡單的協(xié)程模擬,但畢竟不能跨堆棧唱星,局限性很大雳旅,不知道3.x的版本有沒有改進。
-
開發(fā)運維部署
當我第一次使用python開發(fā)項目间聊,我是沒成功安裝上項目需要的包的攒盈,光安裝成功mysql庫就弄了很久。后來哎榴,是一位同事將他整個python目錄打包給我用型豁,我才能正常的將項目跑起來。話說尚蝌,現(xiàn)在有了docker迎变,是多么讓人幸福的一件事情。
而部署python服務的時候飘言,我們需要在服務器上面安裝一堆的包衣形,光是這一點就讓人很麻煩,雖然可以通過puppet热凹,salt這些自動化工具解決部署問題泵喘,但相比而言泪电,靜態(tài)編譯語言只用扔一個二進制文件,可就方便太多了纪铺。
-
代碼失控
python非常靈活簡單相速,寫c幾十行代碼才能搞定的功能,python一行代碼沒準就能解決鲜锚。但是太簡單突诬,反而導致很多同學無法對代碼進行深層次的思考,對整個架構進行細致的考量芜繁。來了一個需求旺隙,啪啪啪,鍵盤敲完開速實現(xiàn)骏令,結果就是代碼越來越混亂蔬捷,最終導致了整個項目代碼失控。
雖然這也有我們自身的原因榔袋,譬如沒好的代碼review機制周拐,沒有好的項目規(guī)范,但個人感覺凰兑,如果一個程序員沒經過良好的編碼訓練妥粟,用python很容易就寫出爛的代碼,因為太自由了吏够。
當然勾给,我這里并不是說用python無法進行大型項目的開發(fā),豆瓣锅知,dropbox都是很好的例子播急,只是在我們項目中,我們的python代碼失控了喉镰。
上面提到的都是我們在實際項目中使用python遇到的問題旅择,雖然最終都解決了,但是讓我愈發(fā)的覺得侣姆,隨著項目復雜度的增大生真,流量性能壓力的增大,python并不是一個很好的選擇捺宗。
為什么選擇go
說完了python柱蟀,現(xiàn)在來說說為什么我們選擇go。其實除了python蚜厉,我們也有其他的選擇长已,java,php,lua(openresty)术瓮,但最終我們選擇了go康聂。
雖然java和php都是最好的編程語言(大家都這么爭的),但我更傾向一門更簡單的語言胞四。而openresty恬汁,雖然性能強悍,但lua仍然是動態(tài)語言辜伟,也會碰到前面說的動態(tài)語言一些問題氓侧。最后,前金山許式偉用的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逊脯,也就是如果不小心更新了一個外部庫,很有可能就導致現(xiàn)有的代碼編譯不過了竣贪。雖然已經有很多開源方案军洼,譬如godep以及現(xiàn)在才出來的gb等,但畢竟不是官方的演怎。貌似google也是通過vendor機制來管理第三方庫的匕争。希望go 1.5或者之后的版本能好好處理下這個問題。
GC爷耀,java的GC發(fā)展20年了甘桑,go才這么點時間,gc鐵定不完善。所以我們仍然不能隨心所欲的寫代碼跑杭,不然在大請求量下面gc可能會卡頓整個服務铆帽。所以有時候,該用對象池德谅,內存池的一定要用锄贼,雖然代碼丑了點,但好歹性能上去了女阀。
泛型宅荤,雖然go有inteface,但泛型的缺失會讓我們在實現(xiàn)一個功能的時候寫大量的重復代碼浸策,譬如int32和int64類型的sort冯键,我們得為分別寫兩套代碼,好冗余庸汗。go 1.4之后有了go generate的支持惫确,但這種的仍然需要自己根據go的AST庫來手動寫相關的parser,難度也挺大的蚯舱。雖然也有很多開源的generate實現(xiàn)改化,但畢竟不是官方的。
當然還有很多值得吐槽的地方枉昏,就不一一列舉了陈肛,但是go仍舊有它的優(yōu)勢。
- 靜態(tài)語言兄裂,強類型句旱。靜態(tài)編譯能幫我們檢查出來大量的錯誤,go的強類型甚至變態(tài)到不支持隱式的類型轉換晰奖。雖然寫代碼感覺很別扭谈撒,但減少了犯錯的可能。
- gofmt匾南,應該這是我知道的第一個官方提供統(tǒng)一格式化代碼工具的語言了啃匿。有了gofmt,大家的代碼長一個樣了蛆楞,也就沒有花括號到底放到結尾還是新開一行這種蛋疼的代碼風格討論了溯乒。因為大家的代碼風格一樣,所以看go的代碼很容易臊岸。
- 天生的并行支持橙数,因為goroutine以及channel,用go寫分布式應用帅戒,寫并發(fā)程序異常的容易灯帮。沒有了蛋疼的callback導致的代碼邏輯割裂崖技,代碼邏輯都是順序的。
- 性能钟哥,go的性能可能趕不上c迎献,c++以及openresty,但真的也挺強悍的腻贰。在我們的項目中吁恍,現(xiàn)在單機就部署了一個go的進程,就完全能夠勝任以前200個python進程干的事情播演,而且CPU和MEM占用更低冀瓦。
- 運維部署,直接編譯成二進制写烤,扔到服務器上面就成翼闽,比python需要安裝一堆的環(huán)境那是簡單的太多了。當然洲炊,如果有cgo感局,我們也需要將對應的動態(tài)庫給扔過去。
- 開發(fā)效率暂衡,雖然go是靜態(tài)語言询微,但我個人感覺開發(fā)效率真的挺高,直覺上面跟python不相上下狂巢。對于我個人來說撑毛,最好的例子就是我用go快速開發(fā)了非常多的開源組件,譬如ledisdb隧膘,go-mysql等代态,而這些最開始的版本都是在很短的時間里面完成的。對于我們項目來說疹吃,我們也是用go在一個月就重構完成了第一個版本,并發(fā)布西雀。
實際項目中一些Go Tips
到現(xiàn)在為止萨驶,我們幾乎所有的服務端項目都已經轉向go,當然在使用的時候也遇到了一些問題艇肴,列出來算是經驗分享吧腔呜。
- godep,我們使用godep進行第三方庫管理再悼,但是godep我碰到的最大的坑就是build tag問題核畴,如果一個文件有build tag,godep很有可能就會忽略這個文件冲九。
- IO deadline谤草,如果能自己在應用層處理的都自己處理跟束,go的deadline內部是timer來控制,但timer內部采用一個array來實現(xiàn)的heap丑孩,全局共用一個鎖冀宴,如果大并發(fā)量,并且timer數量過多温学,timeout變動太頻繁略贮,很容易就引起性能問題。
- GC仗岖,這個前面也說了卫枝,多用內存池,對象池站蝠,另外叔锐,我還發(fā)現(xiàn),如果對象的生命周期跟goroutine一致紧帕,對性能的提升也不錯盔然,也在go的group問過相關問題,大家猜測可能是因為一些對象其實是在goroutine的8k棧上面分配的是嗜,所以一起回收沒有額外GC了愈案。
- Go gob,如果要做RPC服務鹅搪,gob并不是一個很好的選擇站绪,首先就跟python的pickle不通用,然后為了做不同系統(tǒng)的數據傳入丽柿,任何包都必須帶上類型的詳細信息恢准,size太大。go里面現(xiàn)在還沒一套官方的RPC方案甫题,gRPC貌似有上位的可能馁筐。
總結
雖然我現(xiàn)在選擇了go,但是并不表示我以后不會嘗試其他的語言坠非。語言沒有好壞敏沉,能幫我解決問題的就是好語言。但至少在很長的一段時間炎码,我都會用go來進行開發(fā)盟迟。Let' go!!!