RbbitMQ自我总结与分析

一.概念

1.生产者(P-producer):生产消息,发送消息
2.消费者(C-consumer):消费自己已绑定队列中的消息
3.交换机(Exchanges):类似路由器(交换机通过路由键和队列进行绑定,分发消息到绑定的消息队列,本身并没有存储功能,所以,如果交换机没有绑定队列,则生产者生产消息一到交换机就消失了),type主要由以下五种

一、简单模型(未经过交换机,一个消息只能被消费一次)
1.简单模型:P—>Q—>C  一生一队一消
2.工作(work)消息模型: P—>Q---->多C  一生一队多消
在这里插入图片描述
二、订阅模型(都经过交换机,由交换机进行分发,这样一个消息才能被消费多次)
3.Fanout:广播,将消息分发给所有绑定交换机的队列 (发送到所绑定有的队列)
 
4.Direct(路由模型):定向,把消息分发给符合指定routing key 的队列 (指向性的发送到选中的绑定队列:生产者发送消息绑定routingKey(路由键),只转发到也绑定该routingKey的队列。如:消息绑定delete路由键能够匹配routingKey为delete的队列)
 
5. Topic(通配符模型):通配符,把消息分发给符合routing pattern的队列(Topic类型的与Direct,实质也是根据routingKey把消息路由到不同的队列。只不过Topic类型交换机可以让队列在绑定routingKey的时候使用通配符!如:消息绑定#.delete.# 能够匹配routingKey为delete.a,gg.delete.a.bc的队列)
注意: * 匹配一个词,# 匹配一个或多个词

4.消息队列(Queues):接收,存储,转发消息----->类似家门口的邮件邮箱(消息就像一封封信件,等着消费者去消费)
5.Channel:信道,消息读写等操作在信道中进行。客户端可以建立多个信道,每个信道代表一个会话任务。在Idea操作RabbitMQ时所有Api都是通过Channel来操作(1.获取连接,2.创建信道,3.通过信道声明队列,和消费者以及相关绑定)

本质:

生产者:发送消息到交换机
交换机:接收到消息,根据type分发到绑定的消息队列
消息队列:队列接收消息,进行存储等待消费者(C)进行消费。
消费者:启动消费者,消费者就会去其绑定的队列进行消费。
 
在这里插入图片描述

绑定:

第一,消费者与消息队列进行绑定,这样消费者才能消费该队列里的消息。
第二,消息队列与交换机进行绑定(通过路由键进行绑定,这样交换机能通过路由键确定发送到哪个绑定的队列)。
在这里插入图片描述

二、扩展:
消息确认机制(ACK):

ACK(Acknowledge character):即是确认字符
  RabbitMQ有一个ACK机制。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收,如果消费者消费消息后没有发送ACK,则消息不会被消费

当消费者开始进行消费时,消息队列中的消息会进入Unacked状态,只有接收到消费者的ACK,那么该消息才被消费,否则状态返回Ready等待消费

在这里插入图片描述

在代码中ACK分两种情况:

  • 自动ACK:消息一旦被接收,消费者自动发送ACK(无论消费者在消费时有没有异常,都会把消息从队列中移出)
  • 手动ACK:消息接收后,不会发送ACK,需要手动调用(只有调用时发送了ACK,消息才会从队列中移出。否则消息仍然回到Ready状态)
  // 监听队列,第二个参数:是否自动进行消息确认。
     channel.basicConsume(QUEUE_NAME, true, consumer);
        // 手动进行ACK
        channel.basicAck(envelope.getDeliveryTag(), false);
能者多劳模式

BabbitMQ在多个消费者消费队列中的消息时,默认的是平均分配,BabbitMQ会将消息先平均分配给多个消费者,然后让他们自己慢慢去消费
如90个消息 a:30,b:30 ,c:30
这样就会出现资源浪费,比如a的机子比较快,处理消息能力强,应该多消费一些,这样可以提升效率

 // 设置每个消费者同时只能处理一条消息
    channel.basicQos(1);

这样设置后就可以开启能者多劳模式了

消息持久化

如何避免消息丢失?
1) 消费者的ACK机制。可以防止程序异常导致消费者丢失消息。
2) 但是,如果在消费者消费之前,MQ就宕机了,消息就没了。
是可以将消息进行持久化呢?
我们创建的交换机、队列、消息默认都是储存在内存中,所以要将消息持久化,
前提是:队列、Exchange(交换机)都持久化
在这里插入图片描述
可以看到RabbitMQ自带默认的交换机Features都有个D标志=durable持久化
而我们的direct_exchange_test则没有,消息队列同理
在这里插入图片描述
如果交换机,队列没有设置durable持久化,那么服务器一旦宕机,那么交换机和队列就会消失了,因为他们没有持久化在服务器中,那么消息是存在队列中的也自然不会持久。
所以,要将消息持久化,前提是:队列、Exchange(交换机)都持久化
交换机持久化(在生产者声明交换机时进行设置,告诉RabbitMQ我的交换机要持久化储存,durable=true)
在这里插入图片描述
消息持久化(在生产者发送消息时进行设置,告诉RabbitMQ这个消息是持久化的)
在这里插入图片描述
队列持久化(在消费者声明队列时进行设置,告诉RabbitMQ这个队列是持久化的)
在这里插入图片描述
妈妈再也不用担心我的消息会丢失了!

