RabbitMQ的简单使用

RabbitMQ

RabbitMQ是一个基于AMQP(Advance Message Queue Protocol)通信协议的一种消息队列技术
JMS(Java Message Service) 是Java程序操作MQ的API规范,jms是在java应用API层面定义的一个标准,而AMQP为协议层定义的标准(具备跨语言的特性)。

RabbitMQ属于服务间的异步通信技术(不同于Dubbo和SpringCloud,它们属于同步通信技术),分布式系统的远程通信技术有以下两种方式:

远程过程调用(RPC)
借助消息中间件(MQ)

MQ具有以下几种优点:

  • 应用解耦
  • 消峰填谷 : 通过MQ中可以将短时间内大量的请求放入MQ的队列,处理消息的服务可以在自己能力范围内对队列中的消息进行处理;
  • 应用提速 : 得益于MQ的异步通信属性,可以节省同步通信中调用服务等待返回值的时间,进而提升了系统之间的运行速度;

MQ同时也具有一下几种使用的局限性(缺点):

  • 系统的复杂性提高了
  • 系统的可用性降低了
  • 系统内事务会有一段短暂的不一致

综上,当服务调用者不需要返回值(短时间内),且能够容忍一定时间的不一致性,及使用MQ能带来一定的收益才选择使用MQ;

在这里插入图片描述

RabbitMQ的基本操作

1. 导入rabbitMQ的依赖Jar包
2. 利用ConnectionFactory对象创建Connection对象(ConnectionFactory对象需设置连接rabbitMQ的相关配置)
3. 利用Connection对象创建Channel对象
4. 利用Channel对象可以声明Queue(队列),Exchange(交换机) 及队列和交换机的绑定
5. 利用channel对象的basicPublish&basicConsumer方法分别可进行消息的发送和接收

RabbitMQ的消息发送

 ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");//设置rabbitMQ服务所在主机地址
        connectionFactory.setPort(5672);//设置端口
        connectionFactory.setUsername("andrew");//设置用户名
        connectionFactory.setPassword("123");//设置密码
        connectionFactory.setVirtualHost("/testVH");//设置rabbitMQ的虚拟机

        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("myQueue1",true,false,false,null);//申明队列 参数见源码
        channel.queueDeclare("myQueue2",true,false,false,null);

        channel.exchangeDeclare("topicExchange1", BuiltinExchangeType.TOPIC);//申明交换机

        channel.queueBind("myQueue1","topicExchange1","routingKey.*.aa");//队列与交换机的绑定
        channel.queueBind("myQueue2","topicExchange1","routingKey.*.bb");
		
		//向rabbitMQ发送消息
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            channel.basicPublish("topicExchange1","routingKey.aa",null,("Hi this is a new message from andrew by directExchange1"+new Date().toLocaleString()).getBytes());
            channel.basicPublish("topicExchange1","routingKey.bb",null,("Hi this is a new message from andrew by directExchange1"+new Date().toLocaleString()).getBytes());
            System.out.println("provider send message successfully!!!");
        }

        channel.close();
        connection.close();

RabbitMQ的消息接收

rabbitMQ的消息接收代码大致与发送代码相同,调用channel对象的basicConsume方法即可监听指定队列
  ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("andrew");
        connectionFactory.setPassword("123");
        connectionFactory.setVirtualHost("/testVH");

        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("myQueue2",true,false,false,null);

		//创建一个DefaultConsumer类并重写其消费方法
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumer receive the message successfully!!! message is :");
                System.out.println(new String(body));
                System.out.println("*****************************");
                System.out.println();
            }
        };
		
		//监听myqQUeue2队列 当该队列有消息时 会触发 consumer对象的消费方法
        channel.basicConsume("myQueue2", true,consumer);

RbbitMQ的消息工作模式

Work Queue:
最简单的模式
队列绑定直接通过队列名称绑定,即指定交换机和队列名称即可完成绑定(交换机默认为空字符串)

在这里插入图片描述
Publish&Subscribe

相较于Work Queue模式,需指定交换机且该交换机类型为Fanout,交换机通过队列名称相互绑定即一个交换机可绑定多个队列,进而实现一个消息被多个消费者消费;

在这里插入图片描述

Routing

在publish&subscribe模式的基础上,增加了RoutingKey的限制条件,进而实现了更加灵活的队列 交换机的绑定规则;

在这里插入图片描述

Topic

相较于Routing模式,topic模式在声明队列的时候所定义的RoutingKey支持通配符 # *,且交换机类型为Topic ;

