我為什么從python轉向go

jopen 9年前發布 | 46K 次閱讀 Google Go/Golang開發 Python

原文  http://siddontang.com/2015/05/16/why-python-to-go/


應puppet大拿劉宇的邀請,我去西山居運維團隊做了一個簡短分享,談談為什么我要將我們的項目從python轉向go。

坦白的講,在一幫python用戶面前講為什么放棄python轉而用go其實是一件壓力蠻大的事情,語言之爭就跟vim和emacs之爭一 樣,是一個永恒的無解話題,稍微不注意就可能導致粉絲強烈地反擊。所以我只會從我們項目實際情況出發,來講講為什么我最終選擇了go。

為什么放棄python

首先,我其實得說說為什么我們會選擇python。在我加入企業快盤團隊之前,整個項目包括更早的金山快盤都是采用python進行開發的。至 于為什么這么選擇,當時的架構師蔥頭告訴我,主要是因為python上手簡單,開發迅速。對于團隊里面大部分完全沒服務端開發經驗的同學來 說,python真的是一個很好的選擇。

python的簡單高效,我是深有體會的。當時私有云項目也就幾個程序員,但是我們要服務多家大型企業,進行定制化的開發,多虧了python,我們才能快速出活。后來企業快盤掛掉之后,我們啟動輕辦公項目,自然也使用python進行了原始版本的構建。

python雖然很強大,但我們在使用的時候也碰到了一些問題,主要由如下幾個方面:

  • 動態語言

    python是一門動態語言,不是強類型系統。對于一個變量,我們有時候壓根不知道它是什么類型,然后就可能出現int + string這樣的運行時錯誤。

    在python里面,可以允許同名函數的出現,后一個函數會覆蓋前一個函數,有一次我們系統一個很嚴重的錯誤就是因為這個導致的。

    上面說到的這些,靜態語言在編譯的時候就能幫我們檢測出來,而不需要等到運行時出問題才知道。雖然我們有很完善的測試用例,但總有case遺漏的情況。所以每次出現運行時錯誤,我心里都想著如果能在編譯的時候就發現該多好。

  • 性能

    其實這個一直是很多人吐槽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并不是一個很好的選擇。

為什么選擇go

說完了python,現在來說說為什么我們選擇go。其實除了python,我們也有其他的選擇,java,php,lua(openresty),但最終我們選擇了go。

雖然java和php都是最好的編程語言(大家都這么爭的),但我更傾向一門更簡單的語言。而openresty,雖然性能強悍,但lua仍然 是動態語言,也會碰到前面說的動態語言一些問題。最后,前金山許式偉用的go,前快盤架構師蔥頭也用的go,所以我們很自然地選擇了go。

go并不是完美,一堆值得我們吐槽的地方。

  • error,好吧,如果有語言潔癖的同學可能真的受不了go的語法,尤其是約定的最后一個返回值是error。項目里面經常會充斥這樣的代碼:

    if err := doA(); err != nil { if _, err := doB(); err != nil {
          }
      }

    難怪有個梗是對于一個需求,java的程序員在寫配置的時候,go程序員已經寫了大部分代碼,但是當java的程序員寫完的時候,go程序員還在寫err != nil。

    </li>
  • 包管理,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實現, 但畢竟不是官方的。

  • </ul>

    當然還有很多值得吐槽的地方,就不一一列舉了,但是go仍舊有它的優勢。

    • 靜態語言,強類型。靜態編譯能幫我們檢查出來大量的錯誤,go的強類型甚至變態到不支持隱式的類型轉換。雖然寫代碼感覺很別扭,但減少了犯錯的可能。
    • gofmt,應該這是我知道的第一個官方提供統一格式化代碼工具的語言了。有了gofmt,大家的代碼長一個樣了,也就沒有花括號到底放到結尾還是新開一行這種蛋疼的代碼風格討論了。因為大家的代碼風格一樣,所以看go的代碼很容易。
    • 天生的并行支持,因為goroutine以及channel,用go寫分布式應用,寫并發程序異常的容易。沒有了蛋疼的callback導致的代碼邏輯割裂,代碼邏輯都是順序的。
    • 性能,go的性能可能趕不上c,c++以及openresty,但真的也挺強悍的。在我們的項目中,現在單機就部署了一個go的進程,就完全能夠勝任以前200個python進程干的事情,而且CPU和MEM占用更低。
    • 運維部署,直接編譯成二進制,扔到服務器上面就成,比python需要安裝一堆的環境那是簡單的太多了。當然,如果有cgo,我們也需要將對應的動態庫給扔過去。
    • 開發效率,雖然go是靜態語言,但我個人感覺開發效率真的挺高,直覺上面跟python不相上下。對于我個人來說,最好的例子就是我用 go快速開發了非常多的開源組件,譬如ledisdb,go-mysql等,而這些最開始的版本都是在很短的時間里面完成的。對于我們項目來說,我們也是 用go在一個月就重構完成了第一個版本,并發布。

    實際項目中一些Go Tips

    到現在為止,我們幾乎所有的服務端項目都已經轉向go,當然在使用的時候也遇到了一些問題,列出來算是經驗分享吧。

    • godep,我們使用godep進行第三方庫管理,但是godep我碰到的最大的坑就是build tag問題,如果一個文件有build tag,godep很有可能就會忽略這個文件。
    • IO deadline,如果能自己在應用層處理的都自己處理,go的deadline內部是timer來控制,但timer內部采用一個array來實現的 heap,全局共用一個鎖,如果大并發量,并且timer數量過多,timeout變動太頻繁,很容易就引起性能問題。
    • GC,這個前面也說了,多用內存池,對象池,另外,我還發現,如果對象的生命周期跟goroutine一致,對性能的提升也不錯,也在go的group問過相關問題,大家猜測可能是因為一些對象其實是在goroutine的8k棧上面分配的,所以一起回收沒有額外GC了。
    • Go gob,如果要做RPC服務,gob并不是一個很好的選擇,首先就跟python的pickle不通用,然后為了做不同系統的數據傳入,任何包都必須帶上類型的詳細信息,size太大。go里面現在還沒一套官方的RPC方案,gRPC貌似有上位的可能。

    總結

    雖然我現在選擇了go,但是并不表示我以后不會嘗試其他的語言。語言沒有好壞,能幫我解決問題的就是好語言。但至少在很長的一段時間,我都會用go來進行開發。Let’ go!!!

    </div> </div>

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!