RabbitMQ(2020版)

什么是MQ

消息队列(message queue),通过典型的生产者和消费者模型,生产者生产消息写入消息队列中,消费者从消息队列中取出消息消费。因为生产者和消费者是异步,且只关心消息发送和接受,没有业务逻辑的侵入,轻松实现系统间的解耦。
在这里插入图片描述

MQ有哪些呢

比较流行的消息中间件有,ActiveMQ、RabbitMQ、kafka、阿里巴巴的RocketMQ等

不同MQ的特点

  1. ActiveMQ
    apache出品,完全支持JMS规范,丰富的API,多种架构集群模式让ActiveMQ成为业界老牌的消息中间件,在中小型企业很受欢迎
  2. kafka
    apache出品,追求高吞吐量,一开始就是用于日志的收集和传输,0.8版本后支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合大数据开发
  3. RocketMQ
    RocketMQ是阿里出品,它是纯java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用特点。它对消息的可靠传输及事务做了优化,目前在阿里被广泛用于交易、充值、流计算、消息推送等场景
  4. RabbitMQ
    RabbitMQ使用erlang语言开发,基于AMQP协议实现,AMQP主要特征是面向消息、队列、路由、可靠性、安全,它对数据一致性、稳定性和可靠性具有很高的要求,对性能和吞吐量还是其次。

RabbitMQ在数据可靠性、一致性、稳定性方面比kafka更可靠,kafka更适合大数据开发,高吞吐量的处理

RabbitMQ的安装

因为rabbitmq是erlang语言写的,所以需要先安装erlang依赖

  1. 下载erlang, 下载地址
  2. 下载rabbitmq,下载地址
  3. erlang与rabbitmq对应版本要求,查看地址
    在这里插入图片描述
  4. 上传erlang与rabbitmq安装包到服务器
    在这里插入图片描述
  5. 安装erlang与rabbitmq
 rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
 yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm 

在这里插入图片描述
6. 修改配置文件名称
默认安装完后,配置文件在/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目录中,需要将配置文件复制到/etc/rabbitmq目录中,并修改名称为rabbitmq.config

cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config

在这里插入图片描述
7. 修改配置文件内容

 vim /etc/rabbitmq/rabbitmq.config

去掉注释以及逗号
在这里插入图片描述
8. 启动rabbitmq插件管理

rabbitmq-plugins enable rabbitmq_management

在这里插入图片描述
9. 启动rabbitmq/关闭rabbitmq/查看状态rabbitmq

systemctl start rabbitmq-server
systemctl stop rabbitmq-server
systemctl status rabbitmq-server
systemctl restart rabbitmq-server

在这里插入图片描述
10. 访问rabbitmq的web页面
开启15672端口 ,并使之立即生效,访问服务器地址

http://192.168.0.120:15672/   #192.168.0.104是服务器地址

首次登陆,用户名和密码都是guest
在这里插入图片描述

引入依赖

  <!--引入rabbitmq相关依赖    -->
    <dependency>
      <groupId>com.rabbitmq</groupId>
      <artifactId>amqp-client</artifactId>
      <version>5.7.2</version>
    </dependency>

rabbitmq的helloworld模型

在这里插入图片描述
工具类
没必要每次都去重新创建连接工厂和设置参数,使用静态代码块,随着类的加载而加载,只执行一次

public class RabbitmqUtils {
    
    

    private static ConnectionFactory factory;

    //没必要每次都去重新创建连接工厂和设置参数,创建一次,随着类的加载而创建一次就行
    //静态代码块
     static {
    
    
         //创建连接工厂factory
         factory = new ConnectionFactory();
         //2.设置服务器地址
         factory.setHost("192.168.0.120");
         //3.设置连接rabbitmq的端口号
         factory.setPort(5672);
         //4.设置要连接哪台虚拟机
         factory.setVirtualHost("/ems");
         //5.设置连接的虚拟机用户名和密码
         factory.setUsername("ems");
         factory.setPassword("123456");
    }

    //得到连接
    public static Connection getConnetion() throws IOException, TimeoutException {
    
    

        return factory.newConnection();
    }

    //关闭连接、通道
    public static void closeConneAndChann(Connection connection, Channel channel) throws IOException, TimeoutException {
    
    
        //关闭通道、连接
        channel.close();
        connection.close();
    }
}