#:匹配一个或多个词(词与词之间采用 . 分隔)
:匹配一个词
(aa.
.bb)即可匹配aa.bb或者aa.cc.bb和aa.cc.ee.bb

Spring整合RabbitMQ

Spring整合RabbitMQ主要需要在spring配置文件中配置对应的声明队列&交换机以及ConnectionFactory和RabbitTemplate;

使用步骤:

  1. 导入对应的jar包:

    spring-rabbit
    
  2. 编写配置文件:

  <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>
    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-returns="true"
                               publisher-confirms="true"/>
    <!--定义管理交换机、队列 AdminTemplate对象与connectionFactory绑定-->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!--创建声明队列对象-->
    <rabbit:queue id="queue1" name="queue1" auto-declare="true">
        <rabbit:queue-arguments>
        	设置队列的TTL(Time To Live)
            <entry key="x-message-ttl" value="20000" value-type="java.lang.Integer"/>
            设置队列的DLX 当消息成为死消息(DeadMessage)使消息进入DLX
            <entry key="x-dead-letter-exchange" value="deadLetterExchange" value-type="java.lang.String"/>
            设置队列最大长度(超出长度部分的消息会成为死消息)
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>


    <!--死信队列 使用fanout类型 并绑定一个队列-->
    <rabbit:fanout-exchange id="deadLetterExchange" name="deadLetterExchange" auto-declare="true">
       <rabbit:bindings>
           <rabbit:binding queue="deadLetterQueue"></rabbit:binding>
       </rabbit:bindings>
    </rabbit:fanout-exchange>

    <rabbit:queue id="queue2" name="queue2" auto-declare="true"/>
    <rabbit:queue id="deadLetterQueue" name="deadLetterQueue" auto-declare="true"/>
    <rabbit:queue id="queue3" name="queue3" auto-declare="true"/>
    <rabbit:queue id="queue4" name="queue4" auto-declare="true"/>

    <!--交换机为FANOUT 模式 交换机直接将消息路由给所有绑定的队列-->
   <!-- <rabbit:fanout-exchange name="fanoutExchange" id="fanoutExchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding queue="queue1"></rabbit:binding>
            <rabbit:binding queue="queue2"></rabbit:binding>
            <rabbit:binding queue="queue3"></rabbit:binding>
            <rabbit:binding queue="queue4"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:fanout-exchange>-->


    <!--交换机为Direct模式 会把消息路由给匹配RoutingKey的队列-->
   <!-- <rabbit:direct-exchange name="directExchange" id="directExchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding key="key1" queue="queue1"></rabbit:binding>
            <rabbit:binding key="key2" queue="queue2"></rabbit:binding>
            <rabbit:binding key="key3" queue="queue3"></rabbit:binding>
            <rabbit:binding key="key4" queue="queue4"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>-->

    <!--交换机为Topic模式 会把消息路由给和routingKey匹配带有通配符的队列-->

    <rabbit:topic-exchange name="topicExchange" id="topicExchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="keys.*" queue="queue1"></rabbit:binding>
            <rabbit:binding pattern="key.aa.*" queue="queue2"></rabbit:binding>
            <rabbit:binding pattern="key.#" queue="queue3"></rabbit:binding>
            <rabbit:binding pattern="keytest" queue="queue4"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!--定义队列监听对象-->
    <bean id="queue1Listener1" class="com.andrew.rabbitmq.listener.Listen1"/>
    <bean id="queue1Listener2" class="com.andrew.rabbitmq.listener.Listen2"/>
    <bean id="queue1Listener3" class="com.andrew.rabbitmq.listener.Listen3"/>
    <bean id="queue1Listener4" class="com.andrew.rabbitmq.listener.Listen4"/>

    <!--监听器容器 把监听对象和队列相互绑定-->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
        <rabbit:listener ref="queue1Listener1" queue-names="deadLetterQueue"/>
    <!--    <rabbit:listener ref="queue1Listener2" queue-names="queue2"/>
        <rabbit:listener ref="queue1Listener3" queue-names="queue3"/>
        <rabbit:listener ref="queue1Listener4" queue-names="queue4"/>-->
    </rabbit:listener-container>
  1. 使用rabbitTemplate对象可以发送消息:

     rabbitTemplate.convertAndSend("", "queue1", "this message will be expired in 5 seconds");
    
  2. 定义实现了MessageListener接口的监听类,并注入容器(当需要实现消息拒收功能时实现ChannelAwareMesageListener)

消息的可靠投递

