配置预热请求以提高性能

在将应用代码加载到新创建的实例时,您可以使用预热请求来缩短请求与响应的延迟时间。

App Engine 经常需要将应用代码加载到全新实例中。以下情况下可能会加载实例:

  • 重新部署应用版本时。
  • 由于请求产生的负载超过当前运行中实例集的容量而创建新实例时。
  • 维护和维修底层基础架构或物理硬件时。

将应用代码加载到新实例会产生加载请求。加载请求可能会延长用户的请求延迟时间,但您可以使用预热请求来避免此延时。预热请求会在任何实际请求到达新实例之前,先将应用的代码加载到新实例中。

如果您为应用启用了预热请求,则 App Engine 会尝试检测您的应用何时需要新实例,并发出预热请求来对新实例进行初始化。但是,此类检测尝试并不适用于所有情况。因此,即使应用中启用了预热请求,您也可能会遇到加载请求。例如,如果您的应用目前没有任何流量,则对应用的第一个请求始终会是加载请求,而不是预热请求。

与任何其他发送至 App Engine 应用的请求一样,预热请求也会使用实例小时数。在大多数启用了预热请求的情况下,您不会注意到实例小时数的增加,因为您的应用只是在预热请求(而非加载请求)中进行初始化。如果您决定执行更多工作,例如在预热请求期间预先缓存,则使用的实例小时数可能会增加。如果您将 min_idle_instances 设置为大于 0,则在这些实例首次启动时,您可能会遇到预热请求,但在此之后这些实例将保持可用。

启用预热请求

预热请求由 App Engine 调度器使用,该调度器根据用户提供的配置来控制实例的自动扩缩。启用预热请求后,App Engine 会向 /_ah/warmup 发出 GET 请求。您可以针对此请求实现处理程序,以执行预先缓存应用数据等应用特定任务。

如果调度程序判断需要更多实例,便会启动实例。由于调度器使用预热请求来启动实例,因此即使停用了预热请求,它们也可能会出现在日志中。

请注意,系统不一定会调用预热请求。在某些情况下,系统会改为发送加载请求:例如,如果实例是第一个被启动的实例,或者流量出现急剧上升。但是,如果启用了预热请求,系统将“尽力”尝试向已经预热的实例发送请求。

要启用预热请求,请在 app.yaml 文件中的 inbound_services 指令下添加 warmup 元素,例如:

inbound_services:
- warmup

创建处理程序

创建一个处理程序,用于处理发送到 /_ah/warmup 的请求。您的处理程序应执行应用所需的任何预热逻辑。


// Sample warmup demonstrates usage of the /_ah/warmup handler.
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"cloud.google.com/go/storage"
)

var startupTime time.Time
var client *storage.Client

func main() {
	// Perform required setup steps for the application to function.
	// This assumes any returned error requires a new instance to be created.
	if err := setup(context.Background()); err != nil {
		log.Fatalf("setup: %v", err)
	}

	// Log when an appengine warmup request is used to create the new instance.
	// Warmup steps are taken in setup for consistency with "cold start" instances.
	http.HandleFunc("/_ah/warmup", func(w http.ResponseWriter, r *http.Request) {
		log.Println("warmup done")
	})
	http.HandleFunc("/", indexHandler)

	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)
	}
}

// setup executes per-instance one-time warmup and initialization actions.
func setup(ctx context.Context) error {
	// Store the startup time of the server.
	startupTime = time.Now()

	// Initialize a Google Cloud Storage client.
	var err error
	if client, err = storage.NewClient(ctx); err != nil {
		return err
	}

	return nil
}

// indexHandler responds to requests with our greeting.
func indexHandler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	uptime := time.Since(startupTime).Seconds()
	fmt.Fprintf(w, "Hello, World! Uptime: %.2fs\n", uptime)
}