RabbitMQ学习笔记

1 什么是MQ

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

不同的MQ特点

  • ActiveMQ 是Apache出品,最流行,能力强劲的开源消息总线,它是一个完全支持JMS规范的消息中间件。
    丰富的API,多种集群架构模式让ActiveMQ在业界成为老牌消息中间件,在中小企业中很受欢迎
  • Kafka 是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。kafka主要的特点是基于Pull的模式来处理消息消费。追求高吞吐量,一开始的目的就是用于
    日志手机和传输。0.8版本开始支持负载,不支持事务,对消息的重复、丢失、错误没有严格要求,适合生产大量数据互联网数据收集业务
  • RocketMQ 是阿里开源的消息中间件,他是纯java开发,具有高吞吐量,高可用性,适合大规模分布式系统应用的特点,RocketMQ 思路起于与Kafka,但并不是Kafka的一个Copy,
    他对消息可靠传输及事务做了优化,目前在阿里集团广泛应用于交易、充值、流计算、消息推送,日志流式处理,binglog分布式等场景
  • RabbitMQ 是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息,队列、路由(包括点对点和发布、订阅)、可靠性、安全。
    AMQP协议更多用在企业系统对数据一致性、稳定性和可靠性要求较高的场景,对性能和吞吐量要求其次。

2 Rabbitmq的引言

实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。
所有主要的编程语言均有与代理接口通讯的客户端库。
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
在这里插入图片描述
在这里插入图片描述

2.1 安装MQ

网上很多就不演示了,后期有空了在尝试自己安装试试

2.2 第一种模型(直连)

在这里插入图片描述
在上图模型中,有以下概念:

  • P生产者,也就是要发送消息的程序
  • C消费者,消息的接受者,会一直等待消息到来
  • Queue消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从中获取消息。
    生产者
public class Provider {
    @Test
    public void testSendMessage() throws IOException, TimeoutException {
        //创建连接mq连接工厂对象
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //连接主机 端口 虚拟主机 虚拟主机的用户名和密码
        connectionFactory.setHost("47.110.39.229");
        connectionFactory.setPort(3302);
        //connectionFactory.setVirtualHost("");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("xxx");
        //创建连接 获取通道
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        /**
         * 通道绑定对应消息队列
         * String queue,参数1 队列名称 如果队列不存在自动创建
         * boolean durable,参数2 用来定义队列特性是否需要持久化 true 持久化队列 false不持久化
         * boolean exclusive, 参数3 是否独占队列 true独占队列 false 不独占
         * boolean autoDelete, 是否消费完后自动删除队列 true自动删除 false 不自动删除
         * Map<String, Object> arguments 额外附加参数
         */
        channel.queueDeclare("hello",true,false,false,null);
        //发布消息
        //参数1交换机名称,参数2队列名称,BasicProperties 参数3传递消息额外设置 body参数4消息的具体内容
        channel.basicPublish("","hello",null,"hello rabbitmq".getBytes());

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

消费者

public class Customer {
    public static void main(String[] args)throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("47.110.39.229");
        connectionFactory.setPort(3302);
        //connectionFactory.setVirtualHost("");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("xxx");

        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("hello",true,false,false,null);
        /**
         * 消费消息
         *参数1 消费那个队列的消息 队列名称
         *参数2 开始消费自动确认机制
         *参数3 消费时的回调接口
         */
        channel.basicConsume("hello",true,new DefaultConsumer(channel){
            //最后一个参数:消息队列中的消息
            public void handleDelivery(String consumerTag, Envelope envelope,AMQP.BasicProperties properties,byte[] body){
                System.out.println("body======>"+new String(body));
            }
        });
    }
}

工具封装

public class RabbitMQUtils {
    private static  ConnectionFactory connectionFactory;
    //静态代码块,类加载的时候只执行一次
    static {
        connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("47.110.39.229");
        connectionFactory.setPort(3302);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("xxx");
    }

