springboot应用rabbitmq实现消息队列

示例达到的目的


1.通过消息队列执行通知、回调、延迟执行的任务等

2.实现延迟消息,可设定消息在多少时间后抵达

3.实现持久化,确保当项目重启、rabbitmq重启等异常情况发生时,重启完成后消息能继续被执行

4.可异步处理消息,根据服务器配置调整,避免阻塞主线程

5.完整的重试机制,可配置重试次数、阶段性等待时长

安装并启用rabbitmq

1.从docker拉取最新的rabbitmq镜像

docker pull rabbitmq

2.创建并运行rabbitmq容器(参数不做解释,不懂问chatgpt)

docker run -d -p 15673:15672 -p 5674:5672 \
        --restart=always \
        -e RABBITMQ_DEFAULT_VHOST=ant_applet  \
        -e RABBITMQ_DEFAULT_USER=admin \
        -e RABBITMQ_DEFAULT_PASS=dainiquheiye \
        --hostname antApplet \
        --name rabbitmq\
        rabbitmq:latest

3.启动web客户端,通过ip:15673访问

docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management

访问web客户端可能会遇到Stats in management UI are disabled on this node弹窗提示,按以下步骤可关闭弹窗

# 进入到启动的rabbitmq的容器中
docker exec -it rabbitmq /bin/bash

# 切换到rabbitmq的配置文件目录
cd /etc/rabbitmq/conf.d/

# 修改配置文件
echo management_agent.disable_metrics_collector = false > management_agent.disable_metrics_collector.conf

# 查看配置文件,看看是否修改成功
cat management_agent.disable_metrics_collector.conf

# 退出容器
exit

# 重启容器
docker restart rabbitmq

4.开启延迟插件

# 下载插件,选择与rabbitmq版本相近的版本
https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases

# 上传至服务器
cd /home/rabbitmq
docker cp rabbitmq_delayed_message_exchange-3.13.0.ez rabbitmq:/plugins

# 进入RabbitMQ容器:
docker exec -it rabbitmq bash
(此时执行rabbitmq-plugins list可以看到其他插件的版本,可参照版本号进行下载)

# 启动插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

# 退出容器
exit

5.在maven或gradle中配置依赖

//gradle
implementation 'org.springframework.boot:spring-boot-starter-amqp'

6.在application.yml中配置rabbitmq

spring:  
  rabbitmq:
    host: 127.0.0.1
    port: 5674
    username: admin
    password: dainiquheiye
    virtual-host: ant_applet # 虚拟主机配置
    listener:
      simple:
        acknowledge-mode: manual # 手动确认

异步配置

package com.ant.rabbitmq;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 异步配置
 */
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    /**
     * 引入日志
     */
    private final static Logger logger = LoggerFactory.getLogger(AsyncConfig.class);

    @Bean(name = "async")
    public ThreadPoolTaskExecutor executor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(5);
        //线程池维护线程的最大数量,只有在缓存队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(10);
        //缓存队列
        taskExecutor.setQueueCapacity(20);
        //设置线程的空闲时间,当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(300);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("ant-async-");
        /*
         * AbortPolicy 丢弃任务并抛出异常
         * DiscardPolicy 丢弃任务,不抛出异常
         * DiscardOldestPolicy 丢弃队列最前面的任务,然后重新尝试该任务
         * CallerRunsPolicy 重试当前任务,直到成功为止
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }

    /**
     * 指定默认线程池
     */
    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }

    /**
     * 异常捕捉
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (e, method, params) -> logger.error("线程池执行任务异常:" + e.getMessage() + ",执行方法:" + method.getName());
    }
}

配置消息队列

package com.ant.rabbitmq;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置消息队列
 */
@Configuration
public class RabbitMQConfig {
    /**
     * 定义队列名称
     */
    public static final String DELAY_QUEUE = "delay.queue";

    /**
     * 定义RoutingKey
     */
    public static final String ROUTING_KEY = "delay";

    /**
     * 定义交换器名称
     */
    public static final String DELAY_EXCHANGE = "delay.exchange";

    /**
     * 重试机制次数上限
     */
//    public static final int[] maxRetry = {10};
    public static final int[] maxRetry = {10, 50, 100};

    /**
     * 重试等待时长(毫秒ms)
     */
//    public static final int[] retryDelay = {3000};
    public static final int[] retryDelay = {3000, 30000, 300000};

    /**
     * 配置队列
     */
    @Bean
    public Queue queue() {
        // 创建一个持久化的队列
        return new Queue(DELAY_QUEUE, true);
    }

