最近在工作上遇到一个关于 json unmarshal 的问题。
参考文章
人在家中坐,锅从天上来
某天,组里的小哥对我说,某个服务的 function test failed,可能是与我的最近一次代码改动有关。我有点震惊,就那小改动,居然会影响。既然锅都来了,那就看看我的代码,看看是不是真的有问题。
首先,我们定位到 test failed 的地方,TestUpdateDriverTier function 失败了。接着我们去 kibana 看看 log, 发现错误是1
error=no tier config found for cityID : 1 and taxiTypes [233]
既然日志说没有 no found,我又去检查配置中心,结果配置中心是有对应的 json 配置的。
坑一
我随后又再看了一眼 function test ,离谱的地方是 TestUpdateDriverTier 失败了,但是 TestBulkUpdateDriverTier 成功了。我检查了两个代码的区别:UpdateDriverTier 和 BulkUpdateDriverTier 的唯一不同是:
BulkUpdateDriverTier 是一个 for 循环单个更新,而 UpdateDriverTier 是只有一个更新。两者背后调用的 function 都是一样了,这就使我百思不得其解了: 为什么只有 TestUpdateDriverTier 失败了。
难上加难的地方在于:function test 不像 unit test 可以轻松的在本地测试,function test 需要在远程服务器上测试。为了复现这个问题, 我在自己的电脑上调用那个 API, 结果还真就只有 UpdateDriverTier 报错误, BulkUpdateDriverTier 没有报错。
踩坑一是因为我自己检查代码是时候不够仔细, UpdateDriverTier 和 BulkUpdateDriverTier 还有一个巨大的区别。1
2
3
4
5
6
7
8
9
10
11// UpdateDriverTier ...
func (s *Service) UpdateDriverTier(ctx context.Context, driverTier *dto.DriverTier) error {
if err := s.TierCalculator.ForceUpdateTier(ctx, driverTier.DriverID, driverTier.Progress); err != nil {
log.Error(logkey.Error, "error in force update driver tier", logkey.DriverID, driverTier.DriverID, logkey.Error, err)
s.statsClient.Count1(statsdkey.ServiceContextTag, updateDriverTierTag, statsdkey.FailedTag)
return err
}
return nil
}
结果发现 BulkUpdateDriverTier 是异步的,即使有错误,gorutine 也不会返回到 main gorutine,所以 API 得到的结果是没有错误的。1
2
3
4
5
6
7
8
9
10
11
12
13
14// BulkUpdateDriverTier ...
func (s *Service) BulkUpdateDriverTier(ctx context.Context, driverTiers []*dto.DriverTier) error {
go func(driverTiers []*dto.DriverTier) {
for _, driverTier := range driverTiers {
if err := s.TierCalculator.ForceUpdateTier(ctx, driverTier.DriverID, driverTier.Progress); err != nil {
log.Warn(logkey.Message, "error in bulk force update driver", logkey.DriverID, driverTier.DriverID, logkey.Error, err)
s.statsClient.Count1(statsdkey.ServiceContextTag, bulkUpdateDriverTierTag, statsdkey.FailedTag)
}
}
}(driverTiers)
return nil
}
坑二
其实我在最开始的时候,是没有发现坑一的。我为了搞清楚为什么找不到配置,我就临时添加了代码打 log,把那个配置的变量给打印出来。结果发现了配置的 point nil1
feiyang="&{0 0 0 false <nil> map[] 0}"
我又换了一个输入,正常的配置应该是这个样子的1
feiyang="&{0 0 0 true 0xc0214e8e00 map[41:0xc01627b810 42:0xc01627b880 43:0xc01627b8f0 44:0xc01627b9d0] 0}"
我就开始搜索这个错误,发现一个类似的:反序列化Map或Slice的类型要求要与 序列化时Map或Slice的类型必须保持一致
差不多就是下面这个例子,golang struct 里面要求的是 Rank int,结果 json 传入的是 string,最终 Unmarshal 出错,得到默认初始值的 struct
1 | package main |
运行结果如下
1 | tierjson type is: []uint8 |
解决
我太菜了,搞了 2 天终于发现是因为配置 json 文件里面数据类型不一致导致的问题后,再次去配置中心检查,在密密麻麻的配置中,终于发现了那个错误(rank 是 string 类型)。下一步是:我们如何在配置的时候就就行校验,提前预防这样的错误呢?