    public static Connection getConnection() {
        try {
            return connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void closeConnectionAndChannel(Connection con, Channel channel){
        try {
            if(channel!=null){
                channel.close();
            }
            if(con!=null){
                con.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

源码分析

String queue, 队列名称,如果队列不存在自动创建
boolean durable, 用来定于队列是否需要持久化 true 持久化队列 false 不持久化
boolean exclusive,是否独占队列 true独占队列 false 不独占
boolean autoDelete, 是否消费完后自动删除队列 true自动删除 false 不自动删除
Map<String, Object> arguments 额外附加参数

public com.rabbitmq.client.AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException {
    com.rabbitmq.client.AMQP.Queue.DeclareOk ok = this.delegate.queueDeclare(queue, durable, exclusive, autoDelete, arguments);
    this.recordQueue(ok, queue, durable, exclusive, autoDelete, arguments);
    return ok;
}

String exchange, 交换机名称
String routingKey,队列名称
BasicProperties props,传递消息的额外设置
在这里插入图片描述
byte[] body 消息的具体内容

public void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException {
    this.delegate.basicPublish(exchange, routingKey, props, body);
}

2.3 第二种模式(工作)

Work queues,也被称为(task queues),任务模型。当消息处理比较耗时,可能生产消息的速度会远远大于消息的消费速度。
长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work模型;让多个消费者绑定一个队列,共同消费队列中的消息。
队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
在这里插入图片描述
角色:

  • P生产者:任务的发布者
  • C1消费者1,领取任务并且完成任务,假设完成速度较慢
  • C2消费者2,领取任务并且完成任务,假设完成速度较快
    生产者
public class Provider {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("work",true,false,false,null);
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("","work",null,("hello work queue " +i).getBytes());
        }
        RabbitMQUtils.closeConnectionAndChannel(connection,channel);
    }
}

消费者1

public class Customer1 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("work",true,false,false,null);
        channel.basicConsume("work",true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body){
                System.out.println("消费者-1:"+new String(body));
            }
        });
    }
}

消费者2

public class Customer1 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare("work",true,false,false,null);
        channel.basicConsume("work",true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body){
                System.out.println("消费者-1:"+new String(body));
            }
        });
    }
}

信息打印
在这里插入图片描述
在这里插入图片描述
总结:
经过测试,我们发现默认情况下,rabbitMQ将按顺序将每个消息发送到下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。

消息的自动确认机制
关闭自动确认实现能者多劳
在这里插入图片描述
生产者不变~消费者3

public class Customer3 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        final Channel channel = connection.createChannel();

        channel.basicQos(1);//每次只能消费一个信息
        channel.queueDeclare("work",true,false,false,null);
        //参数2 自动确认 false关闭自动确认 true 开启自动确认
        channel.basicConsume("work",false,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
                System.out.println("消费者-3:"+new String(body));
                //手动确认 ~ 参数1 确认队列中那个具体消息 参数2 是否开启多个消息同时确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }
}

生产者不变~消费者4

public class Customer4 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        final Channel channel = connection.createChannel();

        channel.basicQos(1);//每次只能消费一个信息
        channel.queueDeclare("work",true,false,false,null);
        //参数2 自动确认 false关闭自动确认 true 开启自动确认
        channel.basicConsume("work",false,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
                try {
                   Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者-4:"+new String(body));
                //手动确认 ~ 参数1 确认队列中那个具体消息 参数2 是否开启多个消息同时确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        });
    }
}

信息打印
在这里插入图片描述
在这里插入图片描述

2.4 第三种模式(fanout) ~ 广播模式

Fanout 扇出,也称广播模型
在这里插入图片描述
在广播模式下,消息发送流程是这样的:
可以有多个消费者

  • 每个消费者有自己的queue(队列)
  • 每个队列都要绑定到自己的Exchange(交换机)
  • 生产者发送消息,只需要发送到交换机,交换机来决定要给那个队列,生产者无法决定。
  • 交换机把消息发送给绑定过的所有队列
  • 队列的消费者都能拿到消费。实现一条消息被多个消费者消费
    生产者
public class Provider {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //将通道声明指定交换机 参数1 交换机名称 参数2 交换机类型 fanout 广播模式
        channel.exchangeDeclare("logs","fanout");
        //发送消息
        channel.basicPublish("logs","",null,"fanout type message".getBytes());
        RabbitMQUtils.closeConnectionAndChannel(connection,channel);
    }
}

消费者1

public class Customer1 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //通道绑定交换机
        channel.exchangeDeclare("logs","fanout");
        //临时队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定消息和队列
        channel.queueBind(queueName,"logs","");
        //消费
        channel.basicConsume(queueName,true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
                System.out.println("fanout 消费者-1:"+new String(body));
            }
        });
    }
}

消费者2

public class Customer2 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //通道绑定交换机
        channel.exchangeDeclare("logs","fanout");
        //临时队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定消息和队列
        channel.queueBind(queueName,"logs","");
        //消费
        channel.basicConsume(queueName,true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
                System.out.println("fanout 消费者-2:"+new String(body));
            }
        });
    }
}

消费者3

public class Customer3 {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //通道绑定交换机
        channel.exchangeDeclare("logs","fanout");
        //临时队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定消息和队列
        channel.queueBind(queueName,"logs","");
        //消费
        channel.basicConsume(queueName,true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
                System.out.println("fanout 消费者-3:"+new String(body));
            }
        });
    }
}

测试 所有消费者都输出结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.5 第四种模型(Routing)~ 路由模式

2.5.1 routing之订阅模型-Direct(直连)

