RabbitMQ之任务分发篇

来源:互联网 发布:新还珠格格知乎 编辑:程序博客网 时间:2024/05/22 00:25

消息队列研究之RabbitMQ任务分发篇

 

在上一篇文章消息队列研究之RabbitMQ入门篇 中,介绍了关于RabbitMQ入门的第一个程序Hello World!,该程序演示了在RabbitMQ中,单个发送者和单个接收者的使用,而实际使用中,多线程环境是最为常见的,也就是同时有多个消息订阅者同时订阅,而发送者也可以同时有多个,而本篇文章就介绍下在多线程订阅时,该如何使用及注意的事项。

 

l   例子描述

l   轮循分发

l   手动应答

l   持久化

l   转发数

 

1、例子描述

本篇的例子很简单:首先,注册三个消息的接收者,然后,创建一个消息的发送者,发送10条消息,注册的三个消息接受者,会区域平均分配接收这10条消息,因为是10条消息,不能被三个接受者完全平分,所以RabbitMQ会随机从三者中选出一个比较空闲的一个接受者,给于其发送4条消息,其它两者各发送3条消息(如果发送的是9条消息,那么每次发送时,三个接受者都会接收3条消息,这也是RabbitMQ轮循发送机制,其不会考虑哪个线程目前的忙闲状态,只是平均分发)。

 

2、轮循分发

接收者:

publicclass ReceiverHandlerextendsBaseConnectorimplements Runnable,Consumer {

      privateMessageInfo messageInfo =new MessageInfo();

      privateint hashCode;

     

      privatevolatile Thread thread;

 

      publicReceiverHandler(String queueName)throws IOException, TimeoutException {

            super(queueName);

      }

     

      publicvoid receiveMessage() {

                  Stringop_result = channel.basicConsume(queueName, true,this);  //指定消费队列

                  if("".equals(op_result)){

                        System.out.println("BasicConsumeConfig Consumer Queue Error!");

                  }

            }catch (IOException e) {

                  System.out.println("Consumer Delivery Error,Msg info:" + e.getMessage());

            }catch (Exception e) {

                  System.out.println("ErrorIs Opening,Msg info:" + e.getMessage());

            }finally {

                  //TODO

            }

      }

 

      @Override

      publicvoid handleCancel(String arg0)throws IOException {

      }

 

      @Override

      publicvoid handleCancelOk(String arg0) {

      }

 

      @Override

      publicvoid handleConsumeOk(String consumeOk) {

      }

 

      @Override

      publicvoid handleDelivery(String consumerTag, Envelope env,

                  BasicPropertiesprops,byte[] body)throws IOException {

            messageInfo= (MessageInfo) SerializationUtils.deserialize(body);

      System.out.println(msgInfo.getHashCode()+ " [REC] Received '" + messageInfo.getContent() + "'");

      }

 

      @Override

      publicvoid handleRecoverOk(String arg0) {

      }

 

      @Override

      publicvoid handleShutdownSignal(String arg0, ShutdownSignalException arg1) {

      }

 

      @Override

      publicvoid run() {

            receiveMessage();

      }

     

    publicvoid start() {

       if(thread == null){

            thread = new Thread(this);

            thread.start();

       }

    }

}

 

发送者:

publicclass PublisherHandlerextendsBaseConnector {

     

      publicPublisherHandler(String queueName)throws IOException,TimeoutException {

            super(queueName);

      }

 

      publicvoid sendMessage(MessageInfo messageInfo) {

            channel.basicPublish("",queueName, null, SerializationUtils.serialize(messageInfo));    

      }

     

      publicvoid close() {

            super.close();

      }

}

 

测试入口:

