一:队列任务常用处理方案
在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 "任务已入队";
}
}