    /**
     * 配置交换器
     */
    @Bean
    public DirectExchange exchange() {
        return ExchangeBuilder.directExchange(DELAY_EXCHANGE).delayed().durable(true).build();
    }

    /**
     * 配置队列与交换器的绑定
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(exchange()).with(ROUTING_KEY);
    }
}

创建生产者

package com.ant.rabbitmq;

import com.alibaba.fastjson.JSON;
import com.ant.common.util.StringUtil;
import com.ant.context.cache.CacheContext;
import com.ant.context.data.DataContext;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.bind.annotation.*;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * RabbitMQ 消息队列
 */
@RestController
@RequestMapping("/rabbitmq")
@EnableAsync
public class RabbitMQProducer {
    /**
     * 引入日志
     */
    private final static Logger logger = LoggerFactory.getLogger(RabbitMQProducer.class);

    /**
     * 引入缓存工具类
     */
    @Resource
    private CacheContext cacheContext;

    private static RabbitTemplate rabbitTemplate;

    public RabbitMQProducer(RabbitTemplate rabbitTemplate) {
        RabbitMQProducer.rabbitTemplate = rabbitTemplate;
    }

    /**
     * 主动发送消息
     */
    @PostMapping("/send/{key}")
    public void sendMessage(@PathVariable String key, @RequestBody Map<String, Object> params) {
        send(key, "", params, 0, cacheContext);
    }

    /**
     * 发送消息
     */
    public static void send(String key, String id, Map<String, Object> params, int delay, CacheContext cacheContext) {
        int retryCount = 0;
        id = StringUtil.isBlank(id) ? DataContext.getSequenceId("RT") : id;

        //判断参数
        if (cacheContext == null) {
            logger.error("[" + id + "]:执行任务 " + key + " 时发生逻辑错误(cacheContext缓存对象不能为null)");
            return;
        }

        try {
            Map<String, Object> message = new HashMap<>();
            message.put("key", key);
            message.put("id", id);
            message.put("params", params);

            //从缓存中获取该id的重试次数
            Object obj = cacheContext.getTempObj(id);
            if (obj != null) {
                retryCount = Integer.parseInt(obj.toString());
            }

            //执行消息,获取返回值
            if (retryCount == 0) {
                logger.info("[" + id + "]:即将执行任务 " + key + ",携带参数:" + JSON.toJSONString(params));
            }

            //将消息转换为字节数组
            byte[] messageBytes = JSON.toJSONString(message).getBytes(StandardCharsets.UTF_8);

            //执行发送
            rabbitTemplate.convertAndSend(RabbitMQConfig.DELAY_EXCHANGE, RabbitMQConfig.ROUTING_KEY, messageBytes, msg -> {
                msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                msg.getMessageProperties().setDelay(delay);
                return msg;
            });
        } catch (Exception e) {
            logger.error("[" + id + "]:任务 " + key + " 在进行第" + (retryCount + 1) + "次尝试时发生异常(" + e.getMessage() + ")");
            retryCount++;
            cacheContext.setTempHour(id, retryCount, 24);
            RabbitMQReceiver rabbitMQReceiver = new RabbitMQReceiver();
            rabbitMQReceiver.retry(key, id, params, cacheContext);
        }
    }
}

创建消费者

package com.ant.rabbitmq;

import com.alibaba.fastjson.JSON;
import com.ant.common.bean.ApiStatus;
import com.ant.common.util.StringUtil;
import com.ant.context.cache.CacheContext;
import com.ant.task.Task;
import com.rabbitmq.client.Channel;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;

/**
 * 接收消息并处理
 */
@Component
@EnableAsync
public class RabbitMQReceiver {

    /**
     * 引入缓存工具类
     */
    @Resource
    private CacheContext cacheContext;

    /**
     * 引入日志
     */
    private final static Logger logger = LoggerFactory.getLogger(RabbitMQReceiver.class);

