產品環境中Go語言的最佳實務

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

在SoundCloud,我們為客戶構建了產品的API。或者說,我們主要的網站、手機用戶端和手機應用是該API的第一批客戶。該API背後是一個領域性的服務:SoundCloud基本上以面向服務體繫結構的形式運作。

 

我們也是通曉多種語言的組織,因為我們使用了很多語言。並且這些服務(和基礎設施支援)的許多部分是使用Golang開發的。事實上,我們都是早期Golang的使用者:目前,我們已在產品中使用Golang有兩年半的時間。相關項目包括:

 

  • Bazooka,我們內部服務平台;產品思想非常類似於Keroku或Flynn。
  • 我們外圍的傳輸層使用通用的nginx, HAProxy等等,但是它們要和Golang服務協作。
  • 我們的音頻儲存在AWS S3上,但是上傳、轉碼和產生連結等需要Golang服務協調處理。
  • 搜尋採用了Elasticsearch, 探測使用複雜的機器學習模型,但是它們都與由Golang開發的基礎設施相整合。
  • Prometheus,一個早期階段的遙測系統純粹是有Golang開發。
  • 當前,流處理採用Cassandra,但是我們正打算(幾乎)完全使用Golang代替。
  • 我們也正在實驗用Golnag開發的HTTP流媒體直播服務。
  • 許多其他面向產品的小服務。

這些項目大概有六個團隊開發,包括十多人的SoundCloud勤雜工,他們中的大部分會全職使用Golang。畢竟在這個時候,這些項目和這樣混雜的工程師中,我們已經逐漸形成了在產品中使用Golang的最好實踐方法。我們的這些教訓將對其他開始大舉投資Golang的組織提供協助。

 

開發環境

在我們的筆記本上,我們已經設定了單一、全域的GOPATH。就個人而言,我喜歡使用$HOME,但是許多其他人使用$HOME下的一個子目錄。我們複製倉庫進入GOPATH的相對路徑,然後就可直接工作。即,

我們中的許多人在早期一直和約定俗成的事情做鬥爭,以保持我們自己特有的程式碼群組織方法。事實上,它根本不值得如此麻煩。

 

對於編輯器,許多使用者使用Vim以及各種外掛程式。(我使用的vim-go就不錯。)還有許多人,包括我自己也是,結合GoSublime使用Sublime Text。也有少數人使用Emacs,但沒有人用IDE。我不確定這是不是個最佳的實踐,但標出來挺有趣的。

 

庫結構

我們的最佳實務是確保任何事情簡單。許多服務源碼半打包在main包中。

比如我們的搜尋調度器,兩年後仍然是這樣。在確定需要前不要建立新結構。

 

也許在某些時候你需要建立一個新的支援包。在你的main庫中使用子目錄,並使用完整的限定名匯入。如果該包只有一個檔案或一個結構,那麼它肯定不需要分拆出來。

 

有時一個倉庫中需要包含多個二進位檔案;比如這個任務需要一個服務,一個背景工作處理序,或一個監控。在這種情況下,將每個二進位檔案放在特定main包的單獨的子目錄中,並使用其他的子目錄(或包)來實現共用的功能。

請注意,不要引入asrc目錄。由於vendor子目錄異常(下面介紹更多內容)不要在倉庫中包含src目錄,或將其添加到GOPATH。

 

格式及樣式

通常來說,首先配置你的編輯器儲存代碼交給go fmt(或goimports),使用預設參數。這意味使用tab縮排,用空格對齊。格式不正確的代碼將不能提交。

 

過去的風格指南非常廣泛,但Google最近發布了他們的 代碼審查意見 文檔,這幾乎就是我們應遵守的公約。因此,我們使用它。

 

實際上我們把它推進了一點:


避免命名返回參數,除非他們能明確和顯著地提高透明度。

避免用 make 和 new,除非他們是必要的(new(int),或 make(Chan int)),或者我們能提前知道要分配的東西的尺寸( make(map[int]string,n),或 make([]int,0,256))。