消息生产者

//生产者
public class Producer {
    
    
    @Test
    public void produceMessage() throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
        Channel channel = connection.createChannel();

        /**通道与队列进行 绑定
         * 参数1:队列名称,如果队列不存在创建队列
         * 参数2:是否持久化队列,消息不持久化
         * 参数3:是否被通道或链接独占队列
         * 参数4:消费完后是否自动删除队列
         * 参数5:额外参数
         */
        channel.queueDeclare("hello",false,false,false,null);


        /**发布消息
         * 参数1:交换机名称
         * 参数2:队列名称
         * 参数3:传递消息额外参数   MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久化
         * 参数4:消息内容
         */
        channel.basicPublish("","hello",null,"hello rabbitmq".getBytes());

        //关闭通道、连接
        RabbitmqUtils.closeConneAndChann(connection,channel);
    }

}

消费者
这里需要用main方法,消费者消费完消息后并进行相关处理,比如:打印出消息

//消费者
public class Consumer {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
        Channel channel = connection.createChannel();

        /**通道与队列进行 绑定
         * 参数1:队列名称,如果队列不存在创建队列
         * 参数2:是否持久化队列
         * 参数3:是否被一个通道或链接独占队列
         * 参数4:消费完后是否自动删除队列
         * 参数5:额外参数
         */
        channel.queueDeclare("hello",false,false,false,null);


        /**消费消息
         * 参数1:队列名称
         * 参数2:开始消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume("hello",true,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println(new String(body));
            }
        });

    }
}

运行结果
在这里插入图片描述注意事项
启动生产者生产消息放入队列中,需要关闭服务器防火墙,不然无法创建队列

work queue模型

定义
当生产消息的速度远大于消费消息的速度,长此以往,消息越来越多无法及时处理,用work queue模型,让多个消费者绑定一个队列,共同消费队列中的消息,队列中的消息一旦消费完,队列就会消失,因此任务不会被重复执行
在这里插入图片描述
生产者

public class Producer {
    
    
    @Test
    public void produceMessage() throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到通道
        Channel channel = connection.createChannel();

        /**通道与队列进行 绑定
         * 参数1:队列名称,如果队列不存在创建队列
         * 参数2:是否持久化队列
         * 参数3:是否被一个通道或链接独占队列
         * 参数4:消费完后是否自动删除
         * 参数5:额外参数
         */
        channel.queueDeclare("work",true,false,true,null);

        for(int i=0;i<10;++i){
    
    
            /**发布消息
             * 参数1:交换机名称
             * 参数2:队列名称
             * 参数3:传递消息额外参数     MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久化
             * 参数4:消息内容
             */
            channel.basicPublish("","work",null,(i+"hello work queue").getBytes());
        }


        //关闭通道、连接
        RabbitmqUtils.closeConneAndChann(connection,channel);
    }
}

消费者1

//消费者1
public class Consumer1 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
        Channel channel = connection.createChannel();

        /**通道与队列进行 绑定
         * 参数1:队列名称,如果队列不存在创建队列
         * 参数2:是否持久化队列
         * 参数3:是否被一个通道或链接独占队列
         * 参数4:消费完后是否自动删除
         * 参数5:额外参数
         */
        channel.queueDeclare("work",true,false,true,null);


        /**消费消息
         * 参数1:队列名称
         * 参数2:开始消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume("work",true,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("消费者1:"+new String(body));
            }
        });

    }

}

消费者2

//消费者2
public class Consumer2 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
        Channel channel = connection.createChannel();

        /**通道与队列进行 绑定
         * 参数1:队列名称,如果队列不存在创建队列
         * 参数2:是否持久化队列
         * 参数3:是否被一个通道或链接独占队列
         * 参数4:消费完后是否自动删除
         * 参数5:额外参数
         */
        channel.queueDeclare("work",true,false,true,null);


        /**消费消息
         * 参数1:队列名称
         * 参数2:开始消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume("work",true,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("消费者2:"+new String(body));
            }
        });

    }

}

运行结果
在这里插入图片描述结论
work queue模型是按照平均方式消费消息,无论多少条消息,每个消费者收到都是相同数量的消息进行消费,这种方式成为循环。

改进work queue模型为能者多劳模型

消费者1

//消费者
public class Consumer1 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
        final Channel channel = connection.createChannel();

        /**通道与队列进行 绑定
         * 参数1:队列名称,如果队列不存在创建队列
         * 参数2:是否持久化队列
         * 参数3:是否被一个通道或链接独占队列
         * 参数4:消费完后是否自动删除
         * 参数5:额外参数
         */
        channel.queueDeclare("work",true,false,true,null);

        channel.basicQos(1);   //通道一次只传送一条消息
        /**消费消息
         * 参数1:队列名称
         * 参数2:开始消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume("work",false,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                try {
    
    
                    System.out.println("消费者1:"+new String(body));
                    Thread.sleep(1000);
                    channel.basicAck(envelope.getDeliveryTag(),false);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                /**开启手动确认消息机制
                 * 参数1:确认是队列中哪个消息
                 * 参数2:是否开启多个消息同时确认
                 */


            }
        });

    }

}

