你應該知道Go語言的幾個優點

要提及GO語言的優點,咱們就得從GO語言的歷史講起了……php

本文由 騰訊技術工程官方號發表在 騰訊雲+社區

2007年,受夠了C++煎熬的Google首席軟件工程師Rob Pike糾集Robert Griesemer和Ken Thompson兩位牛人,決定創造一種新語言來取代C++, 這就是Golang。出如今21世紀的GO語言,雖然不能如願對C++取而代之,可是其近C的執行性能和近解析型語言的開發效率以及近乎於完美的編譯速度,已經風靡全球。特別是在雲項目中,大部分都使用了Golang來開發,不得不說,Golang早已深刻人心。而對於一個沒有歷史負擔的新項目,Golang或許就是個不二的選擇。python

被稱爲GO語言之父的Rob Pike說,你是否贊成GO語言,取決於你是承認少就是多,仍是少就是少(Less is more or less is less)。Rob Pike以一種很是樸素的方式,歸納了GO語言的整個設計哲學--將簡單、實用體現得淋漓盡致。程序員

不少人將GO語言稱爲21世紀的C語言,由於GO不只擁有C的簡潔和性能,並且還很好的提供了21世紀互聯網環境下服務端開發的各類實用特性,讓開發者在語言級別就能夠方便的獲得本身想要的東西。數據庫

發展歷史

2007年9月,Rob Pike在Google分佈式編譯平臺上進行C++編譯,在漫長的等待過程當中,他和Robert Griesemer探討了程序設計語言的一些關鍵性問題,他們認爲,簡化編程語言相比於在臃腫的語言上不斷增長新特性,會是更大的進步。隨後他們在編譯結束以前說服了身邊的Ken Thompson,以爲有必要爲此作一些事情。幾天後,他們發起了一個叫Golang的項目,將它做爲自由時間的實驗項目。編程

2008年5月 Google發現了GO語言的巨大潛力,獲得了Google的全力支持,這些人開始全職投入GO語言的設計和開發。數組

2009年11月 GO語言第一個版本發佈。2012年3月 第一個正式版本Go1.0發佈。安全

2015年8月 go1.5發佈,這個版本被認爲是歷史性的。徹底移除C語言部分,使用GO編譯GO,少許代碼使用匯編實現。另外,他們請來了內存管理方面的權威專家Rick Hudson,對GC進行了從新設計,支持併發GC,解決了一直以來廣爲詬病的GC時延(STW)問題。而且在此後的版本中,又對GC作了更進一步的優化。到go1.8時,相同業務場景下的GC時延已經能夠從go1.1的數秒,控制在1ms之內。GC問題的解決,能夠說GO語言在服務端開發方面,幾乎抹平了全部的弱點。服務器

在GO語言的版本迭代過程當中,語言特性基本上沒有太大的變化,基本上維持在GO1.1的基準上,而且官方承諾,新版本對老版本下開發的代碼徹底兼容。事實上,GO開發團隊在新增語言特性上顯得很是謹慎,而在穩定性、編譯速度、執行效率以及GC性能等方面進行了持續不斷的優化。微信

開發團隊

img

GO語言的開發陣營能夠說是空前強大,主要成員中不乏計算機軟件界的歷史性人物,對計算機軟件的發展影響深遠。Ken Thompson,來自貝爾實驗室,設計了B語言,創立了Unix操做系統(最初使用B語言實現),隨後在Unix開發過程當中,又和Dennis Ritchie一同設計了C語言,繼而使用C語言重構了Unix操做系統。Dennis Ritchie和Ken Thompson被稱爲Unix和C語言之父,並在1983年共同被授以圖靈獎,以表彰他們對計算機軟件發展所做的傑出貢獻。Rob Pike,一樣來自貝爾實驗室,Unix小組重要成員,發明了Limbo語言,而且和Ken Thompson共同設計了UTF-8編碼,《Unix編程環境》、《編程實踐》做者之一。多線程

能夠說,GO語言背靠Google這棵大樹,又不乏牛人坐鎮,是名副其實的「牛二代」。

img

大名鼎鼎的Docker,徹底用GO實現,業界最爲火爆的容器編排管理系統kubernetes,徹底用GO實現,以後的Docker Swarm,徹底用GO實現。除此以外,還有各類有名的項目如etcd/consul/flannel等等,均使用GO實現。有人說,GO語言之因此出名,是遇上了雲時代,但爲何不能換種說法,也是GO語言促使了雲的發展?

