go實現singlefight,防止緩存擊穿

簡介

在分佈式的系統當前,有的時候我們會把常用到數據放到緩存當中,並且設置該緩存的有效時間,一旦到達有效時間之後,自動刪除該數據。服務端接收到請求之後,優先請求緩存,如果緩存沒有命中,再通過數據庫查詢。有的時候緩存失效的瞬間,一下子湧入大量的數據請求,這些請求會全部轉發到數據庫。會了解決這種流量的波動,可以利用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來執行,發現同一時間只能有一個進程處於執行的狀態,其他的都返回的是相同的結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章