Envio de SMS com Alta Disponibilidade em Alta Concorrência (Pool de Conexões HTTP + Assíncrono)

Configuração de Dependências

<properties>
    <spring.boot.version>2.5.5</spring.boot.version>
    <lombok.version>1.18.16</lombok.version>
</properties>

<dependencies>
    <!--Inicialização do Spring Boot-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>${spring.boot.version}</version>
    </dependency>
    <!--Spring Boot Web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${spring.boot.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
        <version>${spring.boot.version}</version>
    </dependency>

    <!--Ferramenta Lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
    </dependency>
    <!-- Dependências OSS específicas para cada projeto conforme necessário -->
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.10.2</version>
    </dependency>

    <!--Dependências de teste-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.26-SNAPSHOT</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
        <version>${spring.boot.version}</version>
        <scope>test</scope>
    </dependency>
    <!--Guava-->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>30.1-jre</version>
    </dependency>
</dependencies>

appliaction.propreties

server.port=8001

spring.application.name=Servico-SMS

#----Configuração de SMS-------
sms.codigo-app=8b620f10437441f5889d91caa0267a06
sms.id-template=JM1000372

ServicoSMS

import com.xtw.config.ConfiguracaoSMS;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
@Slf4j
public class ServicoSMS {

    @Autowired
    private RestTemplate modeloRestTemplate;

    @Autowired
    private ConfiguracaoSMS configuracaoSms;

    /**
     * Modelo de URL para API de envio de SMS
     */
    private static final String MODELO_URL = "https://jmsms.market.alicloudapi.com/sms/send?mobile=%s&templateId=%s&value=%s";

    /**
     * Enviar código de verificação
     */
    public void enviar(String destinatario, String template, String valor){

        String url = String.format(MODELO_URL, destinatario, template, valor);

        HttpHeaders cabecalhosHttp = new HttpHeaders();
        cabecalhosHttp.set("Authorization","APPCODE "+configuracaoSms.getCodigoApp());

        HttpEntity<Object> entidade = new HttpEntity<>(cabecalhosHttp);
        ResponseEntity<String> respostaEntidade = modeloRestTemplate.exchange(url, HttpMethod.POST, entidade, String.class);

        if(respostaEntidade.getStatusCode().is2xxSuccessful()){
            log.info("Código de verificação enviado com sucesso");
        }else {
            log.error("Falha no envio do SMS: {}",respostaEntidade.getBody());
        }

    }

}

ConfiguracaoSMS

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@ConfigurationProperties(prefix = "sms")
@Configuration
@Data
public class ConfiguracaoSMS {
    /**
     * Código de app para envio de SMS
     */
    private String codigoApp;

    /**
     * ID do template de conteúdo SMS
     */
    private String idTemplate;
}

ConfiguracaoRestTemplate (Usando protooclo HTTP para requisições)

import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfiguracaoRestTemplate {
    @Bean
    public RestTemplate modeloRestTemplate(ClientHttpRequestFactory fabricaRequisicaoHttp){
        return new RestTemplate(fabricaRequisicaoHttp);
    }

    @Bean
    public ClientHttpRequestFactory fabricaRequisicaoHttp(){
        return new HttpComponentsClientHttpRequestFactory();
    }
    
    @Bean
    public HttpClient clienteHttp(){
        Registry<ConnectionSocketFactory> registro = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();

        PoolingHttpClientConnectionManager gerenciadorConexao = new PoolingHttpClientConnectionManager(registro);

        //Define o máximo de 500 conexões no pool
        gerenciadorConexao.setMaxTotal(500);
        //MaxPerRoute é uma subdivisão do maxtotal, com máximo de 300 conexões por host
        gerenciadorConexao.setDefaultMaxPerRoute(300);

        RequestConfig configuracaoRequisicao = RequestConfig.custom()
                //Timeout para recebimento de dados
                .setSocketTimeout(20000)
                //Timeout para conexão com o servidor
                .setConnectTimeout(10000)
                //Timeout para obter conexão do pool
                .setConnectionRequestTimeout(1000)
                .build();

        return HttpClientBuilder.create().setDefaultRequestConfig(configuracaoRequisicao)
                .setConnectionManager(gerenciadorConexao)
                .build();
    }
}

Testes

import com.xtw.AplicacaoSms;
import com.xtw.component.ServicoSMS;
import com.xtw.config.ConfiguracaoSMS;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes= AplicacaoSms.class)
@Slf4j
public class TestesSms {

    @Autowired
    private ConfiguracaoSMS configuracaoSms;
    @Autowired
    private ServicoSMS servicoSms;

    @Test
    public void testarPropriedades(){
        System.out.println(configuracaoSms.getCodigoApp());
    }

    @Test
    public void testarEnvio(){
        servicoSms.enviar("15070159890", configuracaoSms.getIdTemplate(), "652421");
    }
}

Configuração Assíncrona

@Configuration
@EnableAsync
public class ConfiguracaoTarefaAssincrona {

    @Bean("executorTarefaPool")
    public ThreadPoolTaskExecutor executorTarefaPool(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        //Número de threads principais
        executor.setCorePoolSize(16);

        //Capacidade da fila de bloqueio
        executor.setQueueCapacity(3000);

        //Número máximo de threads
        executor.setMaxPoolSize(300);

        //Tempo de vida de threads não principais ociosas (30s)
        executor.setKeepAliveSeconds(30);
        //Se allowCoreThreadTimeout=true, threads principais também serão liberadas
        executor.setAllowCoreThreadTimeOut(false);

        //Prefixo do nome da thread
        executor.setThreadNamePrefix("PoolThreadsPersonalizados-");

        //Política de tratamento de estouro de threads
        //CallerRunsPolicy(): A thread que chamou executa a tarefa
        //AbortPolicy(): Política padrão, lança RejectedExecutionException
        //DiscardPolicy(): Descarta a tarefa sem exceção
        //DiscardOldestPolicy(): Descarta a tarefa mais antiga na fila
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

        //Inicialização
        executor.initialize();

        return executor;
    }
}

Utilização Assíncrona

@Async("executorTarefaPool")
public void enviar(String destinatario, String template, String valor) {

    String url = String.format(MODELO_URL, destinatario, template, valor);

    HttpHeaders cabecalhosHttp = new HttpHeaders();
    cabecalhosHttp.set("Authorization","APPCODE "+configuracaoSms.getCodigoApp());

    HttpEntity<Object> entidade = new HttpEntity<>(cabecalhosHttp);
    ResponseEntity<String> respostaEntidade = modeloRestTemplate.exchange("http://localhost:8002/api/visit_stats/v1/str", HttpMethod.POST, entidade, String.class);

    if(respostaEntidade.getStatusCode().is2xxSuccessful()){
        log.info("Código de verificação enviado com sucesso");
    }else {
        log.error("Falha no envio do SMS: {}",respostaEntidade.getBody());
    }
}

Tags: Spring Boot SMS Concorrência HTTP Connection Pool Processamento Assíncrono

Publicado em 6-15 09:17 por Thomas