Golang解析大型XML文件的策略

Golang解析大型XML文件的策略 在我的组织中,我正在处理一个问题:我们需要访问一个内部URL来获取XML信息页面,然后解析这些信息并上传到S3存储桶。目前,我们是在一个运行Node.js应用程序的Lambda函数中完成这项工作的。由于Node.js的futures实现存在内存泄漏问题,我们无法使用并行处理。因此,我们不得不顺序地访问URL并遍历URL链来规避这个问题。

我希望使用Go语言来并行化这项工作,并将Node.js应用程序迁移到Go。

一个需要注意的问题是,HTML文件的大小可能在1MB到4MB之间,并且需要在应用程序中进行最基本的解析。Node.js对XML解析器有很好的支持。

那么在Go语言中,XML解析是怎样的呢?

我遇到了一个问题,这给我的计划泼了一盆冷水。

encoding/xml: very low performance in xml parser

有人能建议在Go语言中处理XML解析的最佳方法是什么吗?


更多关于Golang解析大型XML文件的策略的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

感谢提供的信息。我会尝试使用那个库。

更多关于Golang解析大型XML文件的策略的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


XML 解析主要是受 I/O 限制的。并发解析多个 XML 文件很可能比逐个解析要快。但你需要尝试并进行基准测试。

如果不需要将整个XML文档解析成可以在代码中导航的树结构,你可以使用像 gosax 这样的流式解析器。

以下是一些背景信息:

https://eli.thegreenplace.net/2019/faster-xml-stream-processing-in-go/

在一个应用中,我需要非常粗略的解析。而在另一个应用中,我需要更详细的解析。我有兴趣验证,通过Go获得的并行性是否比我们现在使用的Node.js方案有所改进。

在Go中处理大型XML文件时,encoding/xml包确实存在性能瓶颈,特别是对于大文件。以下是几种有效的处理策略:

1. 使用流式解析(推荐)

对于1-4MB的XML文件,流式解析是最佳选择:

package main

import (
    "encoding/xml"
    "io"
    "net/http"
    "os"
)

type Item struct {
    XMLName xml.Name `xml:"item"`
    ID      string   `xml:"id,attr"`
    Name    string   `xml:"name"`
    Value   string   `xml:"value"`
}

func parseLargeXML(r io.Reader) error {
    decoder := xml.NewDecoder(r)
    
    for {
        tok, err := decoder.Token()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }

        switch se := tok.(type) {
        case xml.StartElement:
            if se.Name.Local == "item" {
                var item Item
                if err := decoder.DecodeElement(&item, &se); err != nil {
                    return err
                }
                // 处理item,可以并发上传到S3
                processItem(item)
            }
        }
    }
    return nil
}

func processItem(item Item) {
    // 处理并上传到S3
    // 这里可以添加并发处理
}

2. 使用第三方高性能解析库

考虑使用libxml2的Go绑定或专门的XML解析器:

// 使用github.com/lestrrat-go/libxml2
import (
    "github.com/lestrrat-go/libxml2"
    "github.com/lestrrat-go/libxml2/xpath"
)

func parseWithLibxml2(xmlData []byte) error {
    doc, err := libxml2.Parse(xmlData)
    if err != nil {
        return err
    }
    defer doc.Free()
    
    // 使用XPath查询
    nodes, err := xpath.NodeList(doc.Find("//item"))
    if err != nil {
        return err
    }
    
    for _, node := range nodes {
        // 处理节点
    }
    return nil
}

3. 并行处理方案

结合流式解析和并发处理:

package main

import (
    "encoding/xml"
    "io"
    "net/http"
    "sync"
)

type WorkerPool struct {
    workers int
    jobs    chan Item
    wg      sync.WaitGroup
}

func NewWorkerPool(workers int) *WorkerPool {
    return &WorkerPool{
        workers: workers,
        jobs:    make(chan Item, 100),
    }
}

func (wp *WorkerPool) Start() {
    for i := 0; i < wp.workers; i++ {
        wp.wg.Add(1)
        go func() {
            defer wp.wg.Done()
            for item := range wp.jobs {
                // 上传到S3
                uploadToS3(item)
            }
        }()
    }
}

func (wp *WorkerPool) Process(r io.Reader) error {
    decoder := xml.NewDecoder(r)
    
    for {
        tok, err := decoder.Token()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }

        switch se := tok.(type) {
        case xml.StartElement:
            if se.Name.Local == "item" {
                var item Item
                if err := decoder.DecodeElement(&item, &se); err != nil {
                    return err
                }
                wp.jobs <- item
            }
        }
    }
    close(wp.jobs)
    wp.wg.Wait()
    return nil
}

func uploadToS3(item Item) {
    // S3上传逻辑
}

4. 完整示例:从URL获取并解析

package main

import (
    "encoding/xml"
    "io"
    "net/http"
    "sync/atomic"
    "time"
)

func fetchAndParseXML(url string, concurrency int) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // 使用带缓冲的解码器
    decoder := xml.NewDecoder(resp.Body)
    decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
        return input, nil
    }

    jobs := make(chan Item, 100)
    var processed int64
    
    // 启动worker
    for i := 0; i < concurrency; i++ {
        go func() {
            for item := range jobs {
                processAndUpload(item)
                atomic.AddInt64(&processed, 1)
            }
        }()
    }

    // 流式解析
    for {
        tok, err := decoder.Token()
        if err == io.EOF {
            break
        }
        if err != nil {
            close(jobs)
            return err
        }

        if se, ok := tok.(xml.StartElement); ok {
            if se.Name.Local == "item" {
                var item Item
                if err := decoder.DecodeElement(&item, &se); err != nil {
                    continue
                }
                jobs <- item
            }
        }
    }
    
    close(jobs)
    
    // 等待处理完成
    for atomic.LoadInt64(&processed) > 0 {
        time.Sleep(100 * time.Millisecond)
    }
    
    return nil
}

性能优化建议:

  1. 调整解码器缓冲区
decoder := xml.NewDecoder(resp.Body)
decoder.Strict = false
decoder.AutoClose = xml.HTMLAutoClose
decoder.Entity = xml.HTMLEntity
  1. 使用pool减少内存分配
var itemPool = sync.Pool{
    New: func() interface{} {
        return new(Item)
    },
}

// 使用时
item := itemPool.Get().(*Item)
// 处理完放回
itemPool.Put(item)
  1. 批量上传S3:积累一定数量的item后批量上传,减少API调用次数。

Go的encoding/xml包在处理大型XML时,通过流式解析和并发处理可以显著提升性能。对于1-4MB的文件,这种方案完全可行,且内存占用可控。

回到顶部