簡介
在分佈式的系統當前,有的時候我們會把常用到數據放到緩存當中,並且設置該緩存的有效時間,一旦到達有效時間之後,自動刪除該數據。服務端接收到請求之後,優先請求緩存,如果緩存沒有命中,再通過數據庫查詢。有的時候緩存失效的瞬間,一下子湧入大量的數據請求,這些請求會全部轉發到數據庫。會了解決這種流量的波動,可以利用singlefight來解決,這個函數可以合併相同的請求,保證同一個時刻只能有一個進程處於運行的狀態,其他的進程會處於等待的狀態。一旦獲取執行的進程執行完成後,所有的進程都會獲得相同的結果。一定程度來保證流量的平穩。
代碼
package main import "sync" type call struct { wg sync.WaitGroup val interface{} err error } type Group struct { mu sync.Mutex //互斥鎖 m map[string]*call //延遲初始化,通過Do函數進行 } // Do 單飛模式 //執行指定的程序,並返現執行的結果。 //確保在同一個時刻只能有一個進程在處理,如果有相同的進程需要處理會處於等待的狀態 //最終能夠保證所有的進程獲取同樣的結果 func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { g.mu.Lock() if g.m == nil { g.m = make(map[string]*call) } if c, ok := g.m[key]; ok { // 如果當前的key,已經處理,那麼等待之前的進程處理完成 g.mu.Unlock() c.wg.Wait() return c.val, c.err } c := new(call) c.wg.Add(1) g.m[key] = c g.mu.Unlock() c.val, c.err = fn() //處理完成,釋放waitGroup並且刪除該key c.wg.Done() delete(g.m, key) g.mu.Unlock() return c.val, c.err }
測試代碼
上面的程序模擬了一下同時刻有20個進程來請求數據
package main import ( "fmt" "math/rand" "sync" "time" ) func rpcRequest() (interface{}, error) { rd := rand.Intn(100) time.Sleep(time.Second) return rd, nil } func main() { var singlefight Group var wg sync.WaitGroup wg.Add(20) for i := 0; i < 20; i++ { go func(i int) { rd, _ := singlefight.Do("rpc", rpcRequest) fmt.Println("rpc result", rd) wg.Done() }(i) } wg.Wait() fmt.Println("執行結束") }
通過singlefight來執行,發現同一時間只能有一個進程處於執行的狀態,其他的都返回的是相同的結果