从一个 PHP 工程师的角度来看 Go

dW5xqvVRHh.png

作为一个 PHP 工程师,你有时是否想过要精通一门其他编程语言?

PHP 曾是一个由许多企业构建大型全栈项目的的首选语言。此外,在过去的五年中下列框架 (Symfony,Laravel, Zend),工具 (Composer, Monolog) 和迅速成长的社区 (PHP-FIG) 帮助过诸多工程师去构建企业型应用。像 Facebook,Yahoo!,Wikipedia,Wordpress,Tumblr 之类的公司在创业之初都使用过原生 PHP 进行开发,这些随后都促使他们走向成功。

然而,一家成功公司的发展,需要更多的工程师来支持。因此公司庞大的组织架构最好把它拆分开来。在某些时刻,这种策略会让组织架构稳定,并且每个组都能专注于自己的独立业务。

在这篇文章中我们将去评估 PHP 能带我们走多远,以及 Go 能解决哪些我们将会遇到的问题。

PHP 和 微服务

作为一名 PHP 工程师,你应该知道如果在一个庞大的项目中一个网络请求的初始化时间只需要 30 毫秒内,这样就很棒了。再加上 50-100 毫秒的时间交给业务去处理,这样应用的响应时间就会很可观。

直到我们打算打破我们原有的复杂项目,我们坚持为我们的服务提供相同的设置,那将会发生什么? 让我们简单的计算一下,如果服务在相同的请求期间互相交互 10 次,我们就需要 300ms 的请求时间。 这将会是多么糟糕的用户体验!

虽然 PHP 在 WEB 开发方面表现出色,但它在微服务体系结构中的功能还不成熟。 PHP 工程师通常不熟悉诸如超时处理,健康检查,度量收集,隔壁,断路器等基本术语。 他们中的大多数可以在许多其他语言框架中找到。 即使存在的话,在 PHP 生态系统中支持它们也是非常糟糕的。

遇见 Go

来自 Wikipedia 百科:

Go (通常称为 golang) 是 Robert Griesemer,Rob Pike 和 Ken Thompson 于 2007 年在 Google 开发的一种开源编程语言。 主要为系统编程而设计,它是 C 和 C ++ 传统的静态类型编译语言,增加了垃圾收集,各种安全功能和 CSP 风格的并发编程功能。

Go 是由谷歌公司开发的一种编程语言,她 (Go 语言) 不是一个有趣的 20% 的项目,也不是为了实现他人无法完成的事情,她是为了解决庞大团队编码的复杂性问题应运而生的,解决了他们使用大型的功能集的语言来编写大型软件的困惑。

尽管她具有诸如容易并发和编译快速等优秀特性,但是她最主要的特性是特别的简单,便于开发。有相关的资料中显示,Go 的 关键字 仅仅有 25 个,而在在 php 中 关键字 有 67 个。

Go 语言的推出大致是在 PHP 语言支持命名空间的时间,也就是大约在 2009 年。在短短的 6 年时间里,已经有包括  Docker, Kubernetes, etcd, InfluxDB 等数十个伟大的项目,不但如此,像 Google,,Dropbox, SoundCloud,Twitter,PayPal 等公司都用 Go 语言构建了他们的后端系统。

回到微服务

让我们回过头来看一下之前讨论过的问题,我们可以从使用 Go 语言获得哪些有益的收获。

Go 语言构建的应用可以很快的编译为机器码,许多编程人员感叹道,Go 语言编译的时间非常的快,当他们切换到浏览器变点击「刷新」是,应用程序就已经重新编译了和重新启动了,并且发出了请求。即使是在构建非常大的代码库时,就像是使用一种像 PHP 这种解释型的语言的感觉。

在编写传统的 API 「Hello World」的时候,用十行代码,响应的时间不到 1 毫秒。Go 语言的多核功能可以让编程人员能够运行沉重的软件。生态系统允许选择任何的传输协议,例如 JSON over HTTP, gRPC, Protocol Buffers 或者 Thrift。请求很容易在  Zipkin 找到。指标从 statsd 导出到  Prometheus。使用限速器强制进入或外出的请求吞吐量,并保护与断路器的客户服务通信。

关于 Go语言
 
Go 是一门静态类型的语言,你需要在初始化变量时声明它的变量类型:


var name string = "golang"

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


var name = "golang"

// 更优雅点:

name := "golang"

现在,让我们回想一下在 PHP 中如何交换两个变量的值:



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

Go 的版本:


first, second = second, first

因为 Go 有多重返回值的特性。下列是多重返回值函数的例子:


func getName() (string, string) {
    return "hello", "world"
}

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 { // 定义一个结构体
    width  int
    height int
}

func (r *rect) area() int { // 在结构体上添加方法
    return r.width * r.height
}

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

有时候定义一个复杂的结构体也是有用的。这里是一个例子:


type Employee struct {
    Name string
    Job  Job
}

type Job struct {
    Employer string
    Position string
}

// 并去结构化它
e := Employee{
    Name: "Sobit",
    Job: {
        Employer: "GetYourGuide",
        Position: "Software Engineer",
    },
}

我渴望谈论其他许多功能,但是这会让我复制官方网站上的 实效 GO 编程  文档,并生成一篇永无止境的文章。但我想花一点时间向您介绍 Go 中的并发,我发现它是该语言最有趣的主题之一。在我们加入之前,你需要知道在 Go 中关于并发的两件事 – goroutines 和 channels。