使用 struct{} 作為標記值,而不是布爾或介面{}。例如,集合是 map[string]struct{};通道是 chan struct{}。它明確標明了資訊的明確缺乏。


打斷長行的參數也很好。那更象是Java的風格:

 
這樣會更好:

 
當構造對象時也同樣分為多行:

 
另外,當分配新的對象時,在初始化部分傳遞成員值(如上面)比下面這樣過後設定要好。

 

配置

我們嘗試了通過多種方式向Go程式傳遞配置:解析設定檔,用 os.Getenv 直接從環境中提取配置,各種增值flag解析包。最後,最合乎經濟原則的就是普通的package flag,它的嚴格類型和簡單語義對我們所需的一切都絕對夠用而且夠好。

 

我們主要部署12-Factor 的應用,12-Factor 應用程式通過環境傳遞配置。但即使這樣,我們也使用一個啟動指令碼來把環境變數轉換為flags。Flags作為程式及其運行環境之間的一個明確和全文檔化的表面地區。他們對於瞭解和操作程式來說是非常寶貴的。


一個關於flags的不錯的習慣是把他們定義到你的main函數中。這樣就能防止你在代碼中隨意的將他們作為全域變數使用,這使你嚴格的遵守依賴注入從而方便測試。

 
日誌和遙測

我們嘗試過幾個日誌架構,他們提供像記錄層級,調試,路由輸出,自訂格式化等等功能。最終我們選定package log。因為我們只記錄可操作資訊。 這意味著需要人工處理的 serious, panic層級的錯誤,或者結構化資料會被其他機器消耗。 舉個例子,搜尋轉寄站發送每一個它使用上下文資訊處理的請求,因此我們的分析工作流程可以看到新西蘭的人們經常搜尋 Lorde, 或者隨便什麼。
 
我們考慮到遙測,在一個運行過程中釋放出的任何其他量:請求回應時間,QPS,運行錯誤,隊列深度等等。並且遙測基本上包括兩種模式:push和pull。


push意味著釋放指標到一個已知的系統。例如Graphite, Statsd, and AirBrake

pull意味著在一些已知的位置暴露指標,並允許已知的系統去擦除它們。例如,expvar和Prometheus(或許還有其他的)


當然兩種方式都有自己的存在性。當你開始使用時,push是直觀和簡單的。但是推送指標的增長卻有悖常理:你得到的越大,成本越高。我們過去發現在特定規模大小的基礎設施上,pull是該尺度下的唯一模型。那也有許多值能反映一個啟動並執行系統。所以,最好的實踐是:expvar或者類似風格的。
 

測試和驗證

在一年的過程中我們嘗試了許多的測試庫和架構,但是很快放棄了他們中的大部分,今天我們所有的測試通過資料驅動(表驅動)測試,用普通的包測試。我們沒有強烈或者明確的抱怨測試/檢查包,除此之外,他們根本沒有提供巨大的價值。有一件事情是有協助的:reflect.DeepEqual讓你更簡單的對任意值進行比較(例如expected對got)。

 

包測試是面向單元測試的,對於整合測試,就會有點麻煩。啟動並執行外部服務依賴於你的Integration Environment,但是我們找到了一個好的方式整合他們。寫一個integration_test.go,給它一個integration的構建標籤。定義(全域)標誌,比如服務地址和連接字串,用他們在你的測試中。

 

go test 和 go build 一樣建立標籤,所以你可以調用 go test -tags=integration 。它也綜合了 flag.Parse 包的 main,所以任何被聲明和可見的 flags 將被處理和提供給你的測試。

 

通過驗證,我的意思是靜態代碼驗證。幸運的是,Go 有一些很好的工具。我發現當考慮使用哪種工具時考慮編寫代碼的階段很有用。


當做這種事時          使用這個
儲存                  go fmt(或 goimports)
構建                  go vet,golint, 或者 go test
部署                  go test -tags=integration


