Azure Functions 在缩减之前需要冷却多长时间?

How long do Azure Functions need to cool down before they scale in?

在我们的开发环境中,我们的函数应用程序通常只需要几台服务器。

周末我们意外地引发了连锁反应,导致逐渐、持续地扩展到大约 140 台服务器。现在我们已经解决了根本问题并清除了队列,activity 似乎已经恢复正常。 (呸!)

奇怪的是(30 分钟后)我们所有的服务器仍然在线。我本以为他们会很快开始下线,但我看到 "servers online" 的数量在大约 110 到 140 之间波动(上下波动)。其中绝大多数都坐在那里有 0 requests/s,没有 CPU,没有记忆。

所以,一些问题:

Scale Controller 是一种闭源专有技术,具有未记录的行为。您的问题没有官方答案。回答它的唯一方法是做你所做的:运行 实验和测量。不过,这种行为可能会随着时间的推移而改变。

函数确实有一些启发式方法,可以根据事件数量和资源利用率确定何时扩展和缩减。过去,他们非常节俭,但这并没有提供足够好的横向扩展速度。现在,他们倾向于错误地配置太多而不是配置太少。

好消息是,除非出于好奇,否则您不应该真正关心缩放。您无需为闲置服务器付费。

只是为@Mikhail 写的内容添加一些内容 - 您的函数缩放方式取决于所用触发器的类型。如果您使用队列,运行时将根据消息的数量查看队列长度和规模 up/down。对于事件中心,此行为取决于中心中的分区数量 - 您拥有的分区越多,您就越渴望扩展您的功能。

可以查看源代码并尝试至少了解部分功能。事实上,它是基于一个计时器和一个工人的概念,它们更新它们的状态并让运行时决定是否需要扩大或缩小规模。

整体算法描述如下:

protected virtual async Task MakeScaleDecision(string activityId, IWorkerInfo manager)
{
    if (DateTime.UtcNow < _scaleCheckUtc)
    {
        return;
    }

    try
    {
        var workers = await _table.ListNonStale();
        _tracer.TraceInformation(activityId, manager, workers.GetSummary("NonStale"));

        if (await TryRemoveIfMaxWorkers(activityId, workers, manager))
        {
            return;
        }

        if (await TryAddIfLoadFactorMaxWorker(activityId, workers, manager))
        {
            return;
        }

        if (await TrySwapIfLoadFactorMinWorker(activityId, workers, manager))
        {
            return;
        }

        if (await TryAddIfMaxBusyWorkerRatio(activityId, workers, manager))
        {
            return;
        }

        if (await TryRemoveIfMaxFreeWorkerRatio(activityId, workers, manager))
        {
            return;
        }

        if (await TryRemoveSlaveWorker(activityId, workers, manager))
        {
            return;
        }
    }
    catch (Exception ex)
    {
        _tracer.TraceError(activityId, manager, string.Format("MakeScaleDecision failed with {0}", ex));
    }
    finally
    {
        _scaleCheckUtc = DateTime.UtcNow.Add(_settings.ScaleCheckInterval);
    }
}

更重要的是,为什么你看到活着的工人也可以在源代码中找到答案:

protected virtual async Task PingWorker(string activityId, IWorkerInfo worker)
{
    // if ping was unsuccessful, keep pinging.  this is to address
    // the issue where site continue to run on an unassigned worker.
    if (!_pingResult || _pingWorkerUtc < DateTime.UtcNow)
    {
        // if PingWorker throws, we will not update the worker status
        // this worker will be stale and eventually removed.
        _pingResult = await _eventHandler.PingWorker(activityId, worker);

        _pingWorkerUtc = DateTime.UtcNow.Add(_settings.WorkerPingInterval);
    }

    // check if worker is valid for the site
    if (_pingResult)
    {
        await _table.AddOrUpdate(worker);

        _tracer.TraceUpdateWorker(activityId, worker, string.Format("Worker loadfactor {0} updated", worker.LoadFactor));
    }
    else
    {
        _tracer.TraceWarning(activityId, worker, string.Format("Worker does not belong to the site."));

        await _table.Delete(worker);

        _tracer.TraceRemoveWorker(activityId, worker, "Worker removed");

        throw new InvalidOperationException("The worker does not belong to the site.");
    }
}

不幸的是,部分实现是密封的(如IWorkerInfo),所以你无法得到全貌,我们只能猜测(或询问;))