goroutine 有一个简单的模型:它是一个和其他 goroutines 在同一地址空间同时执行的函数。当执行完之后,goroutine 会悄悄的退出。 最简单的一个例子就是写一个每秒在终端打印「I’m alive」的心跳函数。


func heartbeat() {
    for {
        time.Sleep(time.Second)
        fmt.Println("I'm still running...")
    }
}
现在, 我们怎样才能让这个函数在后台执行并且允许我们并行的做其他事呢? 答案比你想象中要简单, 只需要在执行函数前加一个 go :

go heartbeat()

// 继续做其他事
这就和 「fire-and-forget」 事件触发一样。但如果我们不需要忘记并且对函数的执行结果有兴趣呢? 这就是 channels 所发挥的地方:

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

func main() {
    c := make(chan string, 2) // 分配一个 channel

    go getName(1, c) // 调用
    go getName(2, c) // 不想等待并且再次调用

    // 继续做其他事

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

总结一下, 我们刚刚执行了同一个函数两次并且让程序并发的执行它们, 并要求它们在我们真真需要的时候给我们执行结果。

工具

Go 的设计,再一次考虑到了简单性。 作者采用了相当激进的方法去解决了工程师整天的普通工作。 对无穷无尽的 「space-vs-tab」争论说 「再见」,社群的代码标准正以某种方式简化工程师共享代码。 Go 自带开箱即用关注代码风格的 go fmt 工具。 不再需要分享 IDE 的配置文件, 不用尝试记住大括号应该是在同一行还是再起一行。

可以使用 go doc 去阅读源码包文档, 而 go vet 将会协助我们查找代码中的问题。 安装第三方包只需要执行 go get [github.com/[vendor]/[package](http://github.com/[vendor]/[package)] 指令, 测试只需要执行 go test [package] 指令。正如你所看到的,工程师在每一个程序每一个语言都用到的大多数工具都在这里开箱即用。

项目部署

无论你选择哪种语言或者你正在构建什么语言,部署项目都是强制性的操作。作为一个 PHP 工程师,在你的职业生涯中,你写过多少个 Capistrano 或者 Envoy 的配置文件?那一天你将多少手动更改的文件通过 FTP 传输到服务器上?

这是最常见和最简单的 PHP 程序部署过程,像下面这样:

从目标服务器上面检出最新的代码放到最新版本的文件夹中
复制缓存的依赖项并安装更新
复制环境特定的配置文件
运行所有的脚本让程序准备运行起来
将当前版本符号链接指向新版本文件夹
重启 PHP-FPM
有些团队会把这个部署的过程变的更高级一些,像下面这样:

从构建服务器上检出最新的代码
「构建」项目(安装依赖,运行缓存,等等)
创建一个可分发的「工件」(一个压缩的 tar.gz 文件)
将「工件」转移到目标服务器
解压缩到最新的发布的文件夹中
将当前版本符号链接指向新版本文件夹
重启 PHP-FPM

当然,除此之外,你需要确保你的目标和构建服务器至少安装了 PHP-FPM 和 PHP,它们之间的版本要相互匹配包括工程师用于本地开发的版本。

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

从构建的服务器上检出最新的代码
构建项目(注意没有引号)
将工件(再次没有引号)传输到目标服务器
重启正在运行的应用
就是这样简单。简单版本和高级版本之间的唯一区别是,是否有一个独立的构建服务器。

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

总结

对于初创企业来说,微服务并不是构建其项目首选。使用 PHP 来进行快速开发,跟上市场节奏依旧是一个明智的选择。毕竟生存是宇宙第一要义。

当一家企业已经稳定发展,并且在可预见的未来没有大的威胁, 此时使用 PHP 构建微服务将会是一件非常痛苦的事情。 这个时候你完全可以使用其他合适的语言来构建微服务。

另一方面,易于编写和理解的 Go 语言是用来构建微服务的不错选择, 它的学习曲线并没有 Java / Scala 那么陡峭,同时其性能与 C 相接近。 高效的编译系统,高并发的支持使得使用 Go 来编写强大的应用程序成为可能。

其它资源

对于一个初学者来说,最好从官方站点文档的学习部分 开始。 它包含一个可以在浏览器中打开的 Go 语法介绍页,当然如果你想写出干净,地道的代码 , Go 代码规范 是一个不错的地方。

然后可以参考由社区维护的 Go 学习资源目录 来提高你的编码水平与技能。

如果你使用的是 JetBrains 家的 IDE,我建议使用 Go plugin for IntelliJ 这个由 JetBrains 官方维护的插件。它可以安装到 JetBrains 全家桶的任何一个 IDE 上,比如 PhpStorm. 完整的支持列表参考这个 wiki 页面。

当你准备好使用 Go 来构建你的微服务的时候,我建议你看一下 Go kit。这个工具集致力于解决构建微服务时的常见问题,让你能够专注于业务逻辑。

值得一提的是, GopherJS 使得使用 Go 构建 Javascript 客户端,原生应用以及 SDK 成为可能 。这里有一个例子说明如何使用 GopherJS 构建 Android 和 iOS 应用。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/258128.html

(0)
上一篇 2022年5月20日
下一篇 2022年5月20日

相关推荐

发表回复

登录后才能评论