结合我上一章总结的RabbitMQ的基本知识,其中的主要消息传递出现在下图三个节点,所以我们要做好消息的可靠投递就要保证这结果地方的信息不能丢失。
实战
1、在我们写代码之前
我们首先要创建好我们自己的 交换机、队列、绑定关系。然后才能进行消息的发送和接受。
这几项的设置最简单的方式可以在我们的RabbitMQ的可视化界面直接操作,也可以在Java代码中设置。
1.1、先介绍可视化界面配置
1.1.1、添加交换机
1.1.2、添加消息队列
1.1.3、绑定交换机和消息队列的关系
1.2、在介绍Java直接配置的方式
public void createExChange() {
DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
log.info("exchange:{}创建完成","hello-java-exchange");
}
public void createQueue() {
Queue queue = new Queue("hello-java-queue",true,false,false);
amqpAdmin.declareQueue(queue);
log.info("queue:{}创建完成","hello-java-queue");
}
public void createBinding() {
Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE,"hello-java-exchange","hello-java",null);
amqpAdmin.declareBinding(binding);
log.info("binding:{}创建完成","hello-java-binding");
}
2、发送消息的接口(生产者)
@GetMapping("/sendMsg")
public void sendMessageTest() {
String msg = "hello world";
OrderEntity orderEntity = new OrderEntity();
orderEntity.setId(111l);
orderEntity.setCreateTime(new Date());
orderEntity.setBillHeader("hello.....");
rabbitTemplate.convertAndSend("hello-java-exchange","hello-java",orderEntity);
log.info("消息发送成功:{}",orderEntity);
}
代码中的 "hello-java-exchange"为交换机,"hello-java" 为路由键,这两个都是我们提前创建好的。
3、接收消息接口(消费者)
@RabbitListener(queues = {"hello-java-queue"})
public void recieveMessage(Message message, OrderReturnReasonEntity content, Channel channel) {
System.out.println("接受消息...内容:" + content );
byte[] body = message.getBody();
// 内部顺序自增
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag==>" + deliveryTag);
}
这样其实已经就实现了我们的消息发送接受的功能,没当我们调用接口发送了一条消息后,消费者这边就会监听到,就会去消费这条消息。
接下来我们开始重点实现消息可靠投递
4、生产者到服务器实现消息可靠投递
这部分解决的就是我们开头说到的:
第一次消息传递、第二次消息传递 。
这两个部分的消息可靠投递
首先我们要开启一个配置。在我们项目中的 application.properties 文件中设置
## 开启发送端确认 (生产者发送消息到服务器)
spring.rabbitmq.publisher-confirms=true
## 开启发送端消息抵达队列的确认(交换机到发送消息到队列)
spring.rabbitmq.publisher-returns=true
## 只要抵达队列 优先异步回调 (这个设置是为了加快回调的速度)
spring.rabbitmq.template.mandatory=true
#手动ack消息确认 (消费者对消息处理成功后,自己去调用ACK通知队列我完成了,可以删除了)
spring.rabbitmq.listener.simple.acknowledge-mode=manual
下面直接放一个配置类代码,我在代码中做了具体的解释。
@Configuration
public class MyRabbitConfig {
@Autowired
RabbitTemplate rabbitTemplate;
@Primary
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
this.rabbitTemplate = rabbitTemplate;
rabbitTemplate.setMessageConverter(messageConverter());
initRabbitTemplate();
return rabbitTemplate;
}
// 把消息使用json序列化的方式
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 定制 RabbitTemplate
* 1、服务器收到消息
* 1、spring.rabbitmq.publisher-confirms=true
* 2、设置确认回调 setConfirmCallback
* 2、 消息正确抵达队列 回调
* 1、## 开启发送端消息抵达队列的确认
* spring.rabbitmq.publisher-returns=true
* 2、## 只要抵达队列 优先异步回调
* spring.rabbitmq.template.mandatory=true
*/
public void initRabbitTemplate() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* ACK 确认机制
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("==============成功发送消息到服务器回调==========" );
System.out.println( "correlationData:" + correlationData + ",ack:" + ack + ",cause:" + cause);
}
});
// 设置消息抵达队列回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 只要消息没投递到指定的队列,就触发失败回调
* @param message
* @param i
* @param s
* @param s1
* @param s2
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("=============交换机发送消息到消息队列失败回调==========");
System.out.println("message===>" + message + "i===>" + i + "s==>" + s + "s1==>" + s1 + "s2==>" + s2);
}
});
}
}
消息发送成功,消费成功时如下图所示:
失败回调可自行测试,当要发送的队列不存在时就会触发失败回调。
5、消费者获取服务器消息的可靠投递
这里处理的就是我们开头提到的 第三次消息传递 的消息可靠投递
直接上代码
@RabbitListener(queues = {"hello-java-queue"})
public void recieveMessage(Message message, OrderReturnReasonEntity content, Channel channel) {
System.out.println("接受消息...内容:" + content );
byte[] body = message.getBody();
// 内部顺序自增
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag==>" + deliveryTag);
// 这里的ack特别类似我们现实中的签收货物, 你可以现在签收也可以选择不签收。。
try {
/**
* 这里就是手动ACK确认机制,
* void basicAck(long var1, boolean var3) throws IOException; 这是源码中的
* 第一个参数:long var1 :是我们这个消息的自增ID值
* 第二个参数:boolean var3 : true/false 意思就是 这个消息处理完 我是否在消息队列中删除。
*/
channel.basicAck(deliveryTag,false);
// 这个是 n ack , 意思就是不确认,就是这条消息不被消费。
// channel.basicNack(deliveryTag, false,false);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
OK,到这里,最基本的RabbitMQ的可靠消息投递就已经实现了,当然了这种可靠投递在实际项目应用中还远远不够,喜欢可以关注我,后续我会继续更新RabbitMQ的更详细的各种操作,如消息丢失、消息重复、消息积压 等等很多问题。