RabbitMQ 笔记

来源:互联网 发布:南丹县政府网络问政 编辑:程序博客网 时间:2024/05/19 20:44

本文出自于EumJi个人博客,仅限于学习用途的转载,转载请注明出处http://www.eumji025.com/article/details/258142

前言

前面一节我们介绍如何编写一个发送和接收来自制定名称的队列。在本节中,我们将介绍如何创建一个消息队列供多个消费者使用。如下图所示。

工作队列介绍

工作队列(又称:任务队列),其主要思想是避免立即执行且需要等待完成的资源密集型任务。我们将后续完成的任务封装成一个消息,并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当你运行很多消费者(worker)时,这些任务将在它们之间共享。

这个概念在Web应用程序中特别有用,在短时间HTTP请求窗口中无法处理复杂的任务。

代码展示

本节的代码都是基于hello world案例进行稍微的改动。

生产者

我们对前面一节的hello world的生产者进行扩展,主要是生产多条消息,以便供多个消费者使用。

主要代码如下:

 //1.创建连接工厂        ConnectionFactory factory = new ConnectionFactory();        //2.配置工厂的RabbitMQ server的主机        factory.setHost("localhost");        Connection connection = null;        Channel channel = null;        try {            //3.新建一个连接            connection = factory.newConnection();            //4.获取一个连接频道            channel = connection.createChannel();            /**             * 5.配置一个队列的信息             */            channel.queueDeclare(QUEUE_WORK,true,false,false,null);            for (int i = 0; i < 10; i++) {                String message = getMessage()+"这是第"+i+"个!!!" ;                /**                 * 6.将消息放在队列中(字节数组形式)                 * PERSISTENT_TEXT_PLAIN  Content-type "text/plain", deliveryMode 2 (persistent), priority zero                 */                channel.basicPublish("",QUEUE_WORK, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());                System.out.println("product Sent message is: '" + message + "'");            }        }catch(Exception e) {          e.pringtStackTrace();        }finally {            try {                //7.关闭连接                channel.close();                connection.close();            } catch (IOException e) {                logger.error("rabbitMQ关闭发生IO异常");                e.printStackTrace();            } catch (TimeoutException e) {                logger.error("rabbitMQ发生关闭超时异常");                e.printStackTrace();            }        }    }

getMessage()方法,模拟设置消息。

channel.queueDeclare(QUEUE_WORK,true,false,false,null)的第二参数设置为true,表明将消息持久化。

主要代码如下:

private static String getMessage() {        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return String.valueOf(System.currentTimeMillis());    }

使用sleep方法模拟消息处理需要耗费一定的时间。

消费者

消费者基本和上一节雷同,主要是使用到了线程,以便模拟多个消费者。

//保证只有一个worker处理一个任务channel.basicQos(1);final Channel finalChannel = channel;            Consumer consumer = new DefaultConsumer(finalChannel){                @Override                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {                    String message = new String(body, 0, body.length, "UTF-8");                    System.out.println(workName+" "+Thread.currentThread().getName()+" get message:"+message);                    try {                        Thread.sleep(800);                        //消息处理完成确认                        finalChannel.basicAck(envelope.getDeliveryTag(),false);                        System.out.println(workName+" "+Thread.currentThread().getName()+"处理消息完毕");                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            };            //消息消费完成确认            channel.basicConsume(QUEUE_WORK,false,consumer);

上述代码主要是消费者的主要逻辑,相比上一节,主要多添加了消息公平分配和消息处理完成确认。下面来介绍这两个特性。

公平分配

我们在多线程中应该经常遇到,因为权重不同的问题,部分线程一直是空闲的,而部分线程却很忙碌。

RabbitMQ为了公平的工作,通过basicQos方法公平分配。

消息确认

执行任务可能需要一定的时间。如果一个消费者开始一个长期的任务,并且仅仅部分地完成它,会发生什么?使用我们当前的代码,一旦RabbitMQ向客户发送消息,它立即将其从内存中删除。在这种情况下,如果一个worker被杀死,我们将丢失正在处理的消息。我们还会丢失所有发送给该特定worker但尚未处理的消息。

但是我们不想失去任何任务。如果一个worker死亡,我们希望把这个任务交给另一个worker。

为了确保消息永远不会丢失,RabbitMQ支持消息确认。从消费者发送一个确认信息(告示)告诉RabbitMQ已经收到,处理了特定的消息,并且RabbitMQ可以自由删除它。

如果消费者死机(其通道关闭,连接关闭或TCP连接丢失),而不发送确认信息,RabbitMQ将会明白消息未被完全处理并重新排队。如果同时有其他消费者在线,则会迅速将其重新提供给另一个消费者。这样就可以确保没有消息丢失,即使worker偶尔出现问题。

消息确认默认情况下打开。在前面的例子中,我们通过autoAck = true 标志明确地将它们关闭。现在是一旦完成任务,将此标志设置为false,并向worker发送正确的确认。

测试

首先运行消费者,等待消息

customer2 wait  message!!!customer wait  message!!!

然后运行生产者,发送消息

product Sent message is: '1496553686995这是第0个!!!'product Sent message is: '1496553688001这是第1个!!!'product Sent message is: '1496553689001这是第2个!!!'product Sent message is: '1496553690002这是第3个!!!'product Sent message is: '1496553691003这是第4个!!!'

我们再观察消费者

customer2 pool-1-thread-4 get message:1496553686995这是第0个!!!customer2 pool-1-thread-4处理消息完毕customer pool-2-thread-4 get message:1496553688001这是第1个!!!customer2 pool-1-thread-5 get message:1496553689001这是第2个!!!customer pool-2-thread-4处理消息完毕customer2 pool-1-thread-5处理消息完毕customer pool-2-thread-5 get message:1496553690002这是第3个!!!customer pool-2-thread-5处理消息完毕customer2 pool-1-thread-6 get message:1496553691003这是第4个!!!

我们从上面的结果中看出,分配是公平的。

结语

本文是根据RabbitMQ的官方教程翻译并加以修改而成,如果存在什么不足和纰漏欢迎指正。

只是简单的介绍多个worker的工作队列的工作原理。

与君共勉!!!

参考资料

RabbitMQ文档-work queue

源码

work queue源码