後端語言選擇

基本要求

1、服務端要求運行時靈活更新,能長時間不停機,有可靠的分佈式解決方案。

2、招聘容易能快速招聘到合格的開發人員


我們在比較流行的語種中進行了選擇:

不用考慮先排除 C和C++以及Rust ,之前有項目用過!性能確實高但靜態語言容易出災難性Bug;

再看Nodejs網易也推出過pomelo,調研後我們認爲內存管理有一定缺陷GC較弱,系統容易崩盤,語種編程範式多在大型分佈式系統的構建中,我們希望代碼更嚴格。

Scala屬於學院派語種,不穩定版本兼容性差,還不如直接用Java。

Python最主要的優勢在於代碼容易撰寫,可讀性很高,但面對大型業務耦合緊密的工程時就不行了。


我們主要在下面三個語言之間做選擇

語言

性能

編碼簡單

熱更新

低耦合度

高容災

高並行

靈活性

性能追蹤

敏捷處理

框架

總分

Go

10

10

2

8

5

9

8

5

4

4

65

Erlang

5

5

10

10

10

10

9

7

8

8

82

Java

10

6

5

5

7

8

6

9

6

10

72

這三類語言的應用框架,我們都嘗試過並運用在很高流水的項目中;

我們最得興應手的是Java,不是因爲語言簡單,主要是因爲團隊迭代不會影響項目,好招聘。

儘管Java得分較高,但代碼上顯得很臃腫,寫起來讓人不怎麼開心。

Golang和Erlang是我優先考慮的,Erlang高容災,Golang高性能。

我之前在Erlang和Golang之間有過徘徊主要是Erlang又愛又恨,原因在於

1、Erlang有最靈活的開發編程模式,控制檯,熱更新

2、Erlang有最天然的容錯能力,進程狀態獨立

但 ---------------------》

3、性能問題,性能比較低,綜合密集性業務處理有一定開發難度

4、開發成本,招人比較難,廣州、成都兩地Erlang程序較多,其他地區如果地方沒有人才吸引力,切記慎用。

下面談談實現

1、併發

Go 對高併發的支持通過 goroutine 實現。goroutine 可以理解爲輕量級的 線程(thread)。同一個 Go 應用創建的 goroutine 共享地址空間。

Erlang 的高併發通過輕量級 進程(process)實現,每一個進程都有獨立的狀態記錄。

另外,使用 goroutine 要注意,goroutine 運行完畢後,佔用的內存放回內存池備用,不會釋放。

對於每一個任務都需要有獨立狀態的場景,Erlang 的 process 更有優勢。

2、搶佔式調度

Erlang 的任務調度器有一個 reduction budget 的概念。進程的任何操作都會造成預算消耗,包括函數調用、調用 BIF、進程堆垃圾回收、ETS 讀寫、發消息(目標郵箱堆積的消息越多,消耗越大)。Erlang 的 正則表達式庫 也被做了修改以支持 reductions。所以如果進程在長時間執行正則表達式匹配,也一樣會消耗 reductions,也會被搶佔。

Go 之前的調度器只在 syscall 發生時調度,優化後可以在任何函數調用時調度。但是要注意,如果在 goroutine 裏寫一個死循環,Go 的調度器不能有效搶佔,同一個調度器的 其他 goroutine 會被掛起。

實際上,這就是爲什麼我說Erlang是真正能夠實現搶佔式多任務並且能真正做好軟實時的少數語言之一的原因。Erlang更看重的是低延遲而不是單純的吞吐量,這在程序設計語言運行時中是不多見的。

再準確地說,搶佔(preemption)[2]指的是調度器能夠強制剝奪任務的執行。所有基於協作(cooperation)的多任務都是做不到搶佔的,例如Python的twisted庫、Node.js和LWT(Ocaml)等。但是更有意思的是,Go(golang.org)和Haskell(GHC)也都不是完全搶佔式的。Go只有在通信的時候會發生上下文切換,因此一個密集的循環就會霸佔整個處理器核心。GHC會在內存分配的時候發生切換(不得不承認內存分配是Haskell程序中一個非常頻繁的操作)。這些系統的問題在於,將處理器核心霸佔一段時間的後果就是影響系統的響應延遲——想象一下這兩種語言執行數組操作的時候的情景。

這就引出了軟實時(soft-realtime)[3]的概念,軟實時指的是如果無法滿足時間截止線需求的時候會導致系統服務水準降級(而不是整個失敗)。假設在運行隊列中有500有100個進程。第一個進程正在做一個耗時50毫秒的數組操作。在Go或Haskell/GHC[注3]中,這意味着任務2-100都需要至少50ms。而在Erlang中則不同,任務1有2000個reduction的預算,相當於大約1ms的時間。然後用完reduction預算後,任務1會被放回運行隊列,這樣任務2-任務100就有機會運行。這自然意味着所有的任務都有公平的時間份額。

3、垃圾回收

像 Java 一樣,Go 的垃圾回收是全局的,這意味着一旦垃圾回收被觸發,所有的 goroutine 都會被暫停,造成一段時間的業務延遲。

Erlang 的垃圾回收是 進程 級別的,每一個進程都有自己獨立的垃圾回收器,一個進程的垃圾回收被觸發,不會造成其他進程被掛起。相對來說帶來的業務延遲小。

4、錯誤處理

Erlang 的每一個進程都有 進程 ID (PID),同時也可以給進程註冊名字,也就是說每一個進程都有獨立的身份,可以有效的監控每一個進程的狀態。進程異常退出時,可以捕捉到退出事件,並重啓進程(參見 otp 的 supervisor/worker)。

Go 的 goroutine 沒有身份識別,goroutine 的狀態沒辦法監控。

5、動態反射

Erlang 動態語言的特點,使它天然支持 REPL,另外 Erlang 支持 remote shell,我們可以在 Erlang 運行時,連接到 remote shell 與任何一個進程交互。這些特性對一個需要長期運行的複雜系統的維護帶來了極大的便利。開發階段也能有一些便利。

Go 是靜態語言,不支持 REPL。

6、靜態編譯

Erlang 是動態語言,有所有動態語言的所有缺點:

  • 運行速度慢
  • 不能做早期錯誤檢查,需要依賴全覆蓋單元測試
  • 代碼規模大了,給編寫帶來困擾

Erlang 現在也引入了 spec,對函數的參數返回值在編譯時做類型檢查,但是跟靜態語言比起來效果差的很遠。

不過正是因爲是動態語言,Erlang 實現了運行時代碼替換,這個特性對一個需要長時間運行的工業級產品,是一個非常重要的功能。

Go 是靜態語言,運行速度快,編譯時做嚴格的類型檢查,可以避免很多隱患。

6、框架

Erlang 的 OTP 框架支持服務器端開發常見的幾種模式(applications, supervisors, wokers),方便代碼的組織。

Go 暫時沒看到類似的框架。

結論 , 根據公司的實際情況人員配置,如果不夠高,團隊迭代,新鮮血液融入困難,就別選Erlang了。

發佈了41 篇原創文章 · 獲贊 10 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章