构建适用于 Amazon Graviton 的 Go 应用程序

了解如何将 Go 应用程序从基于 x86 的 Amazon EC2 实例迁移至基于 ARM64 的 Graviton EC2 实例,从而提高的应用程序可持续性并降低成本。
发布时间:2023 年 8 月 14 日
Graviton
Golang
可持续性
成本优化
部署
迁移
ARM64
EC2
教程
亚马逊云科技
Olawale Olaleye
亚马逊云科技使用经验
200 - 中级
完成所需时间
30 分钟
所需费用

可通过亚马逊云科技免费套餐免费试用,或 2.62 美元

前提条件

注册 / 登录 亚马逊云科技账户
Amazon DynamoDB 表

示例代码

本教程中使用的示例代码来自 GitHub

上次更新时间
2023 年 7 月 20 日

学习内容

  • 如何构建适用于 Amazon Graviton 的 Go 应用程序
  • 如何将现有 Go 应用程序移植至 Amazon Graviton

设置

EC2 设置

为了演示如何将 Go 应用程序迁移至基于 Amazon Graviton 的实例,我在 Go 中构建了一个简单的短链接转换微服务。我并非前端开发人员,因此我将依靠 cURL 来与应用程序的 API 进行交互。此应用程序是使用 Go 1.20.6 和 gin 为我的 HTTP 框架编写的。此应用程序为其所缩短的每个 URL 生成一个唯一的 10 个字符的字符串,并将原始 URL 和 10 个字符的字符串存储在 Amazon DynamoDB 表中。所用到的代码不适合在生产环境中使用,仅作为示例使用。

在本次教程演示中将启动两个 EC2 实例 运行 Linux 2023。第一个实例的类型为 c5.xlarge 实例,第二个实例的类型为 c6g.xlarge 实例。两个实例运行后,便 连接至各个实例安装 Go

代码检查

要查看示例项目,请转至 building-go-applications-for-aws-graviton 并使用以下命令克隆存储库:

git clone https://github.com/build-on-aws/building-go-applications-for-aws-graviton

查看 x86 实例和 Amazon Graviton 实例上的代码。您现在应该有一个 building-go-applications-for-aws-graviton 目录,里面包含所有相应的代码。

DynamoDB 设置

短链接转换应用程序将使用 DynamoDB 作为数据存储。我们的 DynamoDB 表将以按需模式运行。我们创建一个表,名称是 goUrlShortener,分区键为 shortUrl,供应用程序使用。

注意,我们有一个 shortUrl(String 类型)的分区键,和 On-demand(按需)容量模式。

针对 x86 进行编译

在 c5.xlarge 实例上, 导航至 building-go-applications-for-aws-graviton 目录并运行以下命令来构建应用程序:

go build -o goLinkShortener

构建完成后,可能看不到任何输出,但工作目录中应该有一个名为 goLinkShortener 的二进制文件。

针对 Amazon Graviton (ARM64) 进行编译

使用 Go 构建二进制文件主要有两种方法。第一种是对您计划在生产中运行的系统架构上的代码进行本地编译。这就是我们为 x86 编译时所用的方法。另一种选择是针对不同的架构进行交叉编译。这在开发过程的早期试验阶段非常有用。本教程涵盖这两种方法,但我建议尽可能在您计划在生产环境中运行的架构上进行构建。

针对 Amazon Graviton (ARM64) 进行交叉编译

在 c5.xlarge 实例上, 导航至 building-go-applications-for-aws-graviton 目录并运行以下命令来构建应用程序:

export GOOS=linux
export GOARCH=arm64

go build -o goLinkShortener_arm64

和之前一样,构建完成后,可能看不到任何输出,但工作目录中应该有一个名为 goLinkShortener_arm64 的二进制文件。然后,您可以将此二进制文件复制到任何在 ARM64 架构上运行的 Linux 系统,系统便会执行。如果尝试在运行 x86 架构的 Linux 系统上运行,便会运行失败并出现以下错误:

bash: ./goLinkShortener_arm64: cannot execute binary file: Exec format error

Exec 格式错误意味着您正在尝试执行为不同的 CPU 架构构建的二进制文件。

针对 Amazon Graviton (ARM64) 进行本机编译

在 c6g.xlarge 实例上, 导航至 building-go-applications-for-aws-graviton 目录并运行以下命令来构建应用程序:

go build -o goLinkShortener

