You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Spring中如何让@Scheduled定时任务内的耗时逻辑异步执行?

Spring @Scheduled定时任务异步执行耗时FCM推送方案

问题背景

现有Spring应用配置了fixedDelay = 10000的定时拍卖启动任务,遍历待启动拍卖场次执行核心启动逻辑时,两段FCM推送代码耗时过长,会阻塞主任务遍历流程,导致整体任务执行延迟。需要将两段推送逻辑改为异步执行,不阻塞拍卖启动核心流程。
原任务实现代码如下:

@Scheduled(fixedDelay = 10000)
public void startAuction() throws Exception {
    List<SchGoodsAuctionStartListRes> list = schedulerService.schGoodsAuctionStartList();

    for (SchGoodsAuctionStartListRes item : list) {
        schedulerService.schGoodsAuctionStart(item);

        // 第一段耗时逻辑:给卖家推送拍卖启动通知
        PushInfo pushInfo = pushMapper.pushGoodsSeller(item.getGoodsIdx());
        pushInfo.setTitle("Start");
        pushInfo.setBody("[" + pushInfo.getBrand() + "] started.");
        pushInfo.setPushGrp("001");
        pushInfo.setPushCode("003");
        fcmPushUtil.sendPush(pushInfo);

        // 第二段耗时逻辑:给所有竞拍用户推送拍卖启动通知
        List<PushInfo> pushInfos = pushMapper.pushGoodsAuctionAll(item.getIdx());
        for (PushInfo pushInfoItem : pushInfos) {
            pushInfoItem.setTitle("\uD83D\uDD14 open");
            pushInfoItem.setBody("[" + pushInfo.getBrand() + "] started. \uD83D\uDC5C");
            pushInfoItem.setPushGrp("002");
            pushInfoItem.setPushCode("008");
            fcmPushUtil.sendPush(pushInfoItem);
        }
    }
}

实现方案

直接用Spring原生@Async注解实现异步,不需要手动编写线程提交逻辑,侵入性最低。

具体步骤

  • 开启异步支持
    在Spring Boot启动类或者任意配置类上添加@EnableAsync注解,打开Spring异步方法执行开关。
  • 自定义FCM推送专用线程池
    Spring默认异步线程池每次调用都会新建线程,高并发下容易耗尽服务器资源,强烈建议自定义独立线程池,隔离推送任务和其他业务线程。
    @Configuration
    public class AsyncPoolConfig {
        @Bean("fcmPushExecutor")
        public Executor fcmPushExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(5);
            executor.setMaxPoolSize(10);
            executor.setQueueCapacity(200);
            executor.setThreadNamePrefix("fcm-push-task-");
            // 队列满时由调用线程执行任务,避免消息丢失
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.initialize();
            return executor;
        }
    }
    
  • 封装异步推送逻辑
    把两段耗时推送逻辑抽到独立的Service类中,给方法加上@Async注解,指定使用刚才定义的推送专用线程池。
    注意:异步方法必须放在独立的Bean中,不能和定时任务写在同一个类,否则异步不生效
    @Service
    public class AsyncPushTaskService {
        @Resource
        private PushMapper pushMapper;
        @Resource
        private FcmPushUtil fcmPushUtil;
    
        @Async("fcmPushExecutor")
        public void asyncSendAuctionStartPush(SchGoodsAuctionStartListRes auctionItem) {
            // 卖家推送逻辑
            PushInfo sellerPush = pushMapper.pushGoodsSeller(auctionItem.getGoodsIdx());
            sellerPush.setTitle("Start");
            sellerPush.setBody("[" + sellerPush.getBrand() + "] started.");
            sellerPush.setPushGrp("001");
            sellerPush.setPushCode("003");
            fcmPushUtil.sendPush(sellerPush);
    
            // 竞拍用户推送逻辑
            List<PushInfo> userPushList = pushMapper.pushGoodsAuctionAll(auctionItem.getIdx());
            for (PushInfo userPush : userPushList) {
                userPush.setTitle("\uD83D\uDD14 open");
                userPush.setBody("[" + sellerPush.getBrand() + "] started. \uD83D\uDC5C");
                userPush.setPushGrp("002");
                userPush.setPushCode("008");
                fcmPushUtil.sendPush(userPush);
            }
        }
    }
    
  • 改造原定时任务
    删除原任务中同步执行的推送代码,替换为调用异步Service的方法,核心拍卖启动逻辑执行完成后立刻触发异步推送,不需要等待推送执行完成,直接进入下一个场次的处理,完全不会阻塞主流程。
    @Resource
    private AsyncPushTaskService asyncPushTaskService;
    
    @Scheduled(fixedDelay = 10000)
    public void startAuction() throws Exception {
        List<SchGoodsAuctionStartListRes> list = schedulerService.schGoodsAuctionStartList();
        for (SchGoodsAuctionStartListRes item : list) {
            // 核心启动逻辑同步执行,保证拍卖状态一致性
            schedulerService.schGoodsAuctionStart(item);
            // 异步触发推送,不阻塞主循环
            asyncPushTaskService.asyncSendAuctionStartPush(item);
        }
    }
    

踩坑提醒

  • 不要在定时任务所在类直接定义异步方法然后自调用,Spring AOP代理会失效,异步配置完全不生效,还是会同步阻塞。
  • 线程池参数不要直接抄示例,根据自身服务器配置、推送峰值QPS调整,核心线程数不要设置过高,避免并发请求FCM接口被限流。
  • 异常处理、失败重试、推送日志统计这些逻辑直接写在异步方法里即可,不会影响定时任务主流程的执行效率。
  • 如果业务允许少量推送丢失,可以把拒绝策略换成DiscardPolicy,极端情况下直接丢弃溢出的推送任务,完全不会阻塞定时任务;如果不允许丢消息就保留示例里的CallerRunsPolicy。

内容的提问来源于stack exchange,提问作者Adam Lee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 02:48:28