内容简介:在实践 Kubernetes 中应用日志的收集方案时,发现有一些日志无法收集到承接《入门——
写在前面
在实践 Kubernetes 中应用日志的收集方案时,发现有一些日志无法收集到 E lastic S earch(ELK 技术栈中的 E ,其他两个是 L ogstash 和 K ibana)。查了原因后发现原来是因为业务打印的日志“不合规”,不仅无法保存到 ES 中,还会影响 ES 保存日志的效率;业务方不乐意,负责 ES 的同事也不乐意 :pensive:
承接《 Go 反序列化 JSON 字符串的两种常见用法 》,谈谈 Go 标准库对 JSON 的处理效率。
适用人群
入门—— 初级√ ——中级——高级;本文适应初级及以上。
跑一跑 Benchmark 的数据
先看一个 JSON 字符串
// string-of-json JSON 字符串 {"employees":{"first":{"firstName":"Bill","lastName":"Gates"},"second":{"firstName":"George","lastName":"Bush"},"third":{"firstName":"Thomas","lastName":"Carter"}}} // echo ```string-of-json``` | python -m json.tool // 可以得到下面 Pretty 后的 JSON 格式 // Pretty 样式给人看起来比较舒服 { "employees": { "first": { "firstName": "Bill", "lastName": "Gates" }, "second": { "firstName": "George", "lastName": "Bush" }, "third": { "firstName": "Thomas", "lastName": "Carter" } } }
再看看需求
根据 ES 的限制,期望能够把上面的 JSON 字符串转换成为下面的模样:
// ① 把大于第一层的所有内容转换为字符串 { "employees": "{\"first\":{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},\"second\":{\"firstName\":\"George\",\"lastName\":\"Bush\"},\"third\":{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}}" } // ② 把大于第二层的所有内容转换为字符串 { "employees": { "first": "{\"firstName\":\"Bill\",\"lastName\":\"Gates\"}", "second": "{\"firstName\":\"George\",\"lastName\":\"Bush\"}", "third": "{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}" } } // ③ 把大于第三层的所有内容转换为字符串 // 从效果来看,最后一层存在字符串转义,不太期望如此,可是也还好把 { "employees": { "first": { "firstName": "\"Bill\"", "lastName": "\"Gates\"" }, "second": { "firstName": "\"George\"", "lastName": "\"Bush\"" }, "third": { "firstName": "\"Thomas\"", "lastName": "\"Carter\"" } } }
用代码跑跑数据
package hello import ( "encoding/json" "fmt" "testing" ) var data = `{"employees":{"first":{"firstName":"Bill","lastName":"Gates"},"second":{"firstName":"George","lastName":"Bush"},"third":{"firstName":"Thomas","lastName":"Carter"}}}` func Parser(data []byte) interface{} { var i interface{} json.Unmarshal(data, &i) return i } func Test_Unmarshal(t *testing.T) { da := Parser([]byte(data)) fmt.Printf("%v\n", da) } // go test -v -timeout 30s . -run ^Test_Unmarshal$ // map[employees:map[first:map[firstName:Bill lastName:Gates] second:map[firstName:George lastName:Bush] third:map[firstName:Thomas lastName:Carter]]] func Benchmark_Unmarshal(b *testing.B) { for i := 0; i < b.N; i++ { Parser([]byte(data)) } } // go test -benchmem -run=^$ . -bench ^Benchmark_Unmarshal$ // Benchmark_Unmarshal-4 300000 4652 ns/op 2320 B/op 38 allocs/op func Test_Marshal(t *testing.T) { da := Parser([]byte(data)) d, _ := json.Marshal(da) fmt.Println(string(d)) } // go test -v -timeout 30s . -run ^Test_Marshal$ // {"employees":{"first":{"firstName":"Bill","lastName":"Gates"},"second":{"firstName":"George","lastName":"Bush"},"third":{"firstName":"Thomas","lastName":"Carter"}}} func Benchmark_Marshal(b *testing.B) { da := Parser([]byte(data)) for i := 0; i < b.N; i++ { //use b.N for looping json.Marshal(da) } } // go test -benchmem -run=^$ . -bench ^Benchmark_Marshal$ // Benchmark_Marshal-4 200000 6455 ns/op 2192 B/op 49 allocs/op func Test_MarshalLevel1(t *testing.T) { da := Parser([]byte(data)) daJSON := da.(map[string]interface{}) for k, v := range daJSON { d, _ := json.Marshal(v) daJSON[k] = string(d) } s, _ := json.Marshal(daJSON) fmt.Printf("Test_MarshalLevel1 %v\n", string(s)) } // go test -v -timeout 30s . -run ^Test_MarshalLevel1$ // Test_MarshalLevel1 {"employees":"{\"first\":{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},\"second\":{\"firstName\":\"George\",\"lastName\":\"Bush\"},\"third\":{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}}"} func Benchmark_MarshalLevel1(b *testing.B) { for i := 0; i < b.N; i++ { //use b.N for looping da := Parser([]byte(data)) daJSON := da.(map[string]interface{}) for k, v := range daJSON { d, _ := json.Marshal(v) daJSON[k] = string(d) } json.Marshal(daJSON) } } // go test -benchmem -run=^$ . -bench ^Benchmark_MarshalLevel1$ // Benchmark_MarshalLevel1-4 100000 12399 ns/op 4880 B/op 90 allocs/op func Test_MarshalLevel2(t *testing.T) { da := Parser([]byte(data)) daJSON := da.(map[string]interface{}) for k1, v1 := range daJSON { v1Map := v1.(map[string]interface{}) for k2, v2 := range v1Map { d, _ := json.Marshal(v2) v1Map[k2] = string(d) } daJSON[k1] = v1Map } s, _ := json.Marshal(daJSON) fmt.Printf("Test_MarshalLevel2 %v\n", string(s)) } // go test -v -timeout 30s . -run ^Test_MarshalLevel2$ // Test_MarshalLevel2 {"employees":{"first":"{\"firstName\":\"Bill\",\"lastName\":\"Gates\"}","second":"{\"firstName\":\"George\",\"lastName\":\"Bush\"}","third":"{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}"}} func Benchmark_MarshalLevel2(b *testing.B) { for i := 0; i < b.N; i++ { //use b.N for looping da := Parser([]byte(data)) daJSON := da.(map[string]interface{}) for _, v1 := range daJSON { v1Map := v1.(map[string]interface{}) for k2, v2 := range v1Map { d2, _ := json.Marshal(v2) v1Map[k2] = string(d2) } } json.Marshal(daJSON) } } // go test -benchmem -run=^$ . -bench ^Benchmark_MarshalLevel2$ // Benchmark_MarshalLevel2-4 100000 13297 ns/op 4881 B/op 96 allocs/op func Test_MarshalLevel3(t *testing.T) { da := Parser([]byte(data)) daJSON := da.(map[string]interface{}) for k1, v1 := range daJSON { v1Map := v1.(map[string]interface{}) for k2, v2 := range v1Map { v2Map := v2.(map[string]interface{}) for k3, v3 := range v2Map { d, _ := json.Marshal(v3) v2Map[k3] = string(d) } v1Map[k2] = v2Map } daJSON[k1] = v1Map } s, _ := json.Marshal(daJSON) fmt.Printf("Test_MarshalLevel2 %v\n", string(s)) } // go test -v -timeout 30s . -run ^Test_MarshalLevel3$ // Test_MarshalLevel2 {"employees":{"first":{"firstName":"\"Bill\"","lastName":"\"Gates\""},"second":{"firstName":"\"George\"","lastName":"\"Bush\""},"third":{"firstName":"\"Thomas\"","lastName":"\"Carter\""}}} func Benchmark_MarshalLevel3(b *testing.B) { for i := 0; i < b.N; i++ { //use b.N for looping da := Parser([]byte(data)) daJSON := da.(map[string]interface{}) for k1, v1 := range daJSON { v1Map := v1.(map[string]interface{}) for k2, v2 := range v1Map { v2Map := v2.(map[string]interface{}) for k3, v3 := range v2Map { d, _ := json.Marshal(v3) v2Map[k3] = string(d) } v1Map[k2] = v2Map } daJSON[k1] = v1Map } json.Marshal(daJSON) } } // go test -benchmem -run=^$ . -bench ^Benchmark_MarshalLevel3$ // Benchmark_MarshalLevel3-4 100000 14319 ns/op 4720 B/op 105 allocs/op
分析一下数据
# 整合上面的 benchmark 的数据 Benchmark_Unmarshal-4 300000 4652 ns/op 2320 B/op 38 allocs/op Benchmark_Marshal-4 200000 6455 ns/op 2192 B/op 49 allocs/op Benchmark_MarshalLevel1-4 100000 12399 ns/op 4880 B/op 90 allocs/op Benchmark_MarshalLevel2-4 100000 13297 ns/op 4881 B/op 96 allocs/op Benchmark_MarshalLevel3-4 100000 14319 ns/op 4720 B/op 105 allocs/op
go test -count=2 -benchtime 3.1s -benchmem . -bench .
小结
以前在写业务代码的时候,记得有一次对接 C#
技术栈的一个服务,其返回的数据格式是本文中提到的 “大于第 x 层的内容转换为字符串” 类似的存在,当时一直想不明白为什么要有这样的设计。现在来看,很可能是开发者把本该用在日志上的规范用到了 api 接口规范。
- 理论上来说,对于 api 接口,规规矩矩地返回 JSON 就好了嘛,就不要玩嵌套了,避免给调用接口的开发者制造不痛快。
- 对于日志来说,那些已知并不会用作精确检索条件的字段(进一步,指那些内容格式不固定的字段)确实转换为字符串更好。
以上,希望对大家有所启发。
参考
- 如何使 Kubernetes 中的应用日志被收集得又快又稳 前面写的博文,介绍了收集 k8s 里应用日志的方案
- GitHub - bitly/go-simplejson: a Go package to interact with arbitrary JSON 封装了 Go 的 json 标准库的一个第三方库
- GitHub - a8m/djson: Fast Go decoder for dynamic JSON 可以与 Go 的 JSON 标准库做对比
- GitHub - buger/jsonparser 以效率著称,可以与 Go 的 JSON 标准库对比
- Benchmark: Retrieving Values from Deep Nested JSON at Golang · tanaike
- Go 测试,go test 工具的具体指令 flag - Deepzz’s Blog 可以参考 go test 的使用方法
- Golang测试用例的困境与最佳实践 - 敬维 早前写的关于 Go 测试用例的思考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Linux 下三种提高工作效率的文件处理技巧
- 剑指offer题解笔记:时间效率和空间效率的平衡
- Kubernetes云上效率方法论,10倍研发效率提升
- 领导怼程序员:效率高不是不加班的理由!程序员:那降低效率
- 浅谈 “效率” 提升
- 优化ElasticSearch写入效率
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。