    /**
     * 接收并处理消息
     */
    @Async
    @RabbitListener(queues = RabbitMQConfig.DELAY_QUEUE)
    public void receiveMessage(byte[] messageByte, Message message, Channel channel) throws IOException {
        String key = "";
        String id = "";
        int retryCount = 0;
        try {
            //将消息转换成map
            String messageStr = new String(messageByte, StandardCharsets.UTF_8);
            Map<String, Object> data = JSON.parseObject(messageStr);

            //获取消息内容
            key = StringUtil.getString(data, "key");
            id = StringUtil.getString(data, "id");
            Map<String, Object> params = JSON.parseObject(StringUtil.getString(data, "params"));

            //从缓存中获取该id的重试次数
            Object obj = cacheContext.getTempObj(id);
            if (obj != null) {
                retryCount = Integer.parseInt(obj.toString());
            }

            //执行任务
            Map<String, Object> result = Task.doTask(key, id, params);
            int flag = StringUtil.getInt(result, "flag");
            String msg = StringUtil.getString(result, "message");

            //执行成功,清空重试计数
            if (flag == ApiStatus.SUCCESS) {
                logger.info("[" + id + "]:执行任务 " + key + " 成功");
                cacheContext.deleteTemp(id);

                //手动答应消费完成,从队列中删除该消息
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
            //需要重试
            if (flag == ApiStatus.RETRY_TASK) {
                retryCount++;
                //判断重试次数上限
                if (retryCount < Arrays.stream(RabbitMQConfig.maxRetry).max().getAsInt()) {
                    logger.error("[" + id + "]:第" + retryCount + "次执行任务 " + key + " 失败");
                    //执行重试
                    cacheContext.setTempHour(id, retryCount, 24);
                    retry(key, id, params, cacheContext);
                    //手动答应消费完成,从队列中删除该消息(不重回队列)
                    channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
                } else {
                    //达到最大重试次数,记录日志或执行其他操作
                    logger.error("[" + id + "]:第" + retryCount + "次执行任务 " + key + " 失败,已达到最大重试次数");
                    cacheContext.deleteTemp(id);
                    //手动答应消费完成,从队列中删除该消息(不重回队列)
                    channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
                }

                return;
            }
            //执行失败,通常是代码逻辑错误或参数不齐全,没有重试的意义,直接返回异常
            logger.error("[" + id + "]:执行任务 " + key + " 时发生逻辑错误(" + msg + ")");
            cacheContext.deleteTemp(id);
            //手动答应消费完成,从队列中删除该消息(不重回队列)
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            logger.error("[" + id + "]:执行任务 " + key + " 时发生异常(" + e.getMessage() + ")");
            cacheContext.deleteTemp(id);
            //手动答应消费完成,从队列中删除该消息(不重回队列)
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
        }
    }

    /**
     * 重试
     */
    @Async
    public void retry(String key, String id, Map<String, Object> params, CacheContext cacheContext) {
        int retryCount = 1;
        //从缓存中获取该id的重试次数
        Object obj = cacheContext.getTempObj(id);
        if (obj != null) {
            retryCount = Integer.parseInt(obj.toString());
        }

        //判断重试次数上限
        if (retryCount >= Arrays.stream(RabbitMQConfig.maxRetry).max().getAsInt()) {
            //达到最大重试次数,记录日志或执行其他操作
            logger.error("[" + id + "]:第" + (retryCount + 1) + "次执行任务 " + key + " 失败,已达到最大重试次数");
            cacheContext.deleteTemp(id);
            return;
        }

        //根据重试次数设置等待时间
        int delay = getRetryDelay(retryCount);

        logger.warn("[" + id + "]:任务 " + key + " 将在" + delay + "ms后进行第" + (retryCount + 1) + "次尝试");

        //发送消息到指定的交换器和队列
        RabbitMQProducer.send(key, id, params, delay, cacheContext);
    }

    private static int getRetryDelay(int currentRetryCount) {
        for (int i = 0; i < RabbitMQConfig.maxRetry.length; i++) {
            if (currentRetryCount <= RabbitMQConfig.maxRetry[i]) {
                return RabbitMQConfig.retryDelay[i];
            }
        }
        //如果超出最高等级,则使用最高等级的等待时长
        return RabbitMQConfig.retryDelay[RabbitMQConfig.retryDelay.length - 1];
    }
}

创建一个任务管理器

package com.ant.task;

import com.ant.common.bean.ApiStatus;

import java.util.HashMap;
import java.util.Map;

/**
 * 配置消息键key对应的消息实体
 */
public class Task {

    /**
     * 在此处执行消息键key所对应的消息体
     */
    public static Map<String, Object> doTask(String key, String id, Map<String, Object> params) {
        //定义返回值
        Map<String, Object> result = new HashMap<>();

        switch (key) {
            /**
             * 重试机制测试
             */
            case "retry-test" -> result = TestTask.retryTest(params);
            default -> {
                result.put("flag", ApiStatus.ERROR);
                result.put("message", "[" + id + "]根据消息键[" + key + "]没有找到对应可执行的消息体");
                return result;
            }
        }

        return result;
    }
}

编写需要执行的任务

package com.ant.task;

import com.ant.common.bean.ApiStatus;
import com.ant.common.util.StringUtil;

import java.util.HashMap;
import java.util.Map;

/**
 * 测试任务
 */
public class TestTask {

