写点什么

记一次获得 3 倍性能的 Go 程序优化实践

  • 2017-09-13
  • 本文字数:3120 字

    阅读完需:约 10 分钟

Go 的高性能真不是吹的,当然是要在足够的优化之后。获得 3 倍性能的优化实践,值得借鉴。

背景介绍

之前公司一直使用 Logstash 作为日志文件采集客户端程序。Logstash 功能强大,有丰富的数据处理插件及很好的扩展能力,但由于使用 JRuby 实现,性能堪忧。而 Filebeat 是后来出现的一个用 Go 语言实现的、更轻量级的日志文件采集客户端。性能不错、资源占用少,但几乎没有任何解析处理能力。

通常的使用场景是使用 Filebeat 采集到 Logstash 解析处理,然后再上传到 Kafka 或 Elasticsearch。值得注意的是,Logstash 和 Filebeat 都是 Elastic 公司的优秀开源产品。

为了提高客户端的日志采集性能,又减少数据传输环节和部署复杂度,并更充分地将 Go 语言的性能优势利用于日志解析,于是决定在 Filebeat 上通过开发插件的方式,实现针对公司日志格式规范的解析,直接作为 Logstash 的替代品。

实现与优化

Version 1.0

先做一个最简单的实现,即用 Go 自带的正则表达式包 regexp 做日志解析。性能已经比 Logstash(也是通过开发插件做规范日志解析)高出 30%。

这里的性能测试着眼于日志采集的瓶颈——解析处理 环节,指标是在限制只使用一个 CPU core 的条件下(在服务器上要尽量减少对业务应用的资源占用),采集并解析 1 百万条指定格式和长度的日志所花费的时间。

测试环境是 1 台主频为 3.2GHz 的 PC。为了避免 disk IO 及 page cache 的影响,将输入文件和输出文件都放在 /dev/shm 中。对于 Filebeat 的 CPU 限制,是通过启动时指定环境变量 GOMAXPROCS=1 实现的。

这一版本处理 1 百万条日志花费的时间为 122 秒,即每秒 8200 条日志。

Version 2.0

接下来尝试做一些优化,看看这个 Go 插件的性能还可不可以有些提升。首先想到的是替换 regexp 包。Linux 下有一个 C 实现的 PCRE 库, https://github.com/glenn-brown/golang-pkg-pcre 这个第三方包正是将 PCRE 库应用到 Golang 中。CentOS 下需要先安装 pcre-devel 这个包。

这个版本的处理时间为 97 秒,结果显示比第一个版本的处理性能提升了 25%。

Version 3.0

第三个版本,是完全不使用正则表达式,而是针对固定的日志格式规则,利用 strings.Index() 做字符串分解和提取操作。这个版本的处理时间为 70 秒,性能又大大的提升了将近 40%。

Version 4.0

那还有没有进一步提升的空间呢?有。就是 Filebeat 用作序列化输出的 JSON 包。我们的日志上传使用 JSON 格式,而 Filebeat 使用 Go 自带的 encoding/json 包是基于反射实现的,性能一直广受诟病。如果对 JSON 解析有优化的话,性能提高会是很可观的。

既然我们的日志格式是固定的,解析出来的字段也是固定的,这时就可以基于固定的日志结构体做 JSON 的序列化,而不必用低效率的反射来实现。Go 有多个针对给定结构体做 JSON 序列化 / 反序列化的第三方包,我们这里使用的是 easyjson https://github.com/mailru/easyjson。

在安装完 easyjson 包后,对定义了日志格式结构体的程序文件执行 easyjson 命令,会生成一个 xxx_easyjson.go 的文件,里面包含了这个结构体专用的 Marshal/Unmarshal 方法。

这样一来,处理时间又缩短为 61 秒,性能提高 15%。

这时,代码在我面前,已经想不出有什么大的方面还可以优化的了。是时候该本文的另一个主角,火焰图出场了。

on-cpu/off-cpu 火焰图

