1 死信队列 / 延迟队列
死信队列指的是因为某些原因,队列中的某些消息变成了死信(dead letter)后,它们被重新路由到死信交换器(DLX)绑定的队列上,该队列即为死信队列。我们可以监听该死信队列中的消息,以进行相应的处理。
消息变为死信的原因一般有以下三种:
- 消息被拒绝,并且设置requeue参数为false
- 消息过期
- 队列达到最大长度
1.1 生产者代码
以下代码是通过“x-message-ttl”和“x-dead-letter-exchange”参数来模拟实现消息过期的效果:当消息发布10秒后,消息从原来的普通队列进入到死信队列中,消费者订阅死信队列,并从中拿出相应的消息。
package com.hys.rabbitmq;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* 死信队列生产者
* @author Robert Hou
* @date 2019年6月3日
*/
public class DlqProducer {
private static final String IP_ADDRESS = "127.0.0.1";
private static final int PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername("root");
factory.setPassword("root");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare("dlx", "direct", true);
channel.exchangeDeclare("normalExchange", "fanout", true);
Map<String, Object> argsMap = new HashMap<>();
//设置过期时间为10秒
argsMap.put("x-message-ttl", 10000);
//设置死信交换器为"dlx"
argsMap.put("x-dead-letter-exchange", "dlx");
//指定DLX的路由键为"routingKey"
argsMap.put("x-dead-letter-routing-key", "routingKey");
//为普通队列添加DLX
channel.queueDeclare("normalQueue", true, false, false, argsMap);
channel.queueBind("normalQueue", "normalExchange", "");
//创建死信队列及绑定DLX
channel.queueDeclare("dlQueue", true, false, false, null);
channel.queueBind("dlQueue", "dlx", "routingKey");
channel.basicPublish("normalExchange", "rk", MessageProperties.PERSISTENT_TEXT_PLAIN, "This is a dead letter".getBytes());
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " publish dead letter");
channel.close();
connection.close();
}
}
运行结果:
2019-06-04 00:20:59 publish dead letter
由上可以看到,我在24点20分59秒发布了一条消息。
1.2 消费者代码
package com.hys.rabbitmq;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Address;
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;
/**
* 死信队列消费者
* @author Robert Hou
* @date 2019年6月3日
*/
public class DlqConsumer {
private static final String IP_ADDRESS = "127.0.0.1";
private static final int PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
CountDownLatch cdl = new CountDownLatch(1);
Address[] addresses = new Address[] { new Address(IP_ADDRESS, PORT) };
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("root");
factory.setPassword("root");
Connection connection = factory.newConnection(addresses);
final Channel channel = connection.createChannel();
channel.basicQos(64);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " recv message:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
cdl.countDown();
}
};
channel.basicConsume("dlQueue", consumer);
cdl.await();
channel.close();
connection.close();
}
}
运行结果:
2019-06-04 00:21:09 recv message:This is a dead letter
由上可以看到,在24点21分09秒消费者消费了该条消息,距离该条消息被发布的时间正好过了10秒。
其实读到这里就可以发现,上面展示的不仅是死信队列的用法,也是延迟队列的用法。当消息从普通队列中过期后进入到死信队列时,消费者正好就是获取到了这个延迟了10秒钟的消息。
2 优先级队列
优先级队列顾名思义,指的是优先级高的消息可以被优先消费,但如果生产消息的速度远远赶不上消费消息的速度,那么在队列中的消息最多也只会有一个(因为如果生产出了一条消息,就会立马被消费),那么此时候谈论优先级就已经没什么必要了。
2.1 生产者代码
下面的代码演示了发送5条消息到“priorityQueue”这个队列中。
package com.hys.rabbitmq;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.AMQP.BasicProperties.Builder;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* 优先级队列生产者
* @author Robert Hou
* @date 2019年6月4日
*/
public class PqProducer {
private static final String IP_ADDRESS = "127.0.0.1";
private static final int PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername("root");
factory.setPassword("root");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare("priorityExchange", "fanout", true);
Map<String, Object> argsMap = new HashMap<>();
//设置队列的最大优先级为10
argsMap.put("x-max-priority", 10);
channel.queueDeclare("priorityQueue", true, false, false, argsMap);
channel.queueBind("priorityQueue", "priorityExchange", "");
Builder builder = new Builder();
//设置消息的优先级为5
builder.priority(5);
BasicProperties properties = builder.build();
channel.basicPublish("priorityExchange", "rk", MessageProperties.PERSISTENT_TEXT_PLAIN, "This is message1".getBytes());
channel.basicPublish("priorityExchange", "rk", MessageProperties.PERSISTENT_TEXT_PLAIN, "This is message2".getBytes());
channel.basicPublish("priorityExchange", "rk", MessageProperties.PERSISTENT_TEXT_PLAIN, "This is message3".getBytes());
channel.basicPublish("priorityExchange", "rk", MessageProperties.PERSISTENT_TEXT_PLAIN, "This is message4".getBytes());
channel.basicPublish("priorityExchange", "rk", properties, "This is message5".getBytes());
channel.close();
connection.close();
}
}
由上可以看到,message5被设置了优先级为5,其他的四条消息使用的是默认最低的优先级0。
2.2 消费者代码
package com.hys.rabbitmq;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Address;
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;
/**
* 优先级队列消费者
* @author Robert Hou
* @date 2019年6月4日
*/
public class PqConsumer {
private static final String IP_ADDRESS = "127.0.0.1";
private static final int PORT = 5672;
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
CountDownLatch cdl = new CountDownLatch(5);
Address[] addresses = new Address[] { new Address(IP_ADDRESS, PORT) };
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("root");
factory.setPassword("root");
Connection connection = factory.newConnection(addresses);
final Channel channel = connection.createChannel();
channel.basicQos(64);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("recv message:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
cdl.countDown();
}
};
channel.basicConsume("priorityQueue", consumer);
cdl.await();
channel.close();
connection.close();
}
}
在不考虑消息丢失、网络故障,以及消息没发生重发或事务回滚,所有消息都处于同一优先级的情况下,RabbitMQ的消息是能保证顺序性的,也就是说生产者发送消息的顺序和消费者接收到消息的顺序是一致的。拿上面的代码来说,如果message5也用的是默认优先级0的话,生产者发布的消息顺序为message1、message2、message3、message4和message5,消费者接收到的消息顺序也应该为message1、message2、message3、message4和message5。但是在上述的生产者代码中message5被赋予了更高的优先级,所以运行的结果中message5将会是第一个被消费的消息。运行的结果如下所示:
recv message:This is message5
recv message:This is message1
recv message:This is message2
recv message:This is message3
recv message:This is message4
但是有一点需要特别注意:上述的结果只建立在先运行生产者代码,后运行消费者代码的前提下。如果尝试先运行消费者代码,然后运行生产者代码,结果会有所不同:
recv message:This is message1
recv message:This is message2
recv message:This is message3
recv message:This is message4
recv message:This is message5
可以看到message5又变成最后一个被接收的了,那么这是为什么呢?正如我之前所说,如果生产者生产消息的速度远小于消费者消费的速度,也就是说当生产出message1的时候,就会被消费者立即消费;生产出message2的时候,就会被消费者立即消费...每次都是刚生产出一条消息后就会被立即消费,那么此时的优先级就没有任何意义了,消费的顺序只取决于生产的顺序。而先运行生产者代码,后运行消费者代码的情况下,五条消息会被一起发送到RabbitMQ服务器里后再被消费,此时优先级才会起作用,优先级高的会被先消费掉。