消费者2

//消费者
public class Consumer2 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
        final Channel channel = connection.createChannel();

        /**通道与队列进行 绑定
         * 参数1:队列名称,如果队列不存在创建队列
         * 参数2:是否持久化队列
         * 参数3:是否被一个通道或链接独占队列
         * 参数4:消费完后是否自动删除
         * 参数5:额外参数
         */
        channel.queueDeclare("work",true,false,true,null);

        channel.basicQos(1);   //通道一次只传送一条消息
        /**消费消息
         * 参数1:队列名称
         * 参数2:开启消息的自动确认机制    改为false不开启自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume("work",false,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("消费者2:"+new String(body));
                /**开启手动确认消息机制
                 * 参数1:确认是队列中哪个消息
                 * 参数2:是否开启多个消息同时确认
                 */
                channel.basicAck(envelope.getDeliveryTag(), false);

            }
        });

    }

}

运行结果
消费者1每消费1次消息需要1s,消费者不需要花费1s,所以消费者2处理消费的效率更高,按照能者多劳机制,因此消费者2处理更多消息比消费者1
在这里插入图片描述

fanout(publish/subscribe)模型

广播模式工作流程
广播模式下可以有多个消费者,每个消费者都有自己的queue,每个queue绑定到exchange,生产者直接与交换机打交道,生产者生产的消息直接发送给exchange,由交换机决定将消息发送给哪个队列,生产者无法决定,交换机将消息发送给绑定的所有队列,实现一条消息被所有消费者消费
在这里插入图片描述
生产者

public class Producer {
    
    
    @Test
    public void produceMessage() throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到通道
        Channel channel = connection.createChannel();

        /**通道与交换机进行 绑定
         * 参数1:交换机名称
         * 参数2:交换机类型   fanout---广播类型
         */
      channel.exchangeDeclare("pub/subscri","fanout");


            /**发布消息
             * 参数1:交换机名称
             * 参数2:
             * 参数3:传递消息额外参数     MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久化
             * 参数4:消息内容
             */
            channel.basicPublish("pub/subscri","",null,"hello pub/subscri".getBytes());

        //关闭通道、连接
        RabbitmqUtils.closeConneAndChann(connection,channel);
    }
}

消费者1

//消费者
public class Consumer1 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
         Channel channel = connection.createChannel();

        /**通道与交换机进行 绑定
         * 参数1:交换机名称
         * 参数2:交换机类型   fanout---广播类型
         */
        channel.exchangeDeclare("pub/subscri","fanout");

        //得到临时队列
        String queueName = channel.queueDeclare().getQueue();
        /**交换机与队列进行绑定
         * 参数1:队列名
         * 参数2:交换机名
         * 参数3:额外参数
         */
        channel.queueBind(queueName,"pub/subscri","");

        /**消费消息
         * 参数1:队列名称
         * 参数2:是否开始消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume(queueName,false,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                    System.out.println("消费者1:"+new String(body));
            }
        });
    }
}

消费者2、消费者3如上

运行结果
生产者生产消息交给交换机,交换机将该消息分发给与它绑定的所有队列,于是一条消息,所有的消费者都拿到了
在这里插入图片描述

routing模型之direct模型(重点)

在广播模式中,一条消息会被所有消费者消费。但是在某些场景下,我们希望不同的消息被不同的消费者消费,这时就要用到direct类型的交换机。

routing模式下的工作流程
在routing模式中,队列与交换机的绑定不是随意绑定,而是需要指定一个routingkey,消息在向交换机发送时,也必须消息的routingkey,交换机不再把消息发送给绑定的所有队列,而是根据消息的routingkey进行判断,当消息的routingkey与队列的routingkey相同时,才会发送到指定队列
在这里插入图片描述生产者

public class Producer {
    
    
    @Test
    public void produceMessage() throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到通道
        Channel channel = connection.createChannel();

        /**通道与交换机进行 绑定
         * 参数1:交换机名称
         * 参数2:交换机类型   direct---路由模式
         */
      channel.exchangeDeclare("routing-direct","direct");

        //指定routingkey
        String routingKey="info";

            /**发布消息
             * 参数1:交换机名称
             * 参数2:指定routingKey标志
             * 参数3:传递消息额外参数     MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久
             * 参数4:消息内容
             */
            channel.basicPublish("routing-direct",routingKey,null,"hello routing模式,我是direct类型".getBytes());

        //关闭通道、连接
        RabbitmqUtils.closeConneAndChann(connection,channel);
    }
}

