- 前言
之前上一篇博客整理了rabbitmq中的三种交换机的类型,今天整理一下rabbitmq的一些高级特性,包括confirm确认消息,return返回消息,消费端限流策略以及自定义消费者使用等等,我只是想把学到的东西通过博客的方式记录下来,可能有点乱,如果有不正确的地方,希望大家可以指出交流。主要学习教程是慕课网的《RabbitMQ消息中间件技术精讲》。
- Confirm确认消息
Confirm确认消息是指生产者投递消息之后,Broker如果收到消息,会给生产者一个应答。生产者根据应答来确定消息是否正常发送到Broker。
实现步骤:
- 首先在channel开启确认模式。channel.confirmSelect();
- 添加监听,监听成功和失败的返回结果,进行后续处理。channel.addConfirmListener();
代码实现
/**
* 消费者
* @author Y
* @date 2020年2月8日
*/
public class Consumer {
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//获得connection
Connection connection = connectionFactory.newConnection();
//获得channel对象
Channel channel = connection.createChannel();
String exchangeName = "test_confirm_exchange";
String exchangeType = "direct";
String rountingKey = "confirm.add";
String queueName = "test_confirm_queue";
//声明交换机 队列 绑定交换机和队列并且指定对应的路由KEY
channel.exchangeDeclare(exchangeName, exchangeType, true, true, null);
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, exchangeName, rountingKey);
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//设置channel 自动确认
channel.basicConsume(queueName, true,queueingConsumer);
while(true) {
Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody(),"utf-8");
System.out.println("消费端:"+msg);
}
}
}
/**
* 生产者
* @author Y
* @date 2020年2月8日
*/
public class Producer {
public static void main(String[] args) throws Exception {
//获得connection工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//获得connection
Connection connection = connectionFactory.newConnection();
//获得channel
Channel channel = connection.createChannel();
//指定confirm确认消息
channel.confirmSelect();
//生产者发送消息
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.add";
String msg = "test confirm exchange";
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
//监听confirm确认消息
channel.addConfirmListener(new ConfirmListener() {
//消息投递失败
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("----------ack error--------------");
}
//消息投递成功
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("----------ack--------------");
System.out.println(deliveryTag + Boolean.toString(multiple));
}
});
//channel.close();
//connection.close();
}
}
我这边只模拟运行成功的情况,消息发送到了一个存在的交换机上面,运行producer代码,运行结果:
- Return返回消息
Return Listener用于处理一些不可被路由的消息,在某些情况下,指定的exchangeName不存在或者routingKey路由不到,如果我们需要监听这种不可送达的消息,这时候就需要使用Return Listener。
实现步骤
- 在发送消息的时候,我们可以指定mandatory这个参数为true,这样监听器就会监听到不可送达的消息,并且做后续处理。如果设置为false,则服务端默认会删除改消息。
代码实现
public class Consumer {
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//获得connection
Connection connection = connectionFactory.newConnection();
//获得channel对象
Channel channel = connection.createChannel();
String exchangeName = "test_return_exchange";
String exchangeType = "topic";
String rountingKey = "return.*";
String queueName = "test_return_queue";
//声明交换机 队列 绑定交换机和队列并且指定对应的路由KEY
channel.exchangeDeclare(exchangeName, exchangeType, true, true, null);
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, exchangeName, rountingKey);
//设置channel
channel.basicConsume(queueName, true,new MyConsumer(channel));
}
}
public class Producer {
public static void main(String[] args) throws Exception {
//获得connection工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//获得connection
Connection connection = connectionFactory.newConnection();
//获得channel
Channel channel = connection.createChannel();
//消息投递失败后处理
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode,String replyText,String exchange,String routingKey,AMQP.BasicProperties properties,byte[] body)
throws IOException {
System.out.println("after return");
System.out.println("replyCode:"+replyCode+"\n"+"replyText:"
+replyText+"\n"+"exchange:"+exchange+"\n"+
"routingKey:"+routingKey+"\n"+"body:"+new String(body));
}
});
//生产者发送消息
String exchangeName = "test_return_exchange";
String routingKey = "test.add";
String msg = "test return exchange";
//这边设置mandatory 为true 监听未投递成功的消息
channel.basicPublish(exchangeName, routingKey, true, null, msg.getBytes());
}
}
运行生产者代码,这边指定了一个不符合匹配规则的routingKey,消息不会送达,运行结果:
自己测试的时候也指定了一个不存在的交换机,好像并没有走Return Listener监听,不知道为什么。。。
- 自定义消费者使用
以前代码里,消费端接收消息的时候,都是通过consumer.nextDelivery();循环的去获取数据,也可以通过继承DefaultConsumer类,重写handleDelivery方法去实现自定义方法监听的功能,其实上面说明Return返回消息的时候已经说明了,这里就简单贴一下代码。
/**
* 生产者
* @author Y
* @date 2020年2月2日
*/
public class Producer {
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//获得connection
Connection connection = connectionFactory.newConnection();
//获得channel对象
Channel channel = connection.createChannel();
String exchangeName = "test_fanout_exchange";
String rountingKey = "";//fanout不需要路由规则 消息会到所有与交换机绑定的队列上
String msg = "test fanout exchange";
channel.basicPublish(exchangeName,rountingKey, null, msg.getBytes());
channel.close();
connection.close();
}
}
/**
* 消费者
* @author Y
* @date 2020年2月2日
*/
public class Consumer {
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//获得connection
Connection connection = connectionFactory.newConnection();
//获得channel对象
Channel channel = connection.createChannel();
String exchangeName = "test_fanout_exchange";
String exchangeType = "fanout";
String rountingKey = ""; //fanout不需要路由规则 消息会到所有与交换机绑定的队列上
String queueName = "test_fanout_queue";
//声明交换机
channel.exchangeDeclare(exchangeName, exchangeType, true, true, null);
//声明队列
channel.queueDeclare(queueName, false, false, false, null);
//将交换机与队列进行绑定
channel.queueBind(queueName, exchangeName, rountingKey);
//6 设置Channel 自定义消费者
channel.basicConsume(queueName, true, new MyConsumer(channel));
}
}
/**
* 自定义消费者
* @author Y
* @date 2020年2月3日
*/
public class MyConsumer extends DefaultConsumer {
public MyConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.out.println("handle message");
System.out.println("consumerTag:"+consumerTag);
System.out.println("envelope:"+envelope);
System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
}
}
运行结果
可以看出输出了自定义消费监听方法的打印
- 消费端限流策略
如果MQ服务器上面堆积了很多没有处理的消息,这时候我们随便打开一个消费端去消费,很多的数据同时推送过来,单个消费端没有能力去处理这么多的数据。
RabbitMQ提供了一种qos(质量保证服务)功能,在消息不是自动确认的前提之下,如果一定量数目(手动去设置)的消息没有被确认前,不会进行新的消息的消费。
实现步骤
- channel调用BasicQos去设置,这边主要就讲一个参数,prefetchCount用于指定消息没有被ack之前,MQ最多给消费者推送的消息。(自己解释都有点困难。。。)、
代码实现
/**
* 消费端限流
* @author Y
* @date 2020年2月3日
*/
public class Consumer {
public static void main(String[] args) throws Exception {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//获得connection
Connection connection = connectionFactory.newConnection();
//获得channel对象
Channel channel = connection.createChannel();
String exchangeName = "test_limt_exchange";
String exchangeType = "topic";
String rountingKey = "limt.#"; //fanout不需要路由规则 消息会到所有与交换机绑定的队列上
String queueName = "test_limt_queue";
//声明交换机 队列 绑定交换机和队列并且指定对应的路由KEY
channel.exchangeDeclare(exchangeName, exchangeType, true, true, null);
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, exchangeName, rountingKey);
//指定消费端限流策略 消费端一次处理一条消息
channel.basicQos(0, 1, false);
//必须要指定autoAck为false 手动接收
channel.basicConsume(queueName, false, new MyConsumer(channel));
}
}
/**
* 消费端限流
* @author Y
* @date 2020年2月3日
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String exchangeName = "test_limt_exchange";
String routingKey = "limt.add";
String msg = "test limt exchange";
//这边一次发送5条消息
for (int i = 0; i <5 ; i++) {
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
}
}
}
/**
* 自定义消费者
* @author Y
* @date 2020年2月3日
*/
public class MyConsumer extends DefaultConsumer {
private Channel channel;
public MyConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.out.println("handle message");
System.out.println("consumerTag:"+consumerTag);
System.out.println("envelope:"+envelope);
System.out.println("properties:"+properties);
System.out.println("body:"+new String(body));
//手动ack消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
这边生产者发送了5条消息,消费端是等一条消息ack完成之后,消费下一条消息,运行结果:
这边我们如果注释掉消费端ack消息的代码,再次运行,因为我们没有ack消息,消费端会挂起,直到消息被ack。查看RabbitMQ管控平台,也可以看到,有一条消息是Unacked,还有4条消息是Ready状态等待消费。
okok,今天先整理这么多吧。。