RabbitMQ的基本使用(二)

版权声明:禁止转载 https://blog.csdn.net/qq_36505948/article/details/82745622

一、MQ的工作原理?

(1):RabbitMQ作为消息代理,在开发中主要负责转发消息!

(2):对RabbitMQ的理解需要明白以下几个节点的意义:

--------生产者:消息的制造者与发起者,消息的最初是从此处向MQ发送的,类似于制造商品的工厂

--------交换机:直接与生产者沟通,生产者的每个消息都有一个路由键(字符串),交换机根据这个路由键选择 不同队列进行分发

--------消费者:消息的接收者与处理者,从MQ中获取消息并将消息处理掉的,类似于从商店购买商品的客人

--------MQ队列:消息的仓库与流水线,理论上是一个无限大的缓冲区,生产者可以将消息发送到队列中存储起来,也满足消费者逐渐从队列中取出处理掉的流水线!多个生产者可以将消息发送到同一个队列中,多个消费者也可以只从同一个队列接收数据!MQ队列可以直接接收生产者信息(MQ默认一个空白字符串命名的交换器),也可以通过交换机[--exchnge--]接收!  在有交换机的环境下,交换机通过多种方式用绑定键与消息队列互相绑定!

注意:队列和消费者两者之间没有必然的联系,在生产中因为对列中转站的特性,要负载很多的数据,为了避免内存不够,通常是与消费者部署在不同服务器上的!

(3):基本操作流程?

-------创建ConnectionFactory对象,利用抽象工厂模式获取连接

-------从获取的连接中获取渠道

-------创建队列,将渠道与队列绑定

-------生产者通过将消息放入队列中,消费者创建消费对象从队列中获取消息

注意:此处为基本通讯原理,后面有其他改变也是基于此进行封装改造,不会再违背此基础~


二、初步了解MQ的机制?

当我们处理一条消息的时候,网络异常是时刻都可能会发生的!网络问题,系统异常问题,甚至MQ问题,硬件问题等等,这样的后果都可能造成正在处理的消息或者任务因突然中断而丢失。MQ为了确保消息或者任务不会丢失,使用了一个确认消息机制,消息确认-ACK。MQ在推送消息给消费者这个过程中,被推送的数据会被一直保存在容器中,消费者收到队列处理完后会反馈
一个ACK消息给MQ进行确认,MQ确认完成后会将容器中的消息给删除掉。如果消费者在这个处理过程中挂了或者有某个消费者挂了,任务也不会消失也不会超时,而是会被一直保存在队列容器中,只有收到消费者返回的ACK才会进行删除。但是有一点需要注意,在使用MQ时候,如果消费者已经消失,生产者再次推送消息会显示失败!

注意:我们需要明白正常情况下“消息确认机制”是默认打开的,但如果存在忘记的情况或则认为处理的情况,则会导致MQ因迟迟收不到确认消息,而不断保存任务,最终MQ爆满而阻塞!

final Consumer consumer = new DefaultConsumer(channel) {
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    String message = new String(body, "UTF-8");
    System.out.println("我是消费者,我从队列里面接收到消息:'" + message + "'");
    try {
      doSomeThing(message);
    } finally {
      System.out.println(" [x] Done");
      channel.basicAck(envelope.getDeliveryTag(), false);//ACK确认手动关闭
    }
  }
};
==========================================================================================================================================

消息持久化:分为两部分,通过消息确认来保证消费者挂掉消息不丢失,通过消息持久化来保证MQ挂掉消息不丢失queueDeclare()是一个队列,如果需要保证消息的持久化,那么必须保持队列的持久化。也即是说我们需要同时设置队列持久化和消息持久化,单独只设置一个都是无效的!

-------队列持久化:

channel.queueDeclare("queue.persistent.name", dectable, false, false, null);
--------保持dectable=true
==================================================================================================================================================================
源码:Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments) throws IOException;
解释:其中queus是指消息队列,exclusive=true的时候,队列为排他队列,允许首次访问他的排他队列基于该连接可见且持久化,一旦连接断开则队列持久化失效并且队列被删除。
排他队列也允许该连接的不同信道访问,但如果该对列没有任何使用中的消费者也会被自动删除,该队列适用于临时对列,并且具备唯一性,不可同时存在

-------消息持久化:

channel.basicPublish("exchange.persistent", "persistent", MessageProperties.PERSISTENT_TEXT_PLAIN, "persistent_test_message".getBytes());
=========================================================================================================================================
源码:
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
exchange:交换机
routingKey:路由键
props:属性信息
body:消息

