java—为什么向新(动态)示例发出的请求比向常驻示例发出的请求更多?

yhuiod9q  于 2021-07-09  发布在  Java
关注(0)|答案(2)|浏览(449)

有一个java应用程序,在应用程序引擎标准环境中自动伸缩。现在,缩放配置如下:

<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开始的常驻示例的请求日志:

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

如您所见,动态示例正在处理所有的请求(到目前为止是1889个),而驻留示例基本上处于空闲状态(同一时间段中有7个)。如果不是因为驻留示例似乎在创建新的动态示例时被销毁和创建,这仍然是可以的。这意味着在一分钟左右的时间内,所有请求都会看到10-20秒的响应时间。
有人能告诉我怎么配置吗?
我想要的是:
一个空闲示例应该能够在大多数情况下(目前)处理负载。
当更多的请求进来时,启动一个额外的示例。准备好后,开始转移交通。
我试图运行一个合理的负载在一个小本经营的预算网站,所以这是很重要的,我试图保持尽可能接近免费配额。

更新1

因为这两个答案都突出地提到了热身请求,所以我想我应该在这里列出有关它的细节。我用的是 ServletContextListener 来处理初始化。它执行以下操作(使用Guava汁收集时间) Stopwatch 类和用于我已编写/正在显式调用的代码):
注册对象化实体(1.449 s)
freemarker初始化229毫秒
firebase初始化228.2毫秒
除此之外,我还有shiro过滤器、objectify过滤器和jersey过滤器(在jersey中,我想通过显式注册类而不是给它一个包来扫描来避免类路径扫描) web.xml . 不使用任何依赖注入来避免类路径扫描。
这个 /_ah/warmup 请求时间为7.8秒(取上述时间的时间)。但是,一个刚启动的动态示例(其预热已经完成)所处理的请求需要10多秒才能完成,尽管这些调用需要200-700毫秒(两分钟后)。那么,除了我在我的生活中明确地做的事情之外,在后台还发生了什么呢 StartupListener ?
这是日志的第一部分,这是日志的第二部分。

zzwlnbp8

zzwlnbp81#

似乎新创建的示例(在负载增加时创建)开始获取所有传入的请求,而驻留示例的负载非常轻。
我的心理模型是,驻留示例和预热请求只有在gae示例的引导时间很长时才有用(我不确定这是否是我的意图,但这是我观察到的行为)
也就是说,在启动新示例时,通信量被发送到驻留示例(而其他动态示例不能处理它)。一旦新示例启动并运行,流量就被路由到它,而不是常驻示例。
这意味着,如果示例启动时间较短,那么驻留示例将不会做太多工作。f2可以在大约250ms内启动(根据我的测试),因此如果您的平均响应延迟是2000ms,那么新的动态示例将在常驻示例完成处理请求之前完全启动。因此,它将准备好处理后续请求,而不是常驻请求。
这似乎符合你看到的行为模式。
您可以通过查看stackdriver和日志如何区分响应时间和引导时间来确认这一点。如果引导时间真的很短,那么驻留示例可能对您帮助不大。
但是gae的负载均衡器选择将所有请求发送到延迟最高的示例!
遗憾的是,关于gae如何决定向哪个示例发送新数据包的信息并不多。我所发现的就是如何管理示例和调度设置,这些设置更多地讨论了何时启动新示例或不启动新示例的参数。
我知道这不是你问的问题,但2000毫秒的响应时间可能是造成这个问题的原因?如果您的min pending latency设置为2000,那么在生成新示例之前,新请求将在队列中等待2000毫秒。但是如果它是以串行方式(线程安全关闭)进行服务的,那么在1500到2000之间的响应时间仍然可以正常服务。
我建议打开threadsafe,看看这是否对场景有帮助,还可以添加一些自定义跟踪,以防代码做了一些你看不到的奇怪的事情。

iyfamqjs

iyfamqjs2#

空闲示例的作用不是处理通常的流量,而是能够处理溢出—在新示例启动之前,已经运行的动态示例(如果有的话)无法处理的临时流量峰值。
从某种意义上说,这就是为什么他们被称为闲置-大多数时候,他们只是坐在闲置。在预算压力下,imho首先要做的事情之一就是丢弃空闲示例。
也可能相关:在googleappengine中,只有一个示例处理大多数请求
旁注:不是那样的 the GAE's load balancer chooses to send all requests to the instance with the highest latency! . 实际上,首选示例上的延迟是最高的,因为它是获得大部分流量的示例。
要防止gae在新示例准备好处理它们之前向它们发送流量,您需要配置(并正确处理)预热请求:
warmup请求是一种特定类型的加载请求,它在发出任何实时请求之前将应用程序代码提前加载到示例中。要了解有关如何使用预热请求的更多信息,请参阅预热请求。手动或基本缩放示例不会收到 /_ah/warmup 请求。
您可能还需要考虑一下这个答案:如何保持googleappengine示例的运行。基本上,通过cron作业防止动态示例长时间处于空闲状态,从而使它们无限期地运行。
至于在新的动态示例启动之后立即重新启动驻留示例,似乎有点奇怪。我不会太担心—它可能只是某种安全的刷新策略:此时对空闲示例的需求最低,因为新启动的动态示例最不可能被传入的请求淹没。

相关问题