先看代码:
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 () 函数来释放相关资源。