文档章节

大规模Go项目几乎必踏的几个大坑 - 实例分享

n
 nilei
发布于 03/06 22:03
字数 2183
阅读 4673
收藏 147

2个月前开源了Dragonboat这个Go实现的高性能多组Raft共识库,它的一大卖点是其高吞吐性能,在使用内存内的状态机的场景下,能在三组单插服务器上达到千万每秒的吞吐性能。作为个人用Go写的第一个较大的应用库,Dragonboat的开发过程可谓踏坑无数,逐步才具备了目前的性能和可靠性。本文选取几个在各类Go项目中踏坑概率较高的具有普遍性的问题,以Dragonboat踏坑详细过程为背景,具体分享。

Channel的实现没有黑科技

虽然是最核心与基础的内建类型,chan的实现却真的没有黑科技,它的性能很普通。

Dragonboat的旧版中,有大致入下的这样一段核心代码。它在有待处理的读写请求的时候,用以通知执行引擎。名为workReadyCh的channel系统中有很多个,执行引擎的每个worker一个,client用它来提供待处理请求的信息v。而考虑到该channel可能已满且等待的时候系统可能被关闭,一个全局唯一的用于表示系统已被要求关闭的channel会一起被select,用以接收系统关闭的通知。

select {
case <-closeCh:
  return
case workReadyCh<-v:
}

这大概是Go最常见的访问channel的pattern之一,实在太常见了!暂且不论千万每秒的写吞吐意味着每秒千万次的channel的写这一问题本身(前文详细分析),数万并发请求的goroutine通过数十个OS thread同时去select一个全局唯一的closeCh就已足够把高性能秒杀成了低性能蜗牛。

这种大量线程互相踩踏式的select访问一个channel所凸显的chan性能问题Go社群有详细讨论。该Issue讨论里贴出的profiling结果如下,很直观。但很遗憾,runtime层面无解决方案,而无锁channel的实现上虽然众人前赴后继,终无任何突破。现实中的Go runtime没有黑科技,它只提供性能很一般的chan。

为了绕开该坑,还是得从应用设计出发,把上述单一的closeCh分区做sharding,根据不同的Raft组的组号,由不同的chan来负责做系统已关闭这一情况的通知。此改进立刻大幅度缓解了上述性能问题。更进一步的优化,更能完全排除掉上述访问模式,这也是目前的实现方法,篇幅原因这里不展开。

sync.RWMutex随核心数升高其性能伸展性不佳

下面是Dragonboat老版本上抓的一段cpu profiling的结果,RWMutex的RLock和RUnlock性能很差,用于保护这个map的RWMutex上的耗时比访问map本身高一个数量级。

这是因为在高核心数下,大量RLock和RUnlock请求会在锁的同一个内存位置并发的去做atomic write。与上面chan的问题类似,还是高contention。