除了雲項目外,還有像今日頭條、UBER這樣的公司,他們也使用GO語言對本身的業務進行了完全的重構。

GO語言關鍵特性

GO語言之因此厲害,是由於它在服務端的開發中,總能抓住程序員的痛點,以最直接、簡單、高效、穩定的方式來解決問題。這裏咱們並不會深刻討論GO語言的具體語法,只會將語言中關鍵的、對簡化編程具備重要意義的方面介紹給你們,跟隨大師們的腳步,體驗GO的設計哲學。

GO語言的關鍵特性主要包括如下幾方面:

  • 併發與協程
  • 基於消息傳遞的通訊方式
  • 豐富實用的內置數據類型
  • 函數多返回值
  • defer機制
  • 反射(reflect)
  • 高性能HTTP Server
  • 工程管理
  • 編程規範

img

在當今這個多核時代,併發編程的意義不言而喻。固然,不少語言都支持多線程、多進程編程,但遺憾的是,實現和控制起來並非那麼使人感受輕鬆和愉悅。Golang不一樣的是,語言級別支持協程(goroutine)併發(協程又稱微線程,比線程更輕量、開銷更小,性能更高),操做起來很是簡單,語言級別提供關鍵字(go)用於啓動協程,而且在同一臺機器上能夠啓動成千上萬個協程。

對比JAVA的多線程和GO的協程實現,明顯更直接、簡單。這就是GO的魅力所在,以簡單、高效的方式解決問題,關鍵字go,或許就是GO語言最重要的標誌。

基於消息傳遞的通訊方式

img

在異步的併發編程過程當中,只能方便、快速的啓動協程還不夠。協程之間的消息通訊,也是很是重要的一環,不然,各個協程就會成爲脫繮的野馬而沒法控制。在GO語言中,使用基於消息傳遞的通訊方式(而不是大多數語言所使用的基於共享內存的通訊方式)進行協程間通訊,而且將消息管道(channel)做爲基本的數據類型,使用類型關鍵字(chan)進行定義,併發操做時線程安全。這點在語言的實現上,也具備革命性。可見,GO語言自己並不是簡單得沒有底線,偏偏他們會將最實用、最有利於解決問題的能力,以最簡單、直接的形式提供給用戶。

Channel並不只僅只是用於簡單的消息通訊,還能夠引伸出不少很是實用,而實現起來又很是方便的功能。好比,實現TCP鏈接池、限流等等,而這些在其它語言中實現起來並不輕鬆,但GO語言能夠輕易作到。

img

GO語言做爲編譯型語言,在數據類型上也支持得很是全面,除了傳統的整型、浮點型、字符型、數組、結構等類型外。從實用性上考慮,也對字符串類型、切片類型(可變長數組)、字典類型、複數類型、錯誤類型、管道類型、甚至任意類型(Interface{})進行了原生支持,而且用起來很是方便。好比字符串、切片類型,操做簡便性幾乎和python相似。

另外,將錯誤類型(error)做爲基本的數據類型,而且在語言級別再也不支持try…catch的用法,這應該算是一個很是大膽的革命性創舉,也難怪不少人吐槽GO語言不三不四。可是跳出傳統的觀念,GO的開發者認爲在編程過程當中,要保證程序的健壯性和穩定性,對異常的精確化處理是很是重要的,只有在每個邏輯處理完成後,明確的告知上層調用,是否有異常,並由上層調用明確、及時的對異常進行處理,這樣才能夠高程度的保證程序的健壯性和穩定性。雖然這樣作會在編程過程當中出現大量的對error結果的判斷,可是這無疑也加強了開發者對異常處理的警戒度。而實踐證實,只要嚴格按GO推薦的風格編碼,想寫出不健壯的代碼,都很難。固然,前提是你不排斥它,承認它。

img

在語言中支持函數多返回值,並非什麼新鮮事,Python就是其中之一。容許函數返回多個值,在某些場景下,能夠有效的簡化編程。GO語言推薦的編程風格,是函數返回的最後一個參數爲error類型(只要邏輯體中可能出現異常),這樣,在語言級別支持多返回值,就頗有必要了。

Defer延遲處理機制

img