public static void main(String[] args) {

          PublisherHandler publisher = null;

            ReceiverHandlerreceiver = null;

            ReceiverHandlerreceiver2 = null;

            ReceiverHandlerreceiver3 = null;

 

            private  String queueName="mq_demo" ;

 

            try{

                  receiver= new ReceiverHandler(queueName);      //接收者1

                  receiver.start();

                  receiver2= new ReceiverHandler(queueName);     //接收者2

                  receiver2.start();

                  receiver3= new ReceiverHandler(queueName);     //接收者3

                  receiver3.start();

                 

                  publisher= newPublisherHandler(queueName);    //发送者

                  for(inti=0;i<10;i++) {

                  String message = "消息【"+(i+1)+""; 

                        MessageInfomsgInfo =new MessageInfo();

                        msgInfo.setConsumerTag("demo_tag");

                        msgInfo.setChannel("demo");

                        msgInfo.setContent(message);

                        publisher.sendMessage(msgInfo);

                  }

            }catch (IOException | TimeoutException e) {

                  e.printStackTrace();

            }finally {

                  publisher.close();

            }

}

 

 

结果显示:

 

通过线程的hashcode匹对,上图已经证实了最开始例子描述的需求,那么请继续往下阅览!

 

 

3、手动应答

会有这样一种情况存在,某一个或多个消息接受者在接收消息过程中,突然被停止掉或是不能正常工作时,不论该消息接受者是否接收到消息,RabbitMQ都会自动删除它认为已经发送出去的消息,这样就造成接受者接收消息失败数据丢失问题,那么RabbitMQ自然会考虑到,所以我们需要在消息接受者接到消息之后,给予RabbitMQ服务反馈一个应答,告诉其我已经成功收到消息了,你可以删除了,这样就可以解决掉多线程时,订阅者突然挂掉,不能接收消息丢失数据的问题了。

 

接收者改动:

在接收消息时,开启应答模式,并在收到消息后,手动发送应答消息反馈给RabbitMQ消息服务,具体如下:

开启应答模式:

//  ……

// 指定消费队列并开启应答模式(第二个参数为false,代表开启应答,否则反之)

String op_result= channel.basicConsume(queueName, false,this);

//  ……

 

 

手动反馈应答:

@Override

publicvoid handleDelivery(StringconsumerTag, Envelope env,

                  BasicPropertiesprops,byte[] body)throws IOException {

    //应答模式下每发完消息后手动发送应答

      channel.basicAck(env.getDeliveryTag(),false);

   //  ……

}

 

 

4、持久化

也会有这样的情况存在,就是RabbitMQ服务挂掉,这时也会造成数据丢失,并且有时,我们也需要保留发送的消息数据备用,那么这时我们应该怎么处理?答案是,RabbitMQ也为你考虑好,就是设置数据的持久化。RabbitMQ的数据持久化分为队列和消息内容持久化两种,分别如下操作:

 

队列持久化:

此时,我们需要修改优化下BaseConnector队列声明部分,添加如下:

// 声明创建队列并设置队列持久化(第二个参数为true,代表该队列持久化反之)

channel.queueDeclare(queueName,true, false,false,null);     

 

 

消息持久化:

此时,我们需要在发送消息时设置,修改优化下sendMessage(…)方法,具体如下:

// ……

//  消息发送及声明消息持久化bp

BasicProperties bp = MessageProperties.PERSISTENT_TEXT_PLAIN;

channel.basicPublish("",queueName,bp,SerializationUtils.serialize(messageInfo));  

 

 

5、转发数

当订阅者不能正常工作时,RabbitMQ会将余下的消息发送给可以工作的接收订阅者进程继续接收处理消息,但是默认下,RabbitMQ不会评估消息接受者当前的忙闲状态,依旧按部就班的逐个转发一个或是多个消息给到订阅者,这样一旦某个订阅者当前较忙,可依旧收到过多的消息,而其它订阅者比较闲时,就造成了系统瓶颈压力问题,所以我们需要告诉RabbitMQ服务,每次最多转发多少消息,给到目前较空闲的订阅者,那么需要调整订阅者的receiveMessage方法,添加如下内容:

//  ……

//  消息最大转发数默认为1

channel.basicQos(1);

//  ……

 

 

 

