在前面的一篇博文中,我们对RabbitMQ中的交换机有了大致的了解,同时结合Spring Boot的实例,让我们对RabbitMQ的用法有了更清晰的认识。如果忘记了可以去复习一下,RabbitMQ学习(三)——探索交换机(Exchange),结合SpringBoot实战。
今天我们将要对RabbitMQ的消息机制进行更详细的探究,在上一篇文章中其实也涉及到了消息机制的问题。只是没有深入探究,今天我们将要开始详细了解该机制的特点。
我们知道每个消息处理的时间是不同的,换句话说消息的复杂度是不同的,有些消息很复杂需要很久的时间,有些消息很简单,只需要耗时一会儿就可以完成。而在实际情况下如何考虑分配资源,让效率达到最大化,从而实现按能力分配任务,达到物尽其用。这就需要了解消息的分发机制。
在这里我们使用Thread.Sleep()方法来模拟耗时,时间越久,任务就越复杂。
这里我们结合前面的例子,在spring boot的代码架构下使用RabbitMQ来探究一下,分发机制。首先我们结合前面第三节文章的例子,在sender和receiver下新建DistributionSender.java和DistributionReceiver.java来模拟发送者和接收者。
DistributionSender.java:
@Component
public class DistributionSender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send(int i) {
// 发送的消息
String message = "This is a task, and the complexity is " + i + "。" + StringUtils.repeat(".", i);
this.rabbitTemplate.convertAndSend("distribu", message);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
这里采用的是默认交换机,队列为“distribu”。需要在config包下面的RabbitConfig.java里面添加如下队列:
/**
* 申明distribu队列
*
* @return
*/
@Bean
public Queue DistribuQueue() {
return new Queue("distribu");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
DistributionReceiver.java:
@Component
public class DistributionReceiver {
/**
* 消费者A
*
* @param msg
*/
@SuppressWarnings("deprecation")
@RabbitListener(queues = "distribu")
public void processA(Message message) {
String msg = new String(message.getBody());
System.out.println(" DistributionReceiverA : " + msg);
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
System.out.println(" ProccessingA... at " + time.format(new Date()));
try {
for (char ch : msg.toCharArray()) {
if (ch == '.') {
doWork(1000);
}
}
} catch (InterruptedException e) {
} finally {
System.out.println(" A Done! at " + time.format(new Date()));
}
}
private void doWork(long time) throws InterruptedException {
Thread.sleep(time);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
然后在controller包的RabbitTest.java文件里添加Restful接口,模拟发送请求。
@Autowired
private DistributionSender distributionSender;
/**
* 分发机制消息发送测试
*/
@GetMapping("/distribu")
public void distribu() {
distributionSender.send(3);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
运行程序,访问http://localhost:8080/rabbit/distribu,可以得到下面的打印的信息:
DistributionReceiverA : This is a task, and the complexity is 3。...
ProccessingA... at 2018-05-23 21:29:18:0628
A Done! at 2018-05-23 21:29:21:0639
- 1
- 2
- 3
从打印的信息可以看出这里就模拟了完成任务需要3秒钟的时间任务实现。
下面我们更改发送的消息数量,在controller控制器里面进行更改,如下:
/**
* 分发机制消息发送测试
*/
@GetMapping("/distribu")
public void distribu() {
for (int i = 0; i < 5; i++) {
//发送任务复杂度都为1的消息
distributionSender.send(1);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
模拟发送5条消息,并且每条发送的消息的复杂度都是相同的,复杂度都为1。
然后再在receiver包中DistributionReceiver.java新增一个消费者B,如下:
/**
* 消费者B
*
* @param msg
*/
@SuppressWarnings("deprecation")
@RabbitListener(queues = "distribu")
public void processB(Message message) {
String msg = new String(message.getBody());
System.out.println(" DistributionReceiverB : " + msg);
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
System.out.println(" ProccessingB... at " + time.format(new Date()));
try {
for (char ch : msg.toCharArray()) {
if (ch == '.') {
doWork(1000);
}
}
} catch (InterruptedException e) {
} finally {
System.out.println(" B Done! at " + time.format(new Date()));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
再次运行程序,访问接口,结果如下:
DistributionReceiverA : This is a task, and the complexity is 1。.
ProccessingA... at 2018-05-22 23:23:43:0014
DistributionReceiverB : This is a task, and the complexity is 1。.
ProccessingB... at 2018-05-22 23:23:43:0014
A Done! at 2018-05-22 23:23:44:0017
B Done! at 2018-05-22 23:23:44:0017
DistributionReceiverA : This is a task, and the complexity is 1。.
DistributionReceiverB : This is a task, and the complexity is 1。.
ProccessingA... at 2018-05-22 23:23:44:0093
ProccessingB... at 2018-05-22 23:23:44:0093
A Done! at 2018-05-22 23:23:45:0095
B Done! at 2018-05-22 23:23:45:0095
DistributionReceiverB : This is a task, and the complexity is 1。.
ProccessingB... at 2018-05-22 23:23:45:0143
B Done! at 2018-05-22 23:23:46:0148
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
在消息相同,A、B处理能力一样情况下,我们可以发现A、B几乎是同时处理消息,消息发送顺序为A->B->A->B->B。可以看出这里并没有实现A与B平均轮询的情况,在最后的情况B执行了两次。
接着现在我们把A处理能力更改为每个点要Thread.sleep(4000), B为Thread.sleep(1000),就是B的处理能力是A的四倍。运行一下,我们看一下打印的结果:
DistributionReceiverB : This is a task, and the complexity is 1。.
ProccessingB... at 2018-05-22 23:24:48:0623
DistributionReceiverA : This is a task, and the complexity is 1。.
ProccessingA... at 2018-05-22 23:24:48:0623
B Done! at 2018-05-22 23:24:49:0624
DistributionReceiverB : This is a task, and the complexity is 1。.
ProccessingB... at 2018-05-22 23:24:49:0663
B Done! at 2018-05-22 23:24:50:0664
DistributionReceiverB : This is a task, and the complexity is 1。.
ProccessingB... at 2018-05-22 23:24:50:0704
B Done! at 2018-05-22 23:24:51:0709
DistributionReceiverB : This is a task, and the complexity is 1。.
ProccessingB... at 2018-05-22 23:24:51:0748
A Done! at 2018-05-22 23:24:52:0629
B Done! at 2018-05-22 23:24:52:0749
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
现在我们可以清晰地看到在这里B处理了4条消息,而A只处理了1条消息。这里就是按公平分发的机制来发送消息的,即按消费者处理能力来分发消息。
注意现在重点来了,RabbitMQ的分发机制
1、Round-robin dispatch(轮询分发)
这个是RabbitMQ默认的消息分发机制,使用任务队列的优点之一就是可以轻易的并行工作。如果我们有很多要分发的消息,可以通过增加工作者(消费者)来解决这种状况,使得系统的伸缩性更加容易扩展。
在默认情况下,RabbitMQ不会顾虑消息者处理消息的能力,即使其中有的消费者闲置有的消费者高负荷。RabbitMQ会逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。平均每个消费者获得相同数量的消息,这种方式分发消息机制称为Round-Robin(轮询)。
2、Fair dispatch (公平分发)
而公平分发,则是根据消费者的处理能力来进行分发处理的。这里主要是通过设置prefetchCount 参数来实现的。这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理规定的数量级个数的Message。换句话说,在接收到该Consumer的ack前,它不会将新的Message分发给它。 比如prefetchCount=1,则在同一时间下,每个Consumer在同一个时间点最多处理1个Message,同时在收到Consumer的ack前,它不会将新的Message分发给它。
注意:使用公平分发,必须关闭自动应答,改为手动应答。
说完了概念,我们再来思考一下,前面我们的实例。在使用Spring Boot结合RabbitMQ时,我们并没有手动去应答,那么这为啥是采用的公平分发机制?
这个是因为Spring Boot封装的RabbitMQ方法,默认ACK机制是使用手工应答机制,当@RabbitListener修饰的方法被调用且没有抛出异常时, Spring Boot会为我们自动应答。
我们可以在@RabbitListener源码的注解里看到,
如果没有指定containerFactory,将采用默认的containerFactory。然后我们在RabbitListenerContainerFactory中查看到这个接口与MessageListenerContainer有关,
接着查看MessageListenerContainer,这是一个接口,我们查看实现了该接口的类,在源码包了提供了一个SimpleMessageListenerContainer的类,在里面我们找到了DEFAULT_PREFETCH_COUNT,这下就清晰明了了。
默认情况下,Spring Boot中的RabbitMQ采用手动确认机制,只要如果不是程序员编程实现应答,框架就会为我们自动去确认。并且prefetchCount=1,这下就可以解释为啥上面的实例出现的结果了。
下面我们再通过原生的RabbitMQ客户端java代码来讲解一下消息分发机制的情况:
轮询分发
还是在上面的实例里,我们在sender包下,新建文件DistributionSender2.java,代码如下:
DistributionSender2.java :
package net.anumbrella.rabbitmq.sender;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang.StringUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class DistributionSender2 {
private final static String QUEUE_NAME = "test";
public static void main(String[] args) throws IOException, TimeoutException {
/**
* 创建连接连接到MabbitMQ
*/
ConnectionFactory factory = new ConnectionFactory();
// 设置MabbitMQ所在主机ip或者主机名
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
factory.setPort(5672);
// 创建一个连接
Connection connection = factory.newConnection();
// 创建一个频道
Channel channel = connection.createChannel();
// 指定一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 8; i++) {
// 发送的消息
String message = "This is a task, and the complexity is " + i + "。" + StringUtils.repeat(".", i);
// 往队列中发出一条消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" DistributionSender2 Sent '" + message + "'");
}
// 关闭频道和连接
channel.close();
connection.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
模拟发送8条消息,然后再receiver包下,新建DistributionReceiver2.java和DistributionReceiver3.java,代码如下:
DistributionReceiver2.java:
package net.anumbrella.rabbitmq.receiver;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeoutException;
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;
public class DistributionReceiver2 {
private final static String QUEUE_NAME = "test";
public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
factory.setPort(5672);
// 打开连接和创建频道,与发送端一样
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println("Receiver2 waiting for messages. To exit press CTRL+C");
// 创建队列消费者
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(" DistributionReceiver2 : " + message);
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
System.out.println(" Proccessing2... at " + time.format(new Date()));
try {
for (char ch: message.toCharArray()) {
if (ch == '.') {
doWork(1000);
}
}
} catch (InterruptedException e) {
} finally {
System.out.println(" DistributionReceiver2 Done! at " +time.format(new Date()));
}
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
private static void doWork(long time) throws InterruptedException {
Thread.sleep(time);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
DistributionReceiver3.java:
package net.anumbrella.rabbitmq.receiver;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeoutException;
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;
public class DistributionReceiver3 {
private final static String QUEUE_NAME = "test";
public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
factory.setPort(5672);
// 打开连接和创建频道,与发送端一样
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println("Receiver3 waiting for messages. To exit press CTRL+C");
// 创建队列消费者
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(" DistributionReceiver3 : " + message);
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
System.out.println(" Proccessing3... at " + time.format(new Date()));
try {
for (char ch: message.toCharArray()) {
if (ch == '.') {
doWork(4000);
}
}
} catch (InterruptedException e) {
} finally {
System.out.println(" DistributionReceiver3 Done! at " +time.format(new Date()));
}
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
private static void doWork(long time) throws InterruptedException {
Thread.sleep(time);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
我们可以看到DistributionReceiver2是DistributionReceiver3处理消息的4倍。
现在我们先启动DistributionReceiver2.java与DistributionReceiver3.java监听消息,然后再启动DistributionSender2.java发送消息,可以查看结果如下:
发送消息:
DistributionSender2 Sent 'This is a task, and the complexity is 0。'
DistributionSender2 Sent 'This is a task, and the complexity is 1。.'
DistributionSender2 Sent 'This is a task, and the complexity is 2。..'
DistributionSender2 Sent 'This is a task, and the complexity is 3。...'
DistributionSender2 Sent 'This is a task, and the complexity is 4。....'
DistributionSender2 Sent 'This is a task, and the complexity is 5。.....'
DistributionSender2 Sent 'This is a task, and the complexity is 6。......'
DistributionSender2 Sent 'This is a task, and the complexity is 7。.......'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
DistributionReceiver2.java收到的消息:
Receiver2 waiting for messages. To exit press CTRL+C
DistributionReceiver2 : This is a task, and the complexity is 0。
Proccessing2... at 2018-05-23 23:12:50:0874
DistributionReceiver2 Done! at 2018-05-23 23:12:50:0876
DistributionReceiver2 : This is a task, and the complexity is 2。..
Proccessing2... at 2018-05-23 23:12:50:0876
DistributionReceiver2 Done! at 2018-05-23 23:12:52:0880
DistributionReceiver2 : This is a task, and the complexity is 4。....
Proccessing2... at 2018-05-23 23:12:52:0880
DistributionReceiver2 Done! at 2018-05-23 23:12:56:0890
DistributionReceiver2 : This is a task, and the complexity is 6。......
Proccessing2... at 2018-05-23 23:12:56:0890
DistributionReceiver2 Done! at 2018-05-23 23:13:02:0910
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
DistributionReceiver3.java收到的消息:
Receiver3 waiting for messages. To exit press CTRL+C
DistributionReceiver3 : This is a task, and the complexity is 1。.
Proccessing3... at 2018-05-23 23:12:50:0879
DistributionReceiver3 Done! at 2018-05-23 23:12:54:0884
DistributionReceiver3 : This is a task, and the complexity is 3。...
Proccessing3... at 2018-05-23 23:12:54:0885
DistributionReceiver3 Done! at 2018-05-23 23:13:06:0887
DistributionReceiver3 : This is a task, and the complexity is 5。.....
Proccessing3... at 2018-05-23 23:13:06:0888
DistributionReceiver3 Done! at 2018-05-23 23:13:26:0903
DistributionReceiver3 : This is a task, and the complexity is 7。.......
Proccessing3... at 2018-05-23 23:13:26:0904
DistributionReceiver3 Done! at 2018-05-23 23:13:54:0921
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我们知道DistributionReceiver2处理速度是DistributionReceiver3处理速度的4倍,但是结果两者收到的消息数是相同的。
DistributionReceiver2:0,2,4,6
DistributionReceiver3:1,3,5,7
这里就是采用的轮询分发,与前面讲解的刚刚符合。
公平分发
为了使用公平分发,我们需要通过basicQos( prefetchCount = 1)方法,来设置prefetchCount参数,同时更改确认方式为手动确认。
int prefetchCount = 1;
channel.basicQos(prefetchCount);
- 1
- 2
同理我们在sender包下新建DistributionSender3.java,代码如下:
package net.anumbrella.rabbitmq.sender;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang.StringUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class DistributionSender3 {
private final static String QUEUE_NAME = "test";
public static void main(String[] args) throws IOException, TimeoutException {
/**
* 创建连接连接到MabbitMQ
*/
ConnectionFactory factory = new ConnectionFactory();
// 设置MabbitMQ所在主机ip或者主机名
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
factory.setPort(5672);
// 创建一个连接
Connection connection = factory.newConnection();
// 创建一个频道
Channel channel = connection.createChannel();
// 指定一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
int prefetchCount = 1;
// 限制发给同一个消费者不得超过1条消息
channel.basicQos(prefetchCount);
for (int i = 0; i < 8; i++) {
// 发送的消息
String message = "This is a task, and the complexity is " + i + "。" + StringUtils.repeat(".", 1);
// 往队列中发出一条消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" DistributionSender3 Sent '" + message + "'");
}
// 关闭频道和连接
channel.close();
connection.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
更改为手动确认需要添加如下代码,basicAck 方法的第二个参数 multiple 取值为 false 时,表示通知 RabbitMQ 当前消息被确认;如果为 true,则额外将比第一个参数指定的 delivery tag 小的消息一并确认。(在RabbitMQ的每个信道中,每条消息的 Delivery Tag 从 1 开始递增,这里的批量处理是指同一信道中的消息):
channel.basicAck(envelope.getDeliveryTag(), false);
- 1
通过更改auto为false,即更改自动确认为false。如下:
channel.basicConsume(QUEUE_NAME, false, consumer);
- 1
DistributionReceiver4.java:
package net.anumbrella.rabbitmq.receiver;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeoutException;
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;
public class DistributionReceiver4 {
private final static String QUEUE_NAME = "test";
public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
factory.setPort(5672);
// 打开连接和创建频道,与发送端一样
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
// 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println("Receiver4 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(" DistributionReceiver4 : " + message);
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
System.out.println(" Proccessing4... at " + time.format(new Date()));
try {
for (char ch: message.toCharArray()) {
if (ch == '.') {
doWork(1000);
}
}
} catch (InterruptedException e) {
} finally {
System.out.println(" DistributionReceiver4 Done! at " +time.format(new Date()));
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
}
private static void doWork(long time) throws InterruptedException {
Thread.sleep(time);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
DistributionReceiver5.java:
package net.anumbrella.rabbitmq.receiver;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeoutException;
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;
public class DistributionReceiver5 {
private final static String QUEUE_NAME = "test";
public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
factory.setPort(5672);
// 打开连接和创建频道,与发送端一样
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
// 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println("Receiver5 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(" DistributionReceiver5 : " + message);
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
System.out.println(" Proccessing5... at " + time.format(new Date()));
try {
for (char ch: message.toCharArray()) {
if (ch == '.') {
doWork(4000);
}
}
} catch (InterruptedException e) {
} finally {
System.out.println(" DistributionReceiver5 Done! at " +time.format(new Date()));
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
}
private static void doWork(long time) throws InterruptedException {
Thread.sleep(time);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
一样的DistributionReceiver4.java是DistributionReceiver5.java处理速度的4倍。然后我们也把消息的复杂度更改为1,让所有消息任务的复杂度都相同。
同样的现在我们先启动DistributionReceiver4.java与DistributionReceiver5.java监听消息,然后再启动DistributionSender3.java发送消息,可以查看结果如下:
发送消息:
DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
DistributionReceiver4.java接受者:
Receiver4 waiting for messages. To exit press CTRL+C
DistributionReceiver4 : This is a task, and the complexity is 1。.
Proccessing4... at 2018-05-23 23:48:40:0915
DistributionReceiver4 Done! at 2018-05-23 23:48:41:0918
DistributionReceiver4 : This is a task, and the complexity is 1。.
Proccessing4... at 2018-05-23 23:48:41:0956
DistributionReceiver4 Done! at 2018-05-23 23:48:42:0958
DistributionReceiver4 : This is a task, and the complexity is 1。.
Proccessing4... at 2018-05-23 23:48:42:0999
DistributionReceiver4 Done! at 2018-05-23 23:48:44:0002
DistributionReceiver4 : This is a task, and the complexity is 1。.
Proccessing4... at 2018-05-23 23:48:44:0056
DistributionReceiver4 Done! at 2018-05-23 23:48:45:0061
DistributionReceiver4 : This is a task, and the complexity is 1。.
Proccessing4... at 2018-05-23 23:48:45:0099
DistributionReceiver4 Done! at 2018-05-23 23:48:46:0104
DistributionReceiver4 : This is a task, and the complexity is 1。.
Proccessing4... at 2018-05-23 23:48:46:0144
DistributionReceiver4 Done! at 2018-05-23 23:48:47:0149
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
DistributionReceiver5.java接受者:
Receiver5 waiting for messages. To exit press CTRL+C
DistributionReceiver5 : This is a task, and the complexity is 1。.
Proccessing5... at 2018-05-23 23:48:40:0918
DistributionReceiver5 Done! at 2018-05-23 23:48:44:0921
DistributionReceiver5 : This is a task, and the complexity is 1。.
Proccessing5... at 2018-05-23 23:48:44:0960
DistributionReceiver5 Done! at 2018-05-23 23:48:48:0965
- 1
- 2
- 3
- 4
- 5
- 6
- 7
可以看到复杂度相同的8条消息,消费者5处理了2条,消费者4处理了6条。而消费者4是消费者5能力的4倍。完全是按能力分配消息的分发,这就是公平分发机制,也符合上面的定义。
好了,RabbitMQ的消息机制到此就完了,上面的demo还是放在了github上,可以结合运行来学习分析。rabbitmq-demo。