在GO語言中,提供關鍵字defer,能夠經過該關鍵字指定須要延遲執行的邏輯體,即在函數體return前或出現panic時執行。這種機制很是適合善後邏輯處理,好比能夠儘早避免可能出現的資源泄漏問題。

能夠說,defer是繼goroutine和channel以後的另外一個很是重要、實用的語言特性,對defer的引入,在很大程度上能夠簡化編程,而且在語言描述上顯得更爲天然,極大的加強了代碼的可讀性。

img

Golang做爲強類型的編譯型語言,靈活性上天然不如解析型語言。好比像PHP,弱類型,而且能夠直接對一個字符串變量的內容進行new操做,而在編譯型語言中,這顯然不太可能。可是,Golang提供了Any類型(interface{})和強大的類型反射(reflect)能力,兩者相結合,開發的靈活性上已經很接近解析型語言。在邏輯的動態調用方面,實現起來仍然很是簡單。既然如此,那麼像PHP這種解析型語言相比於GO,優點在那裏呢?就我我的而言,寫了近10年的PHP,實現過開發框架、基礎類庫以及各類公共組件,雖然執行性能不足,可是開發效率有餘;而當趕上Golang,這些優點彷佛不那麼明顯了。

img

做爲出如今互聯網時代的服務端語言,面向用戶服務的能力必不可少。GO在語言級別自帶HTTP/TCP/UDP高性能服務器,基於協程併發,爲業務開發提供最直接有效的能力支持。要在GO語言中實現一個高性能的HTTP Server,只須要幾行代碼便可完成,很是簡單。

img

在GO語言中,有一套標準的工程管理規範,只要按照這個規範進行項目開發,以後的事情(好比包管理、編譯等等)都將變得很是的簡單。

在GO項目下,存在兩個關鍵目錄,一個是src目錄,用於存放全部的.go源碼文件;一個是bin目錄,用於存在編譯後的二進制文件。在src目錄下,除了main主包所在的目錄外,其它全部的目錄名稱與直接目錄下所對應的包名保持對應,不然編譯沒法經過。這樣,GO編譯器就能夠從main包所在的目錄開始,徹底使用目錄結構和包名來推導工程結構以及構建順序,避免像C++同樣,引入一個額外的Makefile文件。

在GO的編譯過程當中,咱們惟一要作的就是將GO項目路徑賦值給一個叫GOPATH的環境變量,讓編譯器知道將要編譯的GO項目所在的位置。而後進入bin目錄下,執行go build {主包所在的目錄名},便可秒級完成工程編譯。編譯後的二進制文件,能夠推到同類OS上直接運行,沒有任何環境依賴。

img

GO語言的編程規範強制集成在語言中,好比明確規定花括號擺放位置,強制要求一行一句,不容許導入沒有使用的包,不容許定義沒有使用的變量,提供gofmt工具強制格式化代碼等等。奇怪的是,這些也引發了不少程序員的不滿,有人發表GO語言的XX條罪狀,裏面就不乏對編程規範的指責。要知道,從工程管理的角度,任何一個開發團隊都會對特定語言制定特定的編程規範,特別像Google這樣的公司,更是如此。GO的設計者們認爲,與其將規範寫在文檔裏,還不如強制集成在語言裏,這樣更直接,更有利用團隊協做和工程管理。

API快速開發框架實踐

編程語言是一個工具,它會告訴咱們能作什麼,而怎麼作會更好,一樣值得去探討。這部分會介紹用GO語言實現的一個開發框架,以及幾個公共組件。固然,框架和公共組件,其它語言也徹底能夠實現,而這裏所關注的是成本問題。除此以外,拋開GO語言自己不說,咱們也但願可讓你們從介紹的幾個組件中,獲得一些解決問題的思路,那就是經過某種方式,去解決一個面上的問題,而非一味的寫代碼,最終卻只是解決點上的問題。若是你承認這種方式,相信下面的內容也許會影響你以後的項目開發方式,從根本上提升開發效率。

咱們爲何選擇GO語言

