从 PHPer 的角度看Go(译文) | Go主题月

920 阅读10分钟

image.png

作为一名 PHP 工程师,你有没有问过自己是否需要掌握其他编程语言?

多年来,PHP 一直是构建全栈项目的首选语言。此外,在过去 5 年中,框架(SymfonyLaravelZend)、工具(ComposerMonolog)和快速增长的社区(PHP-FIG)帮助许多工程师构建了企业级软件应用程序。很多公司,比如Facebook,Yahoo!Wikipedia、Wordpress、Tumblr在早期用原生 PHP,这并没有阻碍他们多年来取得成功。

然而,一家成功的企业在成长过程中,它所需要的工程师数量也在增长。公司的组织结构表明,现有的整体最好是分开。在某种程度上,战略趋于稳定,团队现在将重点放在独立服务上。

在本文中,我们将尝试评估 PHP 单独能把我们带到多远的地方,以及 Go 能在哪里介入并解决我们将要面对的问题。

PHP 和 微服务

作为一名 PHP 工程师,如果你在一个大型的应用程序中每次管理初始化请求都在 30 毫秒内,那太好了!为实际的请求处理再增加 50-100 毫秒,您就有了惊人的总体响应时间。

既然我们计划打破我们的原有项目整体,坚持为我们的服务相同的设置,那么会发生什么呢?通过简单的计算得出,如果服务在同一请求中相互交互 10 次,我们就有 300 毫秒的引导时间。多么糟糕的用户体验!

虽然 PHP 在 WEB 开发中大放异彩,但它在微服务体系结构中的能力还相当不成熟。对于 PHP 工程师来说,诸如超时处理、健康检查、度量收集、隔壁、断路器等基本术语通常是不熟悉的。其中大多数都可以在许多其他语言的各种框架中找到。就算有的话,PHP 生态系统对它们的支持也是非常差的。

遇到 Go

来自维基百科:

Go(通常被称为golang)是 2007 年由 robertgriesemer、robpike 和 kenthompson 在 Google 开发的一种开源编程语言。主要是为系统编程而设计的,它是一种编译的、静态类型的语言,在传统的 C 和 C++ 中,具有垃圾收集、各种安全特性和 CSP 风格的并发编程特点。

Go 是谷歌公司开发的。这不是一个有趣的 20% 的项目,也不是为了实现别人做不到的事情。它仅仅是在处理由非常大的团队处理非常大的软件所产生的复杂性时产生的挫败感的基础上创建的,这些软件是用具有大型特性集的语言编写的。

虽然它有一些很好的特性,比如简单的并发或快速的编译,但是它的主要特点是非常简单。与此相关的是,在 PHP 中,关键字有 25 个,而在PHP有 67个。

Go 出现在市场上的时间大致与 PHP 获得名称空间支持的时间相同(2009年)。在短短的 6 年里,已经有几十个伟大的项目,其中包括 Docker、Kubernetes、etcd、InfluxDB,以及像 Cloudflare、Google、Dropbox、SoundCloud、Twitter、PayPal 这样的公司,他们在 Go 中构建了他们的后端系统。

返回微服务

让我们浏览一下前面讨论过的问题,看看使用 Go 有什么好处。

Go 应用程序快速编译成机器代码。许多人声称编译时间如此之快,以至于当他们切换到浏览器并点击「刷新」时,应用程序已经被重新编译、重新启动并服务于请求。即使使用大型代码库,也会有一种感觉,即您正在使用解释语言,就像 PHP 一样。

传统的「Hello World」 API 用 10 行代码编写,响应时间不到一毫秒。Go 的多核功能允许工程师并行运行他们的软件。生态系统允许选择任何传输协议,如 JSON over HTTPgRPCprotocol BuffersThrift。在 Zipkin 中很容易跟踪请求。指标从 statsd 导出到 Prometheus。使用速率限制器强制传入或传出请求吞吐量,并保护与断路器的客户端服务通信。

关于Go

