【Go】使用压缩文件优化io (一) (2)

上传日志时

avg-cpu: %user %nice %system %iowait %steal %idle 6.98 0.00 7.81 7.71 0.00 77.50 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util vda 0.00 0.00 13.40 0.00 242.40 0.00 36.18 0.02 1.63 1.63 0.00 0.19 0.26 vdb 0.40 2.40 269.60 1.20 67184.80 14.40 496.30 4.58 15.70 15.77 0.33 1.39 37.74 avg-cpu: %user %nice %system %iowait %steal %idle 7.06 0.00 8.00 4.57 0.00 80.37 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util vda 0.00 0.00 0.60 0.00 75.20 0.00 250.67 0.00 2.67 2.67 0.00 2.00 0.12 vdb 0.20 0.00 344.80 0.00 65398.40 0.00 379.34 5.66 16.42 16.42 0.00 1.27 43.66

从 iostat 的结果中发现,压缩时程序 r_await 和 w_await 都到了一百多,且 iowait 高达 80.88%,几乎耗尽了所有的 CPU,上传时 iowait 是可以接受的,因为只是单纯的读取压缩文件,且压缩文件也很小。

分析问题

上述结果中发现程序主要运行消耗在压缩日志,那优化也着重日志压缩的逻辑上。

压缩时日志会先压缩成 lzo 文件,然后再上传 lzo 文件到阿里云 OSS 上,这中间发生了几个过程:

读取原始日志文件

压缩数据

写入 lzo 文件

读取 lzo 文件

http 发送读取的内容

压缩时 r_await 和 w_await 都很高,主要发生在读取原始日志文件,写入 lzo 文件, 怎么优化呢?

先想一下原始需求,读取原始文件 -> 上传数据。但是直接上传原始文件,文件比较大,网络传输慢,而且存储费用也比较高,怎么办呢?

这个时候我们期望可以上传的是压缩文件,所以就有了优化前的逻辑,这里面产生了一个中间过程,即使用 lzop 命令压缩文件,而且产生了一个中间文件 lzo 文件。

读取原始文件和上传数据是必须的,那么可以优化的就是压缩的流程了,所以 r_await 是没有办法优化的,那么只能优化 w_await,w_await 是怎么产生的呢,恰恰是写入lzo 时产生的,可以不要 lzo 文件吗?这个文件有什么作用?

如果我们压缩文件数据流,在 读取原始文件 -> 上传数据 流程中对上传的数据流进行实时压缩,把压缩的内容给上传了,实现边读边压缩,对数据流进行处理,像是一个中间件,这样就不用写 lzo 文件了,那么 w_await 就被完全优化没了。

lzo 文件有什么作用?我想只有在上传失败之后可以节省一次文件压缩的消耗。上传失败的次数多吗?我用阿里云 OSS 好几年了,除了一次内网故障,再也没有遇到过上传失败的文件,我想是不需要这个文件的,而且生成 lzo 文件还需要占用磁盘空间,定时清理等等,增加了资源消耗和维护成本。

优化后

根据之前的分析看一下优化之后备份文件需要哪些过程:

读取原始日志

在内存中压缩数据流

http 发送压缩后的内容

这个流程节省了两个步骤,写入 lzo 文件和 读取 lzo 文件,不仅没有 w_await,就连 r_await 也得到了小幅度的优化。

优化方案确定了,可是怎么实现 lzo 对文件流进行压缩呢,去 Github 上找一下看看有没有 lzo 的压缩算法库,发现 github.com/cyberdelia/lzo ,虽然是引用 C 库实现的,但是经典的两个算法(lzo1x_1 和 lzo1x_999)都提供了接口,貌似 Go 可以直接用了也就这一个库了。

发现这个库实现了 io.Reader 和 io.Writer 接口,io.Reader 读取压缩文件流,输出解压缩数据,io.Writer 实现输入原始数据,并写入到输入的 io.Writer。

想实现压缩数据流,看来需要使用 io.Writer 接口了,但是这个输入和输出都是 io.Writer,这可为难了,因为我们读取文件获得是 io.Reader,http 接口输入也是 io.Reader,貌似没有可以直接用的接口,没有办法实现了吗,不会我们自已封装一下,下面是封装的 lzo 数据流压缩方法:

package lzo import ( "bytes" "io" "github.com/cyberdelia/lzo" ) type Reader struct { r io.Reader rb []byte buff *bytes.Buffer lzo *lzo.Writer err error } func NewReader(r io.Reader) *Reader { z := &Reader{ r: r, rb: make([]byte, 256*1024), buff: bytes.NewBuffer(make([]byte, 0, 256*1024)), } z.lzo, _ = lzo.NewWriterLevel(z.buff, lzo.BestSpeed) return z } func (z *Reader) compress() { if z.err != nil { return } var nr, nw int nr, z.err = z.r.Read(z.rb) if z.err == io.EOF { if err := z.lzo.Close(); err != nil { z.err = err } } if nr > 0 { nw, z.err = z.lzo.Write(z.rb[:nr]) if z.err == nil && nr != nw { z.err = io.ErrShortWrite } } } func (z *Reader) Read(p []byte) (n int, err error) { if z.err != nil { return 0, z.err } if z.buff.Len() <= 0 { z.compress() } n, err = z.buff.Read(p) if err == io.EOF { err = nil } else if err != nil { z.err = err } return } func (z *Reader) Reset(r io.Reader) { z.r = r z.buff.Reset() z.err = nil z.lzo, _ = lzo.NewWriterLevel(z.buff, lzo.BestSpeed) }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpygxy.html