Java程序员如何理解并使用RabbitMQ(一),从底层出发讲解MQ消息中间件的经典的七种工作模式

RabbitMQ

前言:

RabbitMQ是一个消息代理 : 负责接受转发消息。
放在生活的角度来看,RabbitMQ就好比快递站点,快递员将快递放到站点,并确信快递员最终会把快递交给收件人。
在这个例子里面,RabbitMQ好比快递站点,快递员。
消费者是收件人。

AMQP协议中的核心思想是生产者和消费者隔离,生产者不直接把消息发给队列消费。
生产者通常不知道是否一个消息会被发送到队列,或者说生产者不知道消费何时会被队列消费,怎么消费。就好比快递员把快递放到站点,他不知道你何时过来取。生产者只是将消息发送到一个交换机,先由exchange接受,然后exchange按照特定的策略转发到Queue进行存储,同理,消费者也是如此,exchange就类似一个交换机,转发各个消息分发到相应的队列中。

在这里插入图片描述

RabbitMQ的使用:

关于RabbitMQ的使用,官方推荐了七种工作模式,在早期版本,只有五种,后面添加了RPC的工作模式,然后又添加了一种。但是新的东西,总不是最核心的。我们从第一种工作模式开始讲起。。

主要介绍前6种,主要有:

简单模式:一个生产者,一个消费者,生产者将消息发送到队列,消费者从队列中获取消息

work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一

消息订阅模式:一个生产者发送的消息会被多个消费者获取

路由模式:发送消息到交换机并且指定路由key,消费者将队列绑定到交换机时需要指定路由key

topic模式:将路由key和某个模式的进行匹配,此时队列绑定到一个模式上,“#”匹配一个词或多个词,*只匹配一个词


简单模式:

简单模式,只有生产者和消费者的概念:

生产者如下:

/**
 *	获取MQ的链接
 *	1.创建链接
 *	2.创建通道
 *	3.
 */
public class Sender {
    private final static String QUEUE_NAME = "simple_queue";
 
    public static void main(String[] args) throws IOException {
        //1.创建连接
        Connection connection = ConnectionUtil.getConnection();
        //2.创建通道
        Channel channel = connection.createChannel();
        //声明队列,需要输入如下参数:
        /**
         *  队列名,声明是哪个队列
         *  是否持久化,是否在消费后还存在
         *  是否排外  即只允许该channel访问该队列   一般等于true的话,用于一个队列只能有一个消费者来消费的场景
         *  是否自动删除  消费完消息后自动删除
         *  其他属性  这里暂时为null,在其他模式会使用
         *
         */
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        //消息内容,参数说明如下:
        /**
         * 交换机,第一种工作模式,不需要交换机的概念
         * 队列名,告诉它是哪个队列的消息
         * 其他属性  路由,第一种工作模式不需要使用
         * 消息body  通过getBytes方式获取
         */
        String message = "Rabbit你好~";
        channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
        System.out.println("[x]Sent '"+message + "'");
 
        //最后关闭通关和连接,资源很珍贵,使用完毕请关闭
        channel.close();
        connection.close();
 
 
    }
}
 

消费者如下:
批注:生产者是创建链接和创建通道,在消费者里需要获取链接和获取通道

public class Receiver {
    private final static String QUEUE_NAME = "simple_queue";
 
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.获取连接
        Connection connection = ConnectionUtil.getConnection();
        //2.获取通道
        Channel channel = connection.createChannel();
 		//参数不再重复说明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(QUEUE_NAME, true, consumer);
 
        while(true){
            //该方法会阻塞
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println("[x] Received '"+message+"'");
 
        }
    }
}

工作模式:

工作模式的概念是,一个生产者,多个消费者,每个消费者获取到的消息唯一,好比发报纸,发报纸的是一个人,获取报纸的是很多人,每个人获取的报纸内容一致
同样只需要利用到生产者和消费的概念

//生产者
public class Sender {
    private final  static String QUEUE_NAME = "queue_work";
 
