RabbitMQ使用入门

MQ概念

MQ(Message Quene) : 翻译为消息队列,通过典型的 生产者消费者模型,生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,轻松的实现系统间解耦。别名为 消息中间件,通过利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。

MQ种类

目前还是有很多主流的消息中间件,例如ActiveMQRabbitMQ,大数据常用的KafkaRocketMQ等。

RabbitMQ

该消息中间件是基于AMQP协议,采用erlang语言开发,是部署最广泛的开源消息中间件,是最受欢迎的开源消息中间件之一。

官网RabbitMQ

RabbitMQ的安装

1、安装包下载:官方下载地址
2、下载的安装包包括(在centos7下安装):
在这里插入图片描述
3、步骤:

1、将上面两个安装包放到centos下的root目录

2、安装Erlang依赖包
- rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm

3、安装RabbitMQ安装包
- yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm

4、安装好后将配置文件复制到指定位置
- cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config

5、修改配置文件
- vim /etc/rabbitmq/rabbitmq.config

修改后如下图所示:在这里插入图片描述

6、启动RabbitMQ中的插件管理
- rabbitmq-plugins enable rabbitmq_management

7、启动RabbitMQ服务
	systemctl start rabbitmq-server				  启动
	systemctl restart rabbitmq-server             重启
	systemctl stop rabbitmq-server                 停止

8、查看服务是否启动
- systemctl status rabbitmq-server

9、这时候在外部是访问不了的,这时候需要关闭centos的防火墙
- systemctl disable firewalld
- systemctl stop firewalld

10、访问Web界面
- http://192.168.1.25:15672

11、默认账号密码都是guest

如下图:
在这里插入图片描述

Web界面总览

主界面

在这里插入图片描述

参数介绍:

  1. connections:无论生产者还是消费者,都需要与RabbitMQ建立连接后才可以完成消息的生产和消费,在这里可以查看连接情况
  2. channels:通道,建立连接后,会形成通道,消息的投递获取依赖通道。
  3. Exchanges:交换机,用来实现消息的路由与转发
  4. Queues:队列,即消息队列,消息存放在队列中,等待消费,消息被消费后被移除队列。

添加用户

在这里插入图片描述
用户权限选择:

  • 超级管理员(administrator)

    可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。

  • 监控者(monitoring)

    可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)

  • 策略制定者(policymaker)

    可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。

  • 普通管理者(management)

    仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。

  • 其他
    无法登陆管理控制台,通常就是普通的生产者和消费者。

添加虚拟主机

1、每个虚拟主机就是一个独立的访问路径,不同用户使用不同路径,各自有自己的队列、交换机,互相不会影响。
2、创建好虚拟主机,给相应用户添加访问权限

RabbitMQ应用场景

  1. 异步处理
    例如:在电商系统中,用户完成付款下单后,需要发送订单生成消息和积分累加消息,通过消息中间件的异步处理,可以同时解决这两件时间,并行的方式能提高处理的时间

  2. 应用解耦
    例如:系统在用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口.
    在这里插入图片描述
    但这种做法有一个缺点:当库存系统出现故障时,订单就会失败。 订单系统和库存系统高耦合,引入消息队列后
    在这里插入图片描述
    订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。

    库存系统:订阅下单的消息,获取下单消息,进行库的操作。 就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失

  3. 流量削峰
    例如:在秒杀活动中,一般都会是在短时间内大量的流量访问,导致服务器承受压力过大,导致应用挂掉,应用消息队列后,可实现:

    	可以控制活动人数,超过此一定阀值的订单直接丢弃
    	可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单并处理) 
    

RabbitMQ入门使用

RabbitMQ支持的处理模型(第6种涉及微服务暂不使用)

在这里插入图片描述
在这里插入图片描述

1、建立Maven项目,导入依赖

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.7.2</version>
</dependency>

2、第一种模型(直连)测试:
在这里插入图片描述
理解:

  • P:生产者,也就是要发送消息的程序
  • C:消费者:消息的接受者,会一直等待消息到来。
  • queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。

封装获取ConnectionFactory对象的方法