選擇GO語言,主要是基於兩方面的考慮

  1. 執行性能 縮短API的響應時長,解決批量請求訪問超時的問題。在Uwork的業務場景下,一次API批量請求,每每會涉及對另外接口服務的屢次調用,而在以前的PHP實現模式下,要作到並行調用是很是困難的,串行處理卻不能從根本上提升處理性能。而GO語言不同,經過協程能夠方便的實現API的並行處理,達處處理效率的最大化。 依賴Golang的高性能HTTP Server,提高系統吞吐能力,由PHP的數百級別提高到數千裏甚至過萬級別。
  2. 開發效率 GO語言使用起來簡單、代碼描述效率高、編碼規範統1、上手快。 經過少許的代碼,便可實現框架的標準化,並以統一的規範快速構建API業務邏輯。 能快速的構建各類通用組件和公共類庫,進一步提高開發效率,實現特定場景下的功能量產。

img

不少人在學習一門新語言或開啓一個新項目時,都會習慣性的是網上找一個認爲合適的開源框架來開始本身的項目開發之旅。這樣並無什麼很差,可是我的以爲,瞭解它內部的實現對咱們會更有幫助。或許你們已經注意到了,所說的MVC框架,其本質上就是對請求路徑進行解析,而後根據請求路徑段,路由到相應的控制器(C)上,再由控制器進一步調用數據邏輯(M),拿到數據後,渲染視圖(V),返回用戶。在整個過程當中,核心點在於邏輯的動態調用。

不過,對API框架的實現相對於WEB頁面框架的實現,會更簡單,由於它並不涉及視圖的渲染,只須要將數據結果以協議的方式返回給用戶便可。

使用GO語言實現一套完整的MVC開發框架,是很是容易的,集成HTTP Server的同時,整個框架的核心代碼不會超過300行,從這裏能夠實際感覺到GO的語言描述效率之高(若是有興趣,能夠參考Uwork開源項目seine)。

也有人說,在GO語言中,就沒有框架可言,言外之意是說,引入一個重型的開源框架,必要性並不大,相反還可能把簡單的東西複雜化。

img

在實際項目開發過程當中,只有高效的開發語言還不夠,要想進一步將開發效率擴大化,不斷的沉澱公共基礎庫是必不可少的,以便將通用的基礎邏輯進一步抽象和複用。

除此以外,通用組件能力是實現功能量產的根本,對開發效率會是質的提高。組件化的開發模式會幫忙咱們將問題的解決能力從一個點上提高到一個面上。如下會重點介紹幾個通用組件的實現,有了它們的存在,才能真正的解放程序員的生產力。而這些強有力的公共組件在Golang中實現起來並不複雜。同時,結合Golang的併發處理能力,相比於PHP的版本實現,執行效率也會有質的提高。這是組件能力和語言效率的完美結合。

img

通用列表組件用於全部可能的二維數據源(如MySQL/MongoDB/ES等等)的數據查詢場景,從一個面上解決了數據查詢問題。在Uwork項目開發中,被大量使用,實現數據查詢接口和頁面查詢列表的量產開發。它以一個JSON配置文件爲中心,來實現對通用數據源的查詢,並將查詢結果以API或頁面的形式自動返回給用戶。整個過程當中幾乎沒有代碼開發,而惟一要作的只是以一種統一的規範編寫配置文件(而不是代碼),真正實現了對數據查詢需求的功能量產。

img

以上是通用列表組件的構建過程,要實現這樣一個功能強大的通用組件,是否是會給人一種可望而不可及的感受?其實並不是如此,只要理清了它的整個過程,將構建思路融入Golang中,並非一件複雜的事情。在咱們的項目中,整個組件的實現,只用了不到700行Go代碼,就解決了一系列的數據查詢問題。另外,經過Golang的併發特性,實現字段處理器的並行執行,進一步的提升了組件的執行效率。能夠說,通用列表和Golang的融合,是性能和效率的完美結合。

img

通用表單組件主要用於對數據庫的增、刪、改場景。該組件在Uwork的項目開發中,也有普遍的應用,與通用列表相似,以一個JSON配置文件爲中心,來完成對數據表數據的增、刪、改操做。特別是近期完成的部件級SDB管理平臺,經過通用表單實現了對整個系統的數據維護,經過高度抽象化,作到了業務的無代碼化生產。

img

以上是通用表單的完整構建過程,而對於這個一個組件的實現,咱們用了不到1000行的GO代碼,就解決了對數據表數據維護整個面上的問題。

img

GO語言自己支持協程併發,協程很是輕量,能夠快速啓動成千上萬個協程工做單元。若是對協程任務的數量控制不當,最後的結果極可能拔苗助長,從而對外部或自己的服務形成沒必要要的壓力。協程池能夠在必定程度上控制執行單元的數量,保證執行的安全性。而在Golang中要實現這樣一個協程池,是很是簡單的,只須要對channel和goroutine稍加封裝,就能夠完成,整個構建過程不到80行代碼。