三、代码示例(防止忘记,多码杀秃):

简单模型(未经过交换机,消息只可消费一次):

简单生产者:

public class Send {
    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 从连接中创建通道,使用通道才能完成消息相关的操作
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 消息内容
        String message = "Hello World!";
        // 向指定的队列中发送消息
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        //关闭通道和连接
        channel.close();
        connection.close();
    }

简单消费者:

public class Recv {
    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息体
                String msg = new String(body);
                System.out.println(" [x] received : " + msg + "!");
            }
        };
        // 监听队列,第二个参数:是否自动进行消息确认。
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

工作(work)模型生产者:

public class Send {
    private final static String QUEUE_NAME = "test_work_queue";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 循环发布任务
        for (int i = 0; i < 50; i++) {
            // 消息内容
            String message = "task .. " + i;
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");

            Thread.sleep(i * 2);
        }
        // 关闭通道和连接
        channel.close();
        connection.close();
    }
}

工作(work)模型消费者:

public class Recv {
    private final static String QUEUE_NAME = "test_work_queue";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 获取通道
        final Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 设置每个消费者同时只能处理一条消息
        channel.basicQos(1);
        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel  ) {
            // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息体
                String msg = new String(body);
                System.out.println(" [消费者1] received : " + msg + "!");
                try {
                    // 模拟完成任务的耗时:1000ms
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
                // 手动ACK
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 监听队列。
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
}

订阅模型(经过交换机,消息可多次消费):

fonout类型交换机生产者:

public class Send {
    private final static String EXCHANGE_NAME = "fanout_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        
        // 声明exchange,指定类型为fanout
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        
        // 消息内容
        String message = "Hello everyone";
        // 发布消息到Exchange
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
        System.out.println(" [生产者] Sent '" + message + "'");

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

fonout消费者:

//消费者1
public class Recv {
    private final static String QUEUE_NAME = "fanout_exchange_queue_1";
    private final static String EXCHANGE_NAME = "fanout_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息体
                String msg = new String(body);
                System.out.println(" [消费者1] received : " + msg + "!");
            }
        };
        // 监听队列,自动返回完成
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

direct类型交换机生产者:

public class Send {
    private final static String EXCHANGE_NAME = "direct_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        // 声明exchange,指定类型为direct
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        // 消息内容
        String message = "商品删除了, id = 1001";
        // 发送消息,并且指定routing key 为:insert ,代表新增商品,并声明消息持久化
        channel.basicPublish(EXCHANGE_NAME, "delete", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
        System.out.println(" [商品服务:] Sent '" + message + "'");

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

direct消费者:

public class Recv {
    private final static String QUEUE_NAME = "direct_exchange_queue_1";
    private final static String EXCHANGE_NAME = "direct_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        
        // 绑定队列到交换机,同时指定需要订阅的routing key。假设此处需要update和delete消息
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "update");
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "delete");
        
        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
            @Override
   public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息体
                String msg = new String(body);
                System.out.println(" [消费者1] received : " + msg + "!");
            }
        };
        // 监听队列,自动ACK
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

topic类型交换机生产者:

public class Send {
    private final static String EXCHANGE_NAME = "topic_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        // 声明exchange,指定类型为topic,并声明交换机持久化
        channel.exchangeDeclare(EXCHANGE_NAME, "topic",true);
        // 消息内容
        String message = "新增商品 : id = 1001";
        // 发送消息,并且指定routing key 为:insert ,代表新增商品
        channel.basicPublish(EXCHANGE_NAME, "item.insert", null, message.getBytes());
        System.out.println(" [商品服务:] Sent '" + message + "'");

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

topic消费者:

public class Recv {
    private final static String QUEUE_NAME = "topic_exchange_queue_1";
    private final static String EXCHANGE_NAME = "topic_exchange_test";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = ConnectionUtil.getConnection();
        // 获取通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        
        // 绑定队列到交换机,同时指定需要订阅的routing key。需要 update、delete
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.update");
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.delete");

        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息体
                String msg = new String(body);
                System.out.println(" [消费者1] received : " + msg + "!");
            }
        };
        // 监听队列,自动ACK
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值