Loading

go分片读取大json避免OOM

背景

项目需要用go读取一个超过2GB的json文件,请求偶尔会触发OOM导致容器重启。
希望牺牲一些效率,分片读取减少内存年占用。

方法

直接调用ReadFile会载入整个文件,需要用到Decoder将json转化为stream
json结构

{
    "category_0": [{...},{...},{...}],
    "category_1": [{...},{...},{...}],
    "category_2": [{...},{...},{...}],
}

每次遇到的具体元素会被抽象为token

// A Token holds a value of one of these types:
//
//   - [Delim], for the four JSON delimiters [ ] { }
//   - bool, for JSON booleans
//   - float64, for JSON numbers
//   - [Number], for JSON numbers
//   - string, for JSON string literals
//   - nil, for JSON null
type Token any

通过Token精确控制读取位置,调用Decode完成局部数据的Unmarshal操作

实现

func ReadJson(ctx context.Context, jsonPath string) error {
	// ReadFile results OOM
	file, err := os.Open(jsonPath)
	if err != nil {
		return err
	}
	defer file.Close()
	decoder := json.NewDecoder(file)
	// Read opening bracket {
	if _, err := decoder.Token(); err != nil {
		return err
	}
	// Process batches to save memory
	const batchSize = 10000
	batch := make([]models.Result, 0, batchSize)
	for decoder.More() {
		// Read category key
		categoryToken, err := decoder.Token()
		if err != nil {
			return err
		}
		category := categoryToken.(string)
		// Read array opening bracket [
		if _, err := decoder.Token(); err != nil {
			return err
		}
		// Process individual elements
		for decoder.More() {
			var result models.Result
			if err := decoder.Decode(&result); err != nil {
				return err
			}
			batch = append(batch, result)
			// Insert when batch is full
			if len(batch) >= batchSize {
				if err := InsertBatch(ctx, &batch, category); err != nil {
					return err
				}
				batch = batch[:0] // clear and reuse memory
			}
		}
		// Insert remaining items
		if len(batch) > 0 {
			if err := InsertBatch(ctx, &batch, category); err != nil {
				return err
			}
			batch = batch[:0]
		}
		// Read array closing bracket ]
		if _, err := decoder.Token(); err != nil {
			return err
		}
	}
	return nil
}
posted @ 2025-08-25 10:29  azureology  阅读(12)  评论(0)    收藏  举报