微商网站如何做推广,网站开发属于软件开发,跨境网站开发,深圳php网站建设1. 前言
之前在设计一个兼容函数的时候#xff0c;使用了sjson动态设入参数#xff0c;从而实现一些参数的兼容。大致的逻辑如下所示#xff1a;
// 有一堆不规则的json数据
{a:aaa,b:bbb,any_key1:{k…1. 前言
之前在设计一个兼容函数的时候使用了sjson动态设入参数从而实现一些参数的兼容。大致的逻辑如下所示
// 有一堆不规则的json数据
{a:aaa,b:bbb,any_key1:{key:value},any_key2:{key:value}
}
// 因为any_key1和any_key2这样的数字字符串的key还会有新增所以这个是无法固定的没有办法给到一个具体的结构体解析于是就人为的兼容给它们在处理的时候包一个外层的key
{a:aaa,b:bbb,number_key_data:{any_key1:{key:value},any_key2:{key:value}}
}
// 这样处理之后我只要定义number_key_data我就可以获取到具体的字符串数字为key的数据2. 实现
采用sjson对对应的非特定key进行新的结构值设入然后做一个兼容即可实现上面的逻辑于是有这段代码出来了。
package gjson_set_studyimport (encoding/jsonfmtgithub.com/tidwall/sjsontesting
)func TestGjsonSet(t *testing.T) {data : {a:aaa,b:bbb,any_key1:{key:value},any_key2:{key:value}}dataMap : map[string]interface{}{}err : json.Unmarshal([]byte(data), dataMap)if err ! nil {panic(err)}fmt.Println(adaptNumberKey(data, dataMap))// output// {a:aaa,b:bbb,number_key_data:{any_key1:{key:value},any_key2:{key:value}}} nil
}type Data struct {A string json:aB string json:bNumberKeyData map[string]struct {Key string json:keyValue string json:value} json:number_key_data
}var specificKeys map[string]bool{a: true,b: true,
}func adaptNumberKey(data string, dataMap map[string]interface{}) (string, error) {var err errorfor k, v : range dataMap {if _, ok : specificKeys[k]; ok {continue}data, err sjson.Delete(data, k) // remove firstif err ! nil {return , fmt.Errorf(delete key data error, key%s, err%w, k, err)}data, err sjson.Set(data, number_key_data.k, v)if err ! nil {// log...return , fmt.Errorf(set number_key_data error, key%s, err%w, k, err)}}return data, nil
}上面的实现不出意外的话是不会有任何的问题但是不出意外的意外出现了当类似的逻辑代码上线之后我们发现一个问题容器会爆内存。这代码也实现了自测也过了QA的测试为什么会突然爆内存呢
3. 问题
容器爆内存的问题出现了但并不是所有的数据都爆内存有一组数据100%会爆内存它们的数据类似
{a:aaa,b:bbb,1000000:{key:value},50000000:{key:value}}比较明显的是出现了数字字符串的key然后结合代码看了一下刚开始也没看出啥异常觉得这个修改后的数据应该是
{a:aaa,b:bbb,number_key_data:{1000000:{key:value},50000000:{key:value}}}但后面发现爆内存的问题又想起了设置数组的方式当前的代码逻辑如果遇到数字key就会被认为是在设置数组开辟几百万甚至上千万长度的数组(细思极恐) 于是就发现了爆内存的问题所在数字key在未经特殊标识的情况下会被认定为数组于是这个设置key的过程就变成了对一个key的长为1000000的数组设置值(后者是50000000)可怕。
4. 解决方法
于是参看源码照着sjson的set方法一路向下看可以发现如果在parsePath 中我们对路径添加了: 的前缀sjson会强制把这个key当做string key而在atoui中不会将其解析为一个具体的数字进而导致对字符串key的设置变成对数组的设值。
func parsePath(path string) (res pathResult, simple bool) {var r pathResultif len(path) 0 path[0] : { // 如果含有:符号这个key会被强制认定为keyr.force truepath path[1:]}for i : 0; i len(path); i { // 对path进行分解if path[i] . {r.part path[:i]r.gpart path[:i]r.path path[i1:]r.more truereturn r, true}if !isSimpleChar(path[i]) {return r, false}if path[i] \\ {// go into escape mode. this is a slower path that// strips off the escape character from the part.// ...}return r, true
}// atoui does a rip conversion of string - unigned int.
func atoui(r pathResult) (n int, ok bool) {if r.force {return 0, false}for i : 0; i len(r.part); i {if r.part[i] 0 || r.part[i] 9 {return 0, false}n n*10 int(r.part[i]-0)}return n, true
}于是修改代码逻辑将所有key的前缀都加上:的标识。
func adaptNumberKey(data string, dataMap map[string]interface{}) (string, error) {var err errorfor k, v : range dataMap {if _, ok : specificKeys[k]; ok {continue}data, err sjson.Delete(data, k) // remove firstif err ! nil {return , fmt.Errorf(delete key data error, key%s, err%w, k, err)}data, err sjson.Set(data, number_key_data.:k, v)if err ! nil {// log...return , fmt.Errorf(set number_key_data error, key%s, err%w, k, err)}}return data, nil
}
// Output: {a:aaa,b:bbb,number_key_data:{1000000:{key:value},50000000:{key:value}}} nil5. 小结
忽然想到遇到的这个小问题当时就觉得还是自己单测的场景不够全面导致了这次爆内存的问题发生还好有临时解决方案不然对线上服务造成的影响还真不小。通过这个事例再一次告诫自己在后续的代码编写中对于通用功能的逻辑代码要尽可能的思考一些边缘case从而避免在上线后边缘case导致代码崩溃的现象出现。