Go 1.11 和 Go 1.12+ 之间的差异

迁移到 Go 1.12+ 运行时后,您可以使用最新的语言功能,并构建可移植性更强的应用(而无需改变惯用的编码方式)。

App Engine Go 1.12+ 运行时中的更改

如果您正在考虑迁移到 Go 1.12+ 运行时,则应注意 App Engine 标准环境中的 Go 1.12+ 与更早版本的运行时之间的以下差异:

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

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

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

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

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

内存用量差异

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

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

CPU 使用率差异

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

请求标头差异

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

app.yaml 文件的更改

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

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

如需了解详情,请参阅 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