【案例实战】高性能SpringBoot整合短线验证码发送(池化思想+异步结合)

简介: 【案例实战】高性能SpringBoot整合短线验证码发送(池化思想+异步结合)

1.需求背景

  • 海量用户下,高性能的发送手机短信验证码。
  • 为什么要用线程池+异步的方式去进行短信验证码的发送呢?
  • 如果是同步发送+RestTemplate未池化最大几百的吞吐量

错误Caused by: java.io.IOException: Broken pipe

  • 服务端向前端socket连接管道写返回数据时 链接(pipe)却断开了
  • 从应用角度分析,这是因为客户端等待返回超时了,主动断开了与服务端链接
  • 连接数设置太小,并发量增加后,造成大量请求排队等待
  • 网络延迟,是否有丢包
  • 内存是否足够多支持对应的并发量

2.第三方短信验证码平台接入

  • 这块只是给大家做个案例演示,公司的项目不一定采用云市场的短信接入,但是思路都是大同小异。

(1)进入链接登入阿里云。

70645b3beb694a0ebe32401d0dda26f5.jpg

(2)大家可以根据自己的需求去进行购买,一般做测试的话就选3元的就可以了。


abb4d2771c7b4cc29be322449253a1ec.jpg(3)购买之后就会又对应的产品密钥。

AppKey:204073759     
AppSecret:LpKyOMk7krrJgU845P7UraJNs3wGtDN7
AppCode:59712f17a3434de8b53b03df9bffe7e4

3.SpringBoot项目搭建

(1)新建Maven项目



a1b8d3abdeb44cfda4fe8d6c0e170fd7.jpg


296345e6c8164d13897d7667f336eb59.jpg(2)引入Maven依赖

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent> 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

ad9f634348594b36906c32cf65d5c827.jpg(3)创建主类

@SpringBootApplication
public class SendApplication {
    public static void main(String[] args) {
        SpringApplication.run(SendApplication.class, args);
    }
}

(4)创建yaml,application.yml

server:
  port: 8011
spring:
  application:
    name: send-server

(5)启动验证

646d2fdb29c54c9884206a33a638e784.jpg

4.开发短信验证码发送

(1)加入Maven依赖

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>

(2)配置RestTemplate连接池

什么是RestTemplate?
* RestTemplate是Spring提供的用于访问Rest服务的客户端
* 底层通过使用java.net包下的实现创建HTTP 请求
* 通过使用ClientHttpRequestFactory指定不同的HTTP请求方式,主要提供了两种实现方式
  * SimpleClientHttpRequestFactory(默认)
    * 底层使用J2SE提供的方式,既java.net包提供的方式,创建底层的Http请求连接
    * 主要createRequest 方法( 断点调试),每次都会创建一个新的连接,每次都创建连接会造成极大的资源浪费,而且若连接不能及时释放,会因为无法建立新的连接导致后面的请求阻塞
  * HttpComponentsClientHttpRequestFactory
    * 底层使用HttpClient访问远程的Http服务

Spring的restTemplate是对httpclient进行了封装, 而httpclient是支持池化机制

/**
 * @description RestTemplate配置类
 * @author lixiang
 */
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory requestFactory){
        return new RestTemplate(requestFactory);
    }
    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {
        return new HttpComponentsClientHttpRequestFactory(httpClient());
    }
    @Bean
    public HttpClient httpClient(){
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        //设置整个连接池最大连接数
        connectionManager.setMaxTotal(500);
        //设置每个主机的最大并发数
        connectionManager.setDefaultMaxPerRoute(200);
        RequestConfig requestConfig = RequestConfig.custom()
                //设置返回数据的超时时间
                .setSocketTimeout(20000)
                //设置连接服务器的超时时间
                .setConnectTimeout(10000)
                //设置从连接池中获取连接的超时时间
                .setConnectionRequestTimeout(1000)
                .build();
        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                .build();
    }
}

(3)Async+ThreadPoolTaskExecutor配置自定义线程池

