Cover

为什么我使用 Golang 撰写容器化应用程序

· Technology

在当前互联网架构圈子,容器化、微服务,成为被主要讨论的话题。

显然,对于互联网的发展趋势来看,项目正在变得愈加庞大。单一的巨大的项目是不利于维护和开发的,开发者的电脑几乎难以直接承载阿里云、Azure这种巨大的项目,那我们就需要对其进行解耦。也就是说,应用的组件化、容器化、微服务化必将成为趋势。

在容器化应用程序开发这一点,我在之前的开发历程中有些许心得,在本文我将对其具体阐述。

先前的语言选用

在开始学习 Golang 之前,我曾尝试过很多语言。 就如我的个人简介所写的一样,较为经典的PHP、Java,较为复杂的CPP,较为有趣的Express.js、Koa.js、Next.js,我都曾使用过。 即使是现在,你也可以在我的GitHub账号找到许多之前的练习品,如 AH-dark/bing-image-api。 但这些语言或多或少都有一些不足之处,因而被我放弃。

Java

Spring 全家桶在中国大陆被广泛地使用,特别是阿里巴巴,几乎可以算是 Spring 的最主要用户之一。 依靠阿里巴巴,SpringBoot 几乎成为每一位中国Java工程师的必修课。

久闻其名,我用约两周时间构建了一个 Java SpringBoot 应用程序,随即抛弃了它。AH-dark/Link

作为一个 Java 程序,即使被编译为字节码,其巨大的体积也是令人望而却步的。 更何况 Java 作为一个基于 VM 的跨平台语言,依赖 JVM 而运行,需要在系统安装一个庞大的 Runtime。在这种情况下,打包的 Docker Image 体积会变得极其庞大,但这和你的代码完全没有关系。JVM 运行的应用程序也会占用大量内存,这显然不利于容器化,因为单一应用对于内存的占用太大,而这并不必要。 而最令人难以接受的,是其启动时间。即使是一个极其微小的 Spring Boot 应用程序也需要至少5秒左右的冷启动时间,对于不持续运行的 RPC 链,这一环可能造成严重的卡顿。这也是我放弃它的主要原因

PHP

PHP 是一个动态类型的脚本语言,既无法被打包成字节码,也无法以一个优雅的方式封装成类似 Jar 的应用程序。

Docker Image 中的 PHP 需要在 Image 内安装各种扩展和 PHP FPM,这是不可接受的。

这往往意味着,在生产环境中,一个Kubernetes集群需要跑几十甚至几百个PHP FPM和其扩展,而不可复用,显然这极大地浪费了运算资源。

C++

我并没有在我的GitHub公开发布使用CPP撰写的Web应用程序,因为我现有的CPP应用程序基本都具有一个极其低下的完成度。

作为一个内存不安全的语言,CPP 应用程序对于撰写者有很大的要求,同时 CPP 项目的开发周期也不是一般的长。

再者便是跨平台相关,CPP 的跨平台可以说是很难处理的,而在容器化应用程序中还是存在一定的跨平台需求的。

综上三条,我没有考虑继续使用 CPP 撰写容器化应用程序。

Node.js

Node.js 曾被我用于撰写 Source Global CDN 的多层RPC链路中间件。

Image

JavaScript 中几乎所有元素都可以以 Object(对象)呈现,也正是因此我对其较为喜爱。而加有静态类型强制的 TypeScript ,是我在生产环境广泛应用的语言(尤其是前端,得益于 React 对 TypeScript 的完美支持,在 React 前端使用 TypeScript 在我看来是一个很享受的事。)。

在使用 JavaScript、TypeScript 和 Express.js、Koa.js 撰写了几乎所有 Source Global CDN 中间件后,我意识到一个问题:封装了 Node.js 的 Docker 镜像在冷启动方面似乎不具备优良的表现。

我也是从这时候开始关注应用程序冷启动速度的。

