从Python切换到Go的九大理由

2023-01-31 02:01:54 切换到 理由 九大

原文:Why we switched from python to Go
作者:Thierry Schellenbach
翻译:雁惊寒

摘要:本文介绍了Stream网站从Python切换到Go的九个理由,以及Go语言存在的三个主要缺点,为那些正在饱受Python折磨的项目团队指出了一条明路。以下是译文。

切换到一种新的编程语言往往是一个大工程,特别是当团队成员对原来语言的经验非常丰富的时候。今年年初,我们将Stream的主要编程语言从Python切换到了Go。这篇文章将解释我们决定从Python切换到Go的一些原因。

使用Go的原因

原因1 – 性能


1.png
Go很快!

Go非常地快。它的性能接近于Java或c++。对于我们这个案例,Go比Python快了30倍。这里有一个小的游戏评测结果:Go vs Java。

原因2 – 语言本身的性能很重要

对于很多应用程序来说,编程语言只是应用程序和数据库之间的粘合剂。语言本身的性能通常并不重要。

Stream是一个api提供商,它为500家公司和2亿多最终用户提供反馈基础设施。多年来,我们一直在不断优化Cassandra、postgresqlRedis等软件的性能,但现在已经达到了所使用编程语言的性能极限。

Python是一个伟大的语言,但是对于诸如序列化/反序列化,排序和聚合等例子来说,其性能相当低下。我们经常遇到性能问题,Cassandra会用1ms的时间来检索数据,而Python将用接下来的10ms将其转换成对象。

原因3 – 开发者效率,以及不要过于创新

来看看这一段出自“如何开始学习Go”这个教程的Go代码。

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
}

如果你是一个刚学Go的新手,那么在阅读这段代码的时候不会有太多的惊喜。它演示了赋值、数据结构、指针、格式化和内置的HTTP库。

当我第一次接触编程时,我一直喜欢使用Python的高级功能。 Python能让你从正在编写的代码中获得非常好的创意。例如,你可以:

  • 在代码初始化时,使用MetaClasses自行注册类
  • 切换True和False
  • 在内置函数列表中添加函数
  • 通过魔术方法重载运算符

这些功能是很有趣,但是,大多数程序员都认为,这会使得阅读他人的代码更加困难。

Go迫使你使用最基础的东西,这使得阅读他人的代码会变得非常容易。

注意:当然,“容易”取决于具体的项目。如果你想创建一个基本的CRUD API,我仍然推荐你使用Django + DRF或者Rails

原因4 – 并发和Channels(通道)

作为一种编程语言,Go一直在尽可能的保持简单。它没有引入太多新概念,因为其目标是创造一个简单易用的编程语言。它唯一有创新的地方是Goroutines和Channels。Goroutines是Go的轻量级线程解决方案,Channels则是Goroutines之间交互的首选方式。

Goroutines的创建非常轻便,只需要几KB的额外内存。而由于Goroutines是如此之轻量级,所以可以有数百甚至数千个Goroutines同时运行。

你可以使用Channels在Goroutines之间进行通信。 Go运行时会处理好内部所有的复杂性。 Goroutines和基于Channels的并发方案使得应用程序可以非常容易使用所有可用的CPU内核,以及处理并发io,而不会让开发变复杂。与Python/Java相比,在Goroutines上运行一个函数只需要很少的一点固定代码。你只需使用关键字“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")
}

https://tour.golang.org/concurrency/1

Go的并发解决方案很容易使用。与node相比,这是一个非常有趣的方案,因为对于Node来说,开发人员必须密切关注异步代码的处理方式。

Go并发性的另一个重点是竞争检测。它使得应用程序能够很容易地知道异步代码中是否存在任何竞争条件。

这里有学习Go和Channels的几个不错的资源:

  • 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
  • Goroutines vs Green threads

原因5 – 编译速度飞快

用Go编写的最大的微型服务项目只需要用6秒钟的时间就能编译完成。与Java和C++等语言的龟速编译相比,Go飞快的编译速度是其主要生产力。


XKCD - Code compiling before Go

原因6 – 组件团队的能力

首先我们来看看这份数据:Go开发人员不像C++和Java之类的那么多。根据StackOverflow统计,38%的开发人员会用Java,19.3%会用C++,但只有4.6%会用Go 。 GitHub数据显示了类似的趋势:Go比Erlang、Scala和Elixir等语言使用更广泛,但不如Java和C++流行。

幸运的是,Go是一种非常简单易学的语言。它只提供了你所需的基本功能,没有其他额外的。它引入了新概念“defer”语句和内置的“go routines”和Channels并发管理。团队中任何一个Python、Elixir、C++、Scala或Java开发人员都可以在一个月内学会用Go编程,因为Go实在太简单了。

与其他语言相比,我们发现建立一个Go开发团队更为容易。如果你在竞争激烈的环境里(如博尔德和阿姆斯特丹)招聘人员的话,这是一个非常重要的优势。

原因7 – 强大的生态系统

对于我们这样规模(约20人)的团队来说,生态系统很重要。如果你必须重新发明所有的功能,那么根本无法为客户创造价值。 Go对我们经常使用的工具提供了很大的支持。比如Redis、RabbitMQ、Postgresql、模板解析、任务调度、表达式解析和RocksDB都可以使用现成的库。

与其他较新的语言(如Rust或Elixir)相比,Go的生态系统优势非常大。虽然它无法与Java、Python或Node相比,但对于许多基本的需求,你都能找到可用的高品质软件包。

原因8 – Gofmt,强制代码格式化