    public static void main(String[] args) throws IOException, InterruptedException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        for(int i = 0; i < 100; i++){
            String message = "冬马小三" + i;
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("[x] Sent '"+message + "'");
            Thread.sleep(i*10);
        }
 
        channel.close();
        connection.close();
    }
}

设置两个消费者

//消费者1
public class Receiver1 {
    private final static  String QUEUE_NAME = "queue_work";
 
    public static void main(String[] args) throws IOException, InterruptedException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        channel.queueDeclare(QUEUE_NAME, false,false, false,null);
        //同一时刻服务器只会发送一条消息给消费者
        channel.basicQos(1);
 
        QueueingConsumer consumer = new QueueingConsumer(channel);
        //关于手工确认 待之后有时间研究下
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        while(true){
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println("[x] Received1 '"+message+"'");
            Thread.sleep(10);//让消费者1睡眠10ms
            //返回确认状态
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
 
    }
}
public class Receiver2 {
    private final static  String QUEUE_NAME = "queue_work";
 
    public static void main(String[] args) throws IOException, InterruptedException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        channel.queueDeclare(QUEUE_NAME, false,false, false,null);
        //同一时刻服务器只会发送一条消息给消费者
        channel.basicQos(1);
 
        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        while(true){
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println("[x] Received2 '"+message+"'");
            Thread.sleep(1000);//让消费者2睡眠1000ms
            //返回确认状态
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

测试结果:

1、 消费者1和消费者2获取到的消息内容是不同的,同一个消息只能被一个消费者获取。

2、 如果把睡眠解除,或者让消费者1,2睡眠一样长的事件,消费者1和消费者2获取到的消息的数量是相同的,一个是奇数一个是偶数。

如果把以下代码注释,那么消费者1,2最终会拿到一样多的消息,只不过消费者1需要执行很久。。也就是说,如果加了这行代码,限定服务器每次只给一个消息给消费者消费,不会造成堵塞,会造成执行快的消费者消费更多的消息。。。‘’能者多劳“

//同一时刻服务器只会发送一条消息给消费者
        channel.basicQos(1);

消息确认:
消息成功从队列拿出,前面提到快递员根本不知道你什么时候过来站点拿快递,也就是说生产者不知道消费者何时归来消费。消费成功没有。

有两种模式可以提供消息确认:
模式1:自动确认
只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。
模式2:手动确认
消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。
手动确认代码如下:
在这里插入图片描述


订阅模式

1.和工作模式不同的是:一个消息 被多个消费者消费
2.生产者不把消息直接发送到队列,而是发送到交换机
3.每个队列都需要和交换机进行绑定
4.生产者发送消息,经过交换机,到达队列,实现一个消息被多个消费者获取的目的

向交换机发送消息:

public class Send {

    private final static String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明exchange,交换机,类型必须是:fanout
        //参数一是交换机名称
        //参数二是交换机类型,必须fanout
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        // 消息内容
        String message = "商品已经被更新,id=1001";
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
        System.out.println(" 后台系统: '" + message + "'");

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

批注:消息发送到没有队列绑定的交换机时,消息将丢失
因为,交换机没有存储消息的能力,消息只能存在在队列中。

消费者1:

public class Recv {

    private final static String QUEUE_NAME = "test_queue_ps_1";

    private final static String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] argv) throws Exception {

        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);

        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);

        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" 前台系统: '" + message + "'");
            Thread.sleep(10);

            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

消费者2:

public class Recv2 {

    private final static String QUEUE_NAME = "test_queue_ps_2";

    private final static String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] argv) throws Exception {

        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);

        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);

        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" 搜索系统: '" + message + "'");
            Thread.sleep(10);

            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

测试结果:

同一个消息被多个消费者获取

常见面试题:

使用订阅模式能否实现商品数据的同步?
答案:可以的。

后台系统就是消息的生产者。
前台系统和搜索系统是消息的消费者。
后台系统将消息发送到交换机中,前台系统和搜索系统都创建自己的队列,然后将队列绑定到交换机,即可实现。

剩余部分下次更新!!!!!
敬爱于明天
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值