现在,您已经有了一个在 ARM64 架构上本地构建的工作二进制文件 goLinkShortener。我们继续下一步并测试我们的应用程序,以确保其能够正常运行。

启动应用程序

在 c5.xlarge 和 c6g.xlarge 实例上运行以下命令来启动应用程序。

#Set GIN_MODE=release to minimize the amount of debug logging that would otherwise occur
export GIN_MODE=release
./golang-link-shortener

测试应用程序

为了测试应用程序,我们将使用 cURL 发出一些请求作为示例并验证我们的应用程序是否正常工作。以下所有命令都可以在两个 EC2 实例上运行。

URL 短链接转换

以下命令可将 URL 转换为。我的 c5.xlarge 实例的 IP 地址为 10.3.71.184,因此我会在命令中使用此地址。确保将 IP 地址替换为您的 EC2 实例的地址:

curl -X POST http://10.3.71.184:8080/shortenURL -H 'Content-Type: application/json' -d '{"OriginalURL":"https://aws.amazon.com/ec2/graviton/"}'

输出结果应如下所示:

{
 "shortURL": "7fcLy5Cqwd",
 "originalURL": "https://aws.amazon.com/ec2/graviton/"
}

7fcLy5Cqwd 是我们的应用程序的 URL 标识符。在完成的应用程序中,shortURL 值将用来将用户从 URL 短链接重定向至其原始 URL。就教程演示而言,JSON 响应就足够了。

检索完整 URL

为了检索原始 URL,我们需要向应用程序再次发出请求并将该值传递给 getFullURL API,如以下命令所示。

将 URL 短链接标识符替换为您从上一个命令中获得的标识符,并确保您的 IP 地址正确。

curl -X GET http://10.3.69.250:8080/getFullURL/7fcLy5Cqwd

输出结果应如下所示:

"https://aws.amazon.com/ec2/graviton/

负载测试结果

在比较多个实例类型时,性能测试是关键。为了比较 c6g.xlarge 和 c5.xlarge 实例,我们要进行负载测试,目的是验证为 Graviton 构建的应用程序是否正常运行。我们在 GitHub 上的 Graviton 技术指南 讨论了各种负载测试方法,并建议使用 wrk2 等框架。wrk2 wrk2 是 wrk 的修改版本,可产生恒定的吞吐量负载并报告准确到百分位数的延迟详细信息。然后,我决定使用 wrk2 测试我们应用程序的 shortenURL 函数,并比较每秒服务的总请求数以及负载测试期间每个百分位数级的延迟。在本教程中,我尽可能地使负载测试简单易用,以此强调测试的重要性。

每次进行软件或硬件更改时,都应该重新评估现有配置和假设,确保新配置的优势能得到充分利用。虽然完整的性能测试和优化超出了本博客的范围,但我们在 GitHub 上的 Graviton 技术指南中提供了全面的性能运行手册和 Go 语言特定页面,涉及7×24支持,always的服务可用性应该可使用“随时”,帮助您解决任何问题。

负载测试设置

因为 shortenURL 函数使用 POST 方法并且需要一些数据,所以我们需要一个 Lua 配置文件来传递数据给 wrk2。我的 post.lua 文件包含以下内容:

wrk.method = "POST"
wrk.headers["content-type"] = "application/json"
wrk.body = "https://aws.amazon.com/ec2/graviton/"

运行负载测试

负载测试非常适合验证应用程序在负载下是否正常运行。对于我们的示例应用程序,假设它应在 70 ms 内处理 99.9% 的请求。然后,我将对每个实例运行负载测试,查看在打破此延迟阈值之前每个实例每秒可以处理多少个请求。我将从与我们的测试实例位于同一可用区的 c5.18xlarge 实例运行负载测试。我之所以使用 c5.18xlarge 实例,是因为我知道它将能够对实例进行彻底的负载测试,因为此实例的大小远大于任何示例实例。

首先,我使用以下命令对每个实例运行了 30 分钟的负载测试。运行各个命令时,请确保 IP 地址正确。-c 参数控制负载测试期间所建立的连接数。-d 测试指定测试的运行时长。-L 参数支持报告百分位数级的延迟数据统计。-R 参数指定负载测试每秒应运行的请求个数。-s 参数允许我们指定前面定义的 Lua 脚本。

# AWS C5 Instance
./wrk -c60 -t30 -d 30m -L -R 600 -s ./post.lua http://10.3.69.250:8080/shortenURL

# AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 600 -s ./post.lua http://10.3.71.184:8080/shortenURL

初步结果

延迟百分位数 C5.xlarge C6g.xlarge
50 31.02 ms 15.02 ms
75 42.88 ms 22.82 ms
90 49.73 ms 26.64 ms
99 57.22 ms 35.42 ms
99.9 69.31 ms 49.38 ms
99.99 93.38 ms 87.87 ms
99.999 180.48 ms 172.80 ms
100 230.65 ms 240.64 ms

驱动更多负载

到目前为止,我们的初步结果看起来很棒。我们的 C5 实例略低于我们的阈值,而我们的 Graviton 实例看起来有足够的开销来承受额外的流量,并且没有超出我们的目标延迟。我们加大实例的负载,看看在更多负载下我们的目标延迟会发生什么。我们将 -R 参数调整为 700,来将请求提高到每秒 700 个。

# AWS C5 Instance
./wrk -c60 -t30 -d 30m -L -R 700 -s ./post.lua http://10.3.69.250:8080/shortenURL

# AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 700 -s ./post.lua http://10.3.71.184:8080/shortenURL
延迟百分位数 C5.xlarge C6g.xlarge
50 29.23 ms 17.15 ms
75 43.07 ms 24.43 ms
90 50.37 ms 28.05 ms
99 57.57 ms 36.70 ms
99.9 72.06 ms 58.56 ms
99.99 312.58 ms 132.74 ms
99.999 427.26 ms 327.17 ms
100 571.39 ms 728.58 ms

测试 Graviton 性能极限

我们的 C5 实例现已超出目标延迟,但 Graviton 实例仍低于阈值。我们还能加多少负载才能碰到极限?我们继续加大负载。把 -R 参数调整为 800,将负载测试的请求增加到每秒 800 个。

# AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 800 -s ./post.lua http://10.3.71.184:8080/shortenURL
延迟百分位数 C6g.xlarge
50 17.10 ms
75 24.43 ms
90 28.03 ms
99 37.12 ms
99.9 58.49 ms
99.99 92.10 ms
99.999 217.85 ms
100 480.00 ms

把 -R 参数调整为 900,看看每秒 900 个请求会发生什么。

# AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 900 -s ./post.lua http://10.3.71.184:8080/shortenURL
延迟百分位数 C6g.xlarge
50 13.42 ms
75 22.45 ms
90 26.38 ms
99 34.72 ms
99.9 56.83 ms
99.99 105.60 ms
99.999 184.83 ms
100 462.59 ms

我们已经非常接近临界点了。下一次测试可能会突破我们所有的延迟阈值,但我们还是尝试一下加以确定。我们将 -R 参数调整为 1000,来将请求提高到每秒 1000 个。

# AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 1000 -s ./post.lua http://10.3.71.184:8080/shortenURL
延迟百分位数 C6g.xlarge
50 12.28 ms
75 20.78 ms
90 25.28 ms
99 57.82 ms
99.9 1.18 s
99.99 2.19 s
99.999 2.4 s
100 2.49 s

每秒 1,000 个请求绝对是太多了,无法在保持可接受的延迟阈值的情况下进行处理。在突破延迟阈值之前,Graviton 实例能够每秒处理 900 个请求。基于 x86 的 C5 实例在超出延迟阈值之前只能处理大约 700 个。这说明我们的应用程序在 C6g 实例上每个实例可以处理的请求比在 C5 实例上多大约 28%。通过在生产场景中进行进一步测试,我们可以利用这一点通过运行更少的实例来服务同等大小的流量,从而提高成本效益和可持续性收益。对于 Go 应用程序,Graviton 实际上是亚马逊云科技目前提供的最具成本效益和可持续性的实例。

清理资源

现在我们已经完成了测试,是时候清理我们在本教程中创建的所有资源了。请务必终止您启动的任何 EC2 实例删除 DynamoDB 表,这样就不会产生任何额外费用。

总结

将 Go 应用程序从 x86 EC2 实例迁移至 Amazon Graviton 支持的实例非常容易,如本教程所示。在我们的负载测试中,Amazon Graviton 支持的实例在所有延迟百分位数上的表现都要更好。与其他 x86 实例相比,Amazon Graviton 能够为每个实例提供更多负载,从而节省成本、提高效率。

有关常见性能注意事项和其他信息,请访问 Github 上的 Graviton 技术指南存储库并立即开始迁移应用程序。