public class ConnectionConfig {
    public static ConnectionFactory create(){
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.1.25");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/test");
        return connectionFactory;
    }
}

生产者

 //获取连接工厂
        ConnectionFactory connectionFactory = ConnectionConfig.create();

        //创建链接对象
        Connection connection = connectionFactory.newConnection();

        //创建通道
        Channel channel = connection.createChannel();
        /**
         *   '参数1':用来声明通道对应的队列
         *   '参数2':用来指定是否持久化队列
         *   '参数3':用来指定是否独占队列
         *   '参数4':用来指定是否自动删除队列
         *   '参数5':对队列的额外配置
         *
         * **/
        channel.queueDeclare("hello",true,false,false,null);
        for (int i=0;i<10;i++){
            channel.basicPublish("","hello",null,(i+"hello,rabbitmq").getBytes());
        }
        channel.close();
        connection.close();

消费者

 ConnectionFactory connectionFactory = ConnectionConfig.create();

        Connection connection = connectionFactory.newConnection();
        //创建通道
        Channel channel = connection.createChannel();
        channel.queueDeclare("hello",true,false,false,null);
        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));
            }
        });

3、第二种模型(work queue)测试
当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
在这里插入图片描述
理解:

  • P:生产者:任务的发布者
  • C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
  • C2:消费者-2:领取任务并完成任务,假设完成速度快

生产者

ConnectionFactory connectionFactory = ConnectionConfig.create();
        //创建链接
        Connection connection = connectionFactory.newConnection();
        //创建通道
        Channel channel = connection.createChannel();
        /**
         *   '参数1':用来声明通道对应的队列
         *   '参数2':用来指定是否持久化队列
         *   '参数3':用来指定是否独占队列
         *   '参数4':用来指定是否自动删除队列
         *   '参数5':对队列的额外配置
         *
         * **/
        channel.queueDeclare("hello2",false,false,false,null);
        for (int i=0;i<30;i++){
            channel.basicPublish("","hello2",null,(i+"欢迎抢夺rabbitmq").getBytes());
        }

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

消费者-1

 ConnectionFactory connectionFactory = ConnectionConfig.create();

        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("hello2",false,false,false,null);
        channel.basicConsume("hello2",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

ConnectionFactory connectionFactory = ConnectionConfig.create();

        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("hello2",false,false,false,null);
        channel.basicConsume("hello2",false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
             try{
                    Thread.sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("消费者2:"+new String(body));
            }
        });

默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。

由上面的测试可发现,消费者2处理消息较慢,但还是平均分配一半消息给他处理,但实际是想实现处理快的消费者能者多劳,处理慢的消费者就尽量分配少的消息

解决方法:消息自动确认机制

 //一次只接受一条消息,这样能实现能者多劳的效果
        channel.basicQos(1);
        channel.queueDeclare("hello2",false,false,false,null);
        channel.basicConsume("hello2",false,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try{
                    Thread.sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }

                System.out.println("消费者2:"+new String(body));
                //手动确认消息
               channel.basicAck(envelope.getDeliveryTag(),false);

4、第三种模型(fanout)测试
在这里插入图片描述
理解:
在广播模式下,消息发送流程是这样的:

  • 可以有多个消费者
  • 每个消费者有自己的queue(队列)
  • 每个队列都要绑定到Exchange(交换机)
  • 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
  • 交换机把消息发送给绑定在交换机上的所有队列
  • 队列上的消费者都能拿到消息。实现一条消息被多个消费者消费

生产者

 ConnectionFactory connectionFactory = ConnectionConfig.create();
        //创建链接
        Connection connection = connectionFactory.newConnection();

        //创建通道
        Channel channel = connection.createChannel();
        //定义交换机名为log,类型为fanout(扇出)
        channel.exchangeDeclare("log", "fanout");   //广播 一条消息多个消费者同时消费

        //将消息发送到交换机上
        for (int i=0;i<20;i++){
            channel.basicPublish("log", "", null, "你好,这是通过交换机广播的信息".getBytes());
        }
        
        channel.close();
        connection.close();

消费者-1

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

        //通道绑定交换机
        channel.exchangeDeclare("log","fanout");
        //创建临时的队列
        String queue = channel.queueDeclare().getQueue();
        //将临时队列绑定到交换机上
        channel.queueBind(queue,"log","");

        //处理消息
        channel.basicConsume(queue,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

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

        //绑定交换机
        channel.exchangeDeclare("log","fanout");
        //创建临时的队列
        String queue = channel.queueDeclare().getQueue();
        //将临时队列绑定到交换机上
        channel.queueBind(queue,"log","");

        //处理消息
        channel.basicConsume(queue,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));
            }
        });