@Configuration
public class ThreadPoolTaskConfig {
    @Bean("threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        //线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
        //如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
        threadPoolTaskExecutor.setCorePoolSize(4);
        //最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
        //当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
        threadPoolTaskExecutor.setMaxPoolSize(8);
        //缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
        threadPoolTaskExecutor.setQueueCapacity(124);
        //当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
        //允许线程空闲时间60秒,当maxPoolSize的线程在空闲时间到达的时候销毁
        //如果allowCoreThreadTimeout=true,则会直到线程数量=0
        threadPoolTaskExecutor.setKeepAliveSeconds(30);
        //spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
        //jdk 提供的ThreadPoolExecutor 线程池是没有 setThreadNamePrefix() 方法的
        threadPoolTaskExecutor.setThreadNamePrefix("小滴课堂Spring自带Async前缀:");
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
        //AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
        //DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
        //DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

(4)配置yml

# sms短信配置
sms:
  #这个appcode就是在下单之后,商家会提供一个appcode
  app-code: 59712f17a3434de8b53b03df9bffe7e4
  #模板ID,这块需要自己去申请自定义的短信模板内容,我这块用的是测试的模板
  template-id: M105EABDEC
  #短信发送URL
  send-url: https://jmsms.market.alicloudapi.com/sms/send?mobile=%s&templateId=%s&value=%s


3a9a7c8f22aa49b6acf7b8fe847d23df.jpg(5)编写短信配置类

@ConfigurationProperties(prefix = "sms")
@Configuration
@Data
public class SmsConfig {
    /**
     * 短信模板ID
     */
    private String templateId;
    /**
     * 短信app-code
     */
    private String appCode;
}

(6)编写短信发送组件

/**
 * 短信发送服务
 * @author lixiang
 */
@Component
@Slf4j
public class SmsComponent {
    @Value("${sms.send-url}")
    private String sendCodeUrl;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private SmsConfig smsConfig;
    @Async("threadPoolTaskExecutor")
    public void send(String to, String templateId, String value) {
        String url = String.format(sendCodeUrl, to, templateId, value);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization","APPCODE "+smsConfig.getAppCode());
        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
        log.info("url:{},body:{}", url, response.getBody());
        if (!response.getStatusCode().is2xxSuccessful()) {
            log.error("短信发送失败,value:{},响应:{}", value,response.getBody());
        }else{
            log.info("短信发送成功,value:{},响应:{}",value, response.getBody());
        }
    }
}

(7)编写Controller测试

@RestController
@RequestMapping("/notify")
public class SendController {
    @Autowired
    private SmsComponent smsComponent;
    @Value("{sms.template-id}")
    private String templateId;
    @GetMapping("send_code")
    public String sendCode(@RequestParam("phone") String phone){
    //定义发送的验证码,公司的业务可以采用随机生成的4位或者6位数字
        String code = "567249";
        //发送短信验证码
        smsComponent.send(phone,templateId,code);
        return "SUCCESS";
    }
}


b0482d8a629d4a11abf451b048f5dce8.jpg

7f041aaa15a14a1eb46985429ff32e9b.jpg


0a9e1244f3b14a65a569397ad5a0f410.jpgok,至此SpringBoot整合短信验证码发送已经完成。下面我们来测试一下性能。

以下是同步发送+池化RestTemplate的压测报告,如果异步发送性能还会更高


f34c4a2df8f24911ad34902cc613f8a5.jpg

相关文章
|
6天前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
50 1
|
10天前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
11天前
|
SQL 前端开发 NoSQL
SpringBoot+Vue 实现图片验证码功能需求
这篇文章介绍了如何在SpringBoot+Vue项目中实现图片验证码功能,包括后端生成与校验验证码的方法以及前端展示验证码的实现步骤。
SpringBoot+Vue 实现图片验证码功能需求
|
12天前
|
NoSQL Java Redis
Redis6入门到实战------ 八、Redis与Spring Boot整合
这篇文章详细介绍了如何在Spring Boot项目中整合Redis,包括在`pom.xml`中添加依赖、配置`application.properties`文件、创建配置类以及编写测试类来验证Redis的连接和基本操作。
Redis6入门到实战------ 八、Redis与Spring Boot整合
|
2天前
|
Java API UED
【实战秘籍】Spring Boot开发者的福音:掌握网络防抖动,告别无效请求,提升用户体验!
【8月更文挑战第29天】网络防抖动技术能有效处理频繁触发的事件或请求,避免资源浪费,提升系统响应速度与用户体验。本文介绍如何在Spring Boot中实现防抖动,并提供代码示例。通过使用ScheduledExecutorService,可轻松实现延迟执行功能,确保仅在用户停止输入后才触发操作,大幅减少服务器负载。此外,还可利用`@Async`注解简化异步处理逻辑。防抖动是优化应用性能的关键策略,有助于打造高效稳定的软件系统。
12 2
|
2天前
|
JSON Java API
解码Spring Boot与JSON的完美融合:提升你的Web开发效率,实战技巧大公开!
【8月更文挑战第29天】Spring Boot作为Java开发的轻量级框架,通过`jackson`库提供了强大的JSON处理功能,简化了Web服务和数据交互的实现。本文通过代码示例介绍如何在Spring Boot中进行JSON序列化和反序列化操作,并展示了处理复杂JSON数据及创建RESTful API的方法,帮助开发者提高效率和应用性能。
12 0
|
2天前
|
监控 Java API
Spring Boot中的异步革命:构建高性能的现代Web应用
【8月更文挑战第29天】Spring Boot 是一个简化 Spring 应用开发与部署的框架。异步任务处理通过后台线程执行耗时操作,提升用户体验和系统并发能力。要在 Spring Boot 中启用异步任务,需在配置类上添加 `@EnableAsync` 注解,并定义一个自定义的 `ThreadPoolTaskExecutor` 或使用默认线程池。通过 `@Async` 注解的方法将在异步线程中执行。异步任务适用于发送电子邮件、数据处理、外部 API 调用和定时任务等场景。最佳实践中应注意正确配置线程池、处理返回值和异常、以及监控任务状态,确保系统的稳定性和健壮性。
|
2天前
|
Java 开发者 Spring
Spring Boot实战宝典:揭秘定时任务的幕后英雄,让业务处理如流水般顺畅,轻松驾驭时间管理艺术!
【8月更文挑战第29天】在现代应用开发中,定时任务如数据备份、报告生成等至关重要。Spring Boot作为流行的Java框架,凭借其强大的集成能力和简洁的配置方式,为开发者提供了高效的定时任务解决方案。本文详细介绍了如何在Spring Boot项目中启用定时任务支持、编写定时任务方法,并通过实战案例展示了其在业务场景中的应用,同时提供了注意事项以确保任务的正确执行。
10 0
|
2天前
|
Java 开发者 Spring
Spring Boot大法好:解耦、隔离、异步,让代码‘活’起来,性能飙升的秘密武器!
【8月更文挑战第29天】解耦、隔离与异步是Spring Boot中的关键设计原则,能大幅提升软件的可维护性、扩展性和性能。本文通过示例代码详细探讨了这些原则的应用:依赖注入和面向接口编程实现解耦;模块化设计与配置文件实现隔离;`@Async`注解和`CompletableFuture`实现异步处理。综合运用这些原则,可以显著提升软件质量和性能,使系统更加健壮、灵活和高效。
|
10天前
|
缓存 Java Spring
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
下一篇
云函数