消费者1

//消费者
public class Consumer1 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
         Channel channel = connection.createChannel();

        /**通道与交换机进行 绑定
         * 参数1:交换机名称
         * 参数2:交换机类型   direct---路由模式
         */
        channel.exchangeDeclare("routing-direct","direct");

        //得到临时队列
        String queueName = channel.queueDeclare().getQueue();

        /**交换机与队列进行绑定
         * 参数1:队列名
         * 参数2:交换机名
         * 参数3:指定routingkey
         */
        channel.queueBind(queueName,"routing-direct","error");

        /**消费消息
         * 参数1:队列名称
         * 参数2:是否开启消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume(queueName,false,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                    System.out.println("消费者1:"+new String(body));
            }
        });


    }

}

消费者2

//消费者
public class Consumer2 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
         Channel channel = connection.createChannel();

        /**通道与交换机进行 绑定
         * 参数1:交换机名称
         * 参数2:交换机类型   direct---路由模式
         */
        channel.exchangeDeclare("routing-direct","direct");

        //得到临时队列
        String queueName = channel.queueDeclare().getQueue();

        /**交换机与队列进行绑定
         * 参数1:队列名
         * 参数2:交换机名
         * 参数3:指定routingkey
         */
        //该队列可以接收多个routingkey类型的消息
        channel.queueBind(queueName,"routing-direct","info");
        channel.queueBind(queueName,"routing-direct","error");
        channel.queueBind(queueName,"routing-direct","waring");
        channel.queueBind(queueName,"routing-direct","debugger");
        /**消费消息
         * 参数1:队列名称
         * 参数2:是否开启消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume(queueName,false,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                    System.out.println("消费者1:"+new String(body));
            }
        });

    }

}

运行结果
生产者发送消息的routingkey是info,消费者1是error,消费者2是info、error、waring、debgger,因此消费者1不会接收到消息,消费者2会接收到
在这里插入图片描述

routing模型之topic模型

topic类型的交换机与direct类型的交换机都是根据routingkey把消息发送到不同队列中,区别在于topic类型的交换机给队列指定routingkey时候可以使用通配符,如:item.insert.hello

通配符
*匹配1个
#匹配1个或多个

audit.*   可以匹配   audit.item
audit.#    可以匹配   audit.item.haha.hello

在这里插入图片描述生产者

public class Producer {
    
    
    @Test
    public void produceMessage() throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到通道
        Channel channel = connection.createChannel();

        /**通道与交换机进行 绑定
         * 参数1:交换机名称
         * 参数2:交换机类型   fanout---广播类型
         */
      channel.exchangeDeclare("routing_topict","topic");

      //指定routingkey
        String routingKey="user.save.dynamic";

            /**发布消息
             * 参数1:交换机名称
             * 参数2:指定routingKey标志
             * 参数3:传递消息额外参数     MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久
             * 参数4:消息内容
             */
            channel.basicPublish("routing_topict",routingKey,null,"hello routing_topic,这是动态routing模式".getBytes());

        //关闭通道、连接
        RabbitmqUtils.closeConneAndChann(connection,channel);
    }
}

消费者1