    /**
     * 重试机制测试
     */
    public static Map<String, Object> retryTest(Map<String, Object> params) {

        int type = StringUtil.getInt(params, "type");

        Map<String, Object> result = new HashMap<>();

        switch (type) {
            case 0 -> {
                result.put("flag", ApiStatus.SUCCESS);
                result.put("message", "测试结果:成功");
            }
            case 1 -> {
                result.put("flag", ApiStatus.RETRY_TASK);
                result.put("message", "测试结果:需要重试");
            }
            case 2 -> {
                result.put("flag", ApiStatus.ERROR);
                result.put("message", "测试结果:失败");
            }
        }

        return result;
    }
}

现在,我们来发送一条消息

- 项目外

//POST
http://127.0.0.1:9000/antApi/rabbitmq/send/retry-test

其中 retry-test 代表key,即需要执行的消息键,对应执行任务管理器中配置的任务

通过body传入该任务需要的所有参数

{
    "type": 0 //0模拟执行成功 1模拟需要重试 2模拟直接失败
}

- 项目内

/**
 * 发送消息到指定的交换器和队列
 * key 消息键
 * id 消息的唯一id,首次执行传空字符串,会自动生成
 * params 消息键对应的任务需要的所有参数
 * delay 消息延迟达到的时间
 * cacheContext 缓存对象,由于此处我使用的缓存,在对内函数无法获取缓存对象,所以需要进行传递(可自行更改为其他缓存)
 */

RabbitMQProducer.send(key, id, params, delay, cacheContext);

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用 Spring Boot 实现 RabbitMQ 延时队列,可以使用 RabbitMQ 的插件 RabbitMQ Delayed Message Exchange(rabbitmq_delayed_message_exchange)。 以下是实现步骤: 1. 首先,需要在 Maven 或 Gradle 中添加 RabbitMQ 的 Java 客户端库和 RabbitMQ Delayed Message Exchange 插件的依赖。例如,在 Maven 中添加以下依赖: ``` <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.11.0</version> </dependency> <dependency> <groupId>net._95point2.fmq</groupId> <artifactId>rabbitmq-delayed-message-exchange</artifactId> <version>2.2.0</version> </dependency> ``` 2. 在 Spring Boot 应用程序中配置 RabbitMQ 连接属性。可以使用 application.yml 或 application.properties 文件来配置。 ``` spring: rabbitmq: host: localhost port: 5672 username: guest password: guest ``` 3. 创建一个配置类来配置 RabbitMQ Exchange。在这个类中,将使用 `CustomExchange` 类来创建一个延时 Exchange。需要为这个 Exchange 指定一个名称和类型,并且设置其参数 `x-delayed-type` 为要延迟的消息的 Exchange 类型,例如 `direct` 或 `topic`。 ``` @Configuration public class RabbitMQConfig { @Bean public CustomExchange delayedExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); return new CustomExchange("delayed_exchange", "x-delayed-message", true, false, args); } } ``` 4. 创建一个生产者来发送延迟消息。在这个生产者类中,注入 RabbitTemplate 并使用其 convertAndSend 方法发送消息。需要为消息设置一个 Routing Key,它将用于将消息路由到正确的队列。还需要为消息设置一个延迟时间,将消息发送到刚刚创建的 Exchange 中。 ``` @Component public class Producer { @Autowired private RabbitTemplate rabbitTemplate; public void sendDelayedMessage(String message, int delay) { rabbitTemplate.convertAndSend("delayed_exchange", "delayed_queue", message, message1 -> { message1.getMessageProperties().setDelay(delay); return message1; }); } } ``` 5. 创建一个消费者来处理延迟消息。在这个消费者类中,注入 RabbitTemplate 并使用其 receiveAndConvert 方法来接收消息。需要为消费者设置一个队列,并将这个队列绑定到刚刚创建的 Exchange 上。 ``` @Component public class Consumer { @RabbitListener(queues = "delayed_queue") public void receiveMessage(String message) { System.out.println("Received message: " + message); } @Bean public Queue delayedQueue() { return QueueBuilder.durable("delayed_queue") .withArgument("x-dead-letter-exchange", "") .withArgument("x-dead-letter-routing-key", "real_queue") .build(); } @Bean public Binding binding() { return BindingBuilder.bind(delayedQueue()).to(delayedExchange()).with("delayed_queue").
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值