Go 使用 buger/jsonparser 优化反序列化性能

前言

最近在做一个工具,从 日志系统 ES 集群导出数据,压缩然后上传到腾讯云存储,作为离线冷备份数据。在此过程中,遇到了数个性能问题。

其中 Elasticsearch SDK 的反序列化性能成为了一个瓶颈,因此在这里记录下处理方案。

反序列化性能

olivere/elastic 是一个常用的功能非常强大的 Go Elasticsearch 客户端 SDK,它将大部分 Elasticsearch 的请求参数封装成结构体和方法,易用程度非常高。在处理请求返回值时 olivere/elastic 会使用 Go 内建的 json 库,将请求结果反序列化为对应的结构体,非常便于后续操作。

但是当导出大量数据的时候,比如用 Search/Scroll 每批次上万条文档,进行索引导出时,经 pprof 分析,反序列化会占用大量的 CPU 循环。

因此,为了特定需求,追求极限性能,有必要对此进行特殊处理。

使用 buger/jsonparser

好在 olivere/elastic 提供了 PerformRequest 方法,返回裸字节,我们可以复用现有的工具构建请求,然后自行对返回原始字节进行处理。

这里选用了 buger/jsonparser 库,这个库可以在原始 JSON 字节上进行特定嵌套字段的搜索,和数组遍历。

相较于反序列化为结构体,再对字段进行处理,这种方法可以极大地提升性能并节约内存,确切说,不会消耗任何额外内存。经验证,反序列化所占用的 CPU 循环已经由原先的 80% 减少到 20%。

buger/jsonparser 的使用方法很简单,可以参考官方文档,也可以参考我下面这个例子:

这个例子就是从 Elasticsearch 的 Search/Scroll 结果中,获取 hits.hits 数组的 _source 字段,并调用外部回调,将 _source 字段的原始 JSON 字节传递出去。

// find hits.hits
	var hitsBuf []byte
	var hitsType jsonparser.ValueType
	if hitsBuf, hitsType, _, err = jsonparser.Get(buf, "hits", "hits"); err != nil {
     
		return
	}
	if hitsType != jsonparser.Array {
     
		err = errors.New("hits.hits is not array")
		return
	}

	// iterate hits.hits
	var itErr error
	var itCount int64
	_, _ = jsonparser.ArrayEach(hitsBuf, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
     
		itCount++
		if itErr != nil {
     
			return
		}
		srcBuf, srcType, _, srcErr := jsonparser.Get(value, "_source")
		if srcErr != nil {
     
			itErr = srcErr
			return
		}
		if srcType != jsonparser.Object {
     
			itErr = errors.New("missing _source in hits.hits")
			return
		}
		if itErr = e.handler(srcBuf, e.count, e.total); err != nil {
     
			return
		}
		atomic.AddInt64(&e.count, 1)
	})

可以参考我以此为基础封装的库 https://github.com/guoyk93/esexporter

当然,基于 []byte 做搜索不算是完全的流式解析,但是已经比反序列化然后再做处理要好很多了。

如果不考虑复用现有的 *elastic.Client 对象,你可以选择使用使用其他的基于 io.Reader 的 JSON 流式解析库,做到完全不占用内存的实时提取。

核心的观点就是,面对特定需求,流式解析会极大地提升性能和内存占用

你可能感兴趣的:(Golang,elasticsearch,go,json)