java确认rabbitmq_RabbitMQ消息确认(发送确认,接收确认)

本文探讨了RabbitMQ消息确认的重要性,详细解释了消息确认的机制,包括发送方和消费方如何确认消息的成功或失败。通过示例展示了使用Spring整合RabbitMQ进行消息确认的代码实现,强调了手动确认模式在防止消息丢失中的作用。
摘要由CSDN通过智能技术生成

前面几篇记录了收发消息的demo,今天记录下关于 消息确认方面的 问题.

下面是几个问题:

1.为什么要进行消息确认?

2.rabbitmq消息确认 机制是什么样的?

3.发送方如何确认消息发送成功?什么样才算发送成功?

4.消费方如何告知rabbitmq消息消费成功或失败?

5.使用spring的代码示例

1.为什么要进行消息确认?

经常会听到丢消息的字眼, 对于前面的demo来说,就存在丢消息的隐患.

发送者没法确认是否发送成功,消费者处理失败也无法反馈.

没有消息确认机制,就会出现消息莫名其妙的没了,也不知道什么情况.

2.rabbitmq消息确认 机制是什么样的?

网上会有很多总结的博客(包括现在看的),很多就是对官网的翻译.所以看资料首先要去官网看看,这很关键.

bf69142764474c2103dd1afb8c387c42.png

看上图官网的介绍.唯一保证消息不丢失的是使用事务,但是性能太差,作为补偿,有了消息确认机制.

并说明了开启方法,以及和事务模式不共存.

还写了一个例子,但是点进去那个链接已经失效了,新版的源码上也没有这个例子,我找了最近一版是3.6.7上面还有.

3.发送的消息什么样才算成功或失败? 如何确认?

判断消息成功或失败,其实就是看进行消息确认的时机,因为成功或失败后就会把结果告诉发送方.还是看官方解释:

750082cf81ef7e8d31404ee47ab824c5.png

意思如下:

确认消息不能路由时(exchange确认不能路由到任何queue),进行确认操作(确认失败).如果发送方设置了mandatory模式,则会先调用basic.return方法.

消息可以路由时,当需要发送的队列都发送成功后,进行消息确认.对于持久化的队列,意味着已经写入磁盘,对于镜像队列,意味着所有镜像都接受成功.

至于如何确认的问题,上面已经写了 basic.ack方法

4.消费方如何告知rabbitmq消息消费成功或失败?

8ecbc6982353d14fb21f7d47ad241d9a.png

如图可知,根据消费方不同的确认模式,确认时机也不同.

自动确认会在消息发送给消费者后立即确认,如果手动则当消费者调用ack,nack,reject几种方法时进行确认.

一般会设置手动模式,业务失败后可以进行一些操作.

5.使用spring的代码示例

下面是一个使用spring整合的代码示例:

首先是rabbitmq的配置文件:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/rabbit

http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd">

id="connectionFactory"

host="${rabbit.host}"

port="${rabbit.port}"

username="${rabbit.username}"

password="${rabbit.password}"

publisher-confirms="true"

/>

confirm-callback="confirmCallBackListener"

return-callback="returnCallBackListener"

mandatory="true"

/>

connection-factory="connectionFactory" acknowledge="manual" >

然后发送方:

import org.springframework.amqp.core.AmqpTemplate;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service("publishService")

public class PublishService {

@Autowired

private AmqpTemplate amqpTemplate;

public void send(String exchange, String routingKey, Object message) {

amqpTemplate.convertAndSend(exchange, routingKey, message);

}

}

消费方:

import org.springframework.amqp.core.Message;

import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;

import org.springframework.stereotype.Service;

import com.rabbitmq.client.Channel;

@Service("receiveConfirmTestListener")

public class ReceiveConfirmTestListener implements ChannelAwareMessageListener {

@Override

public void onMessage(Message message, Channel channel) throws Exception {

try{

System.out.println("consumer--:"+message.getMessageProperties()+":"+new String(message.getBody()));

channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

}catch(Exception e){

e.printStackTrace();//TODO 业务处理

channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);

}

}

}

确认后回调:

import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;

import org.springframework.amqp.rabbit.support.CorrelationData;

import org.springframework.stereotype.Service;

@Service("confirmCallBackListener")

public class ConfirmCallBackListener implements ConfirmCallback{

@Override

public void confirm(CorrelationData correlationData, boolean ack, String cause) {

System.out.println("confirm--:correlationData:"+correlationData+",ack:"+ack+",cause:"+cause);

}

}

失败后return回调:

import org.springframework.amqp.core.Message;

import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;

import org.springframework.stereotype.Service;

@Service("returnCallBackListener")

public class ReturnCallBackListener implements ReturnCallback{

@Override

public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {

System.out.println("return--message:"+new String(message.getBody())+",replyCode:"+replyCode+",replyText:"+replyText+",exchange:"+exchange+",routingKey:"+routingKey);

}

}

测试类:

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.dingcheng.confirms.publish.PublishService;

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = {"classpath:application-context.xml"})

