SpringBoot整合RabbitMQ

0.RabbitMq安装

不多介绍rabbitmq的安装过程,RabbitMq基于erlang开发,所以安装Rabbitmq之前需要先安装对应版本的erlang。

rabbitmq下载地址:https://www.rabbitmq.com/install-windows.html
erlang下载地址:https://www.erlang-solutions.com/resources/download.html

安装完,打开localhost:15672,使用guest,guest可登录rabbitmq,可以进行rabbitmq的操作。
在这里插入图片描述

1.三种交换机模式

       在rabbitmq中有以下三种交换机,主要使用交换机将消息分发给各个队列,然后消费者从各个队列中获取消息,所以主要介绍以下三种发送、接受消息的模式。

1.direct直连型交换机

通过设置routingKey来推送给对应的队列,例如下图中,Q1具有orange的key,Q2具有black和green的key,发送的消息如果带有的是orange,则只发送给Q1,如果带有black或者green,则发送给Q2
在这里插入图片描述
2.fanout扇形交换机

分发给这个交换机所有绑定的队列,使用这个类型的交换机的时候,routingKey是无效的
在这里插入图片描述
3.topic主题模式交换机

同样根据routingKey来分发给对应的队列,跟direct不同的是,主题模式的交换机可以使用通配符#,*
#代表多个单词,可以是0个可以是多个
*代表一个单词,只能是一个
如下图所示Q1队列绑定*.oragne.*,Q2绑定*.*.rabbit和lazy.#
如果发送的消息是one.orange.two,那么会分发给Q1
如果发送的消息是one.two.rabbit,那么会分发给Q2
如果是one.orange.rabbit,那么会分发给Q1和Q2

如果是lazy.one或是lazy.one.two那么会分发给Q2
如果是lazy.orange.one,则会分发给Q1和Q2
在这里插入图片描述

2.在springboot中使用rabbitmq

2.0 新建springboot项目

直接使用springboot项目生成器生成,勾选上web和rabbitmq
在这里插入图片描述

2.1 配置springboot的配置文件
spring:
  rabbitmq:
    host: localhost
    port: 5672
    #登录账户和密码,自己设置
    username: user
    password: 123456
    #虚拟地址,没设置不用写
    virtual-host: /vhost_test
2.2 测试Direct模式

创建一个direct模式的配置类,里面配置exchange交换机,queue队列,和他们之前的绑定关系,设置routingKey为testDirectRouting

@Configuration
public class DirectRabbitMqConfig {

    /** 注册队列 */
    @Bean
    public Queue testDirectQueue(){
        /*
         * Construct a new queue, given a name, durability flag, and auto-delete flag, and arguments.
         * @param name 队列名
         * @param durable 是否可持久化
         * @param exclusive 是否排外的,有两个作用,一:当连接关闭时connection.close()该队列是否会自动删除;
         *                  二:该队列是否是私有的,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,
         *                  如果是排外的,会对当前队列加锁,其他通道channel是不能访问的
         * @param autoDelete 是否自动删除,当最后一个消费者断开连接后是否自动删除
         * @param arguments 额外参数
         * 默认都是false,可以设置一下持久化为true
         */
        return new Queue("test_Direct_Queue",true);
    }

    /** 注册direct类型交换机 */
    @Bean
    public Exchange testDirectExchange(){
        //属性:1.name 2.durable 3.autoDelete
        return new DirectExchange("test_Direct_Exchange",true,false);
    }

    /** 将队列绑定到交换机上,并设置routingKey */
    @Bean
    public Binding bindingDirect(){
        return BindingBuilder.bind(testDirectQueue()).to(testDirectExchange()).with("testDirectRouting").noargs();
    }
}

然后新建一个控制器,使用springboot配置好的rabbitTemplate去发送消息,也可以使用rabbitTemplate去接收消息。
发送消息的时候需要带上配置绑定关系所配置的routingKey:testDirectRouting

@RestController
public class SendMessageController {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @RequestMapping("/direct/send")
    public String directSend(){
        String messageContext="Direct消息体.....";
        String createTime=new Date().toString();
        Map<String, Object> map=new HashMap<>();
        map.put("messageContext",messageContext);
        map.put("createTime",createTime);
        //发送给交换机
        rabbitTemplate.convertAndSend("test_Direct_Exchange","testDirectRouting",map);
        return "ok";
    }
}

然后进行测试,访问localhost:8080/direct/send返回一个ok,去rabbitMq管理页面查看,发现出现了一个消息,还未被消费
在这里插入图片描述

接下来,是去接收消息。主要有两种方式

1.使用rabbitTemplate中的receiveAndConvert方法来接收消息

在控制器中写,访问请求手动接受消息,并显示到控制台

@RequestMapping("/direct/receive")
    public String directReceive(){
        //接受消息
        Object o = rabbitTemplate.receiveAndConvert("test_Direct_Queue");
        String msg=o==null?"":o.toString();
        System.out.println(msg);
        return "ok";
    }