Gofmt是一个极好的的命令行程序,它内置于Go编译器中,用于格式化代码。在功能方面,它类似于Python的autopep8。我们大多数人并不喜欢争论Tab和空格,但格式化这个目标是一致的,实际的格式化标准并不重要。 Gofmt通过一种正式的方式来格式化代码以避免所有这些争论。

原因9 – grpc和Protocol Buffers

Go对Protocol Buffers和grpc提供了一流的支持。这两个工具可以完美的结合在一起,构建出通过RPC进行通信的微服务器。你只需编辑一个定义了RPC调用及其参数的清单文件,服务端和客户端就可以通过这个文件自动生成相应的代码。这样不仅快速,而且网络占用也较小,使用起来也较方便。

可以基于同一个清单文件生成其他语言的客户端代码,比如C++、Java、Python和Ruby。这样,内部的REST接口就不会出现冲突,我们也不用每次编写几乎相同的客户端和服务端代码了。

使用Golang的缺点

缺点1 – 缺乏框架

Go没有一个主打框架,比如Ruby的Rails、Python或Django或PHP的Laravel。这个话题在Go社区争论激烈,许多人认为在启动项目的时候不应该使用现成的框架。在某种情形下,我完全同意这个观点。但是,如果想要搭建一个简单的CRUD API,那么使用Django/DJRF、Rails Laravel或Phoenix将会更容易一些。

缺点2 – 错误处理

Go通过简单地从一个函数返回错误来进行错误处理。虽然这种方案可用,但却很容易丢失错误发生的范围,从而很难向用户提供有价值的错误信息。错误包(errors package))能够解决这个问题,它可以返回错误的上下文以及错误堆栈。

还有一个问题,我们很容易忘记处理错误。虽然像errcheck和megacheck这样的静态分析工具可以地避免出现这些错误,但是总感觉不是很完美。也许我们应该期望语言级别的错误处理方案吧。

缺点3 – 软件包管理

Go的软件包管理并不完美。默认情况下,它没有办法指定特定版本的依赖项,并且无法创建可重复的构建方案。 Python、Node和Ruby都有着更好的软件包管理系统。然而,如果使用了合适的工具,Go的软件包管理工作能变得更方便。

你可以使用Dep来管理依赖项,它能指定以及固定版本。除此之外,我们还提供了一个名为VirtualGo的开源工具,可用于多项目管理


Virtual Go
Virtual Go

Python vs Go

我们做了一个有趣的实验,把原来用Python编写的ranked 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代码都需要执行以下操作才能支持这种排序方法:

  1. 解析score表达式,把“simple_gauss(time)*popularity”转换一个函数,输入活动,输出分数。
  2. 根据JSON配置创建函数。例如,我们希望“simple_gauss”以5天的比例调用“decay_gauss”,偏移1天,衰减因子为0.3。
  3. 解析“defaults”配置,在某个字段没有值的时候,取默认值。
  4. 从步骤1开始使用函数,为 feed 中的所有活动打分。

开发Python版本的排序代码大概花了3天时间,包括写代码、单元测试和写文档。接下来,我们花了大约2个星期来优化代码。其中一个优化是将分数表达式(simple_gauss(time)*popularity)转换为抽象语法树。我们还实现了缓存逻辑,可用于预计算分数。

相比之下,开发此代码的Go版本大约只用了4天的时间,而且后期也无需进一步优化性能。因此,尽管Python在一开始开发得更快,但Go版本最终所需的工作量更少。还有一个优势,Go代码比我们高度优化的Python代码快了大约40倍。

当然,这只是我们切换到Go以来遇到的一个简单的性能提升例子:

  • 该排序代码是我用Go写的第一个项目
  • Go代码是在Python代码之后编写的,因此对该项目的理解更深入
  • Go的表达式解析库质量更高

你的经历可能会有所不同。与Python相比,我们系统的其他一些组件需要更多的时间来用Go构建。作为一个总的趋势,我们看到编写Go代码花的精力更多。但优化代码的性能所花的时间则变少了。

Elixir vs Go

我们要评估的另一种语言是Elixir。 Elixir构建于Erlang虚拟机之上,是一种迷人的语言。我之所以谈到它,是因为我们项目组中有一个成员对这个语言很精通。

对于我们这个按理,我们注意到Go的原始性能更好。 Go和Elixir都支持数千次的并发请求。但是,如果看一下单个请求的性能,那么Go要快得多。我们选择Go的另一个原因是生态系统。对于我们需要的组件,Go有更成熟的库,而Elixir还不适合用于生产。同时,也很难培训和寻找使用Elixir的开发人员也很难与Elixir合作。

结论

Go是一种性能非常高的语言,对并发的支持也非常强大。它几乎和C++和Java一样快。虽然与Python或Ruby相比,Go编译的速度更慢,但你可以在优化代码上节省下很多时间。

我们的Stream开发团队为超过2亿的最终用户提供资讯。Go对于新手来说有着庞大的生态系统,并且易学易用;它在并发方面有着超快的性能强大的支持;而且还有着非常高效的开发环境。这些特点使得Go成为开发人员最合适的选择。

如果你想了解更多有关Go的信息,请阅读下面列出的文章。要了解有关Stream的更多信息,请浏览这个交互式教程。

阅读更多有关切换到Golang的文章

  • https://movio.co/en/blog/migrate-Scala-to-Go/
  • https://hackernoon.com/why-i-love-golang-90085898b4f7
  • https://sendgrid.com/blog/convince-company-go-golang/
  • https://dave.cheney.net/2017/03/20/why-go

Go学习

  • https://learnxinyminutes.com/docs/go/
  • https://tour.golang.org/
  • http://howistart.org/posts/go/1/
  • https://getstream.io/blog/building-a-perfORMant-api-using-go-and-cassandra/
  • https://www.amazon.com/gp/product/0134190440

相关文章