起初,我们认为坚持一门熟悉的语言是负责任的事情——我们是一个小团队,却已经冒了两次险:切换到微服务和完全重写我们的 Web 应用程序(高流量游戏平台)。 但是,最终我们决定放弃 PHP 拥抱 Go,下面我将解释为什么这么做,并分享一些在我们的微服务架构中数据库相关的想法。
微服务和 PHP:概念性错配
我们熟悉的语言是 PHP,它支撑了我们现有的应用程序,有两个模糊的论据可以支持我们继续这么做下去:
- 我们熟悉 PHP,它开发很快题。为什么要放弃对我们有用的东西?
- 市面上有很多 PHP 开发人员。 选择 PHP 让我们更容易扩充团队。
这听起来非常合理,但是当我们清楚 PHP 真的不是我们的正确选择时,我们很快就放弃了这些想法。 我们正在迁移到微服务架构,因为我们希望我们的高流量架构基础设施(200 万日活用户)可更好扩展。从长远来看,随着我们向 1000 万(还会更多)日活用户迈进,而且每天和每小时都能随之改变(译者注:指扩容和缩容):随着整个国家在休息或者白天,我们的基础设施应该随之弹性伸缩到相应的规模。
PHP 不适合我们的主要原因:
1、PHP 具有较高启动开销
PHP 曾经被设计成(或长成)为运行短命令的脚本,因此持久并不是这个语言适合支持的特性。这意味着对于每个请求,数据库连接和类都必须重新被实例化,这增加了不必要的延迟开销。
当然熟悉这方面读者都知道,有解决方案,例如通过 PHP-FPM 或 Apache 的连接池或 C 绑定等方法,可以支持与 Redis 的持久连接。
但是,由于我们追求高性能,这些依赖使我们对选择 PHP 作为合适的工具存在疑虑。
2、容器化 PHP 是一个雷区
PHP 需要 Nginx 和 PHP-FPM(或类似工具)来实现进程和连接池管理等功能。这意味着对于每个部署的微服务,PHP-FPM 和 Nginx 也必须一起运行。这浪费了资源,也降低了扩展的效率。
还有优化配置的问题。优化单 PHP 实例已经很头大了,因为需要了解和配置 PHP,PHP-FPM 和 Nginx 这一堆组合,我们无法想象最终在弹性的 Kubernetes 环境中配置多个 PHP 栈的痛苦情形,您完全不知道在同一台机器上运行了哪些服务。
微服务器的复杂性在架构中:您正在处理一个由简单服务组成并且相互之间作用的复杂系统。既然我们已经致力于这个架构,那么因为语言而增加更多长期开销和概念上的错误就是得不偿失的。
招聘又如何呢?我们发现它对我们的情况是无效的。像微服务一样,我们认为开发人员应该是语言无关的。我们宁愿聘请一位聪明的开发人员学习一门新语言来完成工作,而不是使用一位坚持自己做事方式的语言专家。在这个意义上,移除 PHP 实际上让我们获得了真正自由。
向 Go 迈进
我们偏爱的两个主要语言是 Node.js 和 Golang。我们做了一些研究,并决定了转向 Go 而非 Node。 为什么是 Go?
性能。二进制文件的方式启动一个长期运行的守护进程,意味着每个请求和持续连接的启动成本很低。Go(包括 Goroutine )天生就为网络及多核环境设计,使其在处理大量并发请求方面超快和高效。
Go 可以编译成小巧且可移植的二进制文件。这使它非常适合在 Docker 容器中使用。部署我们的 Go 容器只需几秒钟,因为它们的体积小(大多只有 4 – 5MB),并且由于静态链接的原因,在容器内不需要 OS 或运行时依赖。作为参考,当使用 Node alpine 镜像时,我们的前端容器大约需要 55MB。
Go 是类型严格的。这使代码中的内部通信更为可靠。它也有助于在编译构建期间捕获问题,而不是在运行期间。
Go 有非常好的工具链。虽然工具是很多语言的问题,但 Google 决定从一开始就解决这个问题,提供了大量常用的工具,作为语言安装包的一部分。
当然我们也同时考虑到了 Go 的这些缺点:
- Go 不附带依赖管理工具。官方团队正致力于此,当官方工具发布的时候,很可能非常好用。现在,您可以检查您的 vendors,或者使用 Glide 等工具。
- 更多代码。这是 Go 的优雅和简单的反面。
然而,我们选择接受这一点:使用 Go 确实需要一些努力,但它会产出高质量代码。 这不是说我们把 Go 用到所有地方。对于服务器端渲染,我们选择 Node,因为它允许我们在前端和后端之间共享逻辑。我们也使用 Java 来解决特定的问题,因为 Java 已经存在了很长时间,并且有大量类库。
我们希望使用每个场景最适合的工具,也就是说,对于大多数情况,Go 将是我们的首选工具。
Big Gopher (Gyga8K)
评估 NoSQL
当我们开始用 Go 编写我们的第一个服务时,我们同时也同时思考数据库的问题。我们习惯使用 MySQL,它过去工作良好,但它往往也是性能瓶颈。
在我们的传统技术栈中,我们还大量使用了 Redis 进行缓存,这对于性能来说非常棒,因为它有效地减少了数据库 join 查询的访问压力。
因此当我们开始在新技术栈中进行数据库选型时,评估 NoSQL 就很有必要,可以看看我们是否可以完全避免这些 join 查询。
我们评估了两个数据库:
MongoDB – 我们很好奇去了解一个文档型数据库是否可以用来存储游戏中大量元数据。当然麻烦的是,我们必须在 Google Cloud 中使用,根据社区的说法,这样根本不能很好地扩展。我们尽量避免复杂的 DevOps 工作,因此 MongoDB 出局。
Cassandra – 它是一个已知的可以扩展的数据库,并被一些大型高访问量平台 Netflix 和 Reddit 使用。我们喜欢的特性是:它的速度非常快,并支持线性扩展。不过,我们发现管理太复杂了。
如果您确切知道如何查询您的数据,Cassandra 非常适合。对于具有大量数据的分析服务来说,情况可能如此,但是在敏捷的产品设计开发环境中,随着产品的发展,用户适应性变化,Cassandra 虽然强大,但是对于我们这样的小团队来说难于控制。
继续与 SQL 共舞
我们逐渐走近微型服务的概念,更加坚信构建小型的独立服务的想法,这些服务完成特定任务,并且在需要时可以轻松升级或被更换。
因此我们还是坚持使用 MySQL 作为我们的默认数据库。我们使用了 MySQL 很多年,知道如何设计高性能的数据库模式。虽然它不支持原生的线性伸缩,但现在也不是一个大的问题:由于微服务架构的模块化特性,应用程序负载分布在许多机器的不同微服务上。并且每个微服务器都可以访问自己的 32 核数据库机器和几个只读从库,这种方式还可以继续前行很长的路。
我们非常高兴,现在我们现在还没有过度工程化。如果有一个服务需要 Cassandra 或其他数据库,那么我们也可以轻松迁移该服务。
那么为什么选择 MySQL?现在主要是因为它可以很方便在 Google Cloud 上进行管理,在 DevOps 方面我们是务实的。
我们也考虑尝试 Postgres,因为它开放源码,还有一个强大的社区,并且显然已经运行了很多年。因此,取决于 Google Cloud 未来的 Alpha 版本,我们也可能会尝试 Postgres。