目录
一、消息确认
第一篇文章中有一个命令是Basic.Ack用于客户端向服务端反馈确认消息已经正常消费,当接收到命令后RabbityMQ服务端才会删除消息,从消费者客户端确保消息不会丢失。自然有确认就有拒绝确认,本节将介绍basicAck()、basicReject()、basicNack()
1.1 确认消费basicAck
RabbitMQ反馈确认消费通过命令basicAck()实现,该方法具备两个参数deliveryTag和multiple
channel.basicAck(envelope.getDeliveryTag(),false);
- deliveryTag:确认消息的编号,这是每个消息被消费时都会分配一个递增唯一编号
- multiple:批量确认,true表示所有编号小于目前确认消息编号的待确认消息都会被确认,false则只确认当前消息
特别注意:消息的编码是每个信道Channel范围的,批量确认操作也是针对当前Channel信道的操作。请务必记住这个范围
1.2 拒绝确认basicReject
程序在消费消息过程中抛出异常,或者是消息需要重复消费,这时候就可以将消费的消息拒绝确认。拒绝确认的消息有两种去处,删除、放回队列,通过参数requeue控制,拒绝确认的消息放回队列时会放置在队列首位,拒绝消息不放回队列可以搭配死信队列使用。
1.3 拒绝确认basicNack
确认消费可以批量确认,为什么拒绝确认消息不能批量拒绝?所以为了补充basicReject()不足提出了basicNack()。这个API相对于basicReject()而言多了一个参数multiple,效果与批量确认一致。
void basicNack(long deliveryTag, boolean multiple, boolean requeue)
throws IOException;
二、消息预取
消息消费RabbitMQ采取的策略就是轮询机制,将每个消息发送给唯一的消费者。每个消费者获取到的消息都是平均的,这样的机制会导致下列问题:
- 某些消息耗时超长,平均分配消息后可能导致某些消费者积压过多未消费消息,而同时某些消费者处于空闲状态,导致系统吞吐量下降
通过下面代码可以告诉RabbitMQ服务端,我只接受prefetchCount数量的未确认消息,当消费者客户端未确认消息达到限定值后服务端将不会给该消费者推送数据。第二个参数的含义如下表:
void basicQos(int prefetchCount, boolean global) throws IOException;
三、RPC
使用RabbitMQ完成RPC操作其实比较简单,就是使用了前面讲到过的BasicPeoperties对象。发送消息时消息可以携带该对象,前面使用到了对象的deliveryMod持久化、priority优先级、expiration自动过期删除属性。这里RPC将会使用到replyTo属性告诉RPC服务端执行完毕后回调队列地址,correlationId用于标识请求唯一ID
3.1 RPC客户端
UUID生成correlationId请求唯一标识ID,客户端消费回调队列时确认归属与自己的请求回调
ArrayBlockingQueue阻塞队列用于阻塞主线程等待RPC服务端完成逻辑以后的回调
如果想限制RPC远程超时时间则可以在阻塞队列等待方法take()中添加最大等待时长
@SneakyThrows
public static void main (String[] args) {
Channel channel = TemplateConfigServiceImpl.createChannel();
// 创建BasicProperties赋值replyTo回调队列名称、correlationId请求唯一标识ID
String correlationId = UUID.randomUUID().toString();
String replyQueue = "queue1";
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder().replyTo(replyQueue).correlationId(correlationId).build();
// 客户端向服务端监控队列发送消息
String rpcQueue = "queueName";
channel.basicPublish("",rpcQueue,basicProperties,"RPC测试".getBytes());
// 创建阻塞队列等到消息回调
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
// 监控回调队列消息获取远程调用结果
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery (String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 校验消息唯一标识是否匹配
String replyCorrelationId = properties.getCorrelationId();
long deliveryTag = envelope.getDeliveryTag();
if (correlationId.equals(replyCorrelationId)){
// 将回调消息放到阻塞队列中
arrayBlockingQueue.offer(new String(body));
channel.basicAck(deliveryTag,false);
}else {
// 不匹配消息放回队列
channel.basicReject(deliveryTag,true);
}
}
};
channel.basicConsume(replyQueue,false,"ConsumerTag",defaultConsumer);
// 阻塞等待阻塞队列中消息处理后续逻辑
String take = arrayBlockingQueue.take();
System.out.println(take);
}
3.2 RPC服务端
整体RPC服务端、客户端实现都是最简陋的自行车设计,如果想要更复杂的逻辑请自行完成
@SneakyThrows
public static void main (String[] args) {
Channel channel = TemplateConfigServiceImpl.createChannel();
// 监控RPC队列消息执行任务
String rpcQueue = "queueName";
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery (String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 执行计算逻辑
System.out.println("RPC远程服务端开始执行任务");
System.out.println(new String(body));
// 组装回调消息
String replyTo = properties.getReplyTo();
channel.basicPublish("",replyTo,properties,"RPC远程计算任务完成".getBytes());
// 确认消息消费
long deliveryTag = envelope.getDeliveryTag();
channel.basicAck(deliveryTag,false);
}
};
channel.basicConsume(rpcQueue,false,"ConsumerTag",defaultConsumer);
}