spring boot实现任务异步处理(队列)

一:队列任务常用处理方案

在spring boot常用队列任务方案有三种,分别是:内存阻塞队列、Redis 消息队列、RabbitMQ/RocketMQ 专业消息队列

三种方案的优缺点如下

内存阻塞队列:适合单机、并发不大,解耦异步任务,缺点是单机,重启丢失任务,不能多实例共享队列

redis消息队列:多实例共享、任务持久化,通过 LPUSH / BRPOP 阻塞消费,无需额外中间件,特点是简单、分布式;缺点是没有 ACK,任务失败容易丢失

RabbitMQ/RocketMQ专业消息队列:适合要求高可靠、ACK、死信、重试场景

二:内存阻塞队列

1:增加任务管理器

复制代码
package com.example.demo.queue;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;

@Slf4j
@Component
public class TaskQueue {
    //任务队列
    private final BlockingQueue<Runnable> taskQueue = new LinkedBlockingDeque<>(100);

    private ExecutorService consumerExecutor;

    //启动消费线程
    @PostConstruct
    public void startTask()
    {
        consumerExecutor = Executors.newSingleThreadExecutor();
        consumerExecutor.submit(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    //消费处理
                    Runnable task = taskQueue.take();
                    task.run();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.out.println("任务队列消费线程退出");
                } catch (Exception e) {
                    System.out.println("任务执行异常" + e.getMessage());
                }
            }
        });
    }

    // 添加任务入队
    public boolean addTask(Runnable task) {
        return taskQueue.offer(task);
    }

    // 任务销毁
    @PreDestroy
    public void stop() {
        if(consumerExecutor != null){
            consumerExecutor.shutdownNow();
        }
    }
}

2:使用示例

复制代码
package com.example.demo.controller;

import com.example.demo.queue.TaskQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/task")
public class TaskController {
    @Autowired
    private TaskQueue taskQueue;

    @GetMapping("/base")
    public String base()
    {
        int taskId = 1;
        boolean ok = taskQueue.addTask(() -> {
            System.out.println("执行队列任务,开始");
            try {
                Thread.sleep(2000);
                //业务代码
                System.out.println("队列任务执行" + taskId);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务执行完成");
        });
        return ok ? "任务入队成功" : "队列已满";
    }
}

三:redis消息队列

使用redis消息队列的前提需要先安装spring-boot-starter-data-redis依赖,redis的详细操作可参考spring boot 操作redis_redis 管理平台 springboot-CSDN博客

1:增加队列服务

复制代码
package com.example.demo.queue;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisTaskQueue {

    private static final String QUEUE_KEY = "task:queue";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //添加任务入队
    public void pushTask(String taskContent)
    {
        stringRedisTemplate.opsForList().leftPush(QUEUE_KEY, taskContent);
    }

    //定时轮询消费
    @Scheduled(fixedDelay = 1000)
    public void consumeTask()
    {
        //BRPOP 阻塞读取,超时5s
        var result = stringRedisTemplate.opsForList().rightPop(QUEUE_KEY,5, TimeUnit.SECONDS);
        if (result != null) {
            System.out.println("拿到任务");
            try {
                handleTask(result);
            } catch (Exception e) {
                System.out.println("任务处理失败");
            }
        }
    }

    private void handleTask(String task)
    {
        //业务处理
        System.out.println("执行Redis队列任务,开始: " + task);
        try {
            Thread.sleep(2000);
            System.out.println("Redis队列任务执行");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Redis任务执行完成");
    }
}

2:使用示例

复制代码
package com.example.demo.controller;

import com.example.demo.queue.RedisTaskQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/task")
public class TaskController {
    @Autowired
    private RedisTaskQueue redisTaskQueue;


    @GetMapping("/redis")
    public String redis()
    {
        redisTaskQueue.pushTask("redis队列任务");
        return "Redis任务入队成功";
    }

}

三:RabbitMQ/RocketMQ专业消息队列

使用RabbitMQ/RocketMQ专业消息队列的前提需要先安装spring-boot-starter-amqp依赖

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

在application.yml文件中增加RabbitMQ配置

复制代码
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: 123456

1:增加生产者任务(发送任务消息)

复制代码
package com.example.demo.queue;

import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class QmqpTaskProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 添加任务入队
     * @param taskContent 任务内容,可以传字符串/JSON对象
     */
    public void sendTask(String taskContent)
    {
        rabbitTemplate.convertAndSend(
                "taskExchange",//交换机名称
                "task.routing",//路由键,用来交换机绑定队列的匹配规则
                taskContent //消息内容
        );

        System.out.println("任务投递成功");
    }

}

2:增加消费者任务(监听队列,执行任务,手动 ACK)

复制代码
package com.example.demo.queue;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import com.rabbitmq.client.Channel;

@Service
public class QmqpTaskConsumer {

    /**
     * 监听任务队列
     * @param taskMsg 消息内容
     * @param channel 通道
     * @param deliveryTag 消息标记,用于ack/nack
     */
    @RabbitListener(queues = "task_queue")
    public void consumeTask(String taskMsg, Channel channel, long deliveryTag) {
        try {
            System.out.println("收到任务,开始执行:" + taskMsg);
            // ========== 业务任务逻辑 ==========
            // 比如:生成文件、发送短信、调用第三方接口
            Thread.sleep(2000); // 模拟耗时任务
            // =================================
            // 任务执行成功,手动确认消息
            channel.basicAck(deliveryTag, false);
            System.out.println("任务执行完成:" + taskMsg);
        } catch (Exception e) {
            e.printStackTrace();
            try {
                // 任务异常:nack,requeue=true 消息重新放回队列,等待再次消费
                channel.basicNack(deliveryTag, false, true);
                // 注意:谨慎使用requeue,防止死循环(可以设置重试次数,失败转入死信队列)
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

}

3:使用示例

复制代码
package com.example.demo.controller;

import com.example.demo.queue.QmqpTaskProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/task")
public class TaskController {
    @Autowired
    private QmqpTaskProducer qmqpTaskProducer;


    @GetMapping("/qmqp")
    public String qmqp()
    {
        qmqpTaskProducer.sendTask("qmqp队列任务");

        return "任务已入队";
    }
}
相关推荐
MZA6661 小时前
实战:基于 XXL-JOB + 策略模式 + Nacos 配置化的 CSV 导出任务系统设计
java·spring boot·mybatis
邪修king1 小时前
Re:Linux 系统篇(二十九):动静态库Chapter2:动态库深度辨析 —— 核心本质、制作流程、双阶段查找模型与排错指南
android·java·linux·开发语言
泡海椒2 小时前
告别 iText 繁杂配置:jquick-pdf 极简 PDF 生成实战(零基础上手)
java·开发语言·pdf
Bs_MoneyMagnet5 小时前
基于springboot+vue的滑雪场票务与装备租赁系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计
野生技术架构师10 小时前
2026 Java 面试全套总结,八股 + 场景 + AI 相关面试考点
java·人工智能·面试
香吧香11 小时前
java服务异常日志只打印异常类型,没有堆栈定位分析
java·jvm·异常
李昊哲小课11 小时前
Spring Boot SSE 实时推送教程
spring boot·websocket·flux·sseemitter
qq_25183645711 小时前
AI 疾病自查功能SpringbootAI项目实战 · 技术实现文档
java·ai·ai编程
逆境不可逃12 小时前
Pi Agent 学习笔记:对话太长以后,如何压缩上下文
java