//消费者
public class Consumer1 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
         Channel channel = connection.createChannel();

        /**通道与交换机进行 绑定
         * 参数1:交换机名称
         * 参数2:交换机类型   direct---路由模式
         */
        channel.exchangeDeclare("routing_topict","topic");

        //得到临时队列
        String queueName = channel.queueDeclare().getQueue();

        /**交换机与队列进行绑定
         * 参数1:队列名
         * 参数2:交换机名
         * 参数3:指定routingkey, 使用动态通配符形式
         */
        channel.queueBind(queueName,"routing_topict","user.*");

        /**消费消息
         * 参数1:队列名称
         * 参数2:是否开启消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume(queueName,false,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                    System.out.println("消费者1:"+new String(body));
            }
        });


    }

}

消费者2

//消费者
public class Consumer2 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //得到连接
        Connection connection = RabbitmqUtils.getConnetion();
        //得到连接通道
         Channel channel = connection.createChannel();

        /**通道与交换机进行 绑定
         * 参数1:交换机名称
         * 参数2:交换机类型   direct---路由模式
         */
        channel.exchangeDeclare("routing_topict","topic");

        //得到临时队列
        String queueName = channel.queueDeclare().getQueue();

        /**交换机与队列进行绑定
         * 参数1:队列名
         * 参数2:交换机名
         * 参数3:指定routingkey, 使用动态通配符形式
         */
        channel.queueBind(queueName,"routing_topict","user.#");

        /**消费消息
         * 参数1:队列名称
         * 参数2:是否开启消息的自动确认机制
         * 参数3:消费时的回调接口
         */
        channel.basicConsume(queueName,false,new DefaultConsumer(channel){
    
    
            @Override   //最后一个参数:从消息队列中取出消息消费
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                    System.out.println("消费者1:"+new String(body));
            }
        });


    }

}

运行结果
生产者发送消息指定的routingkey是user.save.dynamic,交换机给消费者1指定的routingkey是动态通配符user.*给消费者2指定的routingkey是user.#,所以最后消费者1没能拿到消息,消费者2拿到了该消息进行消费
在这里插入图片描述

rabbitmq整合springboot

创建springboot项目时引入springweb、spring for rabbitmq依赖
在这里插入图片描述application.properties配置文件

#给springboot应用取名字
spring.application.name=rabbitmq_springboot


#服务器地址
spring.rabbitmq.host=192.168.0.120
#端口号
spring.rabbitmq.port=5672
#哪台虚拟机
spring.rabbitmq.virtual-host=/ems
#用户名
spring.rabbitmq.username=ems
#密码
spring.rabbitmq.password=123456

helloworld模型

生产者

//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
    
    

    //将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
    @Autowired
    private  RabbitTemplate rabbitTemplate;


    //hello world模型     生产者
    @Test
    public void helloWold(){
    
    
        //参数1:队列名称    参数2:消息内容
        rabbitTemplate.convertAndSend("hello","hello world");
    }

消费者

@Component   //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
//消费者监听hello队列,并进行持久化、是否独占、自动删除等配置
@RabbitListener(queuesToDeclare = @Queue(value = "hello",durable = "true",exclusive = "false",autoDelete ="true" ))
public class HelloConsumer{
    
    

    //从队列中取出消息消费
    @RabbitHandler
    public void receive(String message){
    
    
        System.out.println(message);
    }

}

运行结果
在这里插入图片描述

workqueue模型

生产者

//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
    
    

    //将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
    @Autowired
    private  RabbitTemplate rabbitTemplate;

    //workQueue模型     生产者
    @Test
    public void workQueue(){
    
    
        for(int i=0;i<20;++i){
    
    
            //参数1:队列名称    参数2:消息内容
            rabbitTemplate.convertAndSend("work","hello workqueue");
        }

    }
}

消费者

@Component   //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
public class WorkConsumer {
    
    

    //消费者1
    //@RabbitListener用在方法上,就省去@rabbithandler注解
    //消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
    @RabbitListener(queuesToDeclare = @Queue(value = "work",durable = "true",exclusive = "false",autoDelete ="false" ))
    public void receive1(String message){
    
    
        System.out.println("消费者1:"+message);
    }


    //消费者2
    @RabbitListener(queuesToDeclare = @Queue(value = "work",durable = "true",exclusive = "false",autoDelete ="false" ))
    public void receive2(String message){
    
    
        System.out.println("消费者2:"+message);
    }

}