火焰图是性能分析的一个有效工具, http://www.brendangregg.com/flamegraphs.html 这里是它的说明。通常看到的火焰图,是指 on-cpu 火焰图,用来分析 CPU 都消耗在哪些函数调用上。

on-cpu 火焰图

安装完 FlameGraph https://github.com/brendangregg/FlameGraph 工具后,先对目前版本的程序运行一次性能测试,按照说明抓取数据生成火焰图如下。

:FlameGraph 对于 C/Go 程序是通用的。对于 Go 程序,也可以使用自带的 net/http/pprof 包作为数据源,然后安装 Uber 的 go-torch https://github.com/uber/go-torch 工具来自动调用 FlameGraph 脚本生成 on-cpu 火焰图,执行会稍为简便一些。参见 go-torch 说明。

(点击放大图像)

图中纵向代表的是函数调用栈,横向各个方块的宽度代表的是占用 CPU 时间的比例,需要留意的是靠近顶端的大长条。方块的颜色是随机的没有实际意义。

从上图可以看到 CPU 时间占用最多的主要有两块。一块是 Output 处理部分,稍为大头的是 JSON 处理,这块已经优化过没什么可以做的了。另一块就比较奇怪了,是 common.MapStr.Clone() 方法,居然占了 40% 的 CPU 时间。再往上看,主要是 Errorf 的处理。一看代码,马上明白了。

(点击放大图像)

common.MapStr 是在 pipeline 中存放日志内容的结构体,它的 Clone() 方法实现里判断一个子键值是否为嵌套的 MapStr 结构时,是通过判断 toMapStr() 方法是否返回 error。从这里看,生成 error 对象的代价是非常可观的。于是,一个显然的 fix,就是将 toMapStr() 中的判断方法移到 Clone() 中并避免生成 error。

Version 5.0

对修改后的代码重新生成一张火焰图如下。

(点击放大图像)

这时 common.MapStr.Clone() 从图中已经几乎找不见了,证明花费的 CPU 时间已经可以忽略不计。

测试时间一下子缩短到了 46 秒,节省了 33%,非常大的改善!

off-cpu 火焰图

到现在,还有一个之前未提到的问题没有解决——在限制使用一个 core 之后,测试运行时 CPU 利用率只能跑到 80% 多。是不是由于有锁存在影响了性能呢?

这时候,又该请 off-cpu 火焰图 出场了。off-cpu 火焰图,是用来分析程序没有有效利用 CPU 的时候,消耗在什么地方了,在 http://www.brendangregg.com/FlameGraphs/offcpuflamegraphs.html 有详细的介绍。

数据收集比 on-cpu 火焰图要复杂,可以使用大名鼎鼎的春哥提供的 openresty-systemtap-toolkit https://github.com/openresty/openresty-systemtap-toolkit 包。春哥的项目页面中没有详细说明的是 kernel-develdebuginfo 包的安装方法。在此也记录一下。

(点击放大图像)

安装完后按照说明生成了 off-cpu 火焰图如下:

(点击放大图像)

可以明显地看到,对 Registry 文件(Filebeat 用于记录文件采集列表和 offset 数据)的写操作占了一定比例。于是,尝试将 Filebeat 的 spool_size(每完成这么多条日志更新一次 Registry 文件)设置为 10240,默认值的 5 倍,运行测试 CPU 已经可以跑到 95% 以上。而将 Registry 设置到 /dev/shm/ 下也同样可以解决测试时 CPU 跑不满的问题。

这就否定了上面对锁使用不当影响性能的猜测。在实际应用时 spool_size 的设置应当依据结合了 output 端(如写入到 Kafka)的测试数据来决定。

至此,优化结束,性能达到了最初版本的 3 倍!

各个版本的具体运行性能数据如下图所示:

(点击放大图像)

