天天看點

php與go開發的一點心得,go項目的一些心得

php與go開發的一點心得,go項目的一些心得

spacex

最近小夥伴們剛完成廣告系統,第二個直接服務于業務的項目。踩了一些坑,更收獲了不少知識。總結出來與大家分享,沒什麼高大尚技術,都是周邊的小技巧,加深對 go 語言的了解,适合新手,老鳥勿噴。

包管理

很多人都認為 go 的包管理不夠友好,深有感覺。特别是在 github 上給别人提 patch, 我先 fork 到自已目錄下面,如果原作者有引用自已路徑下面的庫,這就麻煩了。

另外一個是版本管理,每個人的 gopath 下面同樣的庫可能有不同版本,官方提供了一個 Godep 來控制版本,我看很多開源項目也在用。但是如果想管理除 go 以外的依賴呢?

我們使用相對路徑的方式,将引用到的庫集中放到 submodule 中,如下圖:

php與go開發的一點心得,go項目的一些心得

包管理

我司将所有語言第三方庫都放到 tinder 裡面,包括多個組之間共用的 thrfit IDL 檔案。Go 第三方庫都放到 golang/lib 目錄下面,共用的内部庫放到 golang/src/common 下面,每次 install 編譯程式時将 gopath 指定到目前項目下的相對目錄。

更新20160629:現在依賴使用govender,第三方的IDL使用submodule

逾時控制與請求跟蹤

由于業務對時延要求高,給我們定 50ms 逾時時間。大家肯定會想到用 Channel 和 timer 來做控制,但是我們還想跟蹤請求在内部的一系列操作,否則 Debug 日志一大堆,無法定位。

此時想到了 golang.org/x/net/context 庫,官方文檔很詳細,用于跨 API 調用很友善,vitess 中大量使用這個庫。// A Context carries a deadline, a cancelation signal, and other values across

// API boundaries.

// Context's methods may be called by multiple goroutines simultaneously.

type Context interface {

// Deadline returns the time when work done on behalf of this context

// should be canceled.  Deadline returns ok==false when no deadline is

// set.  Successive calls to Deadline return the same results.

Deadline() (deadline time.Time, ok bool)

// Done returns a channel that's closed when work done on behalf of this

// context should be canceled.  Done may return nil if this context can

// never be canceled.  Successive calls to Done return the same value.

// See http://blog.golang.org/pipelines for more examples of how to use

// a Done channel for cancelation.

Done()

// Value returns the value associated with this context for key, or nil

// if no value is associated with key.  Successive calls to Value with

// the same key returns the same result.

// Use context values only for request-scoped data that transits

// processes and API boundaries, not for passing optional parameters to

// functions.

// Packages that define a Context key should provide type-safe accessors

// // userKey is the key for user.User values in Contexts.  It is

// // unexported; clients use user.NewContext and user.FromContext

// // instead of using this key directly.

Value(key interface{}) interface{}

}

一個請求過來,每一次流轉都要攜帶 context.Context, 并且首先檢測是否逾時,如果逾時或是被取消,那麼直接傳回。另外 context.Context 會攜帶每次請求 ID,這是由業務傳過來的字段,如果為空,内部會生成一個 uuid 來辨別。

php與go開發的一點心得,go項目的一些心得

最終執行的代碼邏輯

逾時參數由業務傳過來,根據 timeout 生成 context.Context,最終函數要麼由 ctx.Done 逾時傳回,要麼從 rr channel 中擷取業務結果傳回。 真實業務請求會開啟一個匿名 goroutine, 傳入的 context.Context 攜帶了 logid, 内部打日志都會先列印 logid.

php與go開發的一點心得,go項目的一些心得

cancel signal

在每個耗時請求(redis/mysql)的入口,都會先檢測是否逾時。

goroutine和panic

這塊學藝不精,不像 actor 有父子關系,函數派生出來的 goroutine 如果panice 會挂掉整個程式,比如如下代碼:

php與go開發的一點心得,go項目的一些心得

錯誤示例

最開始程式如上圖,原以為會捕獲到 do_something 産生的 panic, 還是太年輕啊。要将 recover 放置在 go func 入口。

緩存髒資料

我們會在 redis 緩存使用者資訊,過期時間 6 小時,如果沒有再 fallback 到資料庫,另外還有一個程式内置 lru cache.

程式更新後,發現測試邏輯不對,uid 始終為0,fix 這個問題後,緩存這時就出現了髒資料。這時有兩個辦法,選擇了第2個。1. 使用 redis-port 批量清除無效緩存

2. 再次更新程式,内部修訂錯誤資料

php thrift 逾時問題

這個問題蠻頭痛,網上也有人遇到過 thrift中的逾時(timeout)坑。底層有三個逾時時間 connect, send 和 recv,最初都設定的 100ms,線上每天大量逾時報錯,後來我們将 recv timeout 調到 1000ms 線上就安靜了。

另外兩個 connect, send 仍然是 100ms,我們更傾向于底層驅動的逾時時間稍長一些,由業務層來控制逾時 ( context 庫)。

對象池

對象池是不同于連接配接池,兩個概念的東西。連接配接池特指 redis/mysql 的長連接配接,常駐記憶體。而對象池是内部執行個體,使用對象池可以減少程式 GC 壓力。目前常用的有兩種  sync.Pool 和 channel 模拟的對象池。官方有對 sync.Pool 的詳細說明,對象會在兩個 GC 之間被回收釋放,而 channel 則會常駐。

php與go開發的一點心得,go項目的一些心得

對象池

代碼很簡單也易懂,Get 時 channel 有資料就傳回,沒有直接 New。至于 channel 緩沖大小,要根據業務壓力來定。

内部服務注冊

在全局 map 注冊服務,這也算是 go 程式标配了,最出名的就是官方 database 庫注冊 mysql driver 的代碼

php與go開發的一點心得,go項目的一些心得

服務注冊

實作在 driver.Driver 接口的服務,直接注冊進來即可,使用時直接根據 name 找到 driver。

ServerOnRun

服務内部子產品有大量初始化的需求,對于全局變量等直接扔到 init() 函數裡即可,但是對于依賴外部服務 (mysql/redis/servervice),在程式啟動時連接配接句柄都不存在,就不能扔到 init() 裡。

一種做法就是在各個子產品裡寫 init_xx()等方法,然後在 main() 啟動初始化外部配置後,去調用,不過這樣在 main 裡維護就很麻煩。

是以要在全局定義 ServerOnRun, 每個無法由 init() 完成的初始化都在這裡進行注冊,最後由 main 周遊 ServerOnRun 來執行即可。

thrift字段變更問題

業務更新改動,時常會加字段,并且為了相容現有代碼,必須設為 optional。另外有時還遇到要将字段類型由 int 換成 string 的問題,比較麻煩,前期還是要設計好

json序列化

程式内部有大量的json序列化需求,官方的稍慢,采用比較流行的 ffjons

有疑問加站長微信聯系(非本文作者)