从 Go 1.11 迁移到最新的 Go 运行时

本页面介绍了从第一代迁移到第二代 Go 运行时的说明。如需升级第二代应用以使用最新支持的 Go 版本,请参阅升级现有应用

Go 1.11 已于 2024 年 1 月 30 日达到支持终止期限。现有的 Go 1.11 应用将继续运行并接收流量。但是,App Engine 可能会阻止重新部署使用支持终止日期之后的运行时的应用。我们建议您按照本页面中的指导迁移到最新支持的 Go 运行时

迁移到受支持的第二代 Go 运行时后,您可以通过惯用的编码方式来利用最新的语言功能并构建可移植性更强的应用。

第二代运行时中的更改

在升级到受支持的第二代 Go 运行时时,请考虑以下差异:

  • 为了减少运行时迁移工作量和复杂性,App Engine 标准环境允许您在第二代运行时中访问许多旧版捆绑服务和 API,例如 Memcache。第二代 Go 应用可以通过 App Engine SDK for Go 调用捆绑服务 API,并访问与 Go 1.11 运行时相同的大多数功能。

    您还可以选择使用 Google Cloud 产品,其可提供与旧版捆绑服务类似的功能。这些 Google Cloud 产品提供适用于 Go 的惯用 Cloud 客户端库。对于在 Google Cloud 中未作为单独产品提供的捆绑式服务,例如图片处理、搜索和消息功能,您可以使用第三方提供商或其他解决方法。

    如需详细了解如何迁移到未捆绑服务,请参阅从捆绑服务迁移

  • 修改了 app.yaml 配置文件中某些元素的行为。如需了解详情,请参阅app.yaml 文件的更改

  • 第二代运行时中的日志记录遵循 Cloud Logging 中的日志记录标准。在第二代运行时中,应用日志不再与请求日志捆绑,而是分隔在不同的记录中。如需详细了解如何在第二代运行时中读取和写入日志,请参阅日志记录指南

内存用量差异

与第一代运行时相比,第二代运行时的内存用量基准更高。这是由多种因素造成的,例如基础映像版本不同,以及两代在计算内存用量的方式上存在差异。

第二代运行时在计算实例内存用量时,会将应用进程的内存用量与内存中动态缓存的应用文件数量相加。为避免内存密集型应用因超出内存限制而关停实例,请升级到具有更多内存的更大实例类

CPU 使用率差异

第二代运行时在实例冷启动时会看到更高的 CPU 使用率基准。根据应用的扩缩配置,这可能会导致意外的副作用,例如,如果应用配置为根据 CPU 利用率进行扩缩,则实例数会高于预期。为避免此问题,请查看并测试应用扩缩配置,以确保实例数在可接受的范围内。

请求标头差异

第一代运行时允许将带有下划线(例如 X-Test-Foo_bar)的请求标头转发给应用。第二代运行时将 Nginx 引入了主机架构。由于此更改,第二代运行时配置为自动移除带有下划线 (_) 的标头。为防止出现应用问题,请避免在应用请求标头中使用下划线。

app.yaml 文件的更改

修改了 app.yaml 配置文件中某些元素的行为:

元素 更改类型 说明
app_engine_apis 对于使用旧版捆绑服务的应用而言是必需的 如果要访问 适用于第二代运行时的旧版捆绑服务,则必须设置为 true
login app_engine_apistrue 时支持 如果您未使用第二代运行时的旧版捆绑服务,请使用这些 替代方法对用户进行身份验证
runtime 已修改 更改 runtime 元素以指定第二代运行时

如需了解详情,请参阅 app.yaml 参考文档

创建 main 软件包

您的服务必须至少在一个源文件中包含 package main 语句。或者,如果您的服务使用的是 google.golang.org/appengine 软件包,则调用 appengine.Main()

编写 main 软件包

如果您的服务尚不包含 main 软件包,请添加 package main 语句并编写 main() 函数。main() 函数至少应该:

  • 读取 PORT 环境变量并调用 http.ListenAndServe() 函数:

    port := os.Getenv("PORT")
    if port == "" {
    	port = "8080"
    	log.Printf("Defaulting to port %s", port)
    }
    
    log.Printf("Listening on port %s", port)
    if err := http.ListenAndServe(":"+port, nil); err != nil {
    	log.Fatal(err)
    }

注册 HTTP 处理程序

您可以通过以下任一方案来注册 HTTP 处理程序:

  • 首选方法是手动将您的软件包中的所有 http.HandleFunc() 调用移入您的 main 软件包中的 main() 函数。
  • 另一种方法是将应用的软件包导入 main 软件包,并确保每个 init() 函数(包含对 http.HandleFunc() 的调用)在启动时运行。

    您可以通过以下 bash 脚本找到所有使用 http.HandleFunc() 调用的软件包,并将输出复制到 main 软件包的 import 块中:

    gp=$(go env GOPATH) && p=$(pwd) && pkg=${p#"$gp/src/"} && find . -name "*.go" | xargs grep "http.HandleFunc" --files-with-matches | grep -v vendor/ | grep -v '/main.go' | sed "s#\./\(.*\)/[^/]\+\.go#\t_ \"$pkg/\1\"#" | sort | uniq
    

组织文件结构

Go 要求每个软件包都有自己的目录。您可以在项目的 app.yaml 文件中使用 main:,让 App Engine 知道您的 main 软件包的位置。例如,如果您的应用的文件结构如下所示:

myapp/
├── app.yaml
├── foo.go
├── bar.go
└── web/
    └── main.go

您的 app.yaml 文件将包含:

main: ./web # Relative filepath to the directory containing your main package.

如需详细了解 main 标志,请参阅 app.yaml 参考文档

将文件移动到 GOPATH

使用以下命令查找您的 GOPATH

go env GOPATH

将所有相关文件和导入内容移动到 GOPATH。如果使用相对导入(例如 import ./guestbook),请将您的导入命令更新为使用完整路径:import github.com/example/myapp/guestbook