需要稍作说明的是:

  • Filebeat 开发是基于 5.3.1 版本,Go 版本是 1.8
  • Logstash 的测试通过 -w 1 参数配置使用一个工作进程,并未限制使用一个 core
  • 执行时间包括了程序的启动时间(Logstash 的启动时间有将近 20 秒)

最终的优化结果是,针对特定格式和长度的日志解析能力在 PC 上达到了每秒 25000 条,即使在 CPU 主频较低的生产服务器上,也可以达到每秒 20000 条。

Go 的高性能真不是吹的,当然是要在足够的优化后:)

最后,关于 Go 的性能有一篇这样的讨论,有兴趣可以看看.

作者介绍

潘卫华 (Peter),唯品会架构师,关注基础架构方向。10 余年通信及IT 研发经验,曾任职爱立信,现任职唯品会基础架构部门。近年对基于ELK 的日志存储系统,以及基于Spark Streaming 的实时日志分析等技术有较多研究。喜欢对问题进行深入分析和创新思考,善于挖掘各种工具的潜力。


感谢雨多田光对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-09-13 17:159640

评论

发布
暂无评论
发现更多内容

再谈BOM和DOM(4):DOM0/DOM2事件处理分析

zhoulujun

DOM DOM事件 DOM0 DOM2

让区块链为“三张牌”赋能

CECBC

架构实战营 模块八作业

netspecial

架构实战营

机器学习

i30M

模块八:设计消息队列存储消息数据的 MySQL 表格

ifc177

11款开发者必备插件,第1款简直神器!

Jackpop

chrome 开发

密码学系列之:memory-bound函数

程序那些事

加密解密 密码学 程序那些事

再谈BOM和DOM(6):dom对象及event对象位值计算—如offsetX/Top,clentX

zhoulujun

DOM event对象

Python打包有没有更好的软件了啊

IT蜗壳-Tango

7月日更

Ansible Playbook - 01

耳东@Erdong

ansible 7月日更 ansible Playbook

2021年,有哪些堪称神器的Python工具包?

Jackpop

Python GitHub

模块七:王者荣耀商城异地多活架构设计

ifc177

再谈BOM和DOM(5):各个大流浪器DOM和BOM里面的那些坑—兼容性

zhoulujun

DOM事件兼容性

抖音打击刷量控评行为:数据造假是互联网行业的毒瘤

石头IT视角

探秘RocketMQ事务机制,如何保证消息零丢失

慕枫技术笔记

Java RocketMQ 后端

在分布式中如何优化大数据存储结构

喵叔

7月日更

Linux之chmod命令

入门小站

Linux

用mysql模拟实现消息队列

白发青年

#架构实战营

设计消息队列存储消息数据的 MySQL 表结构

贯通

架构实战营

GIS坐标系测绘原理:大地水准面/基准面/参考椭球体/EPSG/SRI/WKT

zhoulujun

GIS

数字人民币发展的动因、机遇与挑战

CECBC

MySQL事务分析

卢卡多多

事务 事务隔离 7月日更

在线IEEE浮点二进制计算器工具

入门小站

工具

真的有落地的数据中台么?

escray

学习 极客时间 7月日更 数据中台实战课

再谈BOM和DOM(7):HTML DOM Event 对象属性及DOM事件详细列表

zhoulujun

DOM DOM事件

金融机构数字化转型进行时:隐私计算技术成香饽饽,多家银行已开展试点应用

CECBC

【HikariCP技术专题】原理和使用介绍(原生态开发使用)

码界西柚

HikariCP 7月日更 HikarCP使用 数据源连接池

实时个性化推荐(三十六)

Databri_AI

算法 推荐系统

架构实战营 模块 8 课后作业

༺NPE༻

深入了解Spring之Spring Batch框架

邱学喆

数据分片 spring-batch Tasklet 流式任务

自建开发工具系列-Webkit内存动量监控UI(五)

Tim

typescript js 转 ts tsx tsconfig

记一次获得3倍性能的Go程序优化实践_语言 & 开发_潘卫华_InfoQ精选文章