转用一门新言语通常是一项大决议计划,尤其是当你的团队成员中只要一个运用过它时。本年 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 言语》(http://howistart.org/posts/go/1/)中的一小段代码:
package main
type openWeatherMap struct{}func (w openWeatherMap) temperature(city string) (float64, error) {
resp, err := http.Get("http://api.openweathermap.org/data/2.5/weather?APPID=YOUR_API_KEY&q=" + city)
if err != nil {
return 0, err
}
defer resp.Body.Close()
var d struct {
Main struct {
Kelvin float64 `json:"temp"`
} `json:"main"`
}
if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
return 0, err
}
log.Printf("openWeatherMap: %s: %.2f", city, d.Main.Kelvin)
return d.Main.Kelvin, nil}
假如你是一个新手,看到这段代码你并不会感到吃惊。它展现了多种赋值、数据结构、指针、格局化以及内置的 HTTP 库。
当我第一次编程时,我很喜欢运用 Python 的高阶功用。Python 答应你发明性地运用正在编写的代码,比方,你能够:
在代码初始化时,运用 MetaClasses 自行注册类别
置换真假
增加函数到内置函数列表中
经过美妙的办法重载运算符
毋庸置疑这些代码很风趣,但也使得在读取其别人的作业时,代码变得难以了解。
Go 逼迫你坚持打牢根底,这也就为读取恣意代码带来了便当,并能很快搞理解当下产生的作业。
留意:当然怎么简略仍是要取决于你的运用事例。假如你要创立一个根本的 CRUD API,我仍是主张你运用 Django + DRF,或许 Rails。
原因 4:并发性&通道
Go 作为一门言语致力于使作业简略化。它并未引进许多新概念,而是聚集于打造一门简略的言语,它运用起来反常快速并且简略。其仅有的立异之处是 gorouTInes 和通道。GorouTInes 是 Go 面向线程的轻量级办法,而通道是 gorouTInes 之间通讯的优先办法。
创立 GorouTInes 的本钱很低,只需几千个字节的额定内存,正由于此,才使得一同运转数百个乃至数千个 goroutines 成为或许。你能够凭借通道完成 goroutines 之间的通讯。Go 运转时刻能够表明一切的杂乱性。Goroutines 以及根据通道的并发性办法使其十分简略运用一切可用的 CPU 内核,并处理并发的 IO——一切不带有杂乱的开发。相较于 Python/Java,在一个 goroutine 上运转一个函数需求最小的样板代码。你只需运用关键词「go」增加函数调用:
package main
import (
"fmt"
"time")func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}}func main() {
go say("world")
say("hello")}
Go 的并发性办法十分简略上手,相较于 Node 也很风趣;在 Node 中,开发者有必要亲近重视异步代码的处理。
并发性的另一个优质特性是比赛检测器,这使其很简略弄清楚异步代码中是否存在竞态条件。下面是一些上手 Go 和通道的很好的资源:
https://gobyexample.com/channels
https://tour.golang.org/concurrency/2
http://guzalexander.com/2013/12/06/golang-channels-tutorial.html
https://www.golang-book.com/books/intro/10
https://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html
原因 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、Template parsing、Task scheduling、Expression parsing 和 RocksDB 的安稳的库。
Go 的生态体系比较于 Rust、Elixir 这样的言语有很大的优势。当然,它又略逊于 Java、Python 或 Node 这样的言语,但它很安稳,并且你会发现在许多根底需求上,现已有高质量的文件包可用了。
原因 8:GOFMT,强制代码格局
Gofmt 是一种强壮的命令行功用,内建在 Go 的编译器中来规则代码的格局。从功用上看,它类似于 Python 的 autopep8。格局共同很重要,但实践的格局规范并不总是十分重要。Gofmt 用一种官方的方式标准代码,防止了不必要的评论。
原因 9:gRPC 和 Protocol Buffers
Go 言语对 protocol buffers 和 gRPC 有一流的支撑。这两个东西能一同友爱地作业以构建需求经过 RPC 进行通讯的微服务器(microservices)。咱们只需求写一个清单(manifest)就能界说 RPC 调用产生的状况和参数,然后从该清单将主动生成服务器和客户端代码。这样产生代码不只快速,一同网络占用也十分少。
从相同的清单,咱们能够从不同的言语生成客户端代码,例如 C++、Java、Python 和 Ruby。因而内部通讯的 RESET 端点不会产生分歧,咱们每次也就需求编写简直相同的客户端和服务器代码。
运用 Go 言语的缺陷
缺陷 1:短少结构
Go 言语没有一个首要的结构,如 Ruby 的 Rails 结构、Python 的 Django 结构或 PHP 的 Laravel。这是 Go 言语社区剧烈评论的问题,由于许多人以为咱们不应该从运用结构开端。在许多事例状况中的确如此,但假如仅仅期望构建一个简略的 CRUD API,那么运用 Django/DJRF、Rails Laravel 或 Phoenix 将简略地多。
缺陷 2:过错处理
Go 言语经过函数和预期的调用代码简略地回来过错(或回来调用仓库)而协助开发者处理编译报错。尽管这种办法是有用的,但很简略丢掉过错产生的规模,因而咱们也很难向用户供给有意义的过错信息。过错包(errors package)能够答应咱们增加回来过错的上下文和仓库追寻而处理该问题。
另一个问题是咱们或许会忘掉处理报错。比如 errcheck 和 megacheck 等静态剖析东西能够防止呈现这些失误。尽管这些处理方案十分有用,但或许并不是那么正确的办法。
缺陷 3:软件包办理
Go 言语的软件包办理肯定不是完美的。默许状况下,它没有办法拟定特定版别的依靠库,也无法创立可复写的 builds。比较之下 Python、Node 和 Ruby 都有更好的软件包办理体系。可是经过正确的东西,Go 言语的软件包办理也能够体现得不错。
咱们能够运用 Dep 来办理依靠项,它也能指定特定的软件包版别。除此之外,咱们还能够运用一个名为 VirtualGo 的开源东西,它能轻松地办理 Go 言语编写的多个项目。
Python vs Go
咱们施行的一个风趣试验是用 Python 写排名 feed,然后用 Go 改写。看下面这种排序办法的示例:
{
"functions": {
"simple_gauss": {
"base": "decay_gauss",
"scale": "5d",
"offset": "1d",
"decay": "0.3"
},
"popularity_gauss": {
"base": "decay_gauss",
"scale": "100",
"offset": "5",
"decay": "0.5"
}
},
"defaults": {
"popularity": 1
},
"score": "simple_gauss(time)*popularity"}
Python 和 Go 代码都需求以下要求然后支撑上面的排序办法:
解析得分的表达。在此示例中,咱们想要把 simple_gauss(time)*popularity 字符串转变为一种函数,能够把 activity 作为输入然后给出得分作为输出。
在 JSON config 上创立部分函数。例如,咱们想要「simple_gauss」调用「decay_gauss」,且带有的键值对为”scale”: “5d”、”offset”: “1d”、”decay”: “0.3”。
解析「defaults」装备,便于某个范畴没有清晰界说的状况下有所反应。
从 step1 开端运用函数,为 feed 中的一切 activity 打分。
开发 Python 版别排序代码大约需求 3 天,包含写代码、测验和树立文档。接下来,我么花费大约 2 周的时刻优化代码。其间一个优化是把得分表达 simple_gauss(time)*popularity 转译进一个笼统语法树。咱们也完成了 caching logic,之后会预先核算每次的得分。
比较之下,开发 Go 版别的代码需求 4 天,但之后不需求更多的优化。所以尽管开始的开发上 Python 更快,但 Go 终究需求的作业量更少。此外,Go 代码要比高度优化的 python 代码快了 40 多倍。
以上仅仅咱们转向 Go 所体验到的一种优点。当然,也不能这么做比较:
该排序代码是我用 Go 写的第一个项目;
Go 代码是在 Python 代码之后写的,所以提早了解了该事例;
Go 的表达解析库质量优胜。
Elixir vs Go
咱们评价的另一种言语是 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 来编写。