Programação assíncrona usando o AWS SDK for Java 2.x - AWS SDK for Java 2.x

Programação assíncrona usando o AWS SDK for Java 2.x

O AWS SDK for Java 2.x apresenta clientes assíncronos com suporte a E/S sem bloqueio, que implementam alta simultaneidade usando apenas algumas threads. No entanto, a E/S total sem bloqueio não é garantida. O cliente assíncrono pode fazer chamadas de bloqueio em alguns casos, como recuperação de credenciais, assinatura de solicitações usando o AWS Signature Version 4 (SigV4) ou a descoberta de endpoints.

Os métodos síncronos bloqueiam a execução do seu thread até o cliente receber uma resposta do serviço. Os métodos assíncronos retornam imediatamente, devolvendo o controle ao thread de chamada sem aguardar uma resposta.

Como um método assíncrono retorna antes de uma resposta estar disponível, você precisa de uma maneira de obter a resposta quando ela estiver pronta. Os métodos de cliente assíncrono no 2.x do AWS SDK para Java retornam objetos CompletableFuture que permitem acessar a resposta quando ela está pronta.

Usar APIs de cliente assíncrono

As assinaturas dos métodos do cliente assíncrono são iguais às dos síncronos, mas os métodos assíncronos retornam um objeto CompletableFuture que contém os resultados da operação assíncrona no futuro. Se um erro for gerado durante a execução do método assíncrono do SDK, o erro será lançado como CompletionException.

Uma abordagem que você pode usar para obter o resultado é encadear um método whenComplete() no CompletableFuture retornado pela chamada do método de SDK. O método whenComplete() recebe o resultado ou um objeto Throwable do tipo CompletionException, dependendo de como a chamada assíncrona foi concluída. Você fornece uma ação whenComplete() para processar ou verificar os resultados antes que eles retornem ao código de chamada.

Se você quiser retornar algo diferente do objeto retornado pelo método de SDK, use o método handle(). O método handle() usa os mesmos parâmetros que whenComplete(), mas você pode processar o resultado e retornar um objeto.

Para aguardar a conclusão da cadeia assíncrona e recuperar os resultados da conclusão, você pode chamar o método join(). Se o objeto Throwable não foi gerenciado na cadeia, o método join() lança um CompletionException não verificado que envolve a exceção original. Você acessa a exceção original com CompletionException#getCause(). Você também pode chamar o método CompletableFuture#get() para obter os resultados da conclusão. No entanto, o método get() pode gerar exceções verificadas.

O exemplo a seguir mostra duas variações de como você pode trabalhar com o método listTables() do cliente assíncrono do DynamoDB. A ação passada para whenComplete() simplesmente registra uma resposta bem-sucedida, enquanto a versão handle() extrai a lista de nomes de tabelas e retorna a lista. Em ambos os casos, se um erro for gerado na cadeia assíncrona, ele será relançado para que o código do cliente tenha a chance de gerenciá-lo.

Importações

import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest; import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse; import java.util.List; import java.util.concurrent.CompletableFuture;

Código da