语言

Go 是一种严格类型化的语言,它要求您在初始化期间指定变量的类型:

var name string = "sobit"

变量可以推断右侧值的类型。上述代码等同于其缩写版本:

var name = "sobit"

// or even better:

name := "sobit"

现在,让我们记住如何在 PHP 中交换两个变量的值:

<?php

$buffer = $first;
$first = $second;
$second = $buffer;

Go

first, second = second, first

因为Go 有多个返回值的能力。下面是多返回值函数的示例:

func getName() (string, string) {
    return "sobit", "akhmedov"
}

first, last = getName()

在 Go 代码中,您不会看到如此常用的 foreach、while 和 do-while。它们统一为一个 for 语句:

// foreach ($bookings as $key => $booking) {}
for key, booking := range bookings {}

// for ($i = 0; $i < count($bookings); $i++) {}
for i := 0; i < len(bookings); i++ {}

// while ($i < count($bookings)) {}
for i < len(bookings) {}

// do {} while (true);
for {}

虽然 Go 没有所谓的「类」或「对象」,但它的类型与集成代码和行为的数据结构的相同定义相匹配。在 Go 中,这被称为「结构体」 。我们用一个例子来说明:

type rect struct { // define a struct
    width  int
    height int
}

func (r *rect) area() int { // define a function on struct
    return r.width * r.height
}

r := rect{width: 10, height: 15} // initialize
fmt.Print(r.area())

有时定义一个复杂的结构也很有用。举个例子:

type Employee struct {
    Name string
    Job  Job
}

type Job struct {
    Employer string
    Position string
}

// and to structure it
e := Employee{
    Name: "Sobit",
    Job: {
        Employer: "GetYourGuide",
        Position: "Software Engineer",
    },
}

我还有很多其他的特性很想谈,但那会让我从官方网站上复制一个 Effective Go 文档,并产生一篇永无止境的文章。但是我想花点时间向您介绍一下 Go中的并发性,我发现这是关于该语言最有趣的话题之一。在我们开始之前,关于Go- goroutinesChannel 中的并发性,您需要了解两件事。

goroutine 有一个简单的模型:它是一个与同一地址空间中的其他 goroutine 并发执行的函数。当调用完成时,goroutine 将静默地存在。最简单的示例是创建心跳函数,该函数将每秒向终端打印 I’m alive 消息:

func heartbeat() {
    for {
        time.Sleep(time.Second)
        fmt.Println("I'm still running...")
    }
}

现在,我们如何运行它,使它在后台执行,并允许我们并行地做其他事情嘞?答案比你想象的要简单,只需在 Channel 前面加上 go:

go heartbeat()

// keep doing other stuff

这种方法类似于 fire-and-forget 事件。但是如果我们不需要忘记并且对函数产生的结果感兴趣呢?这就是 Channel 发挥作用的地方:

func getName(id int, c chan string) {
    c <- someHeavyOperationToFindTheName(id)
}

func main() {
    c := make(chan string, 2) // allocate a channel

    go getName(1, c) // trigger
    go getName(2, c) // don't wait and trigger again

    // keep doing other stuff

    fmt.Printf("Employees: %s, %s", <-c, <-c) // combine
}

总而言之,我们只是两次触发同一个函数,让应用程序并行执行它们,并要求在实际需要的时候返回结果。

工具

Go 的设计,再次考虑到简单性。作者采用了一种相当激进的方法来解决工程师们整天做的常见活动。向无休止的 space-vs-tab 之争说 Goodbye ,这是来自社区团体的代码标准,他们试图以某种方式简化工程师共享代码的方式。Go 提供了 go fmt 工具,它负责设计代码的样式。不再共享 IDE 配置文件,也不再试图记住左大括号应该放在同一行还是下一行。

包源代码的文档可以使用 go doc 读取,而 go vet 将有助于发现代码中的潜在问题。安装依赖只需要运行 go get github.com/[vendor]/[package] ,通过触发 go test[package] 命令执行测试。正如您所看到的,工程师为每种语言的每种应用程序提供的大多数工具都已经在这里了。

