SpringBoot 集成RabbitMQ 各种模式超详细版

在前文中,我们通过RabbitMQ原生态的API方式了解到了RabbitMQ 是怎样实现消息的发送、接收、手动签收配置、各种模式的具体实现方式等。接下来我们将以与SpringBoot注解的方式来理解RabbitMQ。

 

Ps:注解的方式简化了实现代码,但是不利于理解。若朋友们觉得不好理解注解方式的话不妨先看一下前一篇文章

RabbitMQ 消息中间件之案例分析Hello、Work 、Publish/Subscribe、Routing、Topics

愿为最亮星,公众号:华星详谈RabbitMQ 消息中间件之案例分析Hello、Work 、Publish/Subscribe、Routing、Topics

引入Maven 依赖

        <!-- RabbitMQ -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

1、简单的hello 模式

       

1.1、hello 模式 application.properties 文件配置

server.port=8080
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest

1.2、hello 模式 生产者代码

packagecom.huaxing.rabbitmq._06springboot;
importorg.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.ResponseBody;
importorg.springframework.web.bind.annotation.RestController;
 
/**
 * @Description SpringBoot 集成RabbitMQ --生产者
 * @author: 姚广星
 * @time: 2020/11/26 21:25
 */
@RestController
public classSendToSpringBootController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
 
    @RequestMapping("/queue")
    @ResponseBody
    public String sendMsg(String msg){
       rabbitTemplate.convertAndSend("","boot_queue",msg);
        return "发送成功";
    }
}

1.3、消费者代码

packagecom.huaxing.rabbitmq._06springboot;
 
importcom.rabbitmq.client.Channel;
importlombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Queue;
importorg.springframework.amqp.rabbit.annotation.RabbitListener;
importorg.springframework.amqp.support.AmqpHeaders;
importorg.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
 
/**
 * @Description
 * @author: 姚广星
 * @time: 2020/11/26 21:52
 */
@Slf4j
@Component
public classRecvToSpringBoot {
    @RabbitListener(
            queuesToDeclare =@Queue("boot_queue")//使用默认的交换机 @Queue:队列消息配置
)
publicvoid receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag,Channel channel) {
        log.info("收到的消息-> deliveryTag:{}",deliveryTag);
        log.info("收到的消息-> channel:{}",channel.toString());
        log.info("收到的消息-> msg:{}",msg);
    }
}

2、集成SpringBoot-手动签收实现方式

2.1 application.properties 文件配置

server.port=8080
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual

2.2 生产者代码

  /**
     * 手动签收实现方式发送端
     *
     * @param msg
     * @return
     */
    @RequestMapping("/sendBootQueueManuallySigned")
    @ResponseBody
    public StringsendBootQueueManuallySigned(String msg) {
       rabbitTemplate.convertAndSend("","boot_queue_ManuallySigned", msg);
        return "发送成功";
    }

2.3 消费者代码

/**
     * 手动签收接受者
     *
     * @param msg         消息
     * @param deliveryTag 消息唯一标识
     * @param channel     通道
     * @throws IOException
     */
 @RabbitListener(
            queuesToDeclare =@Queue("boot_queue_ManuallySigned")//使用默认的交换机 @Queue:队列消息配置
    )
    public voidreceiveMsgToManuallySigned(String msg, @Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, Channel channel) throws IOException {
        try {
            //模拟出现异常
            //int a = 1 / 0;
            log.info("收到的消息-> msg:{}", msg);
            log.info("收到的消息标识-> deliveryTag:{}", deliveryTag);
            log.info("收到的消息-> channel:{}", channel.toString());
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
    }

 

    一般我们在实际的使用中都会把签收方式配置为手动签收的方式。这样更方便于在消息出现异常时进行特殊的处理,如(报警机制、日志记录等)。

 

3、集成SpringBoot-work模式实现方式

   

     Work模式与最基础的hello模式基本一样,他们区别在于work模式支持多个消费者同时接收消息,通过spring.rabbitmq.listener.simple.prefetch配置消息的预读数量控制消费者一次性从队列中读取多少条消息,做到能者多劳的配置(因为在实际的生产环境中每个服务器的配置不可能完全相同,带来的处理消息的时间也不一样)。

 

3.1 application.properties 文件配置

在原本的基础上添加消息预读数量配置。

完全的配置文件如下:

server.port=8080
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息预读数量 1表示每次从队列中读取一条消息
spring.rabbitmq.listener.simple.prefetch=1

3.2 生产者代码

/**
     * work 模式发送端
     *
     * @param msg
     * @return
     */
   @RequestMapping("/sendBootQueueToWork")
    @ResponseBody
    public String sendBootQueueToWork(Stringmsg) {
        for (int i = 0; i < 20; i++) {
           rabbitTemplate.convertAndSend("", "boot_queue_work",i + ":" + msg);
        }
        return "发送成功";
    }

3.3 消费者代码

/**
     * work 模式消费者1
     *
     * @param msg         消息
     * @param deliveryTag 消息唯一标识
     * @param channel     通道
     * @throws IOException
     */
    @RabbitListener(
            queuesToDeclare =@Queue("boot_queue_work")//使用默认的交换机 @Queue:队列消息配置
)
publicvoid receiveMsgToToWork(String msg, @Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, Channel channel) throws IOException {
        try {
            //模拟服务器性能
            TimeUnit.SECONDS.sleep(1);
            log.info("01 消费者收到的消息 -> msg:{}", msg);
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
    }
    /**
     * work 模式消费者2
     *
     * @param msg         消息
     * @param deliveryTag 消息唯一标识
     * @param channel     通道
     * @throws IOException
     */
    @RabbitListener(
            queuesToDeclare =@Queue("boot_queue_work")//使用默认的交换机 @Queue:队列消息配置
    )
    public void receiveMsgToToWork2(String msg,@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throwsIOException {
        try {
            //模拟服务器性能
            TimeUnit.SECONDS.sleep(2);
            log.info("02 消费者收到的消息 -> msg:{}", msg);
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
    }

3.4 运行结果

从运行结果可以看出,01消费者处理消息的速度快一些,处理的数据也就多一些。

4、集成SpringBoot-发布订阅(Publish/Subscribe)模式实现方式

我们将消息传达给多个消费者。这种模式称为“发布/订阅”。

4.1 application.properties 文件配置

#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息预读数量 1表示每次从队列中读取一条消息
spring.rabbitmq.listener.simple.prefetch=1

4.2 生产者代码

/**
     * 发布/订阅模式发送端
     *
     * @param msg
     * @return
     */
    @RequestMapping("/sendBootQueueToPubSub")
    @ResponseBody
    public String sendBootQueueToPubSub(Stringmsg) {
       rabbitTemplate.convertAndSend("boot_queue_pub_sub","", msg);
        return "发送成功";
    }

4.3 消费者代码

/**
     * 发布/订阅模式接收者01
     *
     * @param msg
     * @param deliveryTag
     * @param channel
     * @throws IOException
     */
    @RabbitListener(//RabbitMQ 监听器
            bindings = @QueueBinding(//创建队列和交换机的绑定关系
                    value = @Queue(),//创建个匿名队列
                    exchange = @Exchange(name ="boot_queue_pub_sub", type = "fanout")//定义交换机,指定交换机名称和类型
            )
    )
    public void receiveMsgToPubSub1(String msg,
                                    @Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, //@Header 获取消息体header中的信息
                                    Channelchannel) throws IOException {
        try {
            log.info("01 消费者收到的消息 -> msg:{}", msg);
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
    }
 
    /**
     * 发布/订阅模式接收者02
     *
     * @param msg
     * @param deliveryTag
     * @param channel
     * @throws IOException
     */
    @RabbitListener(//RabbitMQ 监听器
            bindings = @QueueBinding(//创建队列和交换机的绑定关系
                    value = @Queue(),//创建个匿名队列
                    exchange = @Exchange(name ="boot_queue_pub_sub", type = "fanout")//定义交换机,指定交换机名称和类型
            )
    )
    public void receiveMsgToPubSub2(String msg,
                                    @Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, //@Header 获取消息体header中的信息
                                    Channelchannel) throws IOException {
        try {
            log.info("02 消费者收到的消息 -> msg:{}", msg);
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
    }

5.4 运行结果

6 集成SpringBoot-Routing模式

在同一个消息,根据定义的消息路由key,指定消息只发送给固定的路由key对应的消息队列。达到分别的控制。

6.1 application.properties 文件配置

#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息预读数量 1表示每次从队列中读取一条消息
spring.rabbitmq.listener.simple.prefetch=1


6.2 生产者代码

/**
     * routing 模式生产者
     *
     * @param msg
     * @param routingKey    对应的routingKey
     * @return
     */
   @RequestMapping("/sendBootQueueToRouting")
    @ResponseBody
    public String sendBootQueueToRouting(Stringmsg,String routingKey) {
       rabbitTemplate.convertAndSend("boot_queue_routing",routingKey, msg);
        return "发送成功";
    }

6.3 消费者代码

/**
     * routing 模式消费者01
     *
     * @param msg
     * @param deliveryTag
     * @param channel
     * @throws IOException
     */
    @RabbitListener(//RabbitMQ 监听器
            bindings = @QueueBinding(//创建队列和交换机的绑定关系
                    value = @Queue(),//创建个匿名队列
                    exchange = @Exchange(name ="boot_queue_routing", type = "direct"),//定义交换机,指定交换机名称和类型
                    key ={"log.error", "log.info", "log.warning"}//定义routingKey
            )
    )
    public void receiveMsgToRouting01(Stringmsg,
                                     @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, //@Header 获取消息体header中的信息
                                      Channelchannel) throws IOException {
        try {
            log.info("01 消费者收到的消息 -> msg:{}", msg);
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
    }
 
    /**
     * routing 模式消费者02
     *
     * @param msg
     * @param deliveryTag
     * @param channel
     * @throws IOException
     */
    @RabbitListener(//RabbitMQ 监听器
            bindings = @QueueBinding(//创建队列和交换机的绑定关系
                    value = @Queue(),//创建个匿名队列
                    exchange = @Exchange(name ="boot_queue_routing", type = "direct"),//定义交换机,指定交换机名称和类型
                    key ={"log.error"}//定义routingKey
            )
    )
    public void receiveMsgToRouting02(Stringmsg,
                                     @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, //@Header 获取消息体header中的信息
                                      Channelchannel) throws IOException {
        try {
            log.info("02 消费者收到的消息 -> msg:{}", msg);
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
    }

当routingkey为log.error时:

从结果中我们可以看出两个消费者都收到了消息。

当routingKey为log.info时:

从结果中我们可以看出只有消费者01收到了消息,而消费者02并没有收到消息。

我们修改消费者02的代码,测试routing模式是否支持通配符配置:

当我们传入的routingKey为log.error 时,若是支持通配符配置则应该两个消费者都能够收到信息,若是不支持通配符则只有消费者01支持通配符。重新运行后,结果为

发现02消费者并没有收到消息,则可以得出结论:RoutingKey 模式不支持通配符设置。若我们想使用通配符则必须使用topics模式。

 

7 集成SpringBoot-Topic模式实现方式

Topics模式与Routing模式基本一样,他们两个的区别在于Topics模式支持通配,而Routing模式不支持通配。

7.1 application.properties 文件配置

#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息预读数量 1表示每次从队列中读取一条消息
spring.rabbitmq.listener.simple.prefetch=1

  

7.2 生产者代码

/**
     * topics 模式生产者
     *
     * @param msg
     * @param routingKey    对应的routingKey
     * @return
     */
   @RequestMapping("/sendBootQueueToTopics")
    @ResponseBody
    public String sendBootQueueToTopics(Stringmsg,String routingKey) {
        rabbitTemplate.convertAndSend("boot_queue_routing",routingKey, msg);
        return "发送成功";
    }

7.3 消费者代码

/**
     * Topics 模式消费者01
     *
     * @param msg
     * @param deliveryTag
     * @param channel
     * @throws IOException
     */
    @RabbitListener(//RabbitMQ 监听器
            bindings = @QueueBinding(//创建队列和交换机的绑定关系
                    value = @Queue(),//创建个匿名队列
                    exchange = @Exchange(name ="boot_queue_routing", type = "direct"),//定义交换机,指定交换机名称和类型
                    key = {"log.*"}//定义routingKey  使用通配符接收routingKey
            )
    )
    public void receiveMsgToTopics01(Stringmsg,
                                     @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, //@Header 获取消息体header中的信息
                                      Channel channel) throws IOException {
        try {
            log.info("01 消费者收到的消息 -> msg:{}", msg);
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
    }
 
    /**
     * Topics 模式消费者02
     *
     * @param msg
     * @param deliveryTag
     * @param channel
     * @throws IOException
     */
    @RabbitListener(//RabbitMQ 监听器
            bindings = @QueueBinding(//创建队列和交换机的绑定关系
                    value = @Queue(),//创建个匿名队列
                    exchange = @Exchange(name ="boot_queue_routing", type = "direct"),//定义交换机,指定交换机名称和类型
                    key ={"log.error"}//定义routingKey
            )
    )
    public void receiveMsgToTopics02(Stringmsg,
                                      @Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, //@Header 获取消息体header中的信息
                                      Channelchannel) throws IOException {
        try {
            log.info("02 消费者收到的消息 -> msg:{}", msg);
            //手动签收  channel.basicAck(消息唯一标识,是否批量签收);
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            e.printStackTrace();
            //channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
            //消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
            channel.basicNack(deliveryTag,false, false);
        }
}

由以下运行结果可以得出Topics模式是支持通配符设置的。


本文涉及到的文献

官方文档:https://www.rabbitmq.com/

Git 代码地址:https://github.com/17666555910/HuaXing-SpringCloudDemo.git

CSDN 博客地址:https://blog.csdn.net/a767815662/category_10598332.html

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

华星详谈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值