为什么更多请求发往新(动态)实例而不是常驻实例?

Why do more requests go to new (dynamic) instances than to resident instance?

有一个 Java 应用程序在 App Engine 标准环境上自动缩放。现在缩放配置如下:

<instance-class>F2</instance-class>

<automatic-scaling>
    <min-idle-instances>1</min-idle-instances>

    <!-- ‘automatic’ is the default value. -->
    <max-idle-instances>2</max-idle-instances>

    <!-- ‘automatic’ is the default value. -->
    <min-pending-latency>2000ms</min-pending-latency>

    <max-pending-latency>8000ms</max-pending-latency>
    <max-concurrent-requests>60</max-concurrent-requests>
</automatic-scaling>

刚开始尝试 F2 个实例,之前使用 F1 个实例。无论我如何配置自动缩放,新创建的实例(在负载增加时创建)似乎开始接收所有传入请求,而常驻实例的负载非常轻。

这是为什么?当然,我无法实时监控流量(以及它去了哪个实例),但每次我看故事似乎都是一样的。我在下面包含了一些示例屏幕截图。

在以下情况中,三个实例(这与上面的配置略有不同)空闲,但 GAE 的负载均衡器选择将所有请求发送到延迟最高的实例!

再举一个例子:这是今天 10:15:45 AM 开始的 resident 实例的请求日志:

以及 10 秒后启动的 动态 实例的请求日志:

如您所见,动态实例正在处理所有请求(到目前为止 1889 个),而驻留实例基本上处于空闲状态(同一时间段内有 7 个)。如果不是因为驻留实例似乎 被销毁并重新创建 就在新的动态实例被创建的时候 .这意味着在一分钟左右的时间内,所有请求都会看到 10-20 秒的响应时间。

有人可以向我解释一下如何配置吗?

这是我想要的:

我正在尝试 运行 以极低的预算构建一个合理加载的网站,因此我尽量接近免费配额很重要。

更新 1

由于两个答案都突出地谈到了预热请求,所以我想我应该在此处列出有关它的详细信息。我正在使用 ServletContextListener 来处理初始化。它执行以下操作(时间是使用 Guava 的 Stopwatch class 收集的,并且是针对我 written/am 显式调用的代码):

  1. 注册 Objectify 实体(1.449 秒)
  2. Freemarker 初始化 229 毫秒
  3. Firebase 初始化 228.2 毫秒

除此之外,我还有 Shiro 过滤器、Objectify 过滤器和 Jersey 过滤器(在 Jersey 中,我通过显式注册 classes 而不是提供 class 路径扫描(我认为)它是一个要扫描的包)在我的 web.xml 中配置。不使用任何依赖注入来避免class路径扫描。

/_ah/warmup 请求耗时 7.8 秒(即上述时间的来源)。但是,由预热已经完成的新启动的动态实例服务的请求需要 10 多秒才能完成,尽管这些相同的调用在两分钟后需要 200-700 毫秒。那么除了我在 StartupListener?

中明确做的事情之外,还有什么在后台发生的?

这是日志的 part 1 of the log and here's part 2

it seems like the newly-created instance (created when load increases) starts getting all the incoming requests, while the resident instance sits with a very light load.

我的心智模型是,Resident 实例和预热请求仅在 GAE 实例的启动时间较长时才有用。 (我不确定这是否是本意,但这是我观察到的行为)

traffic is sent to resident instances while the new instances are being booted(其他动态实例无法处理)。一旦新实例启动并且 运行,流量就会被路由到它,而不是常驻实例。

这意味着如果您的实例启动时间很短,那么常驻实例将不会做太多工作。 F2 可以在 ~250 毫秒 (by my testing) 内启动,因此如果您的平均响应延迟为 2000 毫秒,那么新的动态实例将在常驻实例完成处理请求之前完全启动。因此,它将准备好处理后续请求而不是常驻请求。

这似乎符合您所看到的行为模式。

您可以通过查看 stackdriver and logging separate out your response time vs boot time 的方式来确认这一点。如果启动时间真的很短,那么常驻实例可能对你帮助不大。

but GAE's load balancer chooses to send all requests to the instance with the highest latency!

遗憾的是,关于 GAE 如何决定将新数据包发送到哪个实例的信息不多。我发现的所有内容是 How instances are managed and scheduling settings,它更多地讨论了有关何时启动新实例的参数。

我知道这不是您问的问题,但 2000 毫秒的响应时间可能是导致此问题的原因?如果您的 min-pending-latency 设置为 2000,那么新请求将在队列中等待 2000 毫秒,然后才会生成新实例。但是,如果它以串行方式(线程安全关闭)进行服务,那么 1500 到 2000 之间的响应时间仍将得到正确服务。

我建议打开 threadsafe to see if that helps the scenario, and also add some custom tracing 以防代码执行一些您看不到的奇怪事情。

空闲实例的作用不是处理通常的流量,而是能够处理溢出 - 已经 运行 动态实例(如果有)不能处理的临时流量高峰' t 处理,直到启动新实例。

从某种意义上说,这就是为什么他们被称为 idle - 大多数时候他们只是闲着。恕我直言,在预算压力下,删除闲置实例是首先要做的事情之一。

也可能相关:In Google app engine only one instance handling most of requests

旁注:不是 the GAE's load balancer chooses to send all requests to the instance with the highest latency!。实际上,该首选实例的延迟最高,因为它是获得大部分流量的实例。

为了防止 GAE 在新实例准备好处理它们之前将流量发送到它们,您需要配置(并正确处理)warmup requests

Warmup requests are a specific type of loading request that load application code into an instance ahead of time, before any live requests are made. To learn more about how to use warmup requests, see Warmup Requests. Manual or basic scaling instances do not receive an /_ah/warmup request.

您可能还想考虑一下这个答案:。基本上尝试通过 cron 作业防止动态实例保持空闲太久,从而无限期地保持动态实例 运行。

至于在新的动态实例启动后立即重新启动常驻实例似乎有点奇怪。我不会太担心 - 它可能只是某种安全的刷新策略:那将是对空闲实例的需求最低的时刻,因为新启动的动态实例最不可能被传入的请求淹没。