Sugestões e truques
Este documento descreve as práticas recomendadas para conceber, implementar, testar e implementar funções do Cloud Run.
Exatidão
Esta secção descreve as práticas recomendadas gerais para conceber e implementar funções do Cloud Run.
Escreva funções idempotentes
As suas funções devem produzir o mesmo resultado, mesmo que sejam chamadas várias vezes. Isto permite-lhe repetir uma invocação se a invocação anterior falhar durante a execução do código. Para mais informações, consulte o artigo sobre como repetir funções acionadas por eventos.
Certifique-se de que as funções HTTP enviam uma resposta HTTP
Se a sua função for acionada por HTTP, lembre-se de enviar uma resposta HTTP, como mostrado abaixo. Se não o fizer, a função é executada até atingir o limite de tempo. Se isto ocorrer, é-lhe cobrado o tempo limite total. Os limites de tempo também podem causar um comportamento imprevisível ou inícios a frio em invocações subsequentes, o que resulta num comportamento imprevisível ou latência adicional.
Node.js
Python
Go
Java
C#
using Google.Cloud.Functions.Framework; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using System.IO; using System.Text.Json; using System.Threading.Tasks; namespace HelloHttp; public class Function : IHttpFunction { private readonly ILogger _logger; public Function(ILogger<Function> logger) => _logger = logger; public async Task HandleAsync(HttpContext context) { HttpRequest request = context.Request; // Check URL parameters for "name" field // "world" is the default value string name = ((string) request.Query["name"]) ?? "world"; // If there's a body, parse it as JSON and check for "name" field. using TextReader reader = new StreamReader(request.Body); string text = await reader.ReadToEndAsync(); if (text.Length > 0) { try { JsonElement json = JsonSerializer.Deserialize<JsonElement>(text); if (json.TryGetProperty("name", out JsonElement nameElement) && nameElement.ValueKind == JsonValueKind.String) { name = nameElement.GetString(); } } catch (JsonException parseException) { _logger.LogError(parseException, "Error parsing JSON request"); } } await context.Response.WriteAsync($"Hello {name}!", context.RequestAborted); } }
Ruby
PHP
<?php use Google\CloudFunctions\FunctionsFramework; use Psr\Http\Message\ServerRequestInterface; // Register the function with Functions Framework. // This enables omitting the `FUNCTIONS_SIGNATURE_TYPE=http` environment // variable when deploying. The `FUNCTION_TARGET` environment variable should // match the first parameter. FunctionsFramework::http('helloHttp', 'helloHttp'); function helloHttp(ServerRequestInterface $request): string { $name = 'World'; $body = $request->getBody()->getContents(); if (!empty($body)) { $json = json_decode($body, true); if (json_last_error() != JSON_ERROR_NONE) { throw new RuntimeException(sprintf( 'Could not parse body: %s', json_last_error_msg() )); } $name = $json['name'] ?? $name; } $queryString = $request->getQueryParams(); $name = $queryString['name'] ?? $name; return sprintf('Hello, %s!', htmlspecialchars($name)); }
Não iniciar atividades em segundo plano
A atividade em segundo plano é tudo o que acontece depois de a sua função ter terminado.
Uma invocação de função termina quando a função devolve ou sinaliza de outra forma a conclusão, como através da chamada do argumento callback
em funções baseadas em eventos do Node.js. Qualquer código executado após o encerramento normal não pode aceder à CPU e não faz qualquer progresso.
Além disso, quando uma invocação subsequente é executada no mesmo ambiente,
a sua atividade em segundo plano é retomada, interferindo com a nova invocação. Isto pode
resultar num comportamento inesperado e em erros difíceis de diagnosticar. Aceder à rede após a terminação de uma função geralmente leva à reposição das ligações (código de erro ECONNRESET
).
A atividade em segundo plano pode ser frequentemente detetada nos registos de invocações individuais, encontrando tudo o que é registado após a linha que indica que a invocação terminou. Por vezes, a atividade em segundo plano pode estar mais oculta no código, especialmente quando estão presentes operações assíncronas, como callbacks ou temporizadores. Reveja o código para se certificar de que todas as operações assíncronas terminam antes de terminar a função.
Elimine sempre ficheiros temporários
O armazenamento em disco local no diretório temporário é um sistema de ficheiros na memória. Os ficheiros que escreve consomem a memória disponível para a sua função e, por vezes, persistem entre invocações. Se não eliminar explicitamente estes ficheiros, pode ocorrer um erro de falta de memória e um reinício a frio subsequente.
Pode ver a memória usada por uma função individual selecionando-a na lista de funções naGoogle Cloud consola e escolhendo o gráfico Utilização de memória.
Não tente escrever fora do diretório temporário e certifique-se de que usa métodos independentes da plataforma/SO para criar caminhos de ficheiros.
Pode reduzir os requisitos de memória quando processa ficheiros maiores através do processamento em pipeline. Por exemplo, pode processar um ficheiro no Cloud Storage criando um fluxo de leitura, passando-o por um processo baseado em fluxo e escrevendo o fluxo de saída diretamente no Cloud Storage.
Functions Framework
Quando implementa uma função, o Functions Framework é adicionado automaticamente como uma dependência, usando a respetiva versão atual. Para garantir que as mesmas dependências são instaladas de forma consistente em diferentes ambientes, recomendamos que fixe a sua função a uma versão específica do Functions Framework.
Para o fazer, inclua a sua versão preferencial no ficheiro de bloqueio relevante
(por exemplo, package-lock.json
para Node.js ou requirements.txt
para Python).
Ferramentas
Esta secção fornece diretrizes sobre como usar ferramentas para implementar, testar e interagir com funções do Cloud Run.
Desenvolvimento local
A implementação de funções demora algum tempo, pelo que é frequentemente mais rápido testar o código da sua função localmente.
Relatório de erros
Em linguagens que usam o processamento de exceções, não lance exceções não capturadas, porque forçam inícios a frio em invocações futuras. Consulte o guia de relatórios de erros para obter informações sobre como comunicar corretamente os erros.
Não saia manualmente
A saída manual pode causar um comportamento inesperado. Em alternativa, use as seguintes expressões idiomáticas específicas do idioma:
Node.js
Não use process.exit()
. As funções HTTP devem enviar uma resposta com
res.status(200).send(message)
, e as funções
baseadas em eventos terminam assim que são devolvidas (implícita ou explicitamente).
Python
Não use sys.exit()
. As funções HTTP devem devolver explicitamente uma resposta como uma string e as funções orientadas por eventos terminam assim que devolvem um valor (implícita ou explicitamente).
Go
Não use os.Exit()
. As funções HTTP devem devolver explicitamente uma resposta como uma string e as funções orientadas por eventos terminam assim que devolvem um valor (implícita ou explicitamente).
Java
Não use System.exit()
. As funções HTTP devem enviar uma resposta com
response.getWriter().write(message)
, e as funções
baseadas em eventos terminam assim que são devolvidas (implícita ou explicitamente).
C#
Não use System.Environment.Exit()
. As funções HTTP devem enviar uma resposta com
context.Response.WriteAsync(message)
, e as funções
baseadas em eventos terminam assim que são devolvidas (implícita ou explicitamente).
Ruby
Não use exit()
nem abort()
. As funções HTTP devem devolver explicitamente uma resposta como uma string e as funções orientadas por eventos terminam assim que devolvem um valor (implícita ou explicitamente).
PHP
Não use exit()
nem die()
. As funções HTTP devem devolver explicitamente uma resposta como uma string e as funções orientadas por eventos terminam assim que devolvem um valor (implícita ou explicitamente).
Use o Sendgrid para enviar emails
As funções do Cloud Run não permitem ligações de saída na porta 25, pelo que não pode estabelecer ligações não seguras a um servidor SMTP. A forma recomendada de enviar emails é usar o SendGrid. Pode encontrar outras opções para enviar emails no tutorial Enviar email a partir de uma instância para o Compute Engine.
Desempenho
Esta secção descreve as práticas recomendadas para otimizar o desempenho.
Use as dependências de forma inteligente
Uma vez que as funções não têm estado, o ambiente de execução é frequentemente inicializado de raiz (durante o que é conhecido como um início a frio). Quando ocorre um arranque a frio, o contexto global da função é avaliado.
Se as suas funções importarem módulos, o tempo de carregamento desses módulos pode aumentar a latência de invocação durante um arranque a frio. Pode reduzir esta latência, bem como o tempo necessário para implementar a sua função, carregando as dependências corretamente e não carregando as dependências que a sua função não usa.
Use variáveis globais para reutilizar objetos em invocações futuras
Não existe garantia de que o estado de uma função seja preservado para invocações futuras. No entanto, as funções do Cloud Run reciclam frequentemente o ambiente de execução de uma invocação anterior. Se declarar uma variável no âmbito global, o respetivo valor pode ser reutilizado em invocações subsequentes sem ter de ser recalculado.
Desta forma, pode colocar em cache objetos que podem ser dispendiosos de recriar em cada invocação de função. Mover esses objetos do corpo da função para o âmbito global pode resultar em melhorias significativas no desempenho. O exemplo seguinte cria um objeto pesado apenas uma vez por instância da função e partilha-o em todas as invocações da função que atingem a instância dada:
Node.js
Python
Go
Java
C#
using Google.Cloud.Functions.Framework; using Microsoft.AspNetCore.Http; using System.Linq; using System.Threading.Tasks; namespace Scopes; public class Function : IHttpFunction { // Global (server-wide) scope. // This computation runs at server cold-start. // Warning: Class variables used in functions code must be thread-safe. private static readonly int GlobalVariable = HeavyComputation(); // Note that one instance of this class (Function) is created per invocation, // so calling HeavyComputation in the constructor would not have the same // benefit. public async Task HandleAsync(HttpContext context) { // Per-function-invocation scope. // This computation runs every time this function is called. int functionVariable = LightComputation(); await context.Response.WriteAsync( $"Global: {GlobalVariable}; function: {functionVariable}", context.RequestAborted); } private static int LightComputation() { int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; return numbers.Sum(); } private static int HeavyComputation() { int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; return numbers.Aggregate((current, next) => current * next); } }
Ruby
PHP
use Psr\Http\Message\ServerRequestInterface; function scopeDemo(ServerRequestInterface $request): string { // Heavy computations should be cached between invocations. // The PHP runtime does NOT preserve variables between invocations, so we // must write their values to a file or otherwise cache them. // (All writable directories in Cloud Functions are in-memory, so // file-based caching operations are typically fast.) // You can also use PSR-6 caching libraries for this task: // https://packagist.org/providers/psr/cache-implementation $cachePath = sys_get_temp_dir() . '/cached_value.txt'; $response = ''; if (file_exists($cachePath)) { // Read cached value from file, using file locking to prevent race // conditions between function executions. $response .= 'Reading cached value.' . PHP_EOL; $fh = fopen($cachePath, 'r'); flock($fh, LOCK_EX); $instanceVar = stream_get_contents($fh); flock($fh, LOCK_UN); } else { // Compute cached value + write to file, using file locking to prevent // race conditions between function executions. $response .= 'Cache empty, computing value.' . PHP_EOL; $instanceVar = _heavyComputation(); file_put_contents($cachePath, $instanceVar, LOCK_EX); } // Lighter computations can re-run on each function invocation. $functionVar = _lightComputation(); $response .= 'Per instance: ' . $instanceVar . PHP_EOL; $response .= 'Per function: ' . $functionVar . PHP_EOL; return $response; }
É particularmente importante colocar em cache as ligações de rede, as referências de bibliotecas e os objetos de cliente da API no âmbito global. Consulte o artigo Otimize a rede para ver exemplos.
Faça a inicialização tardia de variáveis globais
Se inicializar variáveis no âmbito global, o código de inicialização é sempre executado através de uma invocação de arranque a frio, o que aumenta a latência da função.
Em determinados casos, isto provoca limites de tempo intermitentes nos serviços que estão a ser chamados, se não forem processados adequadamente num bloco try
/catch
. Se alguns objetos não forem usados em todos os caminhos de código, considere inicializá-los de forma diferida a pedido:
Node.js
Python
Go
Java
C#
using Google.Cloud.Functions.Framework; using Microsoft.AspNetCore.Http; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace LazyFields; public class Function : IHttpFunction { // This computation runs at server cold-start. // Warning: Class variables used in functions code must be thread-safe. private static readonly int NonLazyGlobal = FileWideComputation(); // This variable is initialized at server cold-start, but the // computation is only performed when the function needs the result. private static readonly Lazy<int> LazyGlobal = new Lazy<int>( FunctionSpecificComputation, LazyThreadSafetyMode.ExecutionAndPublication); public async Task HandleAsync(HttpContext context) { // In a more complex function, there might be some paths that use LazyGlobal.Value, // and others that don't. The computation is only performed when necessary, and // only once per server. await context.Response.WriteAsync( $"Lazy global: {LazyGlobal.Value}; non-lazy global: {NonLazyGlobal}", context.RequestAborted); } private static int FunctionSpecificComputation() { int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; return numbers.Sum(); } private static int FileWideComputation() { int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; return numbers.Aggregate((current, next) => current * next); } }
Ruby
PHP
As funções PHP não podem preservar variáveis entre pedidos. O exemplo de âmbitos acima usa o carregamento diferido para colocar em cache os valores das variáveis globais num ficheiro.
Isto é particularmente importante se definir várias funções num único ficheiro e as diferentes funções usarem variáveis diferentes. A menos que use a inicialização preguiçosa, pode desperdiçar recursos em variáveis que são inicializadas, mas nunca usadas.
Reduza os inícios a frio definindo um número mínimo de instâncias
Por predefinição, as funções do Cloud Run escalam o número de instâncias com base no número de pedidos recebidos. Pode alterar este comportamento predefinido definindo um número mínimo de instâncias que as funções do Cloud Run têm de manter prontas para atender pedidos. A definição de um número mínimo de instâncias reduz os inícios a frio da sua aplicação. Recomendamos que defina um número mínimo de instâncias se a sua aplicação for sensível à latência.
Para saber como definir um número mínimo de instâncias, consulte o artigo Use instâncias mínimas.
Recursos adicionais
Saiba mais sobre a otimização do desempenho no vídeo "Google Cloud Performance Atlas" (Google Cloud Performance Atlas) Tempo de arranque a frio das funções do Cloud Run.