先看代碼:
func AsyncAdd(run func() error) {
//TODO: 扔進異步協程池
go run()
}
func GetInstance(ctx context.Context,id uint64) (string, error) {
data,err := GetFromRedis(ctx,id)
if err != nil && err != redis.Nil{
return "", err
}
// 沒有找到數據
if err == redis.Nil {
data,err = GetFromDB(ctx,id)
if err != nil{
return "", err
}
AsyncAdd(func() error{
return UpdateCache(ctx,id,data)
})
}
return data,nil
}
func GetFromRedis(ctx context.Context,id uint64) (string,error) {
// TODO: 從redis獲取信息
return "",nil
}
func GetFromDB(ctx context.Context,id uint64) (string,error) {
// TODO: 從DB中獲取信息
return "",nil
}
func UpdateCache(ctx context.Context,id interface{},data string) error {
// TODO:更新緩存信息
return nil
}
func main() {
ctx,cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
_,err := GetInstance(ctx,2021)
if err != nil{
return
}
}
原因:因為誤用 導致異步更新失敗
分析#
我們先簡單分析一下,這一段代碼要幹什麼?其實很簡單,我們想要獲取一段信息,首先會從緩存中獲取,如果緩存中獲取不到,我們就從 DB 中獲取,從 DB 中獲取到信息後,在協程池中放入更新緩存的方法,異步去更新緩存。整個設計是不是很完美,但是在實際工作中,異步更新緩存就沒有成功過。。。。
導致失敗的原因就在這一段代碼:
AsyncAdd(func() error{
return UpdateCache(ctx,id,data)
})
錯誤的原因只有一個,就是這個 ctx,如果改成這樣,就啥事沒有了。
AsyncAdd(func() error{
ctxAsync,cancel := context.WithTimeout(context.Background(),3 * time.Second)
defer cancel()
return UpdateCache(ctxAsync,id,data)
})
原因#
在原始的代碼中,UpdateCache 函數被調用時使用的是外部的上下文對象 ctx,這樣會導致 UpdateCache 函數的超時時間受到了 GetInstance 函數傳入的上下文對象的影響。
通過在匿名函數中創建一個新的上下文對象 ctxAsync 並將其傳遞給 UpdateCache 函數,可以確保 UpdateCache 函數的超時時間獨立於外部的上下文對象。
同時,由於 ctxAsync 是在匿名函數內部創建的,所以需要在函數執行結束時調用 cancel () 函數來釋放相關資源。