建立连接
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
conn = factory.newConnection();
channel = conn.createChannel();
建立连接和连接数据库类似。设置ip端口,用户名密码。后续所有的操作都是基于channel上。
发送消息
String QUEUE_NAME = "first";
String message = "hello";
/*
* 参数一:交换机名称,空串表示默认交换机。会投递到与routeKey名称一致的队列
* 参数二:routeKey
* 参数三:消息属性
* 参数四:消息内容
* 所以该消息会相当于直接投递到QUEUE_NAME队列中
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
接收消息
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
/*
* 参数一:队列名
* 参数二:是否自动确认,这里设置成false在具体consumer里确认
* 参数三:指定消息consumer
*/
channel.basicConsume(QUEUE_NAME,true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)throws IOException {
//手动确认消息
System.out.println("receive message:"+new String(body));
}
});
消息发送确认
消息的可靠投递。发送消息时确认消息是成功投递到broker。
//启用发布者确认
channel.confirmSelect();
//绑定确认回调
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("broker接收消息失败");
}
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("broker接收消息成功");
}
});
String msg = "hello";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
绑定一个确认监听器,当消息投递完后,rabbitmq会回调对应的方法。成功调用handleAck方法,失败调用handleNack方法。
消息接收确认
//第二个参数指定是否自动确认
channel.basicConsume(String queue, boolean autoAck, Consumer callback)
如果开启自动确认,消费者拿走消息后默认消息就被消费。如果设置成false就要在处理消息时手动进行确认消息。
channel.basicConsume(QUEUE_NAME,false,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.out.println("receive message:"+new String(body));
/**
* 手动确认消息
* 参数一:消息标签
* 参数二:是否针对多个消息,false只针对当前消息
*/
channel.basicAck(envelope.getDeliveryTag(), false);
/**
* 拒绝消息,一次只能拒绝一条
* 参数一:消息标签
* 参数三:是否将消息重新投递到队列,
*/
channel.basicReject(deliveryTag, requeue);
/**
* 手动拒绝消息
* 参数一:消息标签
* 参数二:是否针对多个消息,false只针对当前消息
* 参数三:是否将消息重新投递到队列,
* false丢弃消息
* 如果设置为true,当测试只有本一个消费者时就发生死循环了。
* 因为重新投递又被自己拿到了又重新投递
*/
channel.basicNack(envelope.getDeliveryTag(), false, true);
}
});
如果不设置自动确认,拿到消息后又未执行ack消息确认,那么该条消息就会变成unacked状态。这条消息会一直是unacked状态,其他消费者也拿不到。只有当当前消费者断开channel连接。rabbitmq才会认为该条消息未被成功处理,又被重新回收变成ready状态。其它消费者才能再取到该条消息。
我也不知道basicReject和basicNack有什么区别。
接收设置QoS
prefetchCount
Channel Prefetch Setting:消费者予获取消息的数量。
//consumer级别设置
channel.basicQos(int prefetchCount)
//第二个参数设置true表示channel级别
channel.basicQos(int prefetchCount, boolean global)
查看源码basicQos(prefetchCount)会调用 basicQos(prefetchCount,false)。
只有消息消费模式为非自动确认模式Qos参数值才有效。
The value defines the max number of unacknowledged deliveries that are permitted on a channel. Once the number reaches the configured count, RabbitMQ will stop delivering more messages on the channel unless at least one of the outstanding ones is acknowledged
这个参数设置channel中最大未确认消息数。当设置为0时就是不限制。当达到这个最大值时,rabbitmq会停止往该channel发送消息。直到有未被确认的消息执行ack后。例一个channel设置Qos为3,当consumer有三条消息未确认后,就不会有新的消息传送给该consumer。如果是因为程序异常导致未执行到消息channel.basicAck方法,试想下这个消费者其实就已经阻塞了,废掉了。3个坑位都被异常消息占了,永远也确认不了。所以消息消费的逻辑一定要处理好。
例1: consumer级别
channel.basicQos(10); // Per consumer limit
channel.basicConsume(queue, false, consumer1);
channel.basicConsume(queue, false, consumer2);
每个consumer最大未确认消息数10.
*例2:*consumer级别和channel级别混用
channel.basicQos(10, false); // consumer级别设置最大10
channel.basicQos(15, true); // channel级别设置最大15
channel.basicConsume(queue, false, consumer1);
channel.basicConsume(queue, false, consumer2);
这样这两个consumer合计未确认消息数(也就是整个通道)是15。同时每个consumer最大还不能超过10。这样rabbitmq就需要一直协调这两个consumer来遵守这两个原则。会导致性能打折扣。
参考
https://www.rabbitmq.com/consumers.html
https://www.rabbitmq.com/confirms.html
https://www.rabbitmq.com/consumer-prefetch.html