对需要导出的数据进行定量拆分(拆分成多个报表文件),然后使用zip进行实时压缩。达到降低网络消耗,加快下载(让甲方爸爸开心)。 如果不想看过程的话,可以直接跳到 最终版本 文中所有指标,针对的是正式业务数据。测试代码中的数据量太小,想疯狂压测的话,可以把数据搞复杂一点。

客户的需求迭代

V1.0

可以导出数据

  • 前端基于Vue构建,有对应的插件可以生成Excel。直接由前端导出 **客户反馈:**报表数据量太小

V2.0

不仅要导出当前页的数据,而且要(当前报表的)所有数据

  • 解除请求中,分页大小的限制 **客户反馈:**导出太慢。内存飙升。很容易卡死

V2.1

  • 台导出excel,并随 response 流返回

这里没有采用异步生成文件,然后通过http的方式下载。是基于工期和数据安全性的考虑 1、在短期内做一个下载校验服务器,不太容易(下载一次后自动删除文件) 2、http的形式基本上就可以任意用户访问/下载。没有安全性

**运维反馈:**K8s上面的项目,不定期的重启。对比服务器负载,及日志得出结论:导出Excel的过程太占内存 **客户反馈:**太慢,文件太大,中断了需要重新下载

V2.2

本地进行了压力测试,在200W条数据的情况下,数据占用内存大概1GB。当生成Excel的时候,内存占用上升到14GB左右。 此时我们就不得不考虑一个上古方案了CSV 压测后发现,200W条数据,在写csv文件时,大概占用3GB内存。但是在1048576行之后的数据就消失了

**客户撒泼:**我就要全部数据

V3.0

异步操作:每100W行,生成一个新的csv文件,然后使用zip打包

最终版本(V3.0)

使用如下代码测试下来,在200W条数据量的前提下,内存并无大幅波动(不到100MB)。压缩比有50(压缩前的csv大概2GB,打包后的zip大概40MB)。效果还算是不错的。

package main

import (
    "archive/zip"
    "fmt"
    "io"
    "os"
)

func main() {
    zipFile, _ := os.Create("a.zip")
    defer zipFile.Close()
    zipWriter := zip.NewWriter(zipFile)
    defer zipWriter.Close()

    const maxLine = 100000 // 设置单文件最大行数
    var csvName string
    var csvWriter io.Writer
    var err error

    // 模拟从数据库中获取到的数据(这里压测数据为200W)
    for i := int64(0); i < 2000000; i++ {
        // 每达到maxLine(10W)条数据的时候,生成一个新的文件
        if i%maxLine == 0 {
            csvName = fmt.Sprintf("%d.csv", i/maxLine)
            fmt.Println("zipWriter.Create(" + csvName + ")")
            csvWriter, err = zipWriter.Create(csvName)
            if err != nil {
                fmt.Println("-----------", err)
            }
            csvWriter.Write([]byte{0xEF, 0xBB, 0xBF}) // UTF-8编码
            csvWriter.Write([]byte("姓名,年龄,班级,分数\r\n"))
        }
        // 这里使用简单的数据,实际场景中的数据比这个复杂几倍
        csvWriter.Write([]byte("张三,10,五年级,100\r\n")) // 写入每一行数据
    }
}