摘要:转用一门新语言通常是一项大决策,尤其是当你的团队成员中只有一个使用过它时。今年Stream团队的主要编程语言从Python转向了Go。本文解释了其背后的九大原因以及如何做好这一转换。
转用一门新语言通常是一项大决策,尤其是当你的团队成员中只有一个使用过它时。今年Stream团队的主要编程语言从Python转向了Go。本文解释了其背后的九大原因以及如何做好这一转换。
Go的优势
原因1:性能
Go极其地快。其性能与Java或C++相似。在我们的使用中,Go一般比Python要快30倍。以下是Go与Java之间的基准比较:
原因2:语言性能很重要
对很多应用来说,编程语言只是简单充当了其与数据集之间的胶水。语言本身的性能常常无关轻重。
但是Stream是一个API提供商,服务于世界500强以及超过2亿的终端用户。数年来我们已经优化了Cassandra、PostgreSQL、Redis等等,然而最终抵达了所使用语言的极限。
Python非常棒,但是其在序列化/去序列化、排序和聚合中表现欠佳。我们经常会遇到这样的问题:Cassandra用时1ms检索了数据,Python却需要10ms将其转化成对象。
原因3:开发者效率不要过于创新
看一下绝佳的入门教程《开始学习Go语言》()中的一小段代码:
1packagemain2typeopenWeatherMapstruct{}func(wopenWeatherMap)temperature(citystring)(float64,error){3resp,err:=(";q="+city)4iferr!=nil{5return0,err6}7()8vardstruct{9Mainstruct{10Kelvinfloat64`json:"temp"`11}`json:"main"`12}13iferr:=().Decode(d);err!=nil{14return0,err15}16("openWeatherMap:%s:%.2f",city,)17,nil}如果你是一个新手,看到这段代码你并不会感到吃惊。它展示了多种赋值、数据结构、指针、格式化以及内置的HTTP库。
当我第一次编程时,我很喜欢使用Python的高阶功能。Python允许你创造性地使用正在编写的代码,比如,你可以:
在代码初始化时,使用MetaClasses自行注册类别
置换真假
添加函数到内置函数列表中
通过奇妙的方法重载运算符
毋庸置疑这些代码很有趣,但也使得在读取其他人的工作时,代码变得难以理解。
Go强迫你坚持打牢基础,这也就为读取任意代码带来了便利,并能很快搞明白当下发生的事情。
注意:当然如何容易还是要取决于你的使用案例。如果你要创建一个基本的CRUDAPI,我还是建议你使用Django+DRF,或者Rails。
原因4:并发性通道
Go作为一门语言致力于使事情简单化。它并未引入很多新概念,而是聚焦于打造一门简单的语言,它使用起来异常快速并且简单。其唯一的创新之处是goroutines和通道。Goroutines是Go面向线程的轻量级方法,而通道是goroutines之间通信的优先方式。
创建Goroutines的成本很低,只需几千个字节的额外内存,正由于此,才使得同时运行数百个甚至数千个goroutines成为可能。你可以借助通道实现goroutines之间的通信。Go运行时间可以表示所有的复杂性。Goroutines以及基于通道的并发性方法使其非常容易使用所有可用的CPU内核,并处理并发的IO——所有不带有复杂的开发。相较于Python/Java,在一个goroutine上运行一个函数需要最小的样板代码。你只需使用关键词「go」添加函数调用:
1packagemain2import(3"fmt"4"time")funcsay(sstring){5fori:=0;i5;i++{6(100*)7(s)8}}funcmain(){9gosay("world")10say("hello")}并发性的另一个优质特性是竞赛检测器,这使其很容易弄清楚异步代码中是否存在竞态条件。下面是一些上手Go和通道的很好的资源:
原因5:快速的编译时间
当前我们使用Go编写的最大微服务的编译时间只需6秒。相较于Java和C++呆滞的编译速度,Go的快速编译时间是一个主要的效率优势。我热爱击剑,但是当我依然记得代码应该做什么之时,事情已经完成就更好了。
Go之前的代码编译
原因6:打造团队的能力
首先,最明显的一点是:Go的开发者远没有C++和Java等旧语言多。据知,有38%的开发者了解Java,19.3%的开发者了解C++,只有4.6%的开发者知道Go。GitHub数据表明了相似的趋势:相较于Erlang、Scala和Elixir,Go更为流行,但是相较于Java和C++就不是了。
幸运的是Go非常简单,且易于学习。它只提供了基本功能而没有多余。Go引入的新概念是「defer」声明,以及内置的带有goroutines和通道的并发性管理。正是由于Go的简单性,任何的Python、Elixir、C++、Scala或者Java开发者皆可在一月内组建成一个高效的Go团队。
原因7:强大的生态系统
对我们这么大小的团队(大约20人)而言,生态系统很重要。如果你需要重做每块功能,那就无法为客户创造收益了。Go有着强大的工具支持,面向Redis、RabbitMQ、PostgreSQL、Templateparsing、Taskscheduling、Expressionparsing和RocksDB的稳定的库。
Go的生态系统相比于Rust、Elixir这样的语言有很大的优势。当然,它又略逊于Java、Python或Node这样的语言,但它很稳定,而且你会发现在很多基础需求上,已经有高质量的文件包可用了。
原因8:GOFMT,强制代码格式
Gofmt是一种强大的命令行功能,内建在Go的编译器中来规定代码的格式。从功能上看,它类似于Python的autopep8。格式一致很重要,但实际的格式标准并不总是非常重要。Gofmt用一种官方的形式规格代码,避免了不必要的讨论。
原因9:gRPC和ProtocolBuffers
Go语言对protocolbuffers和gRPC有一流的支持。这两个工具能一起友好地工作以构建需要通过RPC进行通信的微服务器(microservices)。我们只需要写一个清单(manifest)就能定义RPC调用发生的情况和参数,然后从该清单将自动生成服务器和客户端代码。这样产生代码不仅快速,同时网络占用也非常少。
从相同的清单,我们可以从不同的语言生成客户端代码,例如C++、Java、Python和Ruby。因此内部通信的RESET端点不会产生分歧,我们每次也就需要编写几乎相同的客户端和服务器代码。
使用Go语言的缺点
缺点1:缺少框架
Go语言没有一个主要的框架,如Ruby的Rails框架、Python的Django框架或PHP的Laravel。这是Go语言社区激烈讨论的问题,因为许多人认为我们不应该从使用框架开始。在很多案例情况中确实如此,但如果只是希望构建一个简单的CRUDAPI,那么使用Django/DJRF、RailsLaravel或Phoenix将简单地多。
缺点2:错误处理
Go语言通过函数和预期的调用代码简单地返回错误(或返回调用堆栈)而帮助开发者处理编译报错。虽然这种方法是有效的,但很容易丢失错误发生的范围,因此我们也很难向用户提供有意义的错误信息。错误包(errorspackage)可以允许我们添加返回错误的上下文和堆栈追踪而解决该问题。
另一个问题是我们可能会忘记处理报错。诸如errcheck和megacheck等静态分析工具可以避免出现这些失误。虽然这些解决方案十分有效,但可能并不是那么正确的方法。
缺点3:软件包管理
Go语言的软件包管理绝对不是完美的。默认情况下,它没有办法制定特定版本的依赖库,也无法创建可复写的builds。相比之下Python、Node和Ruby都有更好的软件包管理系统。然而通过正确的工具,Go语言的软件包管理也可以表现得不错。
我们可以使用Dep来管理依赖项,它也能指定特定的软件包版本。除此之外,我们还可以使用一个名为VirtualGo的开源工具,它能轻松地管理Go语言编写的多个项目。
PythonvsGo
我们实施的一个有趣实验是用Python写排名feed,然后用Go改写。看下面这种排序方法的示例:
1{2"functions":{3"simple_gauss":{4"base":"decay_gauss",5"scale":"5d",6"offset":"1d",7"decay":"0.3"8},9"popularity_gauss":{10"base":"decay_gauss",11"scale":"100",12"offset":"5",13"decay":"0.5"14}15},16"defaults":{17"popularity":118},19"score":"simple_gauss(time)*popularity"}Python和Go代码都需要以下要求从而支持上面的排序方法:
解析得分的表达。在此示例中,我们想要把simple_gauss(time)*popularity字符串转变为一种函数,能够把activity作为输入然后给出得分作为输出。
在JSONconfig上创建部分函数。例如,我们想要「simple_gauss」调用「decay_gauss」,且带有的键值对为”scale”:“5d”、”offset”:“1d”、”decay”:“0.3”。
从step1开始使用函数,为feed中的所有activity打分。
开发Python版本排序代码大约需要3天,包括写代码、测试和建立文档。接下来,我么花费大约2周的时间优化代码。其中一个优化是把得分表达simple_gauss(time)*popularity转译进一个抽象语法树。我们也实现了cachinglogic,之后会预先计算每次的得分。
相比之下,开发Go版本的代码需要4天,但之后不需要更多的优化。所以虽然最初的开发上Python更快,但Go最终需要的工作量更少。此外,Go代码要比高度优化的python代码快了40多倍。
以上只是我们转向Go所体验到的一种好处。当然,也不能这么做比较:
该排序代码是我用Go写的第一个项目;
Go代码是在Python代码之后写的,所以提前理解了该案例;
Go的表达解析库质量优越。
ElixirvsGo
我们评估的另一种语言是Elixir。Elixir建立在Erlang虚拟机上。这是一种迷人的语言,我们之所以想到它是因为我们组员中有一个在Erlang上非常有经验。
在使用案例中,我们观察到Go的原始性能更好。Go和Elixir都能很好地处理数千条并行需求,然而,如果是单独的要求,Go实际上更快。相对于Elixir,我们选择Go的另一个原因是生态系统。在我们需求的组件上,Go的库更为成熟。在很多案例中,Elixir库不适合产品使用。同时,也很难找到/训练同样使用Elixir的开发者。
结论
Go是一种非常高效的语言,高度支持并发性。同时,它也像C++和Java一样快。虽然相比于Python和Ruby,使用Go建立东西需要更多的时间,但在后续的代码优化上可以节省大量时间。在Stream,我们有个小型开发团队为2亿终端用户提供feed流。对新手开发者而言,Go结合了强大的生态系统、易于上手,也有超快的表现、高度支持并发性,富有成效的编程环境使它成为了一种好的选择。Stream仍旧使用Python做个性化feed,但所有性能密集型的代码将会用Go来编写。
对每位程序员来说,不管哪种语言,“学的扎实,能学以致用”才是王道!本文就为对Go语言感兴趣的朋友们提供一些口碑不错的学习书籍建议。
一、《Go语言学习笔记》
(未找到对应版本的电子书,大家可以去作者github:)
适合人群:本书不适合编程初学入门,可供有实际编程经验或正在使用Go工作的人群参考。
二、《Go语言实战》
适合人群:全覆盖,侧重初学者
三、《Go语言编程》
推荐理由:作者是业界大神级别的人物,七牛云存储团队的核心技术人员,也是国内最早应用和推广Go语言技术的专家!本书内容简炼,重点突出,将Go语言的特性做了充分的分析和总结,并给出Go实例的代码;内容体系可能更适合有一定编程基础的程序员阅读!本书是国内最早的中文版的Go技术书籍之一,虽然发行时间比较早,但仍可以作为重要参考!电子版和纸质版都有!
适合人群:全覆盖,侧重有经验的程序员
四、《Go语言程序设计》
推荐理由:国外最经典的Go语言著作,Go语言编程的先驱者MarkSummerfield的实践经验总结。这是一本Go语言实战指南,帮你了解Go语言,按Go语言的方式思考,以及使用Go语言来编写高性能软件。
作者展示了如何编写充分利用Go语言突破性的特性和惯用法的代码,以及Go语言在其他语言之上所做的改进,并着重强调了Go语言的关键创新。
注重实践教学,每章都提供了多个经过精心设计的代码示例。
由国内第一个核心服务完全采用Go语言实现的团队——七牛团队核心成员翻译。
适合人群:适用于有一定Go语言编程编程的爱好者,非常适合作为Go语言编程进阶教程。