SpringBoot使用异步线程池实现生产环境批量数据推送(线程)

前言

SpringBoot使用异步线程池:

1、编写线程池配置类,自定义一个线程池;

2、定义一个异步服务;

3、使用@Async注解指向定义的线程池;

这里以我工作中使用过的一个案例来做描述,我所在公司是医疗行业,敏感数据需要上报到某监管平台,所以有一个定时任务在流量较小时(一般是凌晨后)执行上报行为。但特殊时期会存在一定要在工作时间大批量上报数据的情况,且要求短时间内就要完成,此时就考虑写一个专门的异步上报接口手动执行,利用线程池上报,极大提高了速度。


  • 编写线程池配置类

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * 类名称:ExecutorConfig
     * ********************************
     * <p>
     * 类描述:线程池配置
     *
     * @author guoj
     * @date 2021-09-07 09:00
     */
    @Configuration
    @EnableAsync
    @Slf4j
    public class ExecutorConfig {
        /**
         * 定义数据上报线程池
         * @return
         */
        @Bean("dataCollectionExecutor")
        public Executor dataCollectionExecutor() {
    
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    
            // 核心线程数量:当前机器的核心数
            executor.setCorePoolSize(
                    Runtime.getRuntime().availableProcessors());
    
            // 最大线程数
            executor.setMaxPoolSize(
                    Runtime.getRuntime().availableProcessors() * 2);
    
            // 队列大小
            executor.setQueueCapacity(Integer.MAX_VALUE);
    
            // 线程池中的线程名前缀
            executor.setThreadNamePrefix("sjsb-");
    
            // 拒绝策略:直接拒绝
            executor.setRejectedExecutionHandler(
                    new ThreadPoolExecutor.AbortPolicy());
    
            // 执行初始化
            executor.initialize();
    
            return executor;
        }
    
    }

     PS:

    1)、需要注意,这里一定要自己定义ThreadPoolTaskExecutor线程池,否则springboot的异步注解会执行默认线程池,存在线程阻塞导致CPU飙高及内存溢出的风险。这一点可以参考阿里开发手册,线程池定义这块明确提到了这一点;

    2)、在@Bean注解中定义线程池名称,后面异步注解会用到。


  • 编写异步服务

    /**
     * 异步方法的服务, 不影响主程序运行。
     */
    @Service
    @Slf4j
    public class AsyncService {
    
    
        /**
         * 发送短信
         */
        @Async("sendMsgExecutor")
        public void sendMsg(String access_token, Consult item, Map<String, String> configMap) {
            // 此处编写发送短信业务
            // 1、buildConsultData();
            // 2、sendMsg();
        }
    
        /**
         * 发送微信订阅消息
         */
        @Async
        public void sendSubscribeMsg(String access_token, Consult item, Map<String, String> configMap) {
            // 此处编写发送微信订阅消息业务
            // 1、buildConsultData();
            // 2、sendSubscribeMsg();
        }
    
        /**
         * 数据并上报
         */
        @Async("dataCollectionExecutor")
        public void buildAndPostData(String access_token, Consult item, Map<String, String> configMap) {
            // 此处编写上报业务,如拼接数据,然后执行上报。
            // 1、buildConsultData();
            // 2、postData();
        }
       /**
         * 发送微信推送消息
         */
        @Async(value = "dataCollectionExecutor")
        @Transactional(rollbackFor = Exception.class)
        public void sendUserIdMsg(LockEvent lockEvent, String userId) {
            WebSocketServer.sendInfo(JSON.toJSONString(lockEvent), userId);
    }

PS:

1)、以上是代码片段,个人经验认为专门定义一个异步service存放各个异步方法最佳,这样可以避免编码时一些误操作比如异步方法不是void或者是private修饰,导致@Async注解失效的情况,同时可以安排每个注解指向不同的自定义线程池更加灵活;

