好坑啊,调用了同事写的基础代码,bug藏得还挺深!!
起因
事情的起因是我调用了同事的一个函数,这个函数返回了一个map[string]string结构体的变量optionMap(请忽略为什么要返回map结构体,后面有机会再讲),这个函数主要是查DB取获取当前系统的space_id和pkey,返回的内容基本上如下
// 返回
optionMap = map[string]string{
"space_id":"xxx",
"pkey": "xxxx",
}
然后我修改了这个变量,添加了
optionMap["is_base"] = 1
然后我就return出我当前的函数了,然后在同一个请求内,但是当我再一次请求同事的函数时,返回给我的却是
optionMap = map[string]string{
"space_id":"xxx",
"pkey": "xxxx",
"is_base": 1,
}
what! 怎么后面再次请求同事的的函数总是会多一个参数呢!!!!
经过
看了同事写的函数我才发现,原来他在内部使用了gin上下文去做了一个缓存,大概的代码意思减少重复space基础信息的查询,存入上下文中做缓存,提高代码效率,这里我写了一个示例大家可以看下
// 同事的代码
func BadReturnMap(ctx *gin.Context, key string) map[string]interface{} {
m := make(map[string]interface{})
// 查询缓存
value, ok := ctx.Get(key)
if ok {
bm, ok := value.(map[string]interface{})
if ok {
return bm
}
}
// io查询后存入变量
m["a"] = 1
// 保存缓存
fmt.Println("set cache: ")
fmt.Println(m) // map[a:1]
ctx.Set(key, m)
return m
}
// 我的使用
func TestBadReturnMap() {
fmt.Println("bad return map start")
ctx := &gin.Context{}
key := "cached:map_key"
mapOpt := BadReturnMap(ctx, key)
fmt.Printf("%p\n", mapOpt) // 0xc0003a6750 指向地址
mapOpt["b"] = 1
value, ok := ctx.Get(key)
fmt.Printf("%p\n", mapOpt) // 0xc0003a6750 指向地址
if ok {
fmt.Println("get cache: ")
fmt.Println(value.(map[string]interface{})) // map[a:1 def:1]
} else {
fmt.Println("unknown")
}
fmt.Println("bad return map end")
}
打印结果是
bad return map start
set cache:
map[a:1]
0xc0003a6750
0xc0003a6750
get cache:
map[a:1 b:1]
bad return map end
解释
在Go语言中,map是引用类型,当将一个map赋值给另一个变量时,实际上是将它们指向同一个底层的map对象。因此,当你修改其中一个变量的map时,另一个变量也会受到影响。
当你将函数内m变量赋值给外部函数内的变量时,它们实际上指向同一个map对象。所以当你在外部函数内修改mapOpt的值时,原始的缓存也会被修改。
如图所示
如何修改
当然修改方式有很多种,我这里列举了一种就是序列化存储到缓存然后反序列化取,如果你有更好的方式可下方留言
func ReturnMap(ctx *gin.Context, key string) map[string]interface{} {
m := make(map[string]interface{})
value, ok := ctx.Get(key)
if ok {
bytes := value.([]byte)
err := json.Unmarshal(bytes, &m)
if err != nil {
panic(err)
}
return m
}
// io查询后存入变量
m["a"] = 1
jsonBytes, err := json.Marshal(m)
if err != nil {
panic(err)
}
fmt.Println("set cache: ")
fmt.Println(m)
ctx.Set(key, jsonBytes)
return m
}
func TestReturnMap() {
fmt.Println("return map start")
ctx := &gin.Context{}
key := "cached:map_key"
mapOpt := ReturnMap(ctx, key)
fmt.Printf("%p\n", mapOpt)
mapOpt["b"] = 1
fmt.Printf("%p\n", mapOpt)
value, ok := ctx.Get(key)
if ok {
m := make(map[string]interface{})
bytes := value.([]byte)
err := json.Unmarshal(bytes, &m)
if err != nil {
panic(err)
}
fmt.Println("get cache: ")
fmt.Println(m)
} else {
fmt.Println("unknown")
}
fmt.Println("return map end")
}
打印的结果为:
return map start
set cache:
map[a:1]
0xc0003a6870
0xc0003a6870
get cache:
map[a:1]
return map end
知识点
作者:沙蒿同学
来源:juejin.cn/post/7330869056411058239
来源:juejin.cn/post/7330869056411058239