最后,我们给出同时满足应答响应、队列/消息持久化及转发数设定的订阅者、发送者及基类的完整代码:

继承的基类:

publicclassBaseConnector {

      protectedChannel channel;

    protectedConnection connection;

    protectedString  queueName;

 

    publicBaseConnector(String queueName,boolean isQueueDurable)throwsIOException, TimeoutException {

       this. queueName = queueName;

      

       ConnectionFactory factory = new ConnectionFactory();

       factory.setHost("127.0.0.1");    

       connection = factory.newConnection();

       channel = connection.createChannel();

          // 声明创建队列并队列持久化

       channel.queueDeclare(queueName,isQueueDurable,false,false,null); 

    }

   

    protectedvoid close() {

       try {

                  channel.close();

                  connection.close();

            }catch (IOException e) {

                  e.printStackTrace();

            }catch (TimeoutException e) {

                  e.printStackTrace();

            }

    }

}

 

 

消息接收者:

publicclass ReceiverHandlerextendsBaseConnectorimplements Runnable,Consumer {

      privateMessageInfo messageInfo =new MessageInfo();

      privateint hashCode;

     

      privatevolatile Thread thread;

 

      publicReceiverHandler(String queueName, boolean isQueueDurable)throwsIOException, TimeoutException {

            super(queueName,isQueueDurable);

      }

     

      publicvoid receiveMessage() {

                  Stringop_result = channel.basicConsume(queueName, false,this); //指定消费队列并开启应答

                  if("".equals(op_result)){

                        System.out.println("BasicConsumeConfig Consumer Queue Error!");

                  }

            }catch (IOException e) {

                  System.out.println("Consumer Delivery Error,Msg info:" + e.getMessage());

            }catch (Exception e) {

                  System.out.println("ErrorIs Opening,Msg info:" + e.getMessage());

            }finally {

                  //TODO

            }

      }

 

      @Override

      publicvoid handleCancel(String arg0)throws IOException {

      }

 

      @Override

      publicvoid handleCancelOk(String arg0) {

      }

 

      @Override

      publicvoid handleConsumeOk(String consumeOk) {

      }

 

      @Override

      publicvoid handleDelivery(String consumerTag, Envelope env,

                  BasicPropertiesprops,byte[] body)throws IOException {

    //应答模式下每发完消息后手动发送应答

      channel.basicAck(env.getDeliveryTag(),false);

 

            messageInfo= (MessageInfo) SerializationUtils.deserialize(body);

      System.out.println(msgInfo.getHashCode()+ " [REC] Received '" + messageInfo.getContent() + "'");

      }

 

      @Override

      publicvoid handleRecoverOk(String arg0) {

      }

 

      @Override

      publicvoid handleShutdownSignal(String arg0, ShutdownSignalException arg1) {

      }

 

      @Override

      publicvoid run() {

            receiveMessage();

      }

     

    publicvoid start() {

       if(thread == null){

            thread = new Thread(this);

            thread.start();

       }

    }

}

 

 

消息发送者:

publicclass PublisherHandlerextendsBaseConnector {

     

      publicPublisherHandler(String queueName, boolean isQueueDurable)throws IOException,TimeoutException {

            super(queueName,isQueueDurable);

      }

 

      publicvoid sendMessage(MessageInfo messageInfo) {

       // 消息发送及声明消息持久化bp

       BasicProperties bp = MessageProperties.PERSISTENT_TEXT_PLAIN;

            channel.basicPublish("",queueName, bp, SerializationUtils.serialize(messageInfo));

      }

     

      publicvoid close() {

            super.close();

      }

}

 

说明:

队列的类型是固定的,不能动态修改,也就是当队列mq_demo一开始为非持久化队列,那么开启持久化后,会报错,一般需要修改队列名字为新的名字。

 

 

 

 

 

 

 

 

消息队列RabbitMQ任务分发就介绍到这里,由于作者水平有限,如有问题请在评论发言或是QQ群(245389109(新))讨论,谢谢。

 

 

 

1 0
原创粉丝点击