运行结果
采用workqueue模型每个消费者拿到的消息数量相等,采用循环方式消费
在这里插入图片描述

publish/subsucribe模型之fanout模型

生产者

//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
    
    

    //将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
    @Autowired
    private  RabbitTemplate rabbitTemplate;

    //publish/subscribe模型     生产者
    @Test
    public void fanOut(){
    
    
            //参数1:交换机名称    参数2:routingkey    参数3:消息内容
            rabbitTemplate.convertAndSend("pub/sub","","广播模型,fanout_pub/sub");

    }

}

消费者

@Component   //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
public class FanOutConsumer {
    
    

    //消费者1
    //@RabbitListener用在方法上,不需要使用@rabbithandler注解
    //消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
    @RabbitListener(bindings={
    
    @QueueBinding(
            value=@Queue,           //创建临时队列
            //队列和交换机进行绑定,  参数1:交换机名  参数2:交换机类型
            exchange=@Exchange(name = "pub/sub",type="fanout"))})
    public void receive1(String message){
    
    
        System.out.println("消费者1:"+message);
    }


    //消费者2
    //@RabbitListener用在方法上,不需要使用@rabbithandler注解
    //消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
    @RabbitListener(bindings={
    
    @QueueBinding(
            value=@Queue,           //创建临时队列
            //队列和交换机进行绑定,  参数1:交换机名  参数2:交换机类型
            exchange=@Exchange(name = "pub/sub",type="fanout"))})
    public void receive2(String message){
    
    
        System.out.println("消费者2:"+message);
    }

}

运行结果
采用广播模式,生产者生产消息交给交换机,交换机将该消息分发给与它绑定的所有队列,实现一条消息,所有的消费者消费
在这里插入图片描述

routing模型之direct模型

生产者

//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
    
    

    //将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
    @Autowired
    private  RabbitTemplate rabbitTemplate;


    //routing模型之direct     生产者
    @Test
    public void routing(){
    
    
        //参数1:交换机名称    参数2:routingkey    参数3:消息内容
        rabbitTemplate.convertAndSend("routing_direct","info","routing模型,routing_direct模型");

    }

}

消费者

@Component   //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
public class DirectConsumer {
    
    

    //消费者1
    //@RabbitListener用在方法上,不需要使用@rabbithandler注解
    //消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
    @RabbitListener(bindings={
    
    @QueueBinding(
            value=@Queue,           //创建临时队列
            //队列和交换机进行绑定,  参数1:交换机名  参数2:交换机类型
            exchange=@Exchange(name = "routing_direct",type="direct"),
            key = {
    
    "info","error","waring","debugger"})})       //给队列指定routingkey
    public void receive1(String message){
    
    
        System.out.println("消费者1:"+message);
    }



    //消费者2
    //@RabbitListener用在方法上,不需要使用@rabbithandler注解
    //消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
    @RabbitListener(bindings={
    
    @QueueBinding(
            value=@Queue,           //创建临时队列
            //队列和交换机进行绑定,  参数1:交换机名  参数2:交换机类型
            exchange=@Exchange(name = "routing_direct",type="direct"),
            key = {
    
    "error","waring"})})      //给队列指定routingkey
    public void receive2(String message){
    
    
        System.out.println("消费者2:"+message);
    }

}

运行结果
生产者发送消息的routingkey是info,消费者1的routingkey是"info",“error”,“waring”,“debugger”,消费者2是"error",“waring”,因此消费者1会接收到消息,消费者2不会接收到
在这里插入图片描述

routing模型之topict模型(动态路由)

生产者

//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
    
    

    //将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
    @Autowired
    private  RabbitTemplate rabbitTemplate;


    //routing模型之topic模型(动态路由)     生产者
    @Test
    public void routingTopic(){
    
    
        //参数1:交换机名称    参数2:routingkey    参数3:消息内容
        rabbitTemplate.convertAndSend("routing_topic","user.name.password","routing模型,动态路由模型,routing_topic模型");

    }

}

消费者

@Component   //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
public class TopicConsumer {
    
    