在 ServerLess 平台,Docker Image 的调用似乎往往是 调用Docker镜像 -> 运行 -> 发送 Http Request。 而在Node.js应用程序中,因封装Node.js Runtime而变得庞大的Image就很难在500ms以内快速运行镜像。 正是因此,Source Global CDN 在前期不得不引入一层对象存储来对文件进行持久化缓存,以避免因冷启动而导致的卡顿。 后续,我逐步对其中间件进行聚合和重写,重写的语言便是接下来的重头戏——Golang。

Go

首先,我们需要对其进行一定的了解。

介绍

Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

罗伯特·格瑞史莫、罗勃·派克及肯·汤普逊于2007年9月开始设计Go,稍后伊恩·兰斯·泰勒(Ian Lance Taylor)、拉斯·考克斯(Russ Cox)加入项目。Go是基于Inferno操作系统所开发的。Go于2009年11月正式宣布推出,成为开放源代码项目,支持Linux、macOS、Windows等操作系统。

在2016年,Go被软件评价公司TIOBE选为“TIOBE 2016年最佳语言”。

目前,Go每半年发布一个二级版本(即从a.x升级到a.y)。

Go的语法接近C语言,但对于变量的声明有所不同。Go支持垃圾回收功能。Go的并行计算模型是以东尼·霍尔的通信顺序进程(CSP)为基础,采取类似模型的其他语言包括Occam和Limbo,Go也具有这个模型的特征,比如通道传输。通过goroutine和通道等并行构造可以建造线程池和管道等。在1.8版本中开放插件(Plugin)的支持,这意味着现在能从Go中动态加载部分函数。

与C++相比,Go 并不包括如枚举、try-catch 异常处理、继承、虚函数等功能,但增加了切片(Slice) 、泛型、并发、管道、垃圾回收、接口等特性的语言级支持。对于断言的存在,则持负面态度,同时也为自己不提供类型继承来辩护。

不同于Java,Go原生提供了关联数组(也称为哈希表(Hashes)或字典(Dictionaries))。

引用自 Go- Wikipedia

Google 设计 Go 的最初目的便是提高在多核、网络机器、大型代码库情况下的开发效率。也是因此,其性能较好,且对 Runtime 的需求也是较少的。

同时,Go内置了一个轻量的协程,即 Goroutine,实现了基本的并发需求。

容器化

不同于Java、Node.js,Go在容器化有一个天然的优势,即编译型语言。

Go应用程序编译后的体积可以减小到MB级,不需要内置庞大的Runtime,只需一些系统环境即可运行。打包后的 Golang Application Image 通常都是极小的。

Image

上方为 Golang 镜像,下方为 Node.js 镜像

功能性

虽然 Go 很多提案都很扯淡且没有建设性,但 Go 仍然是一个功能性强且统一化的语言。

你在不同的 PHP 项目所见到的 HTTP Client 可能是不同的,比如有人用 curl,有人用 file_get_content 函数。但在 Go 中,通常大家都会选择使用 builtin 的 http 模块。这是一个好处,因为这减小了因规范不同而出现错误的可能性。

性能

Go可以做到在内部调用C语言程序、Rust程序、Assembly程序,这使得Go可以通过引用其他语言来弥补自身的一些性能上的劣势。

不同于 Java 的是,Go 不仅无需 VM 环境,而且内存占用极低,不需要将程序运行在 VM 中。编译后的可执行文件体积也很小,不同 Java 一般字节码体积巨大。

Go 应用程序对我来说最大的优点在于冷启动。Source Global CDN 重写后的 Go 后端程序的冷启动时间提高了约 3000%。这是一个巨大的提升,也可以显著地改善用户体验。这也是我选择 Go 作为 Source Global CDN 项目日后主要开发语言的主要原因。

说 C++ 的请想想开发周期和开发难度。

总结

Go 将会是我未来一段时间主要使用的语言,因为它大多数设计基本都是符合我审美观念的。 泛型的傻逼设计,还有未来的手动内存控制,就不要说了。 同时,我也推荐诸位尝试使用 Go,尽管其部分设计是非常愚蠢的,但总体上来看还是值得一试的。

我才不会说我马上要开始深入学习 Rust

参考资料

Comments

Send Comments

Markdown supported. Please keep comments clean.