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 "任务已入队";
    }
}
相关推荐
程序猿乐锅4 小时前
从0-1一文详解RabbitMQ
java·分布式·后端·中间件·rabbitmq·ruby
考虑考虑7 小时前
JDK26中的List.ofLazy()
java·后端·java ee
小蒜学长7 小时前
基于SpringBoot的公寓报修管理系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·公寓报修管理系统·多角色协同
for_ever_love__8 小时前
MySQL 主从复制讲透:binlog 原理、搭建步骤与读写分离
java·数据库·mysql·binlog·读写分离·原理·主从复制
小π军10 小时前
操作系统面试题
java·linux·服务器
倔强的石头10610 小时前
应用账号最小权限实践:读写账号、报表账号、运维账号分层
java·运维·开发语言
Shaoshing11 小时前
ThreadLocal
java·jvm·threadlocal
写后端的胖头鱼11 小时前
一文详解CAS
java·jvm·spring·cas·乐观锁·自旋
鱼宵11 小时前
Spring AI 提示词模板:{变量} 参数化 + few-shot,一条提示词反复用
java·人工智能·spring·few-shot·提示词工程·springai