whenComplete() variation
public class DynamoDbAsyncListTables { public static void main(String[] args) { Region region = Region.US_EAST_1; DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbAsyncClient.builder().region(region).build(); try { ListTablesResponse listTablesResponse = listTablesWhenComplete(dynamoDbAsyncClient).join(); // The join() method may throw a CompletionException. if (listTablesResponse.hasTableNames()){ System.out.println("Table exist in this region: " + region.id()); } } catch (RuntimeException e) { // Handle as needed. Here we simply print out the class names. System.out.println(e.getClass()); // Prints 'class java.util.concurrent.CompletionException'. System.out.println(e.getCause().getClass()); // Prints 'class software.amazon.awssdk.services.dynamodb.model.DynamoDbException'. } } public static CompletableFuture<ListTablesResponse> listTablesWhenComplete(DynamoDbAsyncClient client) { return client.listTables(ListTablesRequest.builder().build()) .whenComplete((listTablesResponse, throwable) -> { if (listTablesResponse != null) { // Consume the response. System.out.println("The SDK's listTables method completed successfully."); } else { RuntimeException cause = (RuntimeException) throwable.getCause(); // If an error was thrown during the SDK's listTables method it is wrapped in a CompletionException. // The SDK throws only RuntimeExceptions, so this is a safe cast. System.out.println(cause.getMessage()); // Log error here, but rethrow so the calling code can handle as needed. throw cause; } }); }
handle() variation
public class DynamoDbAsyncListTables { public static void main(String[] args) { Region region = Region.US_EAST_1; DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbAsyncClient.builder().region(region).build(); try { List<String> tableNames = listTablesHandle(dynamoDbAsyncClient).join(); // The join() method may throw a CompletionException. tableNames.forEach(System.out::println); } catch (RuntimeException e) { // Handle as needed. Here we simply print out the class names. System.out.println(e.getClass()); // Prints 'class java.util.concurrent.CompletionException'. System.out.println(e.getCause().getClass()); // Prints 'class software.amazon.awssdk.services.dynamodb.model.DynamoDbException'. } } public static CompletableFuture<List<String>> listTablesHandle(DynamoDbAsyncClient client) { return client.listTables(ListTablesRequest.builder().build()) .handle((listTablesResponse, throwable) -> { if (listTablesResponse != null) { return listTablesResponse.tableNames(); // Return the list of table names. } else { RuntimeException cause = (RuntimeException) throwable.getCause(); // If an error was thrown during the SDK's listTables method it is wrapped in a CompletionException. // The SDK throws only RuntimeExceptions, so this is a safe cast. System.out.println(cause.getMessage()); // Log error here, but rethrow so the calling code can handle as needed. throw cause; } }); } }

Gerenciar o streaming em métodos assíncronos

Para métodos assíncronos com streaming de conteúdo, é necessário fornecer um AsyncRequestBody para entregar o conteúdo de forma incremental ou um AsyncResponseTransformer para receber e processar a resposta.

O exemplo a seguir faz upload de um arquivo para o Amazon S3 de forma assíncrona usando a forma assíncrona da operação PutObject.

Importações

import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectResponse; import java.nio.file.Paths; import java.util.concurrent.CompletableFuture;

Código da

/** * To run this AWS code example, ensure that you have setup your development environment, including your AWS credentials. * * For information, see this documentation topic: * * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html */ public class S3AsyncOps { public static void main(String[] args) { final String USAGE = "\n" + "Usage:\n" + " S3AsyncOps <bucketName> <key> <path>\n\n" + "Where:\n" + " bucketName - the name of the Amazon S3 bucket (for example, bucket1). \n\n" + " key - the name of the object (for example, book.pdf). \n" + " path - the local path to the file (for example, C:/AWS/book.pdf). \n" ; if (args.length != 3) { System.out.println(USAGE); System.exit(1); } String bucketName = args[0]; String key = args[1]; String path = args[2]; Region region = Region.US_WEST_2; S3AsyncClient client = S3AsyncClient.builder() .region(region) .build(); PutObjectRequest objectRequest = PutObjectRequest.builder() .bucket(bucketName) .key(key) .build(); // Put the object into the bucket CompletableFuture<PutObjectResponse> future = client.putObject(objectRequest, AsyncRequestBody.fromFile(Paths.get(path)) ); future.whenComplete((resp, err) -> { try { if (resp != null) { System.out.println("Object uploaded. Details: " + resp); } else { // Handle error err.printStackTrace(); } } finally { // Only close the client when you are completely done with it client.close(); } }); future.join(); } }

O exemplo a seguir obtém um arquivo do Amazon S3 usando a forma assíncrona da operação GetObject.

Importações

import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import java.nio.file.Paths; import java.util.concurrent.CompletableFuture;

Código da

/** * To run this AWS code example, ensure that you have setup your development environment, including your AWS credentials. * * For information, see this documentation topic: * * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html */ public class S3AsyncStreamOps { public static void main(String[] args) { final String USAGE = "\n" + "Usage:\n" + " S3AsyncStreamOps <bucketName> <objectKey> <path>\n\n" + "Where:\n" + " bucketName - the name of the Amazon S3 bucket (for example, bucket1). \n\n" + " objectKey - the name of the object (for example, book.pdf). \n" + " path - the local path to the file (for example, C:/AWS/book.pdf). \n" ; if (args.length != 3) { System.out.println(USAGE); System.exit(1); } String bucketName = args[0]; String objectKey = args[1]; String path = args[2]; Region region = Region.US_WEST_2; S3AsyncClient client = S3AsyncClient.builder() .region(region) .build(); GetObjectRequest objectRequest = GetObjectRequest.builder() .bucket(bucketName) .key(objectKey) .build(); CompletableFuture<GetObjectResponse> futureGet = client.getObject(objectRequest, AsyncResponseTransformer.toFile(Paths.get(path))); futureGet.whenComplete((resp, err) -> { try { if (resp != null) { System.out.println("Object downloaded. Details: "+resp); } else { err.printStackTrace(); } } finally { // Only close the client when you are completely done with it client.close(); } }); futureGet.join(); } }

Configurar opções assíncronas avançadas

O AWS SDK para Java 2.x usa Netty, uma estrutura de aplicativo de rede assíncrona orientada por eventos, para lidar com threads de E/S. O AWS SDK para Java 2.x cria um ExecutorService por trás do Netty para concluir os futuros retornos da solicitação do cliente HTTP para o cliente Netty. Essa abstração reduz o risco de um aplicativo interromper o processo assíncrono se os desenvolvedores optarem por suspender ou desabilitar threads. Por padrão, cada cliente assíncrono cria um threadpool com base no número de processadores e gerencia as tarefas em uma fila dentro do ExecutorService.

Você pode especificar uma implementação específica do JDK de ExecutorService ao criar o cliente assíncrono. O trecho a seguir cria um ExecutorService com um número fixo de threads.

Código da

S3AsyncClient clientThread = S3AsyncClient.builder() .asyncConfiguration( b -> b.advancedOption(SdkAdvancedAsyncClientOption .FUTURE_COMPLETION_EXECUTOR, Executors.newFixedThreadPool(10) ) ) .build();

Para otimizar o desempenho, você pode gerenciar seu próprio executor de grupo de threads e incluí-lo ao configurar o cliente.

ThreadPoolExecutor executor = new ThreadPoolExecutor(50, 50, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(<custom_value>), new ThreadFactoryBuilder() .threadNamePrefix("sdk-async-response").build()); // Allow idle core threads to time out executor.allowCoreThreadTimeOut(true); S3AsyncClient clientThread = S3AsyncClient.builder() .asyncConfiguration( b -> b.advancedOption(SdkAdvancedAsyncClientOption .FUTURE_COMPLETION_EXECUTOR, executor ) ) .build();