消息可靠性
在项目中使用RabbitMQ时,我们可能会遇到这样的问题:如一个订单系统当用户付款成功时我们往消息中间件添加一条记录期望消息消费者修改订单状态,但是最终实际订单状态并没有被修改成功。遇到这种问题我们排查的思路如下:
1.消息是否已经成功发送到消息中间件
2.消息是否有丢失的情况 消息是否已经被消费成功
在生产环境下是不容许出现消息投递/消费错误的情况的,因为这可能会对企业产生巨大的损失,本博客将介绍RabbitMQ如何保证消息的可靠性(生产者保证消息可靠投递,消费者保证消息可靠消费,RabbitMQ持久化)
本系列博客源码GIT地址:https://github.com/RobertoHuang/RGP-RABBITMQ.git
生产者保证消息可靠投递
为了保证消息被正确投递到消息中间件,RabbitMQ提供了如下两个配置来保证消息投递的可靠性:
1.在发送消息的时候我们可以设置Mandatory属性。如果设置了Mandatory属性则当消息不能被正确路由到队列中去时将会触发Return Method,这样我们可以在Return Method中进行相关业务处理,如果Mandatory没有设置则当消息不能正确路由到队列中去的时候,Broker将会丢弃该消息
2.RabbitMQ还提供了消息确认机制(Publisher Confirm)。生产者将Channel设置成Confirm模式,当设置Confirm模式后所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始,ID在同个Channel范围是唯一的),一旦消息被投递到所有匹配的队列之后Broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了
如果消息和队列是可持久化的那么确认消息会将消息写入磁盘之后出,Broker回传给生产者的确认消息中DeliverTag域包含了确认消息的序列号,此外Broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理(multiple如果为true则表示小于等于deliveryTag的消息都被投递成功,如果为false则表示只有等于deliveryTag的消息已经被投递成功)
除了使用Publisher Confirm方式,RabbitMQ还提供了事务机制保证消息投递,但是使用事务会大大降低系统的吞吐量,就失去了消息中间件存在的意义,本博客不进行探讨。Publisher Confirm模式最大的好处在于他是异步的,一旦发布一条消息生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用可以通过回调ACK方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,生产者应用可以通过回调NACK方法来处理该确认消息
Publisher Confirm机制在性能上要比事务优越很多,但是Publisher Confirm机制无法进行回滚,一旦服务器崩溃生产者无法得到Confirm信息,生产者其实本身也不知道该消息是否已经被持久化,只有继续重发来保证消息不丢失,但是如果原先已经持久化的消息并不会被回滚,这样队列中就会存在两条相同的消息,系统需要支持去重
RabbitMQ Java Client
1.创建连接工厂工具类
public class ChannelUtils {
public static Channel getChannelInstance(String connectionDescription) {
try {
ConnectionFactory connectionFactory = getConnectionFactory();
Connection connection = connectionFactory.newConnection(connectionDescription);
return connection.createChannel();
} catch (Exception e) {
throw new RuntimeException("获取Channel连接失败");
}
}
private static ConnectionFactory getConnectionFactory() {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.56.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("roberto");
connectionFactory.setPassword("roberto");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(10000);
Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
connectionFactoryPropertiesMap.put("description", "RGP订单系统V2.0");
connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
connectionFactory.setClientProperties(connectionFactoryPropertiesMap);
return connectionFactory;
}
}
2.创建消息生产者 将channel.basicPublish()方法的Mandatory属性设置为true,并为Channel添加ReturnListener,同时使用channel.confirmSelect()开启消息确认模式并且为channel添加ConfirmListener,发送两条消息(修改其中一条消息的Routing Key让消息不能被正确路由)
public class MessageProducer {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = ChannelUtils.getChannelInstance("RGP订单系统消息生产者");
channel.exchangeDeclare("roberto.order", BuiltinExchangeType.DIRECT, true, false, false,new HashMap<>());
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().deliveryMode(2).contentType("UTF-8").build();
// 当消息没有被正确路由时 回调ReturnListener
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("replyCode:" + replyCode);
System.out.println("replyText:" + replyText);
System.out.println("exchange:" + exchange);
System.out.println("routingKey:" + routingKey);
System.out.println("properties:" + properties);
System.out.println("body:" + new String(body, "UTF-8"));
}
});
// 开启消息确认
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("----------Ack----------");
System.out.println(deliveryTag);
System.out.println(multiple);
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("----------Nack----------");
System.out.println(deliveryTag);
System.out.println(multiple);
}
});
// 将mandatory属性设置成true
channel.basicPublish("roberto.order", "add", true, basicProperties, "订单信息".getBytes());
channel.basicPublish("roberto.order", "addXXX", true, basicProperties, "订单信息".getBytes());
}
}
3.创建消息消费者
public class MessageConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = ChannelUtils.getChannelInstance("RGP订单系统消息消费者");
AMQP.Queue.DeclareOk declareOk = channel.queueDeclare("roberto.order.add", true, false, false, new HashMap<>());
channel.exchangeDeclare("roberto.order", BuiltinExchangeType.DIRECT, true, false, false, new HashMap<>());
channel.queueBind(declareOk.getQueue(), "roberto.order", "add", new HashMap<>());
channel.basicConsume(declareOk.getQueue(), true, "RGP订单系统ADD处理逻辑消费者", new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(consumerTag);
System.out.println(envelope.toString());
System.out.println(properties.toString());
System.out.println("消息内容:" + new String(body));
}
});
}
}
4.先后启动消息消费者和生产者,控制台输出如下
replyCode:312
replyText:NO_ROUTE
exchange:roberto.order
routingKey:addXXX
properties:#contentHeader<basic>(content-type=UTF-8, content-encoding=null, headers=null, delivery-mode=2, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
body:订单消息
----------Ack----------
2
false
----------Ack----------
1
false
以上输出说明当消息不能被确认路由时调用了ReturnListener的handleReturn方法,同时说明如果消息没有被正确路由仍然走的是ACK方法,Publisher Confirm只能保证消息到达消息中间件。如果要测试NACK方法可以通过在发送消息还没被确认时,停止RabbitMQ服务进行测试
Spring AMQP消息可靠投递
1.创建消息生产者配置类 将CachingConnectionFactory的PublisherReturns设置为true,设置RabbitTemplate的Mandatory属性为true,并且为RabbitTemplate设置ReturnCallback。将CachingConnectionFactory的PublisherConfirms设置为true,同时为RabbitTemplate设置ConfirmCallback
@Configuration
public class SpringAMQPProducerConfig {
@Bean
public ConnectionFactory connectionFactory() {
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.setHost("192.168.56.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("roberto");
connectionFactory.setPassword("roberto");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(10000);
Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
connectionFactoryPropertiesMap.put("description", "RGP订单系统V2.0");
connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
connectionFactory.setClientProperties(connectionFactoryPropertiesMap);
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
// 将CachingConnectionFactory的PublisherConfirms设置为true
cachingConnectionFactory.setPublisherConfirms(true);
// 将CachingConnectionFactory的PublisherReturns设置为true
cachingConnectionFactory.setPublisherReturns(true);
return cachingConnectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 设置RabbitTemplate的Mandatory属性为true
rabbitTemplate.setMandatory(true);
// 为RabbitTemplate设置ReturnCallback
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
try {
System.out.println("replyCode:" + replyCode);
System.out.println("replyText:" + replyText);
System.out.println("exchange:" + exchange);
System.out.println("routingKey:" + routingKey);
System.out.println("properties:" + message.getMessageProperties());
System.out.println("body:" + new String(message.getBody(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
});
// 为RabbitTemplate设置ConfirmCallback
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println(ack);
System.out.println(cause);
System.out.println(correlationData.getId());
}
});
return rabbitTemplate;
}
}
2.创建生产者启动类 发送两条消息(修改其中一条消息的Routing key让消息不能被正确路由),同时在发送消息时设置CorrelationData属性
@ComponentScan(basePackages = "roberto.growth.process.rabbitmq.dependable.deliver.spring.amqp.producer")
public class ProducerApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProducerApplication.class);
RabbitAdmin rabbitAdmin = context.getBean(RabbitAdmin.class);
RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
rabbitAdmin.declareExchange(new DirectExchange("roberto.order", true, false, new HashMap<>()));
MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
messageProperties.setContentType("UTF-8");
Message message = new Message("订单信息".getBytes(), messageProperties);
rabbitTemplate.send("roberto.order", "add", message, new CorrelationData("201210704116"));
rabbitTemplate.send("roberto.order", "addXXX", message, new CorrelationData("201210704116"));
}
}
3.创建消费者配置类
@Configuration
public class SpringAMQPConsumerConfig {
@Bean
public ConnectionFactory connectionFactory() {
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.setHost("192.168.56.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("roberto");
connectionFactory.setPassword("roberto");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(10000);
Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
connectionFactoryPropertiesMap.put("description", "RGP订单系统V2.0");
connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
connectionFactory.setClientProperties(connectionFactoryPropertiesMap);
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
return cachingConnectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
@Bean
public Queue queue() {
return new Queue("roberto.order.add", true, false, false, new HashMap<>());
}
@Bean
public Exchange exchange() {
return new DirectExchange("roberto.order", true, false, new HashMap<>());
}
@Bean
public Binding binding() {
return BindingBuilder.bind(new Queue("roberto.order.add")).to(new DirectExchange("roberto.order")).with("add");
}
@Bean
public MessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer messageListenerContainer = new SimpleMessageListenerContainer();
messageListenerContainer.setConnectionFactory(connectionFactory);
messageListenerContainer.setQueueNames("roberto.order.add");
messageListenerContainer.setConcurrentConsumers(5);
messageListenerContainer.setMaxConcurrentConsumers(10);
Map<String, Object> argumentMap = new HashMap();
messageListenerContainer.setConsumerArguments(argumentMap);
messageListenerContainer.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String s) {
return "RGP订单系统ADD处理逻辑消费者";
}
});
messageListenerContainer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
System.out.println(new String(message.getBody(), "UTF-8"));
System.out.println(message.getMessageProperties());
} catch (Exception e) {
e.printStackTrace();
}
}
});
return messageListenerContainer;
}
}
4.创建消费者启动类
@ComponentScan(basePackages = "roberto.growth.process.rabbitmq.dependable.deliver.spring.amqp.consumer")
public class ConsumerApplication {
public static void main(String[] args) {
new AnnotationConfigApplicationContext(ConsumerApplication.class);
}
}
5.依次启动消息消费者和生产者 控制台输出如下
replyCode:312
replyText:NO_ROUTE
exchange:roberto.order
routingKey:addXXX
properties:MessageProperties [headers={}, contentType=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0]
body:订单信息
true
null
201210704116
true
null
201210704116
以上输出说明当消息不能被确认路由时调用了ReturnCallback的returnedMessage方法,同时说明如果消息没有被正确路由ACK的值仍为true,Publisher Confirm只能保证消息到达消息中间件。如果要使得ACK的值为false可以通过在发送消息还没被确认时,停止RabbitMQ服务进行测试
同时Spring AMQP对Publisher Confirm进行了封装,我们可以在发送消息时传递CorrelationData,当调用消息确认回调方法时我们可以获取到发送消息时传递的CorrelationData,该功能为我们业务处理提供了极大便利,我们不再需要花成本去维护Delivery Tag,可以直接使用CorrelationData的getId()方法获取业务主键
消费者保证消息可靠消费
以上章节我们使用消息返回和消息确认机制保证消息能够到达消息中间件并被正确路由到队列,但是在消费者消费消息时我们无法得到反馈信息,我们无法得知消息是否已经被消费成功。为了实现该功能RabbitMQ提供了Consumer Acknowledgements机制,使用Consumer Acknowledgements能在消费者消费消息后给Broker进行反馈,Broker根据反馈对消息进行处理
RabbitMQ Java Client
1.创建连接工厂工具类
public class ChannelUtils {
public static Channel getChannelInstance(String connectionDescription) {
try {
ConnectionFactory connectionFactory = getConnectionFactory();
Connection connection = connectionFactory.newConnection(connectionDescription);
return connection.createChannel();
} catch (Exception e) {
throw new RuntimeException("获取Channel连接失败");
}
}
private static ConnectionFactory getConnectionFactory() {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.56.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("roberto");
connectionFactory.setPassword("roberto");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(10000);
Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
connectionFactoryPropertiesMap.put("description", "RGP订单系统V2.0");
connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
connectionFactory.setClientProperties(connectionFactoryPropertiesMap);
return connectionFactory;
}
}
2.创建消息生产者 发送两条消息
public class MessageProducer {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = ChannelUtils.getChannelInstance("RGP订单系统消息生产者");
channel.exchangeDeclare("roberto.order", BuiltinExchangeType.DIRECT, true, false, false, new HashMap<>());
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().deliveryMode(2).contentType("UTF-8").build();
channel.basicPublish("roberto.order", "add", false, basicProperties, "订单信息".getBytes());
channel.basicPublish("roberto.order", "add", false, basicProperties, "订单信息2".getBytes());
}
}
3.创建消息消费者,使用channel.basicConsume()方法将消息自动确认设置为false,在消费消息的时候手动使用channel.basicAck()进行消息确认,channel.basicNack()进行消息拒绝
public class MessageConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = ChannelUtils.getChannelInstance("RGP订单系统消息消费者");
AMQP.Queue.DeclareOk declareOk = channel.queueDeclare("roberto.order.add", true, false, false, new HashMap<>());
channel.exchangeDeclare("roberto.order", BuiltinExchangeType.DIRECT, true, false, false, new HashMap<>());
channel.queueBind(declareOk.getQueue(), "roberto.order", "add", new HashMap<>());
channel.basicConsume(declareOk.getQueue(), false, "RGP订单系统ADD处理逻辑消费者", new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
System.out.println(consumerTag);
System.out.println(envelope.toString());
System.out.println(properties.toString());
System.out.println("消息内容:" + new String(body));
if ("订单信息2".equals(new String(body))) {
throw new RuntimeException();
} else {
channel.basicAck(envelope.getDeliveryTag(), false);
}
} catch (Exception e) {
channel.basicNack(envelope.getDeliveryTag(), false, true);
}
}
});
}
}
4.先后启动消息消费者和生产者,查看RabbitMQ管理控制台
通过以上图片我们可以看到两条消息先是处于未确认状态,其中一条消息在调用channel.basicAck()后消息被成功消费,另外一条消息由于未确认重新发送回队列,所有又回到Ready状态,如此循环
Spring AMQP消息可靠消费
1.创建消息生产者配置类
@Configuration
public class SpringAMQPProducerConfig {
@Bean
public ConnectionFactory connectionFactory() {
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.setHost("192.168.56.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("roberto");
connectionFactory.setPassword("roberto");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(10000);
Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
connectionFactoryPropertiesMap.put("description", "RGP订单系统V2.0");
connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
connectionFactory.setClientProperties(connectionFactoryPropertiesMap);
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
return cachingConnectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
return new RabbitTemplate(connectionFactory);
}
}
2.创建生产者启动类
@ComponentScan(basePackages = "roberto.growth.process.rabbitmq.dependable.consumer.spring.amqp.producer")
public class ProducerApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProducerApplication.class);
RabbitAdmin rabbitAdmin = context.getBean(RabbitAdmin.class);
RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
rabbitAdmin.declareExchange(new DirectExchange("roberto.order", true, false, new HashMap<>()));
MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
messageProperties.setContentType("UTF-8");
Message message = new Message("订单信息".getBytes(), messageProperties);
rabbitTemplate.send("roberto.order", "add", message, new CorrelationData("201210704116"));
MessageProperties messageProperties2 = new MessageProperties();
messageProperties2.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
messageProperties2.setContentType("UTF-8");
Message message2 = new Message("订单信息2".getBytes(), messageProperties2);
rabbitTemplate.send("roberto.order", "add", message2, new CorrelationData("201210704116"));
}
}
3.创建消费者配置类 将MessageListenerContainer的AcknowledgeMode设置成手动的,同时为MessageListenerContainer设置ChannelAwareMessageListener,在消费消息的时候手动使用channel.basicAck()进行消息确认,channel.basicNack()进行消息拒绝
@Configuration
public class SpringAMQPConsumerConfig {
@Bean
public ConnectionFactory connectionFactory() {
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.setHost("192.168.56.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("roberto");
connectionFactory.setPassword("roberto");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(10000);
Map<String, Object> connectionFactoryPropertiesMap = new HashMap();
connectionFactoryPropertiesMap.put("principal", "RobertoHuang");
connectionFactoryPropertiesMap.put("description", "RGP订单系统V2.0");
connectionFactoryPropertiesMap.put("emailAddress", "RobertoHuang@foxmail.com");
connectionFactory.setClientProperties(connectionFactoryPropertiesMap);
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
return cachingConnectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
@Bean
public Queue queue() {
return new Queue("roberto.order.add", true, false, false, new HashMap<>());
}
@Bean
public Exchange exchange() {
return new DirectExchange("roberto.order", true, false, new HashMap<>());
}
@Bean
public Binding binding() {
return BindingBuilder.bind(new Queue("roberto.order.add")).to(new DirectExchange("roberto.order")).with("add");
}
@Bean
public MessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer messageListenerContainer = new SimpleMessageListenerContainer();
messageListenerContainer.setConnectionFactory(connectionFactory);
messageListenerContainer.setQueueNames("roberto.order.add");
messageListenerContainer.setConcurrentConsumers(5);
messageListenerContainer.setMaxConcurrentConsumers(10);
Map<String, Object> argumentMap = new HashMap();
messageListenerContainer.setConsumerArguments(argumentMap);
messageListenerContainer.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String s) {
return "RGP订单系统ADD处理逻辑消费者";
}
});
// 设置消息确认模式为手动模式
messageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
messageListenerContainer.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
System.out.println(new String(message.getBody(), "UTF-8"));
System.out.println(message.getMessageProperties());
if ("订单信息2".equals(new String(message.getBody(), "UTF-8"))) {
throw new RuntimeException();
} else {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
} catch (Exception e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
});
return messageListenerContainer;
}
}
4.创建消费者启动类
@ComponentScan(basePackages = "roberto.growth.process.rabbitmq.dependable.consumer.spring.amqp.consumer")
public class ConsumerApplication {
public static void main(String[] args) {
new AnnotationConfigApplicationContext(ConsumerApplication.class);
}
}
如上代码使用Spring AMQP实现的效果和RabbitMQ Java Client中实现的效果一致
注意:AcknowledgeMode有三个可选值分别是NONE,MANUAL,AUTO。NONE为自动确认等效于autoAck=true,MANUAL手动确认等效于autoAck=false,AUTO是根据方法的执行情况来决定是确认还是拒绝,如果消息被成功消费了则自动确认,如果在消费消息时抛出AmqpRejectAndDontRequeueException消息会被拒绝并且不会重新入队列,如果抛出ImmediateAcknowledgeAmqpException则消息会被确认,如果抛出其他异常则消息会被拒绝,并且重新入队列。更多详细信息可查阅SimpleMessageListenerContainer的doReceiveAndExecute()方法进行获取
RabbitMQ持久化
以上篇幅我们介绍了如何保证生产者和消费者消息的可靠性,但是假设在运行过程中RabbitMQ服务端宕机了,若此前没有进行持久化操作则消息就会丢失。所以使用RabbitMQ通常建议开启持久化功能
1.交换机持久化 在声明时指定durable为true
2.队列持久化 在声明时指定durable为true
3.消息持久化 在声明时指定delivery_mode为2
只有进行如上几个操作,我们才能保证RabbitMQ消息的可靠性