2)、@Async注解中的名称就是上面定义的自定义线程池名称,这样业务执行时就会从指定线程池中获取异步线程。


  • 异步批量上报数据

    @Autowired
    private AsyncService asyncService;
    
        private void saveLockEventPower(LockInfoBo lockInfoBo, String eventName, String deviceId) {
            log.info(JSON.toJSONString(lockInfoBo) + "------------------");
            LockEvent lockEvent = new LockEvent()
                    .setCreateTime(new Date())
                    .setDeviceId(deviceId)
                    .setContext(eventName)
                    .setType(LockUserType.TYPE2)
                    .setPower(lockInfoBo.getPower())
                    .setMode(lockInfoBo.getMode())
                    .setLatch(lockInfoBo.getLatch())
                    .setContent(lockInfoBo.getContent());
            lockEventService.save(lockEvent);
            log.info("转为json" + JSON.toJSONString(lockEvent));
            //查询userId
            List<Long> lockUserIdList = lockUserMapper.selectUserList(deviceId);
            //websocket发送消息给前端
            lockUserIdList.forEach((userId) -> {
                try {
                    // 异步调用,使用线程池。
                    asyncService.sendUserIdMsg(lockEvent, userId.toString());
                } catch (Exception ex) {
                    log.error("微信推送消息通知异常", ex);
                }
            });
    }


    •  总结

      以上方式已经在生产环境运行,在工作时间内执行过很多次,一次数万条记录基本是几分钟内就全部上报完毕,而正常循环遍历时一次大概需要半个小时左右。

      线程池的使用方式往往来源于业务场景,如果类似的业务不存在紧急处理的情况,大体还是以任务调度执行为主,因为更安全。如果存在紧急处理的情况,那么使用SpringBoot+线程池的方式不仅能节省非常多的时间,且不占用主线程的执行空间。

      喜欢就点个关注吧~~

### 回答1: Java中可以使用线程实现一个推送微信、邮箱和短信的接口。具体实现方式可以采用创建多个线程分别负责微信、邮箱和短信的推送,这样可以使得三种推送方式可以并行执行。下面是一个简单的实现示例: ``` public class PushService { public void push(String message) { Thread wechatThread = new Thread(() -> { // 推送微信 }); Thread emailThread = new Thread(() -> { // 推送邮箱 }); Thread smsThread = new Thread(() -> { // 推送短信 }); wechatThread.start(); emailThread.start(); smsThread.start(); } } ``` 不过这个例子采用的是线程实现线程有时间和资源限制,我们可以用线程池来替代,可以提高性能。 java有很多的实现线程池的方法,例如 Executor,ExecutorService,ThreadPoolExecutor 都可以 ``` Executor executor = Executors.newFixedThreadPool(3); executor.execute(() -> { // 推送微信 }); executor.execute(() -> { // 推送邮箱 }); executor.execute(() -> { // 推送短信 }); ``` 不过请注意代码里面具体实现需要自己补充 ### 回答2: Java线程可以实现一个同时推送微信、邮箱和短信的接口。首先,我们可以创建一个推送任务类Task,该类实现了Runnable接口,在run方法中实现推送微信、邮箱和短信的逻辑。 具体地,我们可以在Task类的构造函数中传入推送的内容,并将微信、邮箱和短信的推送逻辑在run方法中实现。在run方法中,我们可以使用线程的方式分别推送微信、邮箱和短信。 为了实现线程,我们可以使用Java线程池来管理线程的创建和销毁。首先,我们可以创建一个线程池对象,设置线程池的大小为3,表示我们可以同时处理3个推送任务。然后,我们可以使用线程池的submit方法将任务提交给线程池执行。 当任务被提交给线程池后,线程池会自动创建一个线程执行任务。由于我们设置了线程池的大小为3,所以最多同时会有3个线程执行任务。当一个线程完成任务后,线程池会立即将其回收,以便提供给下一个任务使用。 通过使用线程池,我们可以实现并发执行多个推送任务的效果,提高推送的效率。同时,线程池会自动管理线程的创建和销毁,免去了手动创建和销毁线程的麻烦。这样,我们就可以实现一个同时推送微信、邮箱和短信的接口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值