5、第四种模型Direct(直连)测试:
第三种模型(fanout)中,一条消息会被订阅到交换机上的所有队列消费,在某些场景下,并不适用,我们希望在某些场景下不同的消息被不同的队列消费。

Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
  • 消息的发送方在 向Exchange发送消息时,也必须指定消息的 RoutingKey
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
    在这里插入图片描述

理解:

  • P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给与routing key完全匹配的队列
  • C1:消费者,其所在队列指定了需要routing key 为 error 的消息
  • C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

生产者

ConnectionFactory connectionFactory = ConnectionConfig.create();
        //创建链接
        Connection connection = connectionFactory.newConnection();

        //创建通道
        Channel channel = connection.createChannel();
        //定义交换机名为log,类型为direct
        channel.exchangeDeclare("log_direct", "direct");

        for (int i=0;i<20;i++){
            channel.basicPublish("log_direct", "info", null, "你好,这是通过直连交换机广播的info信息".getBytes());     //rout-key为info类型
            channel.basicPublish("log_direct", "error", null, "你好,这是通过直连交换机广播的error信息".getBytes());    //rout-key为error类型
        }
        channel.close();
        connection.close();

消费者-1(消费info类型信息)

 ConnectionFactory connectionFactory = ConnectionConfig.create();

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

        //绑定交换机
        channel.exchangeDeclare("log_direct","direct");
        //创建临时的队列
        String queue = channel.queueDeclare().getQueue();
        //将临时队列绑定到交换机上,并指定routing-key,这样方便接收指定的消息
        channel.queueBind(queue,"log_direct","info");

        //处理消息
        channel.basicConsume(queue,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));
            }
        });

消费者-2(消费error类型信息)

 ConnectionFactory connectionFactory = ConnectionConfig.create();

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

        //绑定交换机
        channel.exchangeDeclare("log_direct","direct");
        //创建临时的队列
        String queue = channel.queueDeclare().getQueue();
        //将临时队列绑定到交换机上
        channel.queueBind(queue,"log_direct","error");

        //处理消息
        channel.basicConsume(queue,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));
            }
        });

6、第五种模型-订阅模型(Topic)测试:
在这里插入图片描述

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!这种模型Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

统配符

	*    匹配不多不少恰好1个词
	#   匹配一个或多个词

生产者

 ConnectionFactory connectionFactory = ConnectionConfig.create();
        //创建链接
        Connection connection = connectionFactory.newConnection();

        //创建通道
        Channel channel = connection.createChannel();
        //定义交换机名为log_topic,类型为topic
        channel.exchangeDeclare("log_topic", "topic");

        for (int i=0;i<20;i++){
            channel.basicPublish("log_topic", "info.message", null, "你好,这是通过直连交换机广播的info信息".getBytes());
            channel.basicPublish("log_topic", "info.message.m2", null, "你好,这是通过直连交换机广播的info2信息".getBytes());
            channel.basicPublish("log_topic", "error.message", null, "你好,这是通过直连交换机广播的error信息".getBytes());
            channel.basicPublish("log_topic", "error.message.m3", null, "你好,这是通过直连交换机广播的error2信息".getBytes());
        }


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

消费者-1(通配符为*)

 ConnectionFactory connectionFactory = ConnectionConfig.create();

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

        //绑定交换机
        channel.exchangeDeclare("log_topic","topic");
        //创建临时的队列
        String queue = channel.queueDeclare().getQueue();
        //将临时队列绑定到交换机上
        channel.queueBind(queue,"log_topic","info.*");

        //处理消息
        channel.basicConsume(queue,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));
            }
        });