RabbitMQ提供三种方法能保证消息的可靠投递,分别为:

  • 发送消息端 Confirm : 当消息发送至交换机(Exchange)失败时,通过设置connectionFactory的 属性publisher-confirms=“true” 并通过RabbitTemlate的setConfimCallback方法定义当消息发送至Exchange失败的执行的回调函数
  • 发送消息端 Return : 当消息经由Exchange路由至Queue时失败时,通过设置connectionFactory的属性publisher-returns=“true”,设置RabbitTemplate的setMantory为true(false发送失败会默认将该消息丢弃),并通过RabbitTemplate的setReturnCallback方法来定义消息发送到queue失败的回调函数;
  • **监听端 ** :通过实现ChannelAwareMessageListener接口,并通过RabbitTemplate的setMantory方法,由channel的baiscAck&basicNack方法对消息进行接收和拒收;

RabbitMQ的死队列(DeadLetterExchange)
当消息成为死消息(DeadMessage)使,会被定义的DLX接收并路由至绑定的队列,消息成为死消息原因有以下三种:

  • 消息过期: 通过队列的x-message-ttl属性可以设置队列内消息的有效期限,过期后队列内的消息会被设置的DLX接收,单个消息也可设置自己的有效期(当队列有效期和消息有效期重叠时以时间短的为准),且单个消息到达自身设置的有效期时,不会直接被DLX接收(当监听者消费该消息时才会检查该消息是否过期,进而才会移除无效的消息)
  • 队列溢出: 当队列的消息数量超出自身设置的消息数量上限时,超出的部分也会成为死消息
  • 被监听者拒收 : 当监听者使用拒收消息时,该消息也会成为一条死消息

延时队列
延时队列中的消息,只有在指定的时间后才能被监听者消费,在RabbitMQ通过使用死队列可以实现延时队列的功能:
即设置一条队列,并不为该队列设置监听者,设置该队列的消息有效期,期限过后便会把消息转至死队列,监听死队列即可实现延时队列的效果;

SpringBoot整合RabbitMQ

需引入spring-boot-starter-amqp和spring-rabbit的Jar包依赖并配置application…yml

#配置rabbitMQ连接信息
spring:
  rabbitmq:
    host: 192.168.142.1
    username: andrew
    password: 123
    virtual-host: /testVH
    port: 5672
    publisher-confirm-type: CORRELATED #设置对发布端回调函数的支持
    template:
      mandatory: true  #设置发布端 支持Return回调函数


通过注入RabbitTemplate对象可完成消息的指定发送

 @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void test() throws InterruptedException {
       while (true){
           Thread.sleep(1000);
           rabbitTemplate.convertAndSend("topicExchange","routingKey1","this message is send by rabbitTemplate");
       }
    }

通过使用@RabbitListener注解标记指定方法来处理监听队列的消息

@Component
public class RabbitMqConsumer {

	//message与channel会被自动传递进consumer方法
    @RabbitListener(queues = {"queue1"})
    public void consumer(Message message, Channel channel){
        System.out.println("message received successfully!!!");
        System.out.println("message content is : "+new String(message.getBody()));
    }
}

RabbitMQ动态监听

通过SimpleMessageListenerContainner对象,可为容器动态添加监听器:

  @Bean("Listener")//将该对象注入容器
  public SimpleMessageListenerContainer getSmlc(){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        //设置队列的ConnectionFactory
        container.setConnectionFactory(rabbitTemplate.getConnectionFactory());
        //设置消息手动签收
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
		//设置监听队列
        container.addQueueNames(singer);
        //设置监听器
        container.setMessageListener(new MyListener());
        return container;
    }

此方法可用于动态的给容器内的指定监听器添加指定的监听队列:

通过FactoryBean对象可以动态的向容器注入指定Bean 从而实现动态像容器添加指定数量的监听器:

@PostConstruct
    public void createListenerContainer(){
    //获取FactoryBean对象
       DefaultListableBeanFactory factory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();

		//像容器注入指定数量的监听器
       for (String singer : singers) {
       	//获取bean的构造器
           BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(SimpleMessageListenerContainer.class);
           //设置bean的构造方法
           beanDefinitionBuilder.addConstructorArgValue(rabbitTemplate.getConnectionFactory());
			//设置bean的属性
           beanDefinitionBuilder.addPropertyReference("messageListener","listenerToTest");
		
           beanDefinitionBuilder.addPropertyValue("QueueNames",singer);
			//将bean注入容器
           factory.registerBeanDefinition(singer,beanDefinitionBuilder.getBeanDefinition());
       }
   }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值