问题
很多同学都认为如果我知道json.marshal的值,我就不用判断它执行之后返回错误,包过工作5,6年的经验的一些高工也是这么认为的。然而到底要不要判断呢?我这里先不给结论,我们先来看下我们业务中出现的问题。
事故
我们的线上的业务后台是通过前端传方法和方法名过来识别走不同的方法和方法名实现功能,有个同学想把http.request这个变量传到方法里面进行使用,但是我们原来的http的response有做整个数据缓存的逻辑,我们的参数是通过map传入到方法名里面,这个同学在传入参数的时候把http.request当成value,key多加了个requestInfo传入方法名里面。我们的缓存代码是把整个请求参数json.Marshal之后当成key,缓存每次请求的结果在进程内存中。这个同学加完之后,放到测试环境没有特别留意缓存的数据问题,只看了自己写的功能,等到回归测试的之后,测试同学对整体功能回归了一下,发现整个后台数据只显示一个结果。然后这个同学开始定位问题,先看下参数改的请求参数:
func getObjFuncParam(rw http.ResponseWriter, br []byte) (string, string, map[string]interface{}, error) {
input := new(QueryModel)
err := json.Unmarshal(br, &input)
if err != nil { fmt.Errorf("获取参数错误")
}
return input.Obj, input.Func, input.Param, nil
}
看起来没问题,然后看下getFuncResult方法,整个方法是做整体后台调用缓存用的,在这之前已经做了Param["requestInfo"] = *http.Request整个的赋值。看下getFuncResult的代码:
func getFuncResult(rw http.ResponseWriter, obj string, funcName string, Param map[string]interface{}, paramBytes []byte) *model.ResData {
expire, ok := cache.AllCacheMap[obj][funcName]
_, isExport := Param["isCSV"]
if !ok || isExport {
result := xxxxx(rw, obj, funcName, Param)
return result
}
// 走缓存
key, err := cache.GetQueryKey(obj, funcName, paramBytes)
if err != nil {
log.Error("cache.GetQueryKey", zap.Error(err))
}
result, err := cache.GetCacheKey(key)
if err != nil {
// 分发请求
result = xxxxx(rw, obj, funcName, Param)
if result.Code == errors.ADMIN_SECUSS {
// TODO HANK 根据环境设置时间
// 缓存处理
cache.SetQueryData(key, result, expire)
}
}
return result
}
也没看出来问题,看起来一切逻辑正常,然后翻了一下GetCacheKey的代码。
func GetCacheKey(obj, funcName string, param map[string]interface{}) string {
m := map[string]interface{}{
"obj": obj,
"funcName": funcName,
"param": filterSpecialParam(param, true),
}
bin, _ := json.Marshal(m)
hash := fmt.Sprintf("%x", md5.Sum(bin))
appzaplog.Info("GetQueryKey Key", zap.String("Cache2Go Field", hash))
return hash
}
// filterParam -过滤特殊的参数做缓存
func filterSpecialParam(param map[string]interface{}, isFilter bool) map[string]interface{} {
if !isFilter {
return param
}
b, err := json.Marshal(param)
if err != nil {
return param
}
newMap := map[string]interface{}{}
err = json.Unmarshal(b, &newMap)
if err != nil {
return param
}
//删除特殊标识
delete(newMap, "userId")
delete(newMap, "userName")
return newMap
}
后面定位了很久才知道是整个filterSpecialParam没有做错误处理,整个都是另外一个同学协助才查到整个问题。因为filterSpecialParam出错了,每次都是直接返回空的map,主要原因是json.Marshal没有处理错误。
复盘
后面我们单独改造了一下filterSpecialParam,对错误进行处理,也对json.Marshal进行学习,为什么上面的会报错了,是因为http.Request里面包含函数类型,这个是不支持json.Marshal的。
针对于不能json.Marshal的我们还做了一些test。
package my_test_test
import (
"encoding/json"
"fmt"
"reflect"
"testing"
)
type MyJson struct {
Name string `json:"name"`
Age int `json:"age"`
Func func() `json:"func"`
//Point unsafe.Pointer `json:"point"`
//PointInt *int `json:"point_int"`
}
func TestJson(t *testing.T) {
tt := func() {}
fmt.Println(reflect.TypeOf(tt))
tmp := MyJson{}
res, err := json.Marshal(&tmp)
fmt.Println(string(res), err)
}
报错如下:
json: unsupported type: func()
json: unsupported type: unsafe.Pointer
深入看了一下源码支持的有如下几种:
我这里就不详细说了,有兴趣可以看看,我这里是官方的encoding包,这个都是用反射实现的json,有个兼容官方的包,其功能和这个也是一样的,就这样的一个问题能把整个后台功能搞瘫痪,所以编程规范是多么重要。
总结
json.Marshal的error到底要不要判断?
答:要,所有的程序的返回error都要处理,凡是error,我们最好都进行判断,错误都打错误日志,养成良好的开发习惯,这样查问题解决问题就比较快一些。