正常快取沒有資料時會先從DB取資料來回填快取,而如果瞬間查詢過多或者快取利用率過低。
singlefly
當瞬間過多查詢到快取的空值時就會一起去查詢資料庫,帶給資料庫壓力變大。這裡不能直接用 mutex,如果用了拿不到資源的會自旋等待,拿到後繼續查 DB,用 mutex 可能會出現整個邏輯處於一直查 DB 的狀態。
// Cache 是一個簡單的快取結構 type Cache struct { mu sync.RWMutex data map[string]string group singleflight.Group } // NewCache 初始化一個新的 Cache 例項 func NewCache() *Cache { return &Cache{ data: make(map[string]string), } } // Get 從快取中獲取值,如果值不存在則呼叫 fetcher 函式來更新快取 func (c *Cache) Get(key string, fetcher func() (string, error)) (string, error) { c.mu.RLock() value, exists := c.data[key] c.mu.RUnlock() if exists { return value, nil } // 使用 singleflight 確保只有一個請求會觸發 fetcher 呼叫 result, err, _ := c.group.Do(key, func() (interface{}, error) {
// 假設這個 fetcher 方法就是從資料庫拿資料 value, err := fetcher() if err != nil { return nil, err } // 假裝這裡在回填快取 c.mu.Lock() c.data[key] = value c.mu.Unlock() return value, nil }) if err != nil { return "", err } return result.(string), nil }
空快取設定
當資料庫沒有的資料的查詢會反覆查詢資料庫,這時可以在快取中設定空值進行防護。
// 使用 singleflight 確保只有一個請求會觸發 fetcher 呼叫 result, err, _ := c.group.Do(key, func() (interface{}, error) { value, err := fetcher() if err != nil { // 對於獲取失敗的情況,不快取空值 return nil, err } c.mu.Lock() // 如果資料庫返回空結果,快取一個特殊的空值 if value == "" { c.data[key] = "" c.mu.Unlock() return "", errors.New("fetched but empty value") } c.data[key] = value c.mu.Unlock() return value, nil }) if err != nil { return "", err }