从8月份到现在,一直在公司尝试用Go写点东西。虽然我们几乎是清一色的Java开发,但我还是愿意在同事之间推广Go,有时间还是学学Go吧。
认识Go
我大概是一个不太执着的语言控,什么语言喜欢玩玩,在大约在12年时,就开始自学Go,但仅仅是看看语法,写写Helloword之类的小程序而已。在13年底,我被抽去分析Cloud Foundry的架构与实现机制。当时的CF是V2版本,其中的GoRouter,HM9000已采用Go重写,另外消息总线NATS也有Go语言版本。而我又重点分析了NATS,HM,以及部分GoRouter的Go源码。发现居然Go能写出如此简练的代码。性能验证时,又发现Go版本的NATS比Ruby版本的强得不是一点点,我们在单板上测试出有50万+的QPS。14年做融合架构,又把我们原有的消息中间件RabbitMQ换成了NATS。当时的出发点主是能与CF通过NATS融合拉通,另外是看重它的高性能。而RabbitMQ是erlang写的,部门熟悉erlang人几乎没有,维护成本高。当然到现在来看,NATS太简单了,并不是个消息队列,很多的特性都没有。
14年的Docker以席卷全球之势火了一把。在15年,我又投入到平台集成Docker的分析,于是又开始了Go语言之旅,重点研究了Docker Distribution的代码,以及由其它部门开发的Index等相关部件的代码(都是基于Go)。自己也是顺便练练手,如把系统中Java的通用加解密库(是基于AES与HamcSHA256之上的封装库),转换Go实现。经测试发现原来在Java对于HamcSHA256迭代6W+次数时需要差不多一分钟,而Go只需要几秒。由于系统需要对接Docker的API,涉及到Http Hijack,于是又得去分析Docker源码中这一块是怎么实现的,把它的实现转换成Java(虽有开源的Java Client API,但不能满足我们的要求)。所以差不多就是干些Java转Go,Go转Java的体力活。整个版本开发中也协助定位一些Docker相关的问题,需要走读Docker代码。整体来说,我写的Go代码不是太多。
曾经的痛苦
C++
十多年的编码经验,一半是做C/C++开发,一半是Java。目前还记忆犹新的是,在08年写的一个消息缓存中间件,大量使用了共享内存、内存分片技术、大文件操作,以及缓存淘汰算法(主要与业务特性相关)。原本只是一个消息缓存+持久化,但后来做着做着已不再是一个纯粹的缓存,参杂着业务复杂的逻辑,最终也导致代码质量不可控。一出问题就是踩地址(用于大消息缓存,原本设计是小消息),CoreDump文件分析困难。现在来看,其实如果只是做纯粹的缓存与持久化,Redis也能满足当时的需求,可惜那时开源没有现在这么火,更没有听说有Redis,连Memchached都没有听过。后来在09年与10年,又负责过另一个产品的版本稳定与性能提升。在那段日子里,我与另一个兄弟不知解决多少个CoreDump问题,现在还记得一个踩栈地址的问题整整花了我一周时间,通过猜测CoreDump中的地址信息加上反复走读代码才找到问题的原因。为了压榨单板的性能,在做优化时真是极其语言的偏门用法,尤其是在老的代码上为了发挥并发多线程的能力,代码写得真是惨不忍睹(那时也接触过erlang,发现erlang进程模型是多美好)。目前我司还有很多产品为了提高性能与降低时延,甚至是在内核做了一些修改,如零拷贝技术。这些性能上极致的代码也只能是少数人能看得懂。10年还做一件非常痛苦的工作,就是把跑到Linux上代码移植到Window,主要用于做开发验证仿真工具。即使采用MGWin,也是苦不堪言,其中的困难是谁做谁知道。
Java
10年底开始做云计算,又开始做Java开发(之前也开发过Java,主要是JNI)。使用C开发时,没有什么开源框架可选,但Java的框架是一大堆,J2EE,OSGI,Spring…无论是哪种,框架都是又臭又硬,太厚重了。大量使用第三方的开源Jar管理也是非常困难。即使我们采用Maven来做工程管理,也是相当的复杂,尤其是对一个大型系统,全编译构建时间都可以与原有C/C++有得一拼(我之前经历过的C/C++写的系统,全编译要花差不多一天时间,拿现的DevOps理念是不可想象的),也尝试使用过Maven的并行编译,但由于部分的Mavne插件不支持也放弃了,只能换成多台机器分布式编译。Java运行环境到14年我们才换成JDK8,之前一直采用JDK6,写多了就觉得Java的语法是硬伤,太不灵活,尤其是一堆的Getter与Setter(采用lombok简化),什么都得先定个interface,总之代码看起不是清爽简洁。Java的打包发布更是一个噩梦,虽有Maven管理,但对于一个大型系统,差不多一百号人的开发团队,系统整体打包是差不多2G的压缩包。我们是花了不少时间去清理不同版本的第三方Jar包(公司要求同一产品依赖的版本要归一),每次做版本升级,换一个Jar的版本会牵出一堆的Jar依赖要升级,也是苦不堪言,其中的痛苦是谁做谁知道。经过不断地努力,目前整个团队使用第三方Jar登记还有100+,整体打包差不多1G的压缩包,对于严格的电信行业说,任何第三方Jar包要做内部开源扫描认证,这是一项浩大的工程。在06年做Java时,为了性能比拼,JVM的性能参数调优也是一个非常要有技术的活,吞吐量与时延两者不能兼顾。
再来说Go
缘由
我为什么喜欢Go,最重要的原因是我目前从事云计算领域的研发。总结之前说了这么多的痛苦,对于C/C++:
- 开发效率低,定位问题复杂,对开发者技术要求高
- C/C++偏底层,对系统依赖度高,系统迁移困难
- 开源框架少,系统API只向后兼容,维护成本高
那Java呢:
- 框架臃肿庞杂,反而简单问题复杂化
- 规范繁多,实现框架也多,选择太多,产品容易被框架绑定
- 语法啰嗦,全OOP化不灵活
技术特征
当然,无论是C/C++还是Java,如果项目决策需要,我还是会继续使用他们。他们的成功有他们成功的原因,纯粹的语言比较都有各自的优缺点。我在这也不是为了说喜欢Go而去有意贬低他们,只是列一下个人觉得遇到痛苦。为什么我喜欢Go,主要原因还是它在云计算相关产品的发力,像Google的K8S,Docker,CoreOS,CloudFoundry等等都大规模地使用Go。在学习与使用Go的过程中,被他的设计理念所折服,它是一个面向工程而简化的语言。从语言学上来说,他可能不是最好的语言,但对于大多数的系统,一般都需要兼顾开发效率,运行性能,维护成本。而Go似乎在这几个方面能做到很好平衡。
- 开发效率
- 语法简单,学习曲线低
- 代码简洁,格式统一
- 静态类型,编译期检查
- 内置GC,Runtime期识别
- 标准库丰富,网络库简单易用
- 运行性能
- 编译为机器码,不依赖其它库
- 语言层面并发模型,可充分利用多核
- 内嵌C支持,可直接利用C的资产
- 启动快,执行效率高,内存占用低
- 标准库质量高,针对性优化
- 维护成本
- 没有什么语法糖,高级特性少,格式统一,阅读方便
- 自带工具链完善,如代码格式化,代码检查,性能分析等工具
- 默认编译为单个执行文件,部署简单,超赞
- 标准库跨平台支持,迁移成本非常低
不足
当然Go在工程方面也不是很完美,就目前个人使用经验来看:
- 缺少Go工程的依赖库版本管理,尤其是使用第三方开源不好控制(注:1.5引入 go vendor)
- 错误机制采用返回值,真是满眼的if来判断错误,代码相似度高
- 接口与实现未分离,对于商用产品,想只提供接口定义来保护知识产权操作不方便
- Goroutine调度切换不能由程序控制,需由上层有严谨的设计,维护困难,容易修改出问题
如果你也是在云计算领域,或会从事服务端的应用开发,如中间件,分布式,网络通讯的系统开发,有时间不妨学习学习Go,他简单易学,多掌握一语言,多一门求生技能。