掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
很多人將GO語言稱為21世紀(jì)的C語言,因為GO不僅擁有C的簡潔和性能,而且還很好的提供了21世紀(jì)互聯(lián)網(wǎng)環(huán)境下服務(wù)端開發(fā)的各種實用特性,讓開發(fā)者在語言級別就可以方便的得到自己想要的東西。

成都創(chuàng)新互聯(lián)公司的客戶來自各行各業(yè),為了共同目標(biāo),我們在工作上密切配合,從創(chuàng)業(yè)型小企業(yè)到企事業(yè)單位,感謝他們對我們的要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊有機(jī)會用頭腦與智慧不斷的給客戶帶來驚喜。專業(yè)領(lǐng)域包括網(wǎng)站建設(shè)、網(wǎng)站制作、電商網(wǎng)站開發(fā)、微信營銷、系統(tǒng)平臺開發(fā)。
在GO語言的版本迭代過程中,語言特性基本上沒有太大的變化,基本上維持在GO1.1的基準(zhǔn)上,并且官方承諾,新版本對老版本下開發(fā)的代碼完全兼容。事實上,GO開發(fā)團(tuán)隊在新增語言特性上顯得非常謹(jǐn)慎,而在穩(wěn)定性、編譯速度、執(zhí)行效率以及GC性能等方面進(jìn)行了持續(xù)不斷的優(yōu)化。
GO語言的開發(fā)陣營可以說是空前強(qiáng)大,主要成員中不乏計算機(jī)軟件界的歷史性人物,對計算機(jī)軟件的發(fā)展影響深遠(yuǎn)。Ken Thompson,來自貝爾實驗室,設(shè)計了B語言,創(chuàng)立了Unix操作系統(tǒng)(最初使用B語言實現(xiàn)),隨后在Unix開發(fā)過程中,又和Dennis Ritchie一同設(shè)計了C語言,繼而使用C語言重構(gòu)了Unix操作系統(tǒng)。Dennis Ritchie和Ken Thompson被稱為Unix和C語言之父,并在1983年共同被授以圖靈獎,以表彰他們對計算機(jī)軟件發(fā)展所作的杰出貢獻(xiàn)。Rob Pike,同樣來自貝爾實驗室,Unix小組重要成員,發(fā)明了Limbo語言,并且和Ken Thompson共同設(shè)計了UTF-8編碼,《Unix編程環(huán)境》、《編程實踐》作者之一。
可以說,GO語言背靠Google這棵大樹,又不乏牛人坐鎮(zhèn),是名副其實的“牛二代”。
大名鼎鼎的Docker,完全用GO實現(xiàn),業(yè)界最為火爆的容器編排管理系統(tǒng)kubernetes,完全用GO實現(xiàn),之后的Docker Swarm,完全用GO實現(xiàn)。除此之外,還有各種有名的項目如etcd/consul/flannel等等,均使用GO實現(xiàn)。有人說,GO語言之所以出名,是趕上了云時代,但為什么不能換種說法,也是GO語言促使了云的發(fā)展?
除了云項目外,還有像今日頭條、UBER這樣的公司,他們也使用GO語言對自己的業(yè)務(wù)進(jìn)行了徹底的重構(gòu)。
GO語言之所以厲害,是因為它在服務(wù)端的開發(fā)中,總能抓住程序員的痛點,以最直接、簡單、高效、穩(wěn)定的方式來解決問題。這里我們并不會深入討論GO語言的具體語法,只會將語言中關(guān)鍵的、對簡化編程具有重要意義的方面介紹給大家,跟隨大師們的腳步,體驗GO的設(shè)計哲學(xué)。
GO語言的關(guān)鍵特性主要包括以下幾方面:
在當(dāng)今這個多核時代,并發(fā)編程的意義不言而喻。當(dāng)然,很多語言都支持多線程、多進(jìn)程編程,但遺憾的是,實現(xiàn)和控制起來并不是那么令人感覺輕松和愉悅。Golang不同的是,語言級別支持協(xié)程(goroutine)并發(fā)(協(xié)程又稱微線程,比線程更輕量、開銷更小,性能更高),操作起來非常簡單,語言級別提供關(guān)鍵字(go)用于啟動協(xié)程,并且在同一臺機(jī)器上可以啟動成千上萬個協(xié)程。
對比JAVA的多線程和GO的協(xié)程實現(xiàn),明顯更直接、簡單。這就是GO的魅力所在,以簡單、高效的方式解決問題,關(guān)鍵字go,或許就是GO語言最重要的標(biāo)志。
在異步的并發(fā)編程過程中,只能方便、快速的啟動協(xié)程還不夠。協(xié)程之間的消息通信,也是非常重要的一環(huán),否則,各個協(xié)程就會成為脫韁的野馬而無法控制。在GO語言中,使用基于消息傳遞的通信方式(而不是大多數(shù)語言所使用的基于共享內(nèi)存的通信方式)進(jìn)行協(xié)程間通信,并且將消息管道(channel)作為基本的數(shù)據(jù)類型,使用類型關(guān)鍵字(chan)進(jìn)行定義,并發(fā)操作時線程安全。這點在語言的實現(xiàn)上,也具有革命性??梢?,GO語言本身并非簡單得沒有底線,恰恰他們會將最實用、最有利于解決問題的能力,以最簡單、直接的形式提供給用戶。
Channel并不僅僅只是用于簡單的消息通信,還可以引申出很多非常實用,而實現(xiàn)起來又非常方便的功能。比如,實現(xiàn)TCP連接池、限流等等,而這些在其它語言中實現(xiàn)起來并不輕松,但GO語言可以輕易做到。
GO語言作為編譯型語言,在數(shù)據(jù)類型上也支持得非常全面,除了傳統(tǒng)的整型、浮點型、字符型、數(shù)組、結(jié)構(gòu)等類型外。從實用性上考慮,也對字符串類型、切片類型(可變長數(shù)組)、字典類型、復(fù)數(shù)類型、錯誤類型、管道類型、甚至任意類型(Interface{})進(jìn)行了原生支持,并且用起來非常方便。比如字符串、切片類型,操作簡便性幾乎和python類似。
另外,將錯誤類型(error)作為基本的數(shù)據(jù)類型,并且在語言級別不再支持try…catch的用法,這應(yīng)該算是一個非常大膽的革命性創(chuàng)舉,也難怪很多人吐槽GO語言不倫不類。但是跳出傳統(tǒng)的觀念,GO的開發(fā)者認(rèn)為在編程過程中,要保證程序的健壯性和穩(wěn)定性,對異常的精確化處理是非常重要的,只有在每一個邏輯處理完成后,明確的告知上層調(diào)用,是否有異常,并由上層調(diào)用明確、及時的對異常進(jìn)行處理,這樣才可以高程度的保證程序的健壯性和穩(wěn)定性。雖然這樣做會在編程過程中出現(xiàn)大量的對error結(jié)果的判斷,但是這無疑也增強(qiáng)了開發(fā)者對異常處理的警惕度。而實踐證明,只要嚴(yán)格按GO推薦的風(fēng)格編碼,想寫出不健壯的代碼,都很難。當(dāng)然,前提是你不排斥它,認(rèn)可它。
在語言中支持函數(shù)多返回值,并不是什么新鮮事,Python就是其中之一。允許函數(shù)返回多個值,在某些場景下,可以有效的簡化編程。GO語言推薦的編程風(fēng)格,是函數(shù)返回的最后一個參數(shù)為error類型(只要邏輯體中可能出現(xiàn)異常),這樣,在語言級別支持多返回值,就很有必要了。
在GO語言中,提供關(guān)鍵字defer,可以通過該關(guān)鍵字指定需要延遲執(zhí)行的邏輯體,即在函數(shù)體return前或出現(xiàn)panic時執(zhí)行。這種機(jī)制非常適合善后邏輯處理,比如可以盡早避免可能出現(xiàn)的資源泄漏問題。
可以說,defer是繼goroutine和channel之后的另一個非常重要、實用的語言特性,對defer的引入,在很大程度上可以簡化編程,并且在語言描述上顯得更為自然,極大的增強(qiáng)了代碼的可讀性。
Golang作為強(qiáng)類型的編譯型語言,靈活性上自然不如解析型語言。比如像PHP,弱類型,并且可以直接對一個字符串變量的內(nèi)容進(jìn)行new操作,而在編譯型語言中,這顯然不太可能。但是,Golang提供了Any類型(interface{})和強(qiáng)大的類型反射(reflect)能力,二者相結(jié)合,開發(fā)的靈活性上已經(jīng)很接近解析型語言。在邏輯的動態(tài)調(diào)用方面,實現(xiàn)起來仍然非常簡單。既然如此,那么像PHP這種解析型語言相比于GO,優(yōu)勢在那里呢?就我個人而言,寫了近10年的PHP,實現(xiàn)過開發(fā)框架、基礎(chǔ)類庫以及各種公共組件,雖然執(zhí)行性能不足,但是開發(fā)效率有余;而當(dāng)遇上Golang,這些優(yōu)勢似乎不那么明顯了。
作為出現(xiàn)在互聯(lián)網(wǎng)時代的服務(wù)端語言,面向用戶服務(wù)的能力必不可少。GO在語言級別自帶HTTP/TCP/UDP高性能服務(wù)器,基于協(xié)程并發(fā),為業(yè)務(wù)開發(fā)提供最直接有效的能力支持。要在GO語言中實現(xiàn)一個高性能的HTTP Server,只需要幾行代碼即可完成,非常簡單。
[[284983]]
在GO語言中,有一套標(biāo)準(zhǔn)的工程管理規(guī)范,只要按照這個規(guī)范進(jìn)行項目開發(fā),之后的事情(比如包管理、編譯等等)都將變得非常的簡單。
在GO項目下,存在兩個關(guān)鍵目錄,一個是src目錄,用于存放所有的.go源碼文件;一個是bin目錄,用于存在編譯后的二進(jìn)制文件。在src目錄下,除了main主包所在的目錄外,其它所有的目錄名稱與直接目錄下所對應(yīng)的包名保持對應(yīng),否則編譯無法通過。這樣,GO編譯器就可以從main包所在的目錄開始,完全使用目錄結(jié)構(gòu)和包名來推導(dǎo)工程結(jié)構(gòu)以及構(gòu)建順序,避免像C++一樣,引入一個額外的Makefile文件。
在GO的編譯過程中,我們唯一要做的就是將GO項目路徑賦值給一個叫GOPATH的環(huán)境變量,讓編譯器知道將要編譯的GO項目所在的位置。然后進(jìn)入bin目錄下,執(zhí)行g(shù)o build {主包所在的目錄名},即可秒級完成工程編譯。編譯后的二進(jìn)制文件,可以推到同類OS上直接運(yùn)行,沒有任何環(huán)境依賴。
GO語言的編程規(guī)范強(qiáng)制集成在語言中,比如明確規(guī)定花括號擺放位置,強(qiáng)制要求一行一句,不允許導(dǎo)入沒有使用的包,不允許定義沒有使用的變量,提供gofmt工具強(qiáng)制格式化代碼等等。奇怪的是,這些也引起了很多程序員的不滿,有人發(fā)表GO語言的XX條罪狀,里面就不乏對編程規(guī)范的指責(zé)。要知道,從工程管理的角度,任何一個開發(fā)團(tuán)隊都會對特定語言制定特定的編程規(guī)范,特別像Google這樣的公司,更是如此。GO的設(shè)計者們認(rèn)為,與其將規(guī)范寫在文檔里,還不如強(qiáng)制集成在語言里,這樣更直接,更有利用團(tuán)隊協(xié)作和工程管理。
編程語言是一個工具,它會告訴我們能做什么,而怎么做會更好,同樣值得去探討。這部分會介紹用GO語言實現(xiàn)的一個開發(fā)框架,以及幾個公共組件。當(dāng)然,框架和公共組件,其它語言也完全可以實現(xiàn),而這里所關(guān)注的是成本問題。除此之外,拋開GO語言本身不說,我們也希望可以讓大家從介紹的幾個組件中,得到一些解決問題的思路,那就是通過某種方式,去解決一個面上的問題,而非一味的寫代碼,最終卻只是解決點上的問題。如果你認(rèn)可這種方式,相信下面的內(nèi)容也許會影響你之后的項目開發(fā)方式,從根本上提高開發(fā)效率。
選擇GO語言,主要是基于兩方面的考慮
很多人在學(xué)習(xí)一門新語言或開啟一個新項目時,都會習(xí)慣性的是網(wǎng)上找一個認(rèn)為合適的開源框架來開始自己的項目開發(fā)之旅。這樣并沒有什么不好,但是個人覺得,了解它內(nèi)部的實現(xiàn)對我們會更有幫助?;蛟S大家已經(jīng)注意到了,所說的MVC框架,其本質(zhì)上就是對請求路徑進(jìn)行解析,然后根據(jù)請求路徑段,路由到相應(yīng)的控制器(C)上,再由控制器進(jìn)一步調(diào)用數(shù)據(jù)邏輯(M),拿到數(shù)據(jù)后,渲染視圖(V),返回用戶。在整個過程中,核心點在于邏輯的動態(tài)調(diào)用。
不過,對API框架的實現(xiàn)相對于WEB頁面框架的實現(xiàn),會更簡單,因為它并不涉及視圖的渲染,只需要將數(shù)據(jù)結(jié)果以協(xié)議的方式返回給用戶即可。
使用GO語言實現(xiàn)一套完整的MVC開發(fā)框架,是非常容易的,集成HTTP Server的同時,整個框架的核心代碼不會超過300行,從這里可以實際感受到GO的語言描述效率之高(如果有興趣,可以參考Uwork開源項目seine)。
也有人說,在GO語言中,就沒有框架可言,言外之意是說,引入一個重型的開源框架,必要性并不大,相反還可能把簡單的東西復(fù)雜化。
在實際項目開發(fā)過程中,只有高效的開發(fā)語言還不夠,要想進(jìn)一步將開發(fā)效率擴(kuò)大化,不斷的沉淀公共基礎(chǔ)庫是必不可少的,以便將通用的基礎(chǔ)邏輯進(jìn)一步抽象和復(fù)用。
除此之外,通用組件能力是實現(xiàn)功能量產(chǎn)的根本,對開發(fā)效率會是質(zhì)的提升。組件化的開發(fā)模式會幫忙我們將問題的解決能力從一個點上提升到一個面上。以下會重點介紹幾個通用組件的實現(xiàn),有了它們的存在,才能真正的解放程序員的生產(chǎn)力。而這些強(qiáng)有力的公共組件在Golang中實現(xiàn)起來并不復(fù)雜。同時,結(jié)合Golang的并發(fā)處理能力,相比于PHP的版本實現(xiàn),執(zhí)行效率也會有質(zhì)的提升。這是組件能力和語言效率的完美結(jié)合。
通用列表組件用于所有可能的二維數(shù)據(jù)源(如MySQL/MongoDB/ES等等)的數(shù)據(jù)查詢場景,從一個面上解決了數(shù)據(jù)查詢問題。在Uwork項目開發(fā)中,被大量使用,實現(xiàn)數(shù)據(jù)查詢接口和頁面查詢列表的量產(chǎn)開發(fā)。它以一個JSON配置文件為中心,來實現(xiàn)對通用數(shù)據(jù)源的查詢,并將查詢結(jié)果以API或頁面的形式自動返回給用戶。整個過程中幾乎沒有代碼開發(fā),而唯一要做的只是以一種統(tǒng)一的規(guī)范編寫配置文件(而不是代碼),真正實現(xiàn)了對數(shù)據(jù)查詢需求的功能量產(chǎn)。
以上是通用列表組件的構(gòu)建過程,要實現(xiàn)這樣一個功能強(qiáng)大的通用組件,是不是會給人一種可望而不可及的感覺?其實并非如此,只要理清了它的整個過程,將構(gòu)建思路融入Golang中,并不是一件復(fù)雜的事情。在我們的項目中,整個組件的實現(xiàn),只用了不到700行Go代碼,就解決了一系列的數(shù)據(jù)查詢問題。另外,通過Golang的并發(fā)特性,實現(xiàn)字段處理器的并行執(zhí)行,進(jìn)一步的提高了組件的執(zhí)行效率。可以說,通用列表和Golang的融合,是性能和效率的完美結(jié)合。
通用表單組件主要用于對數(shù)據(jù)庫的增、刪、改場景。該組件在Uwork的項目開發(fā)中,也有廣泛的應(yīng)用,與通用列表類似,以一個JSON配置文件為中心,來完成對數(shù)據(jù)表數(shù)據(jù)的增、刪、改操作。特別是近期完成的部件級SDB管理平臺,通過通用表單實現(xiàn)了對整個系統(tǒng)的數(shù)據(jù)維護(hù),通過高度抽象化,做到了業(yè)務(wù)的無代碼化生產(chǎn)。
以上是通用表單的完整構(gòu)建過程,而對于這個一個組件的實現(xiàn),我們用了不到1000行的GO代碼,就解決了對數(shù)據(jù)表數(shù)據(jù)維護(hù)整個面上的問題。
GO語言本身支持協(xié)程并發(fā),協(xié)程非常輕量,可以快速啟動成千上萬個協(xié)程工作單元。如果對協(xié)程任務(wù)的數(shù)量控制不當(dāng),最后的結(jié)果很可能適得其反,從而對外部或本身的服務(wù)造成不必要的壓力。協(xié)程池可以在一定程度上控制執(zhí)行單元的數(shù)量,保證執(zhí)行的安全性。而在Golang中要實現(xiàn)這樣一個協(xié)程池,是非常簡單的,只需要對channel和goroutine稍加封裝,就可以完成,整個構(gòu)建過程不到80行代碼。
在API開發(fā)過程中,數(shù)據(jù)校驗永遠(yuǎn)是必不可或缺的一個環(huán)節(jié)。如果只是簡單的數(shù)據(jù)校驗,幾行代碼也許就完成了,可是當(dāng)遇上復(fù)雜的數(shù)據(jù)校驗時,很可能幾百行的代碼量也未必能完成,特別是遇到遞歸類型的數(shù)據(jù)校驗,那簡直就是一個噩夢。
數(shù)據(jù)校驗組件,可以通過一種數(shù)據(jù)模板的配置方式,使用特定的邏輯來完成通用校驗,開發(fā)者只需要配置好相應(yīng)的數(shù)據(jù)模板,進(jìn)行簡單的調(diào)用,即可完成整個校驗過程。而對于這樣一個通用性的數(shù)據(jù)校驗組件,在GO語言中只用了不到700行的代碼量就完成了整個構(gòu)建。
在實際項目開發(fā)過程中,對開發(fā)效率提升最大的,無疑是符合系統(tǒng)業(yè)務(wù)場景的公共組件能力,這點也正好應(yīng)證了Rob Pike那句話(Less is lessor Less is more),真正的高效率開發(fā),是配置化的,并不需要寫太多的代碼,甚至根本就不需要寫代碼,即可完成邏輯實現(xiàn),而這種方式對于后期的維護(hù)成本也是最優(yōu)的,因為做到了高度的統(tǒng)一。
GO的語言描述效率毋庸置疑,對上述所有公共組件的實現(xiàn),均未超過1000行代碼,就解決了某個面上的問題。
(以上的部分代碼已經(jīng)在Uwork開源項目seine中提供)
壓力測試環(huán)境說明:
壓力測試結(jié)果對比
在Golang API框架中,當(dāng)并發(fā)數(shù)>50時,處理QPS在6.5w/s附近波動。表現(xiàn)穩(wěn)定,壓力測試過程無報錯。
Nginx+php-fpm,只在index.php中輸出exit('ok'),當(dāng)并發(fā)數(shù)>50時,處理QPS在1w/s附近波動。表現(xiàn)穩(wěn)定,壓力測試過程無報錯。
Nginx+php-fpm+CI框架中,邏輯執(zhí)行到具體業(yè)務(wù)邏輯點,輸出exit('ok'),當(dāng)并發(fā)數(shù)>50時,處理QPS在750/s附近波動。并且表現(xiàn)不穩(wěn)定,壓力測試過程中隨著并發(fā)數(shù)的增大,錯誤量隨之增加。
通過壓力測試可以發(fā)現(xiàn),Golang和PHP在執(zhí)行性能上,并沒有什么可比性;而使用Golang實現(xiàn)的HTTP API框架,空載時單機(jī)性能QPS達(dá)到6.5w/s,還是非常令人滿意的。
以下是在實際開發(fā)過程中遇到的一些問題,僅供參考:

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流