在fanout模型中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的exchange
在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,waring的消息

生产者

public class DirectProvider {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明交换机 参数1 交换机名称 参数2 direct 路由模式
        String exchange = "logs_direct";
        String type = "direct";
        channel.exchangeDeclare(exchange,type);
        //发送消息
        String routingKey = "info";
        String message = "这是direct模型发布的基于rout key:["+routingKey+"]发送的消息";
        channel.basicPublish(exchange,routingKey,null,message.getBytes());
        RabbitMQUtils.closeConnectionAndChannel(connection,channel);
    }
}

Error~消费者

public class DirectErrorCustomer {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明交换机以及交换机类型
        String exchange = "logs_direct";
        String type = "direct";
        channel.exchangeDeclare(exchange,type);
        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //基于rout key绑定队列和交换机
        String routingKey = "error";
        channel.queueBind(queue,exchange,routingKey);
        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
                System.out.println("Direct Error Customer 消费者:"+new String(body));
            }
        });
    }
}

All~消费者

public class DirectAllCustomer {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明交换机以及交换机类型
        String exchange = "logs_direct";
        String type = "direct";
        channel.exchangeDeclare(exchange,type);
        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //基于rout key绑定队列和交换机
        String routingKey1 = "info";
        String routingKey2 = "warning";
        String routingKey3 = "error";
        channel.queueBind(queue,exchange,routingKey1);
        channel.queueBind(queue,exchange,routingKey2);
        channel.queueBind(queue,exchange,routingKey3);
        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
                System.out.println("Direct All Customer 消费者:"+new String(body));
            }
        });
    }
}

测试结果
当为error时都输出,当info时只有all输出:
在这里插入图片描述
在这里插入图片描述

2.5.2 Routing之订阅模型-topic

Topic类型的exchange与direct相比,都可以根据routingkey把消息路由到不同的队列。
只不过topic类型的exchange可以让队列在绑定routing key的时候使用通配符!
这种模型routingkey一般都是由一个或者多个单词组成,多个单词之间以“.”分割。列如:item.insert
在这里插入图片描述
通配符

(star)匹配不多不少恰好1个词
#(hash)匹配零个或多个词
如:
audit.# 匹配audit.irs.cor或者audit.irs等
audit.
只能匹配audit.irs

生产者

public class TopicProvider {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明交换机 参数1 交换机名称 参数2 direct 路由模式
        String exchangeName = "exchange_topics";
        String type = "topic";
        channel.exchangeDeclare(exchangeName,type);
        //发送消息
        String routingKey = "user";
        String message = "这是topic模型发布的基于rout key:["+routingKey+"]发送的消息";
        channel.basicPublish(exchangeName,routingKey,null,message.getBytes());
        RabbitMQUtils.closeConnectionAndChannel(connection,channel);
    }
}

Star消费者

public class StarCustomer {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明交换机以及交换机类型
        String exchangeName = "exchange_topics";
        String type = "topic";
        channel.exchangeDeclare(exchangeName,type);
        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //基于rout key绑定队列和交换机
        String routingKey = "user.*";
        channel.queueBind(queue,exchangeName,routingKey);
        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
                System.out.println("topic *(star) Customer 消费者:"+new String(body));
            }
        });
    }
}

Hash消费者

public class HashCustomer {
    public static void main(String[] args) throws IOException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明交换机以及交换机类型
        String exchangeName = "exchange_topics";
        String type = "topic";
        channel.exchangeDeclare(exchangeName,type);
        //创建一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //基于rout key绑定队列和交换机
        String routingKey = "user.#";
        channel.queueBind(queue,exchangeName,routingKey);
        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            public void  handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
                System.out.println("topic #(hash) Customer 消费者:"+new String(body));
            }
        });
    }
}

测试:*只能匹配一个,#匹配一个或者多个
在这里插入图片描述
在这里插入图片描述

2 Springboot整合

后续更新持续更新
添加依赖及配置
RabbitTemplate

3 应用场景

3.1 异步处理

3.2 应用解耦

3.3 流量消峰

4 MQ常见面试问题

4.1 RabbitMQ 如何保证消息不丢失?

在这里插入图片描述

第一种:生产者弄丢了数据。生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。(开启confirm模式)
第二种:RabbitMQ 弄丢了数据。MQ还没有持久化自己挂了(开启rabbitMQ持久化)
第三种:消费端弄丢了数据。刚消费到,还没处理,结果进程挂了,比如重启了。(关闭rabbitMQ自动ACK)
在这里插入图片描述

4.2 Rabbitmq保证消息不被重复消费

1.当拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,
那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
2.当拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
3.如果上面两种情况还不行,准备一个第三方存储,来做消费记录。以redis为例,给消息分配一个全局id,
只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值