    //消费者1
    //@RabbitListener用在方法上,不需要使用@rabbithandler注解
    //消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
    @RabbitListener(bindings={
    
    @QueueBinding(
            value=@Queue,           //创建临时队列
            //队列和交换机进行绑定,  参数1:交换机名  参数2:交换机类型
            exchange=@Exchange(name = "routing_topic",type="topic"),
            key = {
    
    "user.*"})})       //给队列指定动态routingkey
    public void receive1(String message){
    
    
        System.out.println("消费者1:"+message);
    }



    //消费者2
    //@RabbitListener用在方法上,不需要使用@rabbithandler注解
    //消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
    @RabbitListener(bindings={
    
    @QueueBinding(
            value=@Queue,           //创建临时队列
            //队列和交换机进行绑定,  参数1:交换机名  参数2:交换机类型
            exchange=@Exchange(name = "routing_topic",type="topic"),
            key = {
    
    "user.#"})})      //给队列指定动态routingkey
    public void receive2(String message){
    
    
        System.out.println("消费者2:"+message);
    }

}

运行结果
生产者发送消息指定的routingkey是user.name.password,交换机给消费者1指定的routingkey是动态通配符user.*给消费者2指定的routingkey是user.#,所以最后消费者1没能拿到消息,消费者2拿到了该消息进行消费
在这里插入图片描述

rabbitmq应用场景

异步处理

场景说明:用户注册后,需要发送注册邮件和短信,传统做法有两种:串行方式,并行方式

  • 串行方式:将注册信息写入到数据库后,再发送注册邮件和注册短信,3个任务完成后才能返回给客户端。这有一个问题,邮件和短信并不是必须的,它只是一个通知,这种做法让客户端等待没有必要等待的东西
    在这里插入图片描述
  • 并行方式:将注册信息写入到数据库后,发送邮件和短信同时进行,三个任务完成后返回给客户端,并行的方式提高处理时间
    在这里插入图片描述
  • 消息队列:邮件和短信对我们正常使用网站没有任何影响,引入消息队列后,把发送邮件,短信不是必须的业务交给消息队列来处理
    在这里插入图片描述

应用解耦

场景:双11购物,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统接口
在这里插入图片描述这种做法有一个缺点:
当库存系统出现故障时,订单就会失败。订单系统和库存系统高度耦合,引入消息队列
在这里插入图片描述

  • 订单系统:用户下单后,订单系统将消息写入到消息队列,返回给用户下单成功
  • 库存系统:库存系统订阅下单的消息,进行库操作。

流量削峰

场景:秒杀业务,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前加个消息队列
在这里插入图片描述作用:

  • 可以控制活动人数,超过一定阈值的订单直接丢弃(我为什么没有秒杀成功)
  • 可以缓解短时间高流量压垮应用

rabbitmq的集群

镜像集群

镜像队列机制就是将队列在三个节点之间设置主从关系,消息在三个节点之间进行自动同步,如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群整体高可用
在这里插入图片描述

镜像集群搭建

准备3个虚拟机,分别为192.168.0.120、192.168.0.121、192.168.0.122,分别在上面启动rabbitmq,添加一个策略,其中120是主节点,121和122是从节点,生产者发送消息hello world,可以发现所有节点通过镜像方式都接受到该消息,如果此时将主节点120宕机,集群会在剩下的从节点中选取一个作为主节点,如果原来的主节点120重新连接回来,只能作为集群从节点使用。
在这里插入图片描述

策略说明

rabbitmqctl set_policy 策略名 队列正则表达式(队列匹配模式) 镜像定义

镜像定义:
包括三个部分ha-mode,ha-params,ha-sync-mode
ha-mode:指定镜像队列的模式,有效值是如下:
------------all:在集群中所有节点上进行镜像
------------exactly:在指定个数的节点上进行镜像,节点的个数由ha-params指定
------------nodes:在指定节点上进行镜像,节点名称由ha-params指定
ha-params:ha-mode模式需要用到的参数
ha-sync-mode:进行队列中消息同步方式,automatic自动和manual手动
priority:可选参数,policy的优先级

查看已有策略

rabbitmqctl list_policies

在这里插入图片描述添加策略

rabbitmqctl set_policy ha-all "^hello" "{"ha-mode":"all","ha-sync-mode":"automatic"}"

在这里插入图片描述删除策略

rabbitmqctl clear_policy ha-all

在这里插入图片描述节点宕机

rabbitmqctl stop_app

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44421869/article/details/111568672
今日推荐