我為什麼選擇go語言
這裡,我並不打算引起語言爭論的口水仗,我並不是什麼大牛,對語言的造詣也不深,只是想通過自己實際的經歷,來說說為什麼我在專案中選擇go。
其他語言的經歷
C++
在接觸go之前,我已經有多年的c++開發經驗。主要用在遊戲服務端引擎開發以及P2P上面,那可是一段痛並快樂的時期,以至於我看到任何的程式釘子問題都覺得可以用c++這把錘子給敲定。但是對於網際網路專案開發來說,除非你的團隊整體的c++技術水平nb,並且有很強的程式碼規範,不然真可能是一場災難,更別說我們現有團隊幾乎沒其他人會這玩意了。
本來,我打算在現有專案中的推送系統中使用c++,並用業餘時間寫好了一個網路底層庫libtnet
Lua
在做遊戲的過程中,我也學會了lua這門語言,並且還有幸接觸並完善了雲風在Lua 不是 C++中提到的那個恐怖的lua,c++粘合層。
lua真的是一門非常好的語言,效能高,開發快速。不光遊戲公司大量使用,在網際網路領域,因為openresty的流行,一些公司(包括我們)也開始在web端使用lua進行開發。(頗為自豪的是還給openresty反饋過幾個bug)
但是,lua因為太短小精悍,功能庫並不多,很多需要自己去實現,而且,寫出高效能,高質量lua程式碼也並不是很容易的事情。另外,因為其動態語言的特性,我們也栽了不少坑,這個後續在詳說。
Python
在我來現有的團隊之前,他們就已經使用python進行整個系統的開發,甚至包括客戶端GUI(這對客戶端童鞋當時就是一個災難,後來換成Qt就舒爽了)。
python的好處不必說,從數不清的公司用它進行開發就知道,庫非常豐富,程式碼簡潔,開發迅速。
但是,在專案中經過兩年多python開發之後,我們漸漸出現了很多問題:
- 效能,python的效能是比較偏低的,對於很多效能熱點程式碼,通常都會採用其他的方式實現。在我們的專案中,需要對任何API呼叫進行簽名認證,認證服務我們開始使用的是tornado實現,但很不幸運的是,放到外網並沒有頂住壓力。所以我們引入openresty,將很多高頻操作實現放到openresty實現,終於頂住了。
- 部署,python的庫因為太豐富了,所以我們的童鞋引入了很多的庫,個人感覺我們的童鞋可沒有造輪子的興趣。有時候發版本的時候,我們會因為忘記安裝一個庫導致程式無法執行。這可能跟我們團隊沒有成熟運維經驗有關,後續通過salt,puppet這種釋出工具應該能解決。
- 質量,通常我們都認為,因為python程式碼的簡潔,我們很容易的能寫出高質量的程式碼,但是如果沒有好的程式碼控制手段,用python也仍然能寫出渣的程式碼,我甚至覺得因為其靈活性,可能會更容易寫出爛的程式碼。這可以說是我們團隊的教訓。
這裡,我並沒有噴python的意思,它真的是一門好語言,我能夠通過它快速的構建原型,驗證我的想法,而且還一直在使用。只是在專案中,我們的一些疏忽,導致程式碼不可控了,到了不得不重構的地步了。
Why GO?
前面說了我的語言經歷,以及專案到了重構地步的原因,但是為什麼會是go呢?我們可以有很多其他的選擇,譬如java,erlang,或者仍然採用python。我覺得有很多因素考量:
-
靜態,go是一門靜態語言,有著強型別約束,所以我們不太可能出現在python中變數在執行時型別不匹配(譬如int + string)這樣的runtime error。 在編譯階段就能夠幫我們發現很多問題,不用等到執行時。(當然,這個靜態語言都能做到)
-
程式碼規範,很多人都比較反感go強制的編碼規範,譬如花括號的位置。但我覺得,就因為強制約定,所以大家寫出來的go程式碼樣子都差不多,不用費心再去深究程式碼樣式問題。而且我發現,因為規範統一,我很容易就能理解別人寫的程式碼。
-
庫支援,go的庫非常豐富,而且能通過go get非常方便的獲取github,google code上面的第三方庫(質量你自己得擔著了),再不行,用go自己造輪子也是很方便的,而且造的輪子通常都比較穩定。
-
開發迅速,不得不說,當你習慣用go開發之後,用go開發功能非常的快,相對於靜態語言c++,開發的效率快的沒話說,我覺得比python都不差,而且質量有保證。我們花了不到一個星期進行推送服務核心功能開發,到現在都沒怎麼變動,穩定執行。
-
部署方便,因為是靜態的,只需要build成一個可執行程式就可以了,部署的時候直接扔一個檔案過去,不需要像python那樣安裝太多的依賴庫。
GO特性
go現在的這個樣子,有些人喜歡,有些人不喜歡,我無法知道為啥google那幫人把go設計成這樣,但是我覺得,既然存在,就有道理,我只需要知道什麼該用,什麼不該用就可以了。
gc
GO提供了gc,這對於c++的童鞋來說,極大的減少了在記憶體上面犯錯的機會,只是go的gc這個效率還真的不好恭維,比起java來說,還有很大的提升空間。
所以有時候寫程式碼,我們還得根據tuning來提升gc的效率,譬如採用記憶體池的方式來管理大塊的slice分配,採用no copy的方式來進行string,slice的互轉。
不過go1.3貌似gc效能有了很大的改善,這點讓我比較期待。
defer
go的defer其實是一個讓人又愛又恨的東西,對於防止資源洩露,defer可是一個很不錯的東西,但是濫用defer可是會讓你面臨很嚴重的記憶體問題,尤其是像下面的程式碼:
for {
defer func(){
//do somthing
}
}
別以為go會在呼叫完成defer之後就好好的進行gc回收defer裡面的東西,在我們進行記憶體profile的時候,發現大量的記憶體佔用都是defer引起的。所以使用起來需要特別謹慎。
但我覺得,這個go應該會稍微改善,在go1.3裡面,也有了對defer的優化。
error
也許error是一個讓人爭議很大的東西,現代方式的exception那裡去呢?但是我覺得error能夠非常明確的告訴使用者該函式會有錯誤返回,如果使用exception,除非文件足夠詳細,我還真不知道哪裡就會蹦出一個異常了。
只是,go又提供了類似exception的defer,panic,recover,這是要鬧哪出。
其實這篇文章我覺得已經解釋的很好了,go程式的慣例是對外的API使用error,而內部錯誤處理可以用defer,recover和panic來簡化流程。
其實這倒跟我一貫的程式設計準則對應,在團隊在用python進行開發的時候,我們都明確要求庫對外提供的API需要使用返回值來表示錯誤,而在內部可以使用try,catch異常機制。
interface
go提供了interface來進行抽象程式設計。何謂介面,最通常的例子就是鴨子的故事,“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子“。
在go裡面,interface就是一堆方法的集合,如果某個物件實現了這些方法,那麼該物件可以就算是該interface。使用interface,我們可以很方便的實現非侵入式程式設計,進行模組功能的替換。
對於長時間沉浸c++和python的童鞋來說,一下子要用interface來解決抽象問題,可能會很不適應。但當習慣之後,你會發現,其實interface非常的靈活方便。
基於哪些痛點設計出Go語言?
當初他們為什麼會有設計一個新語言的衝動呢?讓我們一起來回顧一下這些歷史,也許很多人對他們當年遇到的問題感同身受。
設計Go語言是為了解決當時Google開發遇到的以下這些問題:
-
大量的C++程式碼,同時又引入了Java和Python
-
成千上萬的工程師
-
數以萬計行的程式碼
-
分散式的編譯系統
-
數百萬的伺服器
其主要有以下幾個方面的痛點:
-
編譯慢
-
失控的依賴
-
每個工程師只是用了一個語言裡面的一部分
-
程式難以維護(可讀性差、文件不清晰等)
-
更新的花費越來越長
-
交叉編譯困難
所以,他們當時設計Go的目標是為了消除各種緩慢和笨重、改進各種低效和擴充套件性。Go是由那些開發大型系統的人設計的,同時也是為了這些人服務的;它是為了解決工程上的問題,不是為了研究語言設計;它還是為了讓我們的程式設計變得更舒適和方便。
但是結合Google當時內部的一些現實情況,如很多工程師都是C系的,C語言雖然在系統程式設計以及服務端程式設計方面很強大,但是C的語法“陷阱”和C手工記憶體管理給開發者帶來較大苦惱。雖然那些年市面上也有其他主流語言可供選擇,但同時給開發者帶來的心智負擔太過沉重,比如:C++“宇宙無敵”的學習和使用複雜性、Java超大的資源消耗和龐大且紛繁蕪雜的框架體系、動態語言(ruby、python)無靜態型別而導致執行時crash時除錯的困難、函式式語言(如Haskell、clisp)的過於小眾和非主流。
直到Go的出現,所以新設計的語言一定要易學習,最好是C-like的語言;因為有太多的分散式系統、太多的開發者,所以新的語言一定要可以Scale,這個包括開發、工程師、程式碼、部署和依賴;20年沒有出新的語言了,所以新設計的語言必須是現代化的(例如內建GC)等情況,他們覺得要實現這個目標就需要Go成為一個大家都認可的語言。
Go語言的特點和實戰公司
最後根據實戰經驗,他們向著目標設計了Go這個語言,其主要的特色有:
-
沒有繼承的OO
-
強一致型別
-
Interface但是不需要顯示申明(Duck Type)
-
Function 和Method
-
沒有異常處理(Error is value)
-
基於首字母的可訪問特性
-
不用的Import或者變數引起編譯錯誤
-
完整而卓越的標準庫包
Go釋出之後,很多公司特別是雲端計算公司開始用Go重構他們的基礎架構,很多都是直接採用Go進行了開發,最近熱火朝天的Docker就是採用Go開發的。我們來看看目前為止採用Go的一些國內外公司,國外的如Google、Docker、Apple、Cloud Foundry、CloudFlare、Couchbase、CoreOS、Dropbox、MongoDB、AWS等公司,國內的如阿里雲CDN、百度、小米、七牛、PingCAP、華為、金山軟體、獵豹移動、餓了麼等公司。
Go主要應用的系統
上面那些基本上就是Go的歷史背景和設計初衷,那麼目前Go主要應用於哪些系統呢?
目前Go主要應用在下面這些系統:
-
伺服器程式設計,以前你如果使用C或者C++做的那些事情,用Go來做很合適,例如處理日誌、資料打包、虛擬機器處理、檔案系統等。
-
分散式系統、資料庫代理器等,例如Etcd。
-
網路程式設計,這一塊目前應用最廣,包括Web應用、API應用、下載應用,而且Go內建的net/http包基本上把我們平常用到的網路功能都實現了。
-
資料庫,前一段時間Google開發的Groupcache,Couchbase的部分組建,Tidb,Cockroachdb,Influxdb等。
-
雲平臺,目前國外很多雲平臺在採用Go開發,CloudFoundy的部分組建,前VMare的技術總監自己出來搞的Apcera雲平臺。
Go語言的優缺點
那麼目前Go還存在哪些缺點呢?以下是我自己在專案開發中遇到的一些問題:
1、Go的Import包不支援版本,有時候升級容易導致專案不可執行,所以需要自己控制相應的版本資訊。比較好的現象是從Go 1.5開始Go對此就有重視了並支援Vendor。
2、Go的goroutine一旦啟動後,不同的goroutine之間切換不是受程式控制,runtime排程的時候需要嚴謹的邏輯,不然goroutine休眠,過一段時間邏輯結束了卻突然冒出來又執行了,這會導致邏輯出錯等情況。這個目前無解,應該屬於排程器的優化。
3、GC延遲有點大,我開發的日誌系統傷過一次,同時在併發很大的情況下,處理很大的日誌,GC沒有那麼快,記憶體回收不給力,後來經過Profile程式改進之後得到了改善。目前來看,GC已經優化的非常好了,給大家看一下Go1.5、Go1.6的GC前後對比圖。
圖3是Go1.4升級到Go1.5之後的效果,從300ms到了50ms左右。
圖4是從Go1.5升級到Go1.6,從40ms到了2ms左右,可以說目前GC基本上不是Go的問題了。
4、pkg下面的圖片處理庫很多bug,還是使用成熟產品好,呼叫這些成熟庫imagemagick的介面比較靠譜。總而言之,從工程的角度上來看,對於大多數後臺應用場景,選擇Golang是極為明智的選擇。 這樣可以很輕鬆的兼顧執行效能、開發效率及維護難度這三大讓諸多程式猿欲仙欲死的點。
國內很多雲創業公司都會選擇把Go作為首要語言,例如DaoCloud。為什麼會選擇Go呢?與其他語言的應用相比,它有什麼優點呢?
1、學習曲線
它包含了類C語法、GC內建和工程工具。這一點非常重要,因為Go語言容易學習,所以一個普通的大學生花一個星期就能寫出來可以上手的、高效能的應用。在國內大家都追求快,這也是為什麼國內Go流行的原因之一。
2、效率
Go擁有接近C的執行效率和接近PHP的開發效率,這就很有利的支撐了上面大家追求快速的需求。
3、出身名門、血統純正
之所以說Go出身名門,是因為我們知道Go語言出自Google公司,這個公司在業界的知名度和實力自然不用多說。Google公司聚集了一批牛人,在各種程式語言稱雄爭霸的局面下推出新的程式語言,自然有它的戰略考慮。而且從Go語言的發展態勢來看,Google對它這個新的寵兒還是很看重的,Go自然有一個良好的發展前途。我們看看Go語言的主要創造者,血統純正這點就可見端倪了。
4、自由高效:組合的思想、無侵入式的介面
Go語言可以說是開發效率和執行效率二者的完美融合,天生的併發程式設計支援。Go語言支援當前所有的程式設計正規化,包括程序式程式設計、面向物件程式設計以及函數語言程式設計。程式設計師們可以各取所需、自由組合、想怎麼玩就怎麼玩。
5、強大的標準庫
這包括網際網路應用、系統程式設計和網路程式設計。Go裡面的標準庫基本上已經是非常穩定了,特別是我這裡提到的三個,網路層、系統層的庫非常實用。
6、部署方便:二進位制檔案、Copy部署
我相信這一點是很多人選擇Go的最大理由,因為部署太方便了,所以現在也有很多人用Go開發運維程式。
7、簡單的併發
它包含了降低心智的併發和簡易的資料同步,我覺得這是Go最大的特色。之所以寫正確的併發、容錯和可擴充套件的程式如此之難,是因為我們用了錯誤的工具和錯誤的抽象,Go可以說這一塊做的相當簡單。
8、穩定性
Go擁有強大的編譯檢查、嚴格的編碼規範和完整的軟體生命週期工具,具有很強的穩定性,穩定壓倒一切。那麼為什麼Go相比於其他程式會更穩定呢?這是因為Go提供了軟體生命週期(開發、測試、部署、維護等等)的各個環節的工具,如go tool、gofmt、go test。
這裡引用知乎裡一個同學對Go評論的話:最開始準備上線的時候其實心裡挺忐忑,畢竟一旦出現故障,不僅黑鍋得自己背,面子也上過不去啊。還好結果蠻漂亮,自上線後沒出現過一次突發性BUG,降低運維難度的同時還減少了機器的負載。我相信這也是大多數人用了Go之後的感言。
寫到後面
在使用的時候,我們需要知道go到底適用在什麼地方,譬如我們現在也就將API服務使用go重構,我們可沒傻到用go去替換openresty。
總之,go是一門很新的語言,國內也已經有很多公司開始吃這個螃蟹,也有成功的例子了,而我們也正開始了這段旅程。