消费者-2(通配符为#)

ConnectionFactory connectionFactory = ConnectionConfig.create();

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

        //绑定交换机
        channel.exchangeDeclare("log_topic","topic");
        //创建临时的队列
        String queue = channel.queueDeclare().getQueue();
        //将临时队列绑定到交换机上
        channel.queueBind(queue,"log_topic","error.#");

        //处理消息
        channel.basicConsume(queue,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));
            }
        });

SpringBoot中使用RabbitMQ

1、新建Springboot项目
2、导入依赖

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

3、配置文件

spring.application.name=springboot_rabbitmq
spring.rabbitmq.host=192.168.1.25
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/test

4、测试
RabbitTemplate 用来简化操作 使用时候直接在项目中注入即可使用

第一种模型
生产者

@Autowired
    private RabbitTemplate rabbitTemplate;
    
    //第一种hello world模型
    @Test
    void test01() {
        rabbitTemplate.convertAndSend("hello","hello word");
    }

消费者

@Component
@RabbitListener(queuesToDeclare = @Queue("hello"))
public class consumer {

    @RabbitHandler
    public void handler(String message){
        System.out.println("接收到的消息:"+message);
    }
}

第二种模型( work)
生产者

//第二种work模型使用
    @Test
    public void test02(){
        for (int i=0;i<10;i++){
            rabbitTemplate.convertAndSend("work","hello word");
        }

消费者

@Component
public class WorkConsumer {

    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void handler(String message){
        System.out.println("消费者1: "+message);

    }

    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void handler2(String message){
        System.out.println("消费者2: "+message);

    }
}

第三种模型(Fanout)
生产者

//第三种:Fanout 广播模型
    @Test
    public void test03(){
        for (int i=0;i<10;i++){
            rabbitTemplate.convertAndSend("logs","","这是广播方式");
        }
    }

消费者

@Component
public class FanoutConsumer {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,      //创建临时的队列
            exchange = @Exchange(name = "logs",type = "fanout")      //设置要绑定的交换机和类型
    ))
    public void handler(String message){
        System.out.println("消费者一:消息是: "+message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,      //创建临时的队列
            exchange = @Exchange(name = "logs",type = "fanout")      //设置要绑定的交换机和类型
    ))
    public void handler2(String message){
        System.out.println("消费者二:消息是: "+message);
    }
}

第四种模型(Route)
生产者

//Route 路由模型
    @Test
    public void test04(){
        for (int i=0;i<10;i++){
            rabbitTemplate.convertAndSend("logs_direct","info","这是info的信息");
            rabbitTemplate.convertAndSend("logs_direct","error","这是error的信息");
        }
    }

消费者

@Component
public class DirectConsumer {

    @RabbitListener(bindings = {
            @QueueBinding(
                    value=@Queue(),
                    key = {"info","error"},
                    exchange=@Exchange(name = "logs_direct",type = "direct")
            )
    })
    public void handler(String message){
        System.out.println("message:"+message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value=@Queue(),
                    key = {"info"},
                    exchange=@Exchange(name = "logs_direct",type = "direct")
            )
    })
    public void handler2(String message){
        System.out.println("message2:"+message);
    }
}

第五种模型(Topic动态路由模型)
生产者

//Topic 订阅模型(动态路由模型)
    @Test
    public void test05(){
        for (int i=0;i<10;i++){
            rabbitTemplate.convertAndSend("logs_topic","info.message","这是info的信息");
            rabbitTemplate.convertAndSend("logs_topic","error.message","这是error的信息");
        }
    }

消费者

@Component
public class TopicConsumer {

    @RabbitListener(bindings = {
          @QueueBinding(
                  value = @Queue,
                  key = {"info.*"},
                  exchange= @Exchange(type = "topic",name = "logs_topic")
          )
    })
    public void handler(String message){
        System.out.println("message: "+message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    key = {"error.#"},
                    exchange= @Exchange(type = "topic",name = "logs_topic")
            )
    })
    public void handler2(String message){
        System.out.println("message2 : "+message);
    }
}

RabbitMQ集群

本节后续再更新吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值