交换机持久化:交换机持久化与否对消息不会造成什么影响,但是当MQ重启后,生产者将可能无法正常发送数据,因此将交换机持久化,可以保证即使是MQ重启也不会影响到用户!

channel.exchangeDeclare(exchangeName, “direct/topic/header/fanout”, true);

注意:对于一个正常而稳定的MQ消息,需要保证ACK确认机制,持久化机制


MQ镜像? 除了以上问题外,MQ仍然存在着几个问题可能导致数据丢失?

正常情况下:消息正确存入MQ后,还需要一段时间才能存入磁盘,MQ并没有为每条消息都做异步存储处理,而是先保存在缓存区里面,定时刷新或者缓存区提前满了才会刷新近存储区中持久化! 如果在没有持久化之前MQ挂掉同样也会造成数据丢失,这种情况需要MQ的mirrored-queue来缓解,除非整个集群都出问题,不然能非常有效地避免这种情况。此外也可以引入事务机制来确保生产者消息已经到MQ端!

在写入存储区之前会设置一个大小为1024K的缓存区,每隔25毫秒,刷新一次。如果缓存区满了也会刷新入存储区。每次消息写入缓存区后会等待一定时间,如果进程箱里没有消息,则会直接触发超时写入存储!


开发层面可以注意的问题,以避免数据丢失? 当MQ将消息推送给消费者,消费者刚拿到消息但未处理便挂了,这样情况的数据丢失也算的,尽管存在着MQ的确认机制,但长时间以往,仍然可能导致阻塞,这种情况下需要手动处理:

1.默认结果将ACK给关闭,置为false
 channel.basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;  autoAck=false;  
2.在正确处理完后,主动确认,置为true
channel.basicAck(envelope.getDeliveryTag(), false);

负载均衡机制?

       由于某些时候,某台机器的负载高,处理任务的性能比较低,而另外一台的性能高,处理任务的效率也高。但对MQ来说,她是不会做这些智能化的区分的,也不会根据你ACK的返回速度来决定是推送给谁,一般都是有数据来了就推送!那么这样子则容易造成一种情况,负载高的机器任务繁重,负载低的机器被闲置~,这样子无疑是非常浪费~,MQ提供了一种设置每次推送数据量的方法来认为避免:

channel.basicQos(1);// 每次从队列中获取数量 ,负载均衡设置,告诉MQ不要一次将消息发送给某个队列,要等待逐步完成确认后再处理

结果:处理完一条并成功响应才会推送下一条,但是需要注意的是,当消息量非常大,而消费者处理效率比较低数量有不足的时候,将会导致队列积压的消息爆满而产生阻塞!

==============================================================================================

三、初步使用与API封装

---------生产者基础模块?


public class CreateProduct {

    private static final String TASK_QUEUE_NAME = "create_queue";

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

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);//队列持久化
        //分发消息
        for(int i = 0 ; i < 15; i++){
            String message = "Hello World! " + i;
            channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());//消息持久化
            System.out.println(" 我是生产者,发送消息到队列:'" + message + "'");
        }
        channel.close();
        connection.close();
    }
}
======================================================================================================================
此处为生产者,也即是向交换机发送数据,生产数据的一方!

-----------消费者基础模块?

public class HandlerProduct {
    private static final String TASK_QUEUE_NAME = "create_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        final Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
        System.out.println("消费者2: Waiting for messages. To exit press CTRL+C");
        // 每次从队列中获取数量
        channel.basicQos(1);

        final Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");

                System.out.println("我是消费者2号,从队列中接受消息: '" + message + "'");
                try {
                    doWork(message);
                } finally {
                    System.out.println("消费者2号完成任务");
                    // 消息处理完成确认
                    channel.basicAck(envelope.getDeliveryTag(), true);//手动确认完成
                }
            }
        };
        // 消息消费完成确认
        channel.basicConsume(TASK_QUEUE_NAME, false, consumer);//有任何问题都是false,表示ACK无确认
    }

    //处理模块
    private static void doWork(String task) {
        try {
            Thread.sleep(1000); // 暂停1秒钟
        } catch (InterruptedException _ignored) {
            Thread.currentThread().interrupt();
        }
    }
}
=========================================================================================================================
此处为消费者,基础模块

猜你喜欢

转载自blog.csdn.net/qq_36505948/article/details/82745622