文章中的例子是来自于rabbitmq的官网,所以大家可以去官网上看一看。
这里讲一下循环发送的情况。概念图如下所示:
在这里,我们会模拟一个耗时的任务在消费者中,通过Thread.sleep(1000)这个函数,模拟耗时任务。同时启动多个consumer。
其实可以说是rabbitmq本身就支持这种模式。
package com.bobo.rabbitmq2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;
/**
* 消息队列生产者,这个例子只是简单的通过queue使用,
* 所以是exchange是“”
* @author [email protected]
* @create 2018-11-04 13:36
**/
public class Send {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hello",false,false,false,null);
String message = "hello world";
for (int i =0 ;i <10;i++) {
channel.basicPublish("","hello",null,(message+i).getBytes(Charset.forName("UTF-8")));
}
System.out.println(" [x] Sent '" + message + "'");
//关闭连接
channel.close();
connection.close();
}
}
这里用来发送消息是带一个i,用来在消费者区分。
package com.bobo.rabbitmq2;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消息接收者
* @author [email protected]
* @create 2018-11-04 13:46
**/
public class Receiver {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hello", false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//回调消费消息
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(" [x] Received '" + message + "'");
try {
doWork(message);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(" [x] Done");
}
}
};
channel.basicConsume("hello", true, consumer);
}
private static void doWork(String task) throws InterruptedException {
for (char ch: task.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
}
}
在消费者消费的时候,让线程睡一秒,来模拟耗时操作。
我们可以看到rabbitmq自动做了类似于负载均衡的操作,而且默认使用的轮询算法。所以当一个comsumer处理不过来的时候就可以多添加
几个comsumer。
为了确保消息永远不会丢失,RabbitMQ支持消息确认。消费者将会发送一个确认信息来告诉RabbitMQ,我已经接收到了消息,并且处理完了
,你可以随便删它了。
如果一个消费者在发送确认信息前丢失(连接或通道关闭、TCP连接丢失等),RabbitMQ将会认为该消息没有被完全处理并会重新将消息加入队列。如果此时有其他的消费者,RabbitMQ很快就会重新发送该消息到其他的消费者。通过这种方式,你完全可以保证没有消息丢失,即使某个消费者意外死亡。
channel.basicConsume("hello", true, consumer);
这个方法中的第二个参数就是消息确认机制,当为true的时候表示是有消息确认。
如果关闭RabbitMQ服务或者RabbitMQ服务崩溃了,RabbitMQ就会丢掉所有的队列和消息:除非你告诉它不要这样。要确保RabbitMQ服务
关闭或崩溃后消息不会丢失,要做两件事情:持久化队列、持久化消息。
首先,我们要确保RabbitMQ永远不会丢失我们的队列。怎么做呢?在声明队列的时候,指定durable参数为true。
channel.queueDeclare("hello",true,false,false,null);
queueDeclare的时候,当第二个参数是true,表示的是对列持久化。
// MessageProperties.PERSISTENT_TEXT_PLAIN定义消息持久化
channel.basicPublish("","hello",MessageProperties.PERSISTENT_TEXT_PLAIN,(message+i).getBytes(Charset.forName("UTF-8")));
将消息标记为持久化并不能完全保证消息不会丢失。尽管它告诉RabbitMQ将消息保存到磁盘中,但是在RabbitMQ接收到消息和
保存消息之间会与一个很短的时间窗。同时,RabbitMQ不会为每个消息做fsync(2)处理,消息可能仅仅保存到缓存中而不会真正
地写入到磁盘中。这种持久化保证尽管不够健壮,但已经远远足够我们的简单任务队列
当有多个消费者的时候,rabbitmq主要是通过循环发送(也就是轮询)的方式发送的。
为了改变这种情况,我们可以使用basicQos方法,并将参数prefetchCount设为1。这样做,工作者就会告诉RabbitMQ:不要同
时发送多个消息给我,每次只发1个,当我处理完这个消息并给你确认信息后,你再发给我下一个消息。这时候,RabbitMQ就不
会轮流平均发送消息了,而是寻找闲着的工作者。