img

在API開發過程當中,數據校驗永遠是必不可或缺的一個環節。若是隻是簡單的數據校驗,幾行代碼也許就完成了,但是當趕上覆雜的數據校驗時,極可能幾百行的代碼量也未必能完成,特別是遇到遞歸類型的數據校驗,那簡直就是一個噩夢。

數據校驗組件,能夠經過一種數據模板的配置方式,使用特定的邏輯來完成通用校驗,開發者只須要配置好相應的數據模板,進行簡單的調用,便可完成整個校驗過程。而對於這樣一個通用性的數據校驗組件,在GO語言中只用了不到700行的代碼量就完成了整個構建。

小結

img

在實際項目開發過程當中,對開發效率提高最大的,無疑是符合系統業務場景的公共組件能力,這點也正好應證了Rob Pike那句話(Less is lessor Less is more),真正的高效率開發,是配置化的,並不須要寫太多的代碼,甚至根本就不須要寫代碼,便可完成邏輯實現,而這種方式對於後期的維護成本也是最優的,由於作到了高度的統一。

GO的語言描述效率毋庸置疑,對上述全部公共組件的實現,均未超過1000行代碼,就解決了某個面上的問題。

(以上的部分代碼已經在Uwork開源項目seine中提供)

性能評測

壓力測試環境說明:

  • 服務運行機器:單臺空閒B6,24核CPU、64G內存。
  • PHP API環境:Nginx+PHP-FPM,CI框架。其中Nginx啓動10個子進程,每一個子進程最大接收1024個鏈接,php-fpm使用static模式,啓動2000個常駐子進程。
  • Golang API環境:使用go1.8.6編譯,直接拉起Golang API Server進程(HttpServer),不考慮調優。
  • 客戶發起請求測試程序:使用Golang編寫,協程併發,運行在獨立的另一臺空閒B6上,24核CPU,64G內存,依次在1-2000個不一樣級別(併發數步長爲50)的併發上分別請求20000次。

壓力測試結果對比

img

在Golang API框架中,當併發數>50時,處理QPS在6.5w/s附近波動。表現穩定,壓力測試過程無報錯。

Nginx+php-fpm,只在index.php中輸出exit('ok'),當併發數>50時,處理QPS在1w/s附近波動。表現穩定,壓力測試過程無報錯。

Nginx+php-fpm+CI框架中,邏輯執行到具體業務邏輯點,輸出exit('ok'),當併發數>50時,處理QPS在750/s附近波動。而且表現不穩定,壓力測試過程當中隨着併發數的增大,錯誤量隨之增長。

經過壓力測試能夠發現,Golang和PHP在執行性能上,並無什麼可比性;而使用Golang實現的HTTP API框架,空載時單機性能QPS達到6.5w/s,仍是很是使人滿意的。

開發過程當中須要注意的點

如下是在實際開發過程當中遇到的一些問題,僅供參考:

異常處理統一使用error,不要使用panic/recover來模擬throw…catch,最初我是這麼作的,後來發現這徹底是自覺得是的作法。

原生的error過於簡單,而在實際的API開發過程當中,不一樣的異常狀況須要附帶不一樣的返回碼,基於此,有必要對error再進行一層封裝。

任何協程邏輯執行體,邏輯最開始處必需要有defer recover()異常恢復處理,不然goroutine內出現的panic,將致使整個進程宕掉,須要避免部分邏輯BUG形成全局影響。

在Golang中,變量(chan類型除外)的操做是非線程安全的,也包括像int這樣的基本類型,所以併發操做全局變量時必定要考慮加鎖,特別是對map的併發操做。

全部對map鍵值的獲取,都應該判斷存在性,最好是對同類操做進行統一封裝,避免出現沒必要要的運行時異常。

定義slice數據類型時,儘可能預設長度,避免內部出現沒必要要的數據重組。


此文已由做者受權騰訊雲+社區發佈,原文連接:https://cloud.tencent.com/dev...

歡迎你們前往騰訊雲+社區或關注雲加社區微信公衆號(QcloudCommunity),第一時間獲取更多海量技術實踐乾貨哦~

相關文章
相關標籤/搜索