根据公司的业务需求,使用 Go 编写了一个程序,主要功能是将 HTML 文件中的图片链接提取出来,把其中的 svg 图片下载并转换为 png/jpeg 格式图片,然后插入到原文件中返回

其中使用到了 libvips 库进行图片转换操作,这个库相对 ImageMagick 速度更快,结合 Go 的协程特性可以大幅加快程序的运行时间

使用 govips 库进行开发,转换相关设置的代码如下

const (
    // 控制Go协程并发量
    concurrency = 50
    // 控制Vips线程数量
    DefaultVipsConcurrency = 4
    // 控制Vips最大缓存
    DefaultVipsMaxCacheMem = 1024 * 1024 * 1024
)

func vipsRun(data []byte) ([]byte, error) {
    // ...

    // 转换jpeg格式图片时
    opt := vips.NewJpegExportParams()
    inImg.Flatten(&vips.Color{R: 255, G: 255, B: 255})
    outBuffer, _, err := inImg.ExportJpeg(opt)

    // 转换png格式图片时
    opt := vips.PngExportParams{
        Compression: 9,
        Filter:      vips.PngFilterNone,
        Quality:     50.
    }
    outBuffer, _, err := inImg.ExportPng(&opt)

    // ...
}


测试使用的文件总计包含 1249 张图片链接

开发机拥有 16 线程的 CPU,导出 jpg 格式,总计耗时为 10s

如果转换为 png 格式,导出耗时提升到了 18s

测试线是一台 4 线程的服务器,同样的设置下导出 jpg 耗时为 52s

导出 png 耗时为 65s


根据上面的测试可以看出:

  1. 处理 jpg 格式的性能高于 png 格式
  2. 程序执行的时候如果不加以限制,将会跑满所有 CPU 的核心,可能直接导致服务器中的其他服务停止响应
  3. CPU 线程数对于本程序执行时间的影响非常大


对于问题 1 由于导出图片对透明通道没有强制需求,可以固定导出为 jpg 格式图片,通过增大图片质量设置等获取一个较高的清晰度(满足打印清晰的需求即可)

对于问题 2 可以用以下命令在执行时手动限制程序运行的 CPU 核心以此限制其对于服务器资源的无限制占用

# 将命令在CPU0、1上运行
taskset -c 0,1 go run .

# 将7013(PID)进程在CPU0、1上运行
taskset -p 0,1  7013

对于问题 3 恐怕只有增加服务器性能一条路了,无奈优化到最后还是物理层面的原因限制了最终的性能

以及记录一些其它的优化:

  1. 使用github.com/panjf2000/ants/v2库,用池化方法创建协程来节省资源
  2. 使用runtime.GOMAXPROCS(1)来手动设置当前进程执行使用的最大 CPU 数(此项设置似乎并不生效)
  3. 第三方调用程序时使用流的方式输入和输出数据而不使用文件读写


或许换一个思路来说,如此耗时耗能的功能是否不应该放在服务端执行?是否可以移植到客户端消耗客户端设备的资源来运行?上面的测试只是执行一次导出一份的结果,如果多用户同时执行此程序恐怕会导致灾难级的结果

对这个程序的优化还没有完成,接下来有四个思路来进行优化:

  1. 将此方法放到客户端(前端和 APP 端)来执行,需要对 WASM 的兼容性做测试
  2. 增加异步消息队列来缓解高并发下的问题
  3. 从根源入手:执行一个脚本程序在服务器不间断自动转换所有 svg 图片,这样导出时只需要处理下载图片的流程
  4. 先提升服务器核心数使用此程序暂时应对


参考

  1. https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux_for_real_time/9/html/optimizing_rhel_9_for_real_time_for_low_latency_operation/proc_binding-processes-to-cpus-using-the-taskset-utility_assembly_binding-interrupts-and-processes
  2. https://github.com/libvips/libvips/issues/561
  3. https://www.libvips.org/API/current/VipsForeignSave.html#vips-jpegsave