插曲
到目前為止,還沒東西過於瘋狂。當做調查編撰這個列表的時候,讓我注意的只是如何。。。。。。結論如何的無趣。讓人沉悶。我想強調這些非常輕量,純標準庫的約定能真正推廣到大群體的開發人員和多元化的項目生態系統。你絕對不會僅僅因為你的程式碼程式庫已經超過一定的規模,或者只是因為它可能 增長超過一定行數, 而需要你自己的查錯架構,或者測試庫。你真的是不會需要它的。標準的文法和用法在代碼大規模時仍然功能優雅。


依賴管理
依賴管理的狀態在 Go 生態系統中是一個熱門的爭論點,我們還沒有想到完美的解決方案。但是,我們選用了一個似乎不錯的妥協方案。

 

你的項目有多麼重要?你的依賴管理方案是…
嗯… go get -d,然後祈禱!
很好.  VENDORING

(值得提出的是,我們有令人震驚數量的長期產品服務,依然依賴於第一個選項.然而,因為我們一般沒有使用太多第三方代碼,以及主要問題通常在編譯階段就被檢測到,我們僥倖規避了這個問題.)

 

Vendoring意味著拷貝依賴到項目程式碼程式庫,然後在編譯的時候使用它們.依賴於你下載的內容,這裡有兩個vendoring的最佳實務.


下載           Vendor目錄名         過程
二進位     _vendor               加GOPATH首碼編譯
庫           vendor                  重寫import語句

如果下載二進位,就在程式碼程式庫的根目錄建立一個_vendor子目錄.(帶上底線,這樣,go工具就會在處理時忽略它,例如go test ./...)對待它就像對待GOPATH一樣; 例如,拷貝這個依賴github.com/user/dep 到 _vendor/src/github.com/user/dep. 然後,編寫一個所謂的神聖的編譯過程,它將_vendor加入到可能存在的GOPATH之中. (記住: GOPATH 實際是一個路徑的列表,當go工具處理import時,會按順序搜尋這個列表.)例如,你可能擁有一個頂層的Makefile檔案,如下所示: 

 
如果你正在下載某個類庫在你的根存放庫上建立一個vendor子目錄。處理這件事就像在包目錄上加一個首碼。舉例來說,拷貝來自於github.com/user/dep的項目放到vendor/user/dep。在這之後,重寫你所有的引入(import),及其相互關係。此時是很痛苦的,當剩下的內容需要go get相容的時候,看起來最有效方式是確保事實上可重新構建(actually-reproducible build)。值得注意的是,我們在實踐中很少去下載類庫,因此這個辦法雖然麻煩卻很有效。

 

如何在實際中拷貝一個依賴關係到你自己的存放庫是另外一個熱門的話題。最簡單的方法是從一個複製(clone)中手動複製檔案,如果你不關心上遊部門的推送,這可能是最好的答案。有些人使用git子模組,但我們發現它們非常違反直覺並難以管理(對許多 人來說也是這樣,這是有記錄的)。我們對於git子目錄(的管理)已經很成功,他工作起來就像是子模組。還有大量的工具是用來自動處理這項工作的。現在,它看起來就像godep發展非常積極,而且還很值得研究。


構建與部署

構建與部署有其技巧性,因此它與你的作業環境耦合緊密。我要描述下我們的情境,因為我認為它是個好模型,但它可能無法直接應用到你的組織機構中。

 

就構建而言,我們通常直接使用 go build 來開發,以及一個 Makefile 用於剪裁官方構建。這主要是因為我們熟悉多種語言,並且我們的工具使用需要做到最小功能合集(最小公倍數)。並且,我們的構建系統始於一個空環境,也需要自備編譯器( Makefile 檔案很難看!)。

 

對部署而言,對我們最大的吸引是無狀態之於有狀態。

 

模式     範例 模型 部署名稱 部署形式
無狀態 Request router 12-Factor Scaling        Containers
有狀態 Redis          None,really ProvisioningContainers?

我們主要部署無狀態的服務,方式類似於 Heroku。


結論
我有意讓這些成為一種來自一個大組織在生產環境相對較長地運行Go 的經驗報告。雖然這些都是有根據的意見,但他們仍然只是意見,所以請持保留態度。這就是說,Go 最大優勢是它結構簡單。最終的最佳做法是擁抱簡單,而不是試圖繞過它。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.