Runtime de Java para instâncias gerenciadas do Lambda - AWS Lambda

Runtime de Java para instâncias gerenciadas do Lambda

Para runtimes de Java, as instâncias gerenciadas do Lambda usam threads do sistema operacional para simultaneidade. O Lambda carrega seu objeto manipulador uma vez por ambiente de execução durante a inicialização e, em seguida, cria vários threads. Esses threads são executados paralelamente e exigem um tratamento seguro do estado e dos recursos compartilhados. Cada thread compartilha o mesmo objeto manipulador e quaisquer campos estáticos.

Configuração de simultaneidade

O número máximo de solicitações simultâneas que o Lambda envia para cada ambiente de execução é controlado pela definição PerExecutionEnvironmentMaxConcurrency da configuração da função. Essa é uma definição opcional e o valor padrão varia de acordo com o runtime. Para runtimes de Java, o padrão é 32 solicitações simultâneas por vCPU, ou é possível configurar seu próprio valor. Esse valor também determina o número de threads usados pelo runtime de Java. O Lambda ajusta automaticamente o número de solicitações simultâneas até o máximo configurado com base na capacidade de cada ambiente de execução de absorver essas solicitações.

Funções de construção para multissimultaneidade

É necessário aplicar as mesmas práticas de segurança de thread ao usar instâncias gerenciadas do Lambda como você faria em qualquer outro ambiente de vários threads. Como o objeto manipulador é compartilhado entre todas as threads de operador de runtime, qualquer estado mutável deve ser seguro para threads. Isso inclui coleções, conexões de banco de dados e quaisquer objetos estáticos que sejam modificados durante o processamento da solicitação.

Os clientes de SDK da AWS são seguros para threads e não exigem tratamento especial.

Exemplo: grupos de conexão de banco de dados

O código a seguir usa um objeto de conexão de banco de dados estático que é compartilhado entre threads. Dependendo da biblioteca de conexão usada, isso pode não ser seguro para threads.

public class DBQueryHandler implements RequestHandler<Object, String> { // Single connection shared across all threads - NOT SAFE private static Connection connection; public DBQueryHandler() { this.connection = DriverManager.getConnection(jdbcUrl, username, password); } @Override public String handleRequest(Object input, Context context) { PreparedStatement stmt = connection.prepareStatement(query); ResultSet rs = stmt.executeQuery(); // Multiple threads using same connection causes issues return result.toString(); } }

Uma abordagem segura para threads é usar um grupo de conexões. No exemplo a seguir, o manipulador de funções recupera uma conexão do grupo. A conexão só é usada no contexto de uma única solicitação.

public class DBQueryHandler implements RequestHandler<Object, String> { private static HikariDataSource dataSource; public DBQueryHandler() { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/your_database"); dataSource = new HikariDataSource(config); // Create pool once per Lambda container } @Override public String handleRequest(Object input, Context context) { String query = "SELECT column_name FROM your_table LIMIT 10"; StringBuilder result = new StringBuilder("Data:\n"); // try-with-resources automatically calls close() on the connection, // which returns it to the HikariCP pool (does NOT close the physical DB connection) try (Connection connection = dataSource.getConnection(); PreparedStatement stmt = connection.prepareStatement(query); ResultSet rs = stmt.executeQuery()) { while (rs.next()) { result.append(rs.getString("column_name")).append("\n"); } } catch (Exception e) { context.getLogger().log("Error: " + e.getMessage()); return "Error"; } return result.toString(); } }

Exemplo: coleções

As coleções de Java padrão não são seguras para threads:

public class Handler implements RequestHandler<Object, String> { private static List<String> items = new ArrayList<>(); private static Map<String, Object> cache = new HashMap<>(); @Override public String handleRequest(Object input, Context context) { items.add("list item"); // Not thread-safe cache.put("key", input); // Not thread-safe return "Success"; } }

Em vez disso, use coleções seguras para threads:

public class Handler implements RequestHandler<Object, String> { private static final List<String> items = Collections.synchronizedList(new ArrayList<>()); private static final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>(); @Override public String handleRequest(Object input, Context context) { items.add("list item"); // Thread-safe cache.put("key", input); // Thread-safe return "Success"; } }

Diretório /tmp compartilhado

O diretório /tmp é compartilhado entre todas as solicitações simultâneas no ambiente de execução. Gravações simultâneas no mesmo arquivo podem causar corrupção de dados, por exemplo, se outro processo sobrescrever o arquivo. Para resolver isso, implemente o bloqueio de arquivos para arquivos compartilhados ou use nomes de arquivo exclusivos por thread ou por solicitação para evitar conflitos. Lembre-se de limpar arquivos desnecessários para evitar o esgotamento do espaço disponível.

Registro em log

A intercalação de logs (entradas de logs de solicitações diferentes sendo intercaladas em logs) é normal em sistemas multissimultâneos.

As funções que usam instâncias gerenciadas do Lambda sempre usam o formato de log de JSON estruturado introduzido com controles de registro em log avançados. Esse formato inclui o requestId, permitindo que as entradas de log sejam correlacionadas a uma única solicitação. Quando você usa o objeto LambdaLogger de context.getLogger(), o requestId é incluído automaticamente em cada entrada de log. Para obter mais informações, consulte Uso de controles avançados de registro em log do Lambda com Java.

Contexto da solicitação

O objeto context está vinculado ao thread da solicitação. O uso de context.getAwsRequestId() proporciona acesso com segurança de thread para acessar o ID da solicitação atual.

Use context.getXrayTraceId() para acessar o ID de rastreamento do X-Ray. Isso fornece acesso com segurança de thread ao ID de rastreamento da solicitação atual. O Lambda não oferece suporte à variável de ambiente _X_AMZN_TRACE_ID com instâncias gerenciadas do Lambda. O ID de rastreamento do X-Ray é propagado automaticamente ao usar o AWS SDK.

Se você usar threads virtuais em seu programa ou criar threads durante a inicialização, precisará passar qualquer contexto de solicitação necessário para esses threads.

Inicialização e desligamento

A inicialização da função ocorre uma vez por ambiente de execução. Os objetos criados durante a inicialização são compartilhados entre os threads.

Para as funções do Lambda com extensões, o ambiente de execução emite um sinal SIGTERM durante o desligamento. Esse sinal é usado pelas extensões para acionar tarefas de limpeza, como descarregar buffers. É possível se inscrever em eventos SIGTERM para acionar tarefas de limpeza de funções, como fechar conexões de banco de dados. Para saber mais sobre o ciclo de vida do ambiente de execução, consulte Noções básicas sobre o ciclo de vida do ambiente de execução do Lambda.

Versões de dependências

As instâncias gerenciadas do Lambda exigem as versões mínimas de pacotes a seguir:

  • AWS SDK para Java 2.0: versão 2.34.0 ou posterior

  • AWS X-Ray SDK para Java: versão 2.20.0 ou posterior

  • AWS Distro para OpenTelemetry - Instrumentação para Java: versão 2.20.0 ou posterior

  • Powertools para AWS Lambda (Java): versão 2.8.0 ou posterior

Powertools para AWS Lambda (Java)

O Powertools para AWS Lambda (Java) é compatível com as instâncias gerenciadas do Lambda e fornece utilitários para registro em log, rastreamento, métricas e muito mais. Para obter mais informações, consulte Powertools para AWS Lambda (Java).

Próximas etapas