public class TestConfirm {

@Autowired

private PublishService publishService;

private static String exChange = "DIRECT_EX";

@Test

public void test1() throws InterruptedException{

String message = "currentTime:"+System.currentTimeMillis();

System.out.println("test1---message:"+message);

//exchange,queue 都正确,confirm被回调, ack=true

publishService.send(exChange,"CONFIRM_TEST",message);

Thread.sleep(1000);

}

@Test

public void test2() throws InterruptedException{

String message = "currentTime:"+System.currentTimeMillis();

System.out.println("test2---message:"+message);

//exchange 错误,queue 正确,confirm被回调, ack=false

publishService.send(exChange+"NO","CONFIRM_TEST",message);

Thread.sleep(1000);

}

@Test

public void test3() throws InterruptedException{

String message = "currentTime:"+System.currentTimeMillis();

System.out.println("test3---message:"+message);

//exchange 正确,queue 错误 ,confirm被回调, ack=true; return被回调 replyText:NO_ROUTE

publishService.send(exChange,"",message);

//        Thread.sleep(1000);

}

@Test

public void test4() throws InterruptedException{

String message = "currentTime:"+System.currentTimeMillis();

System.out.println("test4---message:"+message);

//exchange 错误,queue 错误,confirm被回调, ack=false

publishService.send(exChange+"NO","CONFIRM_TEST",message);

Thread.sleep(1000);

}

}

测试结果:

test1---message:currentTime:1483786948506

test2---message:currentTime:1483786948532

consumer--:MessageProperties [headers={spring_return_correlation=445bc7ca-a5bd-47e2-8ba3-f0448420e441}, timestamp=null, messageId=null, userId=null, appId=null, clusterId=null, type=null, correlationId=null, replyTo=null, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, deliveryMode=PERSISTENT, expiration=null, priority=0, redelivered=false, receivedExchange=DIRECT_EX, receivedRoutingKey=CONFIRM_TEST, deliveryTag=1, messageCount=0]:currentTime:1483786948506

test3---message:currentTime:1483786948536

confirm--:correlationData:null,ack:false,cause:channel error; protocol method: #method(reply-code=404, reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60, method-id=40)

confirm--:correlationData:null,ack:false,cause:Channel closed by application

[ERROR] 2017-01-07 19:02:28 org.springframework.amqp.rabbit.connection.CachingConnectionFactory.shutdownCompleted(CachingConnectionFactory.java:281):--> Channel shutdown: channel error; protocol method: #method(reply-code=404, reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60, method-id=40)

return--message:currentTime:1483786948536,replyCode:312,replyText:NO_ROUTE,exchange:DIRECT_EX,routingKey:

confirm--:correlationData:null,ack:true,cause:null

test4---message:currentTime:1483786948546

confirm--:correlationData:null,ack:false,cause:channel error; protocol method: #method(reply-code=404, reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60, method-id=40)

[ERROR] 2017-01-07 19:02:28 org.springframework.amqp.rabbit.connection.CachingConnectionFactory.shutdownCompleted(CachingConnectionFactory.java:281):--> Channel shutdown: channel error; protocol method: #method(reply-code=404, reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60, method-id=40)

代码和配置里面,已经都有注释,就不在多说明了.(callback是异步的,所以测试中sleep1秒钟等待下)

总结下就是:

如果消息没有到exchange,则confirm回调,ack=false

如果消息到达exchange,则confirm回调,ack=true

exchange到queue成功,则不回调return

exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)

备注:需要说明,spring-rabbit和原生的rabbit-client ,表现是不一样的.

测试的时候,原生的client,exchange错误的话,直接就报错了,是不会到confirmListener和returnListener的

RabbitMQ 中,消费者接收消息后,需要发送 ACK 确认接收,以告知 RabbitMQ消息已经被正确地接收并处理。如果消费者接收消息后没有发送 ACK 确认接收,那么 RabbitMQ 将会认为该消息没有被正确地处理,会重新将消息发送给其他消费者。 在 RabbitMQ 中,发送 ACK 确认接收的方式有两种: 1. 自动确认模式 在自动确认模式下,当消费者接收消息后,RabbitMQ 会自动发送 ACK 确认接收,不需要手动发送 ACK。这种模式下,如果消息处理失败,那么消息就会被丢弃,因此,只有在消息处理相对简单、不需要进行复杂的错误处理时,才适合使用自动确认模式。 2. 手动确认模式 在手动确认模式下,当消费者接收消息后,需要手动发送 ACK 确认接收。如果消息处理失败,可以发送 NACK 拒绝接收,然后重新将消息发送给其他消费者。手动确认模式可以保证消息的可靠性和一致性,但需要消费者手动发送 ACK、NACK 等命令,因此比较复杂。 在 RabbitMQJava 客户端中,可以使用 channel.basicAck() 方法手动发送 ACK 确认接收,使用 channel.basicNack() 方法发送 NACK 拒绝接收。例如,以下代码演示了如何手动发送 ACK 确认接收: ```java channel.basicConsume(queueName, false, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 处理消息 // ... // 手动发送 ACK 确认接收 channel.basicAck(envelope.getDeliveryTag(), false); } }); ``` 在上述代码中,第二个参数设置为 false,表示关闭自动确认模式,需要手动发送 ACK 确认接收。当消息处理完成后,调用 channel.basicAck() 方法发送 ACK 确认接收。这样可以保证消息被正确地处理,并且可以避免消息丢失的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值