项目部署

无论您选择哪种语言或构建什么,部署都是一项强制操作。作为一名 PHP 工程师,在你的职业生涯中,你写过多少 CapistranoEnvoy 的配置文件嘞?在过去的几天里,您有多少次通过 FTP 把更改的文件手动传输到服务器上?

最常见、最简单的 PHP 部署过程如下所示:

  • 将目标服务器上的最新代码放到新的发行版文件夹中
  • 复制缓存的依赖项并安装更新
  • 复制特定于环境的配置文件
  • 运行所有脚本来预热应用程序
  • 将当前版本符号链接指向新版本文件夹
  • 重新启动 PHP-FPM

一些团队可以将此过程提升到更高级的级别,如:

  • 拉取服务器上的最新代码
  • "构建"项目(安装依赖项、预热缓存等)
  • 创建一个可分发的"工件"((一个压缩的 tar.gz 文件)
  • 将"工件"传输到目标服务器
  • 解压到新的发行版文件夹
  • 将当前版本符号链接指向新版本文件夹
  • 重新启动 PHP-FPM

当然,除此之外,您还需要确保您的目标服务器和构建服务器至少安装了 PHP-FPM 和 PHP,并且它们之间的版本以及工程师用于本地开发的版本更好地匹配。

现在让我介绍一下常规 Go 应用程序的部署过程:

  • 拉取服务器上的最新代码
  • 构建它(注意没有引号)
  • 将工件(同样没有引号)传输到目标服务器
  • 重新启动正在运行的应用程序

就这样。潜在的简单版本和高级版本之间的唯一区别是是否有独立的构建服务器

美妙之处在于,您甚至不需要在目标服务器上安装 Go,即使它们运行不同的操作系统或基于不同的 CPU 体系结构,您仍然可以从单个构建服务器或任何其他计算机为所有这些服务器(甚至对于 Windows )准备工件。

结论

在系统还不够成熟的时候,从来没有人建议将系统分解成微服务。企业规模虽小,但必须灵活应对市场机遇。对于构建微服务来说,这不是一个理想的环境,取而代之的是一个整体,可以获得更多的好处。PHP 及其生态系统非常适合这种策略。

当组织稳定下来并且很容易识别它的有界上下文时,用 PHP 构建微服务来补充它们可能会很痛苦。许多其他语言中存在各种工具和技术来支持这种体系结构。您可能要冒风险,要么构建自己的工具,要么完全拒绝它们——这两种方法对您的组织来说都是昂贵的。

另一方面,在 Go 中构建微服务是值得考虑的。该语言易于编写和理解,其学习曲线不像 JavaScala 那样陡峭,性能也不远落后于 C。编译时间使工程师保持高效,而对多核或网络机器的支持使编写功能强大的应用程序成为可能。

其它资源

对于一只初学者来说,最好从官方站点文档的学习部分开始。它包括一个可以在浏览器中打开的Go 语法介绍页Go 代码规范 这将有助于了解如何编写清晰,惯用的 Go 代码。

如果你觉得这还不够的话,Go 学习资源目录可以帮助你提高新获得的技能。

如果你和我一样是 JetBrains IDE 的粉丝,请查看 Go plugin for IntelliJ,它主要由同一个 JetBrains 开发人员开发。它可以安装在几乎所有的 ide 上,包括 PhpStorm 。对于其他 ide 和文本编辑器,请访问 wiki 页面

当您想在 Go 中构建您的第一个生产就绪的微服务时,我鼓励您查看 Go kit。它是一个编程工具包,致力于解决分布式系统中的常见问题,让工程师专注于业务逻辑。

最不重要但值得一提的是,可以使用 GopherJS 构建客户端 JavaScript 应用程序和 SDK 应用程序,并且可以构建 iOS 和 Android 应用程序

原文链接:sobit.me/2016/02/25/…