RWMutex的性能问题是一个困扰Go社区很久但至今没有在标准库层面上解决的问题(#17973)。有用户提出过一种称为Big Reader的变种,在牺牲写锁性能的前提下改善读锁的操作性能。但此时写锁的性能是崩跌的,以Intel LGA3647处理器高端双插服务器为例,Big Reader锁在操作写锁的时候需要对112个RWMutex做Lock/Unlock操作,因此只适用于读写比极大的场景,不具备通用性。

Dragonboat中,所观察到的上述RWMutex问题,其本质在于在每次对某个Raft组做读写之前都需要反复去查询获取该指定的Raft节点。显然,无论锁的实现本身如何优化,或是改用sync.Map来替代上述需要锁保护的map的使用,试图去避免反复做此类无意义的重复查询,才是从根本上解决问题。本例中,Big Reader变种是适用的,软件后期也改用了sync.Map,但避免反复的getCluster操作则彻底避免锁操作,完全饶开了锁的实现和用法是否高效这点。减少不必要操作,远比把此类多余的操作变得更高效来的直接有效。

Cgo远没那么烂

前两年网上无脑Go黑的四大必选兵器肯定是:GC性能、依赖管理、Cgo性能和错误处理。GC性能这两年已经在停顿方面吊打Java,吞吐的改进也在积极进行中。Go 1.12版Module的引入从官方工具层面关管住了依赖管理,而Go 2对错误处理也将有大改进。种种这些之外,Cgo的性能依旧误解重重。

多吹无意义,先跑个分,看看Cgo究竟多"慢":

调用一个简单的C实现的函数的开销是60ns级,和一次没有cache的对内存的访问一样。

这是什么概念呢?用个踩过的坑来说明吧。Dragonboat早期版本对RocksDB的WriteBatch的Put操作是一次操作一个Raft Log Entry,一秒该Cgo请求在多个goroutine上共并行操作数百万次。因为听信网上无脑黑对Cgo的评价,起初认为这显然是严重性能问题,于是优化归并后大幅度减少了Cgo调用次数。可结果发现这对延迟、吞吐的性能改进很小很小。事后再跑profiler去看旧的实现,发现旧版的Cgo开销起初便完全不主要。

Go内建了很好的benchmark工具,一切性能的讨论都应该是基于客观有效的benchmark跑分结果,而不是诸如“我认为”、“我感觉”之类的无脑互蒙。

Goroutine泄漏与内存泄漏一样普遍

Goroutine的最大卖点是量大价廉使用方便,一个程序里轻松开启万把个Goroutine基本都不用考虑其本身的代价......一切似乎很美好,直到系统内类型众多的Goroutine开始泄漏。也许是因为Goroutine的特性,它在Go程序里的使用的频度密度远超线程在Java/C++程序中情况,同时用户思维中Goroutine简单易用代价低的概念根深蒂固、与生俱来,无形中更容易放松对资源管理的考虑,因此更容易发生Goroutine泄漏情况。Dragonboat的经验是Goroutine泄漏的概率不比内存泄漏少。

Dragonboat从实现之初就开始使用Goroutine泄漏检查,具体的泄漏检查的实现是来自CockroachDB的一小段代码。效果方面,这个小工具发现过Dragonboat及其依赖的第三方库里多个goroutine泄漏问题,而使用上,在各内建的测试中,只需一行便能完成调用得到结果,绝对是费效比完美。

实现上它也特别简单,就是前后两次分别抓stacktrace,解析出进程里所有的Goroutine ID并对比是否测试运行结束后产生了多余的滞留在系统中的Goroutine。官方虽然不倡导对Goroutine ID做任何操作,但此类仅在测试中仅针对Goroutine泄漏的特殊场景的使用,应该不拘泥于该约束,这就如同官方不怎么推荐用sync/atomic一个道理。

总结

基于Dragonboat的几个具体例子,本文分享了几个常见的Go性能与使用问题。总结来说:

通过sharding分区减少contention是优化常用手段 做的再快也不可能比什么也不做更快,减少不必要操作比优化这个操作有效 多用Go内建的benchmark功能,数据为导向的做决策 官方提倡的东西肯定有他的道理,但在合适的情况下,需懂得如何无视某些官方的提倡

后续将再推出针对Go内存性能优化的文章,敬请期待。在阅读完此干货软文后,也请大家访问Dragonboat项目并点star支持!谢谢阅读。

© 著作权归作者所有

共有 人打赏支持
n
粉丝 30
博文 6
码字总数 11560
作品 1
私信 提问
加载中

评论(22)

xiaour
xiaour
go 吹又出来了
开源中国最大五毛
开源中国最大五毛

引用来自“开源中国最大五毛”的评论

😄

我看过的最高级的func喷法,来自一个rust用户:

rust的fn是写在字典里的,go的func算个什么**

😄

引用来自“银杏卡卡”的评论

你想as3、js都写function,某些语言说是要简化编程,都拿性能和C比较,可没把C的优点继承下来,还整个函数前缀,甚至还有些语言把函数体的大括号还整成类似begin end的,去了解一下早期的高级语言吧,哪怕现在火的py,其背后的人工智能算法还大量依赖C/C++,go出来这么多年也还是不温不火肯定还是有原因的。
😄

你是我见过的更牛逼的func喷法,这个喷法我拿回去喷rust也不错,什么语言,竟然要写fn!

你真的很棒棒哦。


或许你接触的不多,我接触过一些,不能说全部,我关注的公司,一半以上都有__的坑位,大的比如阿里头条,小的比如某些阿里前同事的创业公司,而且__用来做中间件相关的职位比较多,写业务的比较少。
然而你并不知道,你只是想输出情绪。

然而你喷我并没有什么用。
我公司主职搞java的,某些对并发要求比较高的地方用__,
这一块原来是C、C++和java的自留地,公司用__做的静态化、api和im的网关都不错,维护成本低了很多,我在用我知道。
这种事情你看不到,你只是想输出情绪。


😄
至于不温不火,
我业余写c#,UWP的东西,那才是“不温不火”,夸张点说,“几乎没有人用”,“nuget里的包比pecl里多不到哪儿去”,然而这并不妨碍语言论战中普遍被人夸上天,“比java不知道高到哪里去了”,也并不妨碍我写自己的UWP应用。
整日里宣传“我的XX没人用,你的__火起来了”是因为“worse is better”,也不去想到底worse哪里better了,只是想输出情绪。


oschina里我甚至见过另一些语言吹,只是为了黑__,就连nim、crystal这种比“不温不火”都“不温不火”的语言都吹上了天。
为了黑而黑的论战太多了,当事人就只是想输出情绪。


😄

总结一下,我就不说__和其他语言我自己的看法了,毕竟你只是想输出情绪。

鼓励你一下吧。

那你很棒棒哦,请加油。
那你很棒棒哦,请加油。
那你很棒棒哦,请加油。
魂祭心
魂祭心
在各种不同的高负荷条件下,没语言能不做代码优化就能轻松解决,银弹是不存在的
maskwang
maskwang
mark
cyclamenkde
cyclamenkde
都在这喷来喷去,上班不用干活吗?全部扣本月绩效!
Minho
Minho

引用来自“守望辰峰”的评论

居然有人喷 func ???
Java / PHP / Javascript 没有?对,是没有,但有 function
Swift 要不要也喷一下?Kotlin 虽然没有 func ,但要不要也喷一下 fun ???

引用来自“银杏卡卡”的评论

傻逼,java有function修饰函数,你能别丢人不?我就是搞java的,真是笑死人了,傻逼一个。swifit我还真不喜欢,不了解不喜欢也不喷。不过你让我很想笑,鄙视你,无知小二!
看到这么多人喷你我就放心了。
kakai
kakai

引用来自“银杏卡卡”的评论

你说的都对,但我还是喜欢C语系的语法,go语法偏向脚本化语言,你说简单吧它又非得加个func来标示函数,萝卜白菜各有所爱。

引用来自“Minho”的评论

C语言不需要加吗?

引用来自“银杏卡卡”的评论

不需要啊,比如 int max(int num1, int num2) { ... } 这就是一个函数,加什么多余的func的。

引用来自“Minho”的评论

多个返回值怎么处理?还有,也许只有C语言满足你的个人癖好了,目前大部分语言都有函数定义关键字。

引用来自“银杏卡卡”的评论

你逗吧,明显没啥编程经验,连C的函数都不了解还大放厥词,多结果返回不能用数组?作为程序员你能变通不?你以为go有个多结果返回就天下无敌了?或许你也只是懂点go吧,想当初go的编译器还得用C写,连C基础都不了解还谈go,你在搞笑吧。

引用来自“marshalys”的评论

数组能兼容不同类型么?都返回指针再做强制转换么?不要动不动就说别人没有经验。大家关注点不一样。

引用来自“银杏卡卡”的评论

是啊,关注点不同,那别人还说什么不能返回多结果?这不是扯话题?先把扯话题人的搞清楚吧。数组作为多结果返回没任何问题,不说C,拿同样都是基于对象的java来说,用Object兼容任何对象,强转有什么问题?明显说这话的不了解C,以为go有多结果返回,其它返回单结果的就完全没法实现了一样。

引用来自“marshalys”的评论

不是说能不能实现,方不方便而已。要只从能不能实现角度考虑,大家都用机器语言得了。

有时间多钻研钻研技术吧,别天天在这里搞语言之争。
争个毛线,我首先就说“作者说的都对”、“萝卜白菜各有所爱,我喜欢C语系的语言”,我有说go不好,然后接着那人说“函数有不用加修饰的编程语言吗”,我态度非常好的说“有啊,像C、java、C#等”,然后对方说“C可以?”,我又耐心举例,那傻逼尽然说“C只能成为我的癖好”,有些人不知前尾瞎批判,恼火。我可没争什么语言,有些人就是不懂还瞎说,尽然还有说java需要用function修饰函数来反驳我,笑掉大牙,科班生一般编程的入门基础就是C/C++,数据结构一般也是C的,不懂还问来问去,谦和的告诉他还就成了我的“癖好”,有意思的人。
marshalys
marshalys

引用来自“银杏卡卡”的评论

你说的都对,但我还是喜欢C语系的语法,go语法偏向脚本化语言,你说简单吧它又非得加个func来标示函数,萝卜白菜各有所爱。

引用来自“Minho”的评论

C语言不需要加吗?

引用来自“银杏卡卡”的评论

不需要啊,比如 int max(int num1, int num2) { ... } 这就是一个函数,加什么多余的func的。

引用来自“Minho”的评论

多个返回值怎么处理?还有,也许只有C语言满足你的个人癖好了,目前大部分语言都有函数定义关键字。

引用来自“银杏卡卡”的评论

你逗吧,明显没啥编程经验,连C的函数都不了解还大放厥词,多结果返回不能用数组?作为程序员你能变通不?你以为go有个多结果返回就天下无敌了?或许你也只是懂点go吧,想当初go的编译器还得用C写,连C基础都不了解还谈go,你在搞笑吧。

引用来自“marshalys”的评论

数组能兼容不同类型么?都返回指针再做强制转换么?不要动不动就说别人没有经验。大家关注点不一样。

引用来自“银杏卡卡”的评论

是啊,关注点不同,那别人还说什么不能返回多结果?这不是扯话题?先把扯话题人的搞清楚吧。数组作为多结果返回没任何问题,不说C,拿同样都是基于对象的java来说,用Object兼容任何对象,强转有什么问题?明显说这话的不了解C,以为go有多结果返回,其它返回单结果的就完全没法实现了一样。
不是说能不能实现,方不方便而已。要只从能不能实现角度考虑,大家都用机器语言得了。

有时间多钻研钻研技术吧,别天天在这里搞语言之争。
kakai
kakai

引用来自“银杏卡卡”的评论

你说的都对,但我还是喜欢C语系的语法,go语法偏向脚本化语言,你说简单吧它又非得加个func来标示函数,萝卜白菜各有所爱。

引用来自“Minho”的评论

C语言不需要加吗?

引用来自“银杏卡卡”的评论

不需要啊,比如 int max(int num1, int num2) { ... } 这就是一个函数,加什么多余的func的。

引用来自“Minho”的评论

多个返回值怎么处理?还有,也许只有C语言满足你的个人癖好了,目前大部分语言都有函数定义关键字。

引用来自“银杏卡卡”的评论

你逗吧,明显没啥编程经验,连C的函数都不了解还大放厥词,多结果返回不能用数组?作为程序员你能变通不?你以为go有个多结果返回就天下无敌了?或许你也只是懂点go吧,想当初go的编译器还得用C写,连C基础都不了解还谈go,你在搞笑吧。

引用来自“marshalys”的评论

数组能兼容不同类型么?都返回指针再做强制转换么?不要动不动就说别人没有经验。大家关注点不一样。
是啊,关注点不同,那别人还说什么不能返回多结果?这不是扯话题?先把扯话题人的搞清楚吧。数组作为多结果返回没任何问题,不说C,拿同样都是基于对象的java来说,用Object兼容任何对象,强转有什么问题?明显说这话的不了解C,以为go有多结果返回,其它返回单结果的就完全没法实现了一样。
marshalys
marshalys

引用来自“银杏卡卡”的评论

你说的都对,但我还是喜欢C语系的语法,go语法偏向脚本化语言,你说简单吧它又非得加个func来标示函数,萝卜白菜各有所爱。

引用来自“Minho”的评论

C语言不需要加吗?

引用来自“银杏卡卡”的评论

不需要啊,比如 int max(int num1, int num2) { ... } 这就是一个函数,加什么多余的func的。

引用来自“Minho”的评论

多个返回值怎么处理?还有,也许只有C语言满足你的个人癖好了,目前大部分语言都有函数定义关键字。

引用来自“银杏卡卡”的评论

你逗吧,明显没啥编程经验,连C的函数都不了解还大放厥词,多结果返回不能用数组?作为程序员你能变通不?你以为go有个多结果返回就天下无敌了?或许你也只是懂点go吧,想当初go的编译器还得用C写,连C基础都不了解还谈go,你在搞笑吧。
数组能兼容不同类型么?都返回指针再做强制转换么?不要动不动就说别人没有经验。大家关注点不一样。
React 项目中使用 dagre-d3

React 已经成为了公司所有项目的前端必选框架了, 社区非常活跃,而 也几乎成为了前端最火热的框架,所以各种知名的库几乎都已经有了的版本。但是一些库还没有,比如。 是一个来绘制关系图的库...

我就什么话也不说
2018/12/04
0
0
在Android中实现文件上传

本例是我在年前的项目中用到的一个小例子,使用Android自带的HttpURLConnection实现文件上传。网上的资料对这方面的讲解不太多,有两个实例也不太详细,我在开发中用到了,觉得挺实用的,分享...

紫葡萄0
2017/02/15
0
0
分布式大规模数据库系统--HadoopDB

HadoopDB是Abadi领导的开发团队利用不同的组件,包括开源数据库、PostgreSQL、Apache Hadoop数据分类技术和Hive(Facebook公司开发的内部Hadoop项目)开发出的新型数据库。 HadoopDB的查询是...

匿名
2012/01/29
4K
0
MySQL对Like搜索结果按照匹配程度排序

MySQL对Like搜索结果按照匹配程度排序 钇钛网 - 郭宇翔的博客2018-01-091 阅读 匹配mysql排序like搜索 最近项目上遇到一个需求,在原来项目的管理后台上,有一个通过用户昵称进行模糊搜索的功...

钇钛网 - 郭宇翔的博客
2018/01/09
0
0
OpenStack介绍

简介 OpenStack是一个开源的云计算管理平台项目,由几个主要的组件组合起来完成具体工作。OpenStack支持几乎所有类型的云环境,项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计...

桃子红了呐
2016/02/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

欧拉公式

欧拉公式表达式 欧拉公式的几何意 cosθ + j sinθ 是个复数,实数部分也就是实部为 cosθ ,虚数部分也就是虚部为 j sinθ ,对应复平面单位圆上的一个点。 根据欧拉公式和这个点可以用 复指...

sharelocked
26分钟前
2
0
burpsuite无法抓取https数据包

1.将浏览器和burpsuite的代理都设置好 2.在浏览器地址栏输入: http://burp 3.下载下面的证书,并将证书导入浏览器 cacert.der

Frost729
51分钟前
1
0
JeeSite4.x 消息管理、消息推送、消息提醒

实现统一的消息推送接口,包含PC消息、短信消息、邮件消息、微信消息等,无需让所有开发者了解消息是怎么发送出去的,只需了解消息发送接口即可。 所有推送消息均通过 MsgPushUtils 工具类发...

ThinkGem
今天
6
0
OpenML

https://www.openml.org/search?type=data

shengjuntu
今天
2
0
java强引用,软引用,弱引用和虚引用

先来简要说一下这四种引用的特性: 强引用:如果一个对象具有强引用,那垃圾回收器绝不会回收它 软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它 弱引用:在垃圾...

woshixin
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部