访问/direct/receive后控制台正常打印消息
在这里插入图片描述
在rabbitmq管理页面看到消息已经被消费了
在这里插入图片描述
2.使用rabbitListener注解进行监听
创建一个类,并注册进spring容器,然后使用注解监听指定的队列,只要发送消息就会执行方法体

@Component
public class DirectReceiver {

    @RabbitListener(queues = "test_Direct_Queue")
    public void direct(Map msg){
        System.out.println("Direct 接收到消息:"+msg);
    }
}

连续发送3条消息进行测试,发送完,监听到消息后直接执行方法。
在这里插入图片描述

然后说一下convertAndSend和ReceiveAndConvert这两个方法,这俩方法会使用rabbitTemplate初始化时默认的格式化器,对发送的消息进行格式化。springboot的自动配置类中使用的是JDK默认格式化器,
我们可以自己配置一个messageConvert,然后将它注册进容器,就可以使用我们自定义的格式化器。

这里我使用jackson2的messageConvert将消息序列化成json形式。

@Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
2.3 测试Fanout模式

还是先创建相应的配置类,在里面创建一个fanout类型的交换机,和3个队列,然后将3个队列都绑定到这个fanout交换机上。代码如下:

/**
 * fanout扇形交换机
 *      会分发给所有绑定的队列,不管routingKey
 * @author houhaotong
 */
@Configuration
public class FanoutRabbitMqConfig {

    private static final String QUEUE_NAME_1="test_Fanout_Queue_1";
    private static final String QUEUE_NAME_2="test_Fanout_Queue_2";
    private static final String QUEUE_NAME_3="test_Fanout_Queue_3";
    private static final String EXCHANGE_NAME="test_Fanout_Exchange";

    /** 队列 */
    @Bean
    public Queue fanoutQueue1(){
        return new Queue(QUEUE_NAME_1);
    }

    @Bean
    public Queue fanoutQueue2(){
        return new Queue(QUEUE_NAME_2);
    }

    @Bean
    public Queue fanoutQueue3(){
        return new Queue(QUEUE_NAME_3);
    }

    /** fanout扇形交换机 */
    @Bean
    public Exchange fanoutExchange(){
        return new FanoutExchange(EXCHANGE_NAME);
    }

    /** 绑定队列和交换机 */
    @Bean
    public Binding fanoutBinding1(){
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange()).with("").noargs();
    }

    @Bean
    public Binding fanoutBinding2(){
        return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange()).with("").noargs();
    }

    @Bean
    public Binding fanoutBinding3(){
        return BindingBuilder.bind(fanoutQueue3()).to(fanoutExchange()).with("").noargs();
    }
}

不再介绍手动消费消息的方法,直接使用监听器监听队列。代码如下

/**
 * Fanout类型消费者
 *  使用注解监听队列
 *  @author houhaotong
 */
@Component
public class FanoutReceiver {

    @RabbitListener(queues = "test_Fanout_Queue_1")
    public void fanout1(Map msg){
        System.out.println("Fanout_Queue_1 接收到消息:"+msg);
    }

    @RabbitListener(queues = "test_Fanout_Queue_2")
    public void fanout2(Map msg){
        System.out.println("Fanout_Queue_2 接收到消息:"+msg);
    }

    @RabbitListener(queues = "test_Fanout_Queue_3")
    public void fanout3(Map msg){
        System.out.println("Fanout_Queue_3 接收到消息:"+msg);
    }
}

然后在控制器中写send方法,发送到fanout交换机。代码如下:

@RequestMapping("/fanout/send")
    public String fanoutSend(){
        String messageContext="Fanout消息体.....";
        String createTime=new Date().toString();
        Map<String, Object> map=new HashMap<>();
        map.put("messageContext",messageContext);
        map.put("createTime",createTime);
        //发送给交换机
        rabbitTemplate.convertAndSend("test_Fanout_Exchange","",map);
        return "ok";
    }

最后发送一条消息进行测试,可以看到,三个监听器监听各自的队列,都接收到了消息,fanout交换机把消息分发给了所有绑定的队列
在这里插入图片描述

2.4 测试Topic模式

先写配置类,这里定义的三个队列所绑定的routingKey分别是item.#,#.add,item.add

/**
 * topic主题模式
 * @author houhaotong
 */
@Configuration
public class TopicRabbitMqConfig {

    private static final String QUEUE_NAME_1="test_Topic_Queue_1";
    private static final String QUEUE_NAME_2="test_Topic_Queue_2";
    private static final String QUEUE_NAME_3="test_Topic_Queue_3";
    private static final String EXCHANGE_NAME="test_Topic_Exchange";

    /** 队列 */
    @Bean
    public Queue topicQueue1(){
        return new Queue(QUEUE_NAME_1);
    }

    @Bean
    public Queue topicQueue2(){
        return new Queue(QUEUE_NAME_2);
    }

    @Bean
    public Queue topicQueue3(){
        return new Queue(QUEUE_NAME_3);
    }

    /** topic主题模式交换机 */
    @Bean
    public Exchange topicExchange(){
        return new TopicExchange(EXCHANGE_NAME);
    }

    /** 绑定队列和交换机,两个使用通配符,一个不使用 */
    @Bean
    public Binding topicBinding1(){
        return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("item.#").noargs();
    }

    @Bean
    public Binding topicBinding2(){
        return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("#.add").noargs();
    }

    @Bean
    public Binding topicBinding3(){
        return BindingBuilder.bind(topicQueue3()).to(topicExchange()).with("item.add").noargs();
    }
}

控制器中topic发送消息的方法如下:

这里要注意,先设置routingKey为item.add,因为我们的三个队列所绑定的routingKey分别是item.#,#.add,item.add,所以这个时候发送的消息,交换机应该会分发给这三个队列。

@RequestMapping("/topic/send")
    public String topicSend(){
        String messageContext="Topic消息体.....";
        String createTime=new Date().toString();
        //定义routingKey
        String routingKey="item.add";
        Map<String, Object> map=new HashMap<>();
        map.put("messageContext",messageContext);
        map.put("createTime",createTime);
        //发送给交换机
        rabbitTemplate.convertAndSend("test_Topic_Exchange",routingKey,map);
        return "ok";
    }

监听器代码如下:

/**
 * Topic类型消费者
 *  使用注解监听队列
 *  @author houhaotong
 */
@Component
public class TopicReceiver {

    @RabbitListener(queues = "test_Topic_Queue_1")
    public void topic1(Map msg){
        System.out.println("Topic_Queue_1 item.# 接收到消息:"+msg);
    }

    @RabbitListener(queues = "test_Topic_Queue_2")
    public void topic2(Map msg){
        System.out.println("Topic_Queue_2 #.add 接收到消息:"+msg);
    }

    @RabbitListener(queues = "test_Topic_Queue_3")
    public void topic3(Map msg){
        System.out.println("Topic_Queue_3 item.add 接收到消息:"+msg);
    }
}

接下来进行测试,先测试的是发送消息带的routingKey是item.add,三个队列都接受到消息,没有问题
在这里插入图片描述
然后测试发送绑定item.delete,这个时候应该只有第一个队列能接收到消息,没有任何问题
在这里插入图片描述
然后测试one.add,这个时候只有第二个队列能收到消息,没有问题
在这里插入图片描述
*号和别的情况不再测试

2.4 生产者的消息确认

在这里插入图片描述
生产者的消息确认(回调)主要分为两种

  1. ConfirmCallback 只要到broker就会触发,找不到交换机则返回false
  2. ReturnCallback 交换机要分发给队列,但是找不到时,才会触发这个回调

要进行使用,首先需要配置springboot的配置文件

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: user
    password: 123456
    virtual-host: /vhost_test
    #确定消息发送到队列
    publisher-returns: true
    #启用confirmCallback
    publisher-confirm-type: correlated

然后在配置类中,重写一波rabbitTemplate,如下:

    @Bean
    public RabbitTemplate rabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate();
        configurer.configure(template, connectionFactory);
        //设置confirmCallback
        template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if(ack){
                    System.out.println("confirmCallback:  消息成功发送到交换机!!!!!");
                }else {
                    System.out.println("confirmCallback:  消息发送失败!!!!!");
                }
            }
        });
        //设置returnCallback
        template.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("returnCallback:    找不到对应的队列");
                System.out.println("returnCallback:    消息体为:"+message.toString());
            }
        });
        return template;
    }

进行测试:

①首先测试能发送到交换机,而且能分发到队列

confirmCallback返回true,因为分发到了队列,所以不会触发returnCallback
在这里插入图片描述
②测试能发送到交换机,不能分发到队列

confirmCallback返回true,找不到队列,触发了returnCallback
在这里插入图片描述
③测试不能发送到队列

confirmCallback返回false
在这里插入图片描述

2.5 消费者的消息确认

在使用rabbitListener监听器的时候,是默认自动确认的,为AcknowledgeMode.AUTO。
我们使用手动确认时,需要设置为AcknowledgeMode.MANUAL。

在手动确认中,消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功。

  • basic.ack用于肯定确认
  • basic.nack用于否定确认
  • basic.reject用于否定确认,跟nack不同在于一次只能拒绝单条消息

在配置类中重写SimpleMessageListenerContainer,并且指定手动确认,指定自定义的监听处理器,并设置队列。

    @Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container=new SimpleMessageListenerContainer(connectionFactory);
        //设置一个队列
        container.setQueueNames("test_Fanout_Queue_1");
        //设置为手动确认消息
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //设置自定义消息监听器
        container.setMessageListener(new ChannelAwareMessageListener() {
            @Override
            public void onMessage(Message message, Channel channel) throws Exception {
                //获取消息标识
                long tag = message.getMessageProperties().getDeliveryTag();
                try {
                    System.out.println("******消费者消息手动确认*******");
                    //消息确认,单条
                    channel.basicAck(tag,false);
                }catch (Exception e){
                    //requeue:被拒绝后是否重新入队列
                    channel.basicReject(tag,false);
                    e.printStackTrace();
                }
            }
        });
        return container;
    }

进行测试,发送消息到fanout队列,成功输出结果到控制台,手动确认生效
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值