谢孟军:The State of Go | ECUG Con 精粹系列
本月 17 日,Go 1.8 版本火热发布。相较于以往的版本,Go 1.8 具体有哪些新的特性呢?想必这是不少 Gopher 们热切关注和讨论的问题。作为著名的Golang 布道者,Gopher China 社区创始人,谢孟军早在今年一月的 ECUG Con 上就对 Golang 做出了历史版本的回顾和 1.8 版本的分析,本文就是对他的演讲实录。
谢孟军
Gopher China 社区创始人,著名开源框架 beego 开发者,畅销图书《Go Web 编程》作者,同时有 bat、bee 等开源软件。国内 Go 发展的主要推动者之一。
谢孟军:大家好,我是来自 Apple 的工程师,目前主要在从事工业自动化系统的架构和研发,今天很高兴来到这里跟大家分享一下关于 Go 的一些东西。Go 是 Google 的语言,Go 语言已经出来 6 年了,从 1.0 版本到 1.8 版本,今天最主要是跟大家分享一下 Go 在 1.8 版本中带来了哪些新特性。
Go 回顾
2012 年 3 月 Go 1.0 版本发布,这是一个标志性的事件。很多语言发布出来之后再次升级都会有或多或少不兼容的体验,但是 Go 官方团队在发布 1.0 的时候发布申明,后续的版本保证百分之百向前兼容,他们也遵守了当时的承诺。1.0、1.1、1.2、1.3 一直到 1.7,你的代码如果是 1.0 时候写的,现在升级到 1.7,都可以正常编译。其他语言里面我们可能都会有这样的体验,升级了一个新版本之后,需要花很多时间把代码兼容到新升级的版本中。所以对于一个语言来说,特性稳定是非常重要的。
Go 语言基本上保持了半年发布一个版本的节奏:
-
2013年的 5 月份发了 1.1 版本;
-
2013 年 12 月份发了 1.2 版本;
-
2014 年 6 月份发布了 1.3 版本;
-
2014 年 12 月份发布了 1.4 版本;
-
2015 年8 月发布了 1.5 版本(这个版本拖延的时间有点长,官方规定此后半年出一个版本);
-
2016年 2 月份发布了 1.6 版本;
-
2016 年 8 月发布了 1.7 版本;
-
2017年 2 月将会发布 1.8 版本。
Go 1.8 版本带来了哪些新特性
语言层面
几乎没有任何改变,有一个小小的改进是:
struct 里面的字段都是一样的,但是 struct tag 一个是 foo,一个是 bar,新版本现在可以这样赋值了:V1=T1(V2)//now legal。
工具层面
-
编译工具
首先是编译工具。大家知道,1.7 版本之后 Go 引入了 SSA,SSA 的引入把编译之后的二进制文件压缩小了,性能提升了。但是 1.7 版本中只针对了 64 位机器实现了 SSA,其他的全部还是用老的编译。但是在 1.8 版本中全部用 SSA 编译了,性能和大小基本上提升了 20% 到 30%。但是 Go 官方对所有的包测试下来,整体性能相对于 1.7 版本提升了将近 15% 。
-
Go Vet
go vet 大家写完代码之后可以用它检查一下是否符合标准,go vet 在 1.8 版本中增加了一些更加严格的方式:
-
copylocks for len&cap,当值传递到 len 函数的时候,锁拷贝进去之后很容易引起问题,这个时候,go vet 之前是检测不出来这个错误的,但是 1.8 版本可以检测出来这行代码是有问题的,存在锁拷贝。
-
JSON tags,在 1.7 版本之前写完全没有问题,在 1.8 版本中当两个 tag 一模一样时,会检测出两个 tag 之间应该用空格隔出来,而不是用“,”,这个地方必须空格。你的代码编译度没有任何问题,只是运行的时候存在一些问题。所以,go vet 是在你代码编译之前帮你检测代码层面存在的问题,相当于静态文件分析法。
-
Close before checking errors,当有错误的时候,那么 res 就会返回 nil ,这时候程序就崩溃了,1.8 版本的 vet 可以检测出来。 error 检测必须在调用 res 之前,这样的话就可以避免出现 panic 的情况。
-
Default GOPATH
Go 语言安装后之后需要设一个 GOPATH,但是在 Go 1.8 版本中实现了默认装后就会帮你设好一个 GOPATH 的环境变量。如果是在 Unix 环境下,就是在上图中 $HOME/go 那个目录下;如果是 Windows 环境下,就是在上图中 USERPROFILE 那个目录下。即目录已经帮你设好了,方便你装好 Go 就可以直接去用了。
-
plugins
在 Go 1.8 版本中支持动态加载 plugins,目前只支持 Linux 系统,Mac 和 Windows 都不支持。这里举个例子:首先我们定义一个函数,那么怎么把这个编译成插件呢?用一个参数 go build -buildmode=plugin,编译出 SO 文件,SO 文件编译完成之后怎么调用呢?在 1.8 里面增加了标准库的包 plugins,所以你可以使用 plugin.Open 动态地打开 SO 文件,就会返回 p,然后查找里面的函数,这个时候会返回一个符号,首先进行类型转换(类型断言),最后调用它,最后就可以调用 plugins 的东西了。plugin 的引入可以把使得 Go 的程序变成很小的一部分,Go 程序里面又分了微服务的感觉,采用模块化设计。但是这种问题又带来依赖的问题,目前我是持怀疑态度看待 plugin 的引入,但是对于部分企业来说是有用的,因为它可以做到中间件的部分自己去升级。但是我现在测试下来,这个 plugins,刚才 10 行不到的函数编译下来就有 5M 多,而且性能也是一个问题。
-
go bug
Go 里面新增加了一个命令叫 go bug,当你发现一个 bug,它会自动搜集系统的信息,打开浏览器,只要你填发现的问题就好了,非常方便你提 bug 的一个工具。
-
go pprof
Go 在 1.8 版本中开始支持 tts 的调试。
runtime 层面
第一个是 argument liveness。在编写程序时,有些变量我们是希望它常驻内存的,在 1.7 的时候引入了一个函数 runtime.KeepAlive,这个变量保持在内存里面不要给 GC 干掉,它现在可以自己控制这个东西。1.8 版本针对它更加优化了一些。
第二个是 Concurrent Map Misuse。如果针对一个 Map 有并发的读和写是存在竞争的,1.6 版本之前不会把你的程序给崩溃掉,1.6 版本之后程序就会直接退出,这种情况怎么样避免呢?在编译的时候加 race,把代码竞争的情况全部检测出来。在 1.8 版本里面,针对这个东西就进行了更加严格的检测,当你在循环读这个 Map 的时候,Map 在其他地方写的时候会进行检测。
第三个是 memStats Documentaition。Go 的文档大部分都是很好很详细,但是有些地方很简单、简略,1.8 版本中增加了更多的文档。
Performance 层面
Go 从 1.0 开始,一直在持续地改进它的 Performance。首先来看标准库包语言,官方数据说这些包都有做改进,最主要改进的是 runtime 和反射这两个包,反射包的性能提升 20% 到 30% 左右,这个提升比较重要。这个提升有一部分来自于新的编译器可以做到缩小、内存优化。
第二个是 Garbage Collector(简称 GC )。Go 的 GC 从 1.0 发布之后,一直有人说 Go 的 GC 不行。直到 1.5 版本之后,有一个大牛主导 GC 之后,现在没有人吐槽 Go 的 GC 了。 Go 的 GC 和 Java 的 GC 不一样,Java 的 GC 是几百个参数让你去搭配,让你配出来这个东西是最适合自己的场景。但是 Go 不一样,没有什么可以做,但是你可以通过一些其他的方式优化,比如减少对象的分配。但是好消息是 Go 官方一直在改进它,在 Go 1.4 版本的时候它的 GC 在 300 毫秒的时候,但是在 1.5 版本 GC 已经优化得非常好了,压缩到了40 毫秒。从 1.6 版本的 15 到 20 毫秒升级到 1.63 版本的 5 毫秒。又从 1.6.3 升级到 1. 7 版本的 3 毫秒以内,1.8 版本是 1 毫秒以内,基本上可以做到 1 毫秒以下的 GC 级别。
360 碰到 GC 问题最严重,360 整个消息推送系统是用 Go 语言写的,消息要及时送出去,GC 存在 30 毫秒卡住了,消息发送不出去。他们现在用 Go 1.8 测试,现在 GC 已经不是他们的问题了。当然,大家可能会说这有点不可信,GC 降下来了,CPU 使用率就上去了,1.7.3 和 1.8 版本中,CPU 肯定会多利用一些,CPU 的使用率相对上升了一点,但是 GC 有很大的提升。应该说,在 1.8 版本发布之后,1.9 版本现在引入了一个理念——goroutine 级别的GC,所以 1.9 版本可能还有更大的提升。
另一个是 Defer,Defer 在 1.8 版本中性能基本是提升了一半以上。
最后是 CGo,它的性能差不多提升了一半以上。代码等很多东西都是功能先实现,有了功能之后再提升它的。
New Features
-
HTTP/2 Push。在 1.8 版本中支持了 HTTP/2 Push 的功能,即你不需要通过浏览器来主动地搭这个,在服务端的时候,当你访问这个东西的时候,我可以主动地把资源推给你,不用浏览器解析 HTML 的时候浏览器再来发请求。在 1.8 版本在 Go 里面有一个非常好的东西是,针对HTTP/S 和 HTTP/2 是最好的语言,在 Google 内部需要所有的服务接口都通过 HTTP/S,Google HTTP 包非常稳定,因为它们用了大量的应用,同时内部要求全部用 HTTP/S,他们只能硬着头皮把它全部实现好。
-
Graceful Shutdown。很多人说热重启怎么办?这个问题在 1.7 版本之前有很多库,通过各种模拟,各种记录,怎么样实现平滑重启。在 Go 1.8 版本中内置了一个 graceful shutdown 的函数,访问的时候就可以很容易重启这个服务了。
-
Mutex Contention Profiling。我们在写程序的时候会用很多锁,但是怎么样调试这个锁的力度?在 Go 的 1.8 版本中支持了 mutex 的 profile,通过 profile 可以看得到类似输出这样一个东西,可以看得到在哪里实现了有锁的东西,这个锁花了多少时间。
-
database/sql。数据库用的是最多的,先前的 Go 数据库中要执行一个很长的 SQL 数据库,我碰到一些异常,我不要了,但是我不能取消掉它,它还在继续执行,等着它反馈。在 1.8 版本中增加了 Queries Context,可以把它停止掉。Queries Context 通过外部把它停掉,内部才会监听掉 Queries Context 的信号,后面才会退出。
-
New slice sorting API。以前对一个 slice 排序,要把 slice 定义一个类型。在 1.8 版本中相对于增加了一个 sorting slice 函数,可以很方便地匹配,slice 已经排序好了。
关于 Go 1.8 所有的信息就介绍到这里。Go 社区 4 月份即将在上海举办 Gopher China 的大会,也会邀请 Go 领域的各大专家来参会。
在 Go 社区中,中国的用户群最多,为什么 Go 在中国这么红呢?PHP 在国内很红火,PHP 刚开始确实开发很快,但是稍微上一点规模就遇到很大的性能问题,PHP 开始转向 Go,Go 可以解决很多性能问题。Go 语言相对来说比较容易学,因为它还是 C 系列的,PHP 也是 C 系列,还有 Python,逻辑代码太多了,重新去写花的时间太多了,把它编译成 Go。也可以看得到,整个 Google 的态度。
同时,我们也可以看到,云计算其实最初是 Google 提出来的,亚马逊只是把它发扬光大了,提供了公有云,OpenStack 提供了私有云,实际上大家都在抄袭 Google,所有做的云计算的东西都是复制 Google 基础架构的东西。因为 Google 做得最大,整个云计算的基础架构,我们都在学习它的东西。Go 语言也是 Google 出来的,这和他们当初设计 Go 的理念是一致的,为什么会设计 Go?随着云计算的发展,我们的应用是分布式化了、我们的系统是多核了,现在所有的语言都是十几年、二十年之前的,语言层面没有办法解决充分利用多核,所以才会设计了 Go 语言。
再回到我们刚刚讲的,为什么 Go 在中国这么火爆?我们在中国的互联网,特别是移动互联网增长的时候,大家都遇到了性能问题、扩张问题。这个时候,大家开始回想,我们当初写的这个东西是不是正确?想要解决新的问题的话,就想要找 C+ +,这个时候又觉得很复杂。Go 相当于在一个中间的过程,并发快、性能高,它在中间的位置,所以中国有很多人用 Go 写基础架构。
Q:Java 和 Go 分别是什么定位?Go 会替换掉 Java 吗?
谢孟军:不太可能,Java 的体系太多,特别是金融领域,做金融的基本上都是用 Java,支付宝、银行那一套东西,替换比较困难。但是 Go 定位的是什么?定位的是两个东西:1.基础架构;2.云计算,这两方面是 Go 发力的地方。
补充:我觉得 Go 和 Java 更像是公有云和私有云,Java 在企业服务方面是 Go 最难替换的。
(注:本文内容整理自七牛云主办的 ECUG Con 十周年大会,转载请注明出处。)