Spring已经集成了RabbitMq,所以在SpringBoot整合上也是很简单,只要导入相应的启动器,并进行配置
1. 导入启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置RabbitMq
RabbitMq的相关配置可以参考RabbitProperties类
spring:
rabbitmq:
host: 192.168.0.109
port: 5672
username: guest
password: guest
virtual-host: / # 虚拟主机
3. 开启RabbitMq功能
@EnableRabbit // 开启RabbitMq功能
@SpringBootApplication
public class TestDemo{
public static void main(String[] args) {
SpringApplication.run(TestDemo.class, args);
}
}
接下来就开始通过Java代码来操作RabbitMq
4. 创建一个交换机
如果在web后台能正确创建交换机,在Java中也不是啥难事
@Autowired
private AmqpAdmin amqpAdmin;//高级消息队列管理组件
/**
* 创建交换器
* 通过高级消息队列管理组件来创建
* 传入交换器类型的对象
* 交换器类型的对象有:AbstractExchange,CustomExchange,DirectExchange,FanoutExchange,HeadersExchange,TopicExchange
*/
@Test
public void createExchange(){
//交换器对象参数列表:交换器名,是否持久化,是否自动删除,其他参数
//direct 类型
amqpAdmin.declareExchange(new DirectExchange("hello-java-direct-exchange",true,false));
//fanout 类型
amqpAdmin.declareExchange(new FanoutExchange("hello-java-fanout-exchange"));
//topic 类型
amqpAdmin.declareExchange(new TopicExchange("hello-java-topic-exchange"));
}
执行完成后在后台系统可以看到通过Java创建的交换机
5. 创建队列
@Autowired
private AmqpAdmin amqpAdmin;//高级消息队列管理组件
/**
* 创建队列
* 注意导入的Queue的包是org.springframework.amqp.core下的
*/
@Test
public void createQueue() {
//Queue参数列表:队列名,是否持久化,是否排他,是否自动删除,其他参数
String s = amqpAdmin.declareQueue(new Queue("hello-java-queue", true, false, false));
System.out.println(s);//返回队列名
}
创建队列成功后,会返回当前创建的队列名,并且可以再后台系统中看向相应的结果
6. 交换器绑定队列
@Autowired
private AmqpAdmin amqpAdmin;//高级消息队列管理组件
/**
* 交换器绑定队列
*/
@Test
public void createBinding() {
//绑定的对象,注意导入的包是org.springframework.amqp.core下的
//对象的参数列表:目的地,目的地类型,交换器,路由键,其他参数
Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-direct-exchange", "hello.java", null);
amqpAdmin.declareBinding(binding);
}
查看结果
7. 发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
*/
@Test
public void sendMessage() {
//参数列表:交换器,路由键,消息内容
rabbitTemplate.convertAndSend("hello-java-direct-exchange", "hello.java", "hello world");
}
发送成功后在后台系统队列列表中可以看到有一条记录(之前是没有记录的),并且在队列详情页面可以获取到发送的消息
注意,发送的消息体是一个Object类型的,也就是说它可以发送一个实体类对象,注意这个对象必须的序列化,因为在消息队列中的保存类型是application/x-java-serialized-object,也就是序列化保存,它保存的是一个序列化后的结果,如下图,当然这是一串我们看不懂的数据,如果想将保存后的数据换成我们能看懂的,可以配置消息转换策略为json
8. 配置消息转换器
为了能让我们能看懂发送的对象,可以自定义消息转换器,默认的消息转换器对象是SimpleMessageConverter,我们可以使用json的消息转换器Jackson2JsonMessageConverter,这样存储的就是一个我们能看懂的json数据了,注意导入的包是org.springframework.amqp.support.converter下的
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRabbitConfig {
/**
* 配置消息转换器
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
再来测试传对象存队列的效果,成功转成json格式数据了
9. 接收消息
在服务器定义一个方法,并使用 @RabbitListener 注解去监听指定的队列,当监听的队列有消息,就将消息读取出来
/**
* 接收消息
* @param message 队列中的消息(头+体) org.springframework.amqp.core.Message
* @param o 存入队列的消息的具体消息,不包含消息头,Spring会自动转换
* @param channel 当前传输数据的通道 com.rabbitmq.client.Channel
*/
@RabbitListener(queues = {"hello-java-queue"})
public void recieveMessage(Message message, User o, Channel channel) {
System.out.println("具体消息====>" + message.toString());
System.out.println("消息体====>" + o.toString());
}
然后通过单测试发送一个消息来查看测试结果
接下来有这么一个场景,如果我向队列中发布不同参数类型的消息,那么该怎么来接收这个消息呢?@RabbitListener是直接监听消息队列然后将获取到的消息交给方法。我们可以使用**@RabbitListener** 和 @RabbitHandler组合的方式来进行接收不同类型的消息,@RabbitListener 可以用在类和方法上,而 @RabbitHandler 只能用在方法上
@RestController
@RabbitListener(queues = {"hello-java-queue"})
public class RabbitDemoController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/send/msg")
public String sendMessage(Integer num) {
for (int i = 0; i < num; i++) {
if (i % 2 == 0) {
UserTest user = new UserTest();
user.setUsername("UserTest==>" + i);
rabbitTemplate.convertAndSend("hello-java-direct-exchange", "hello.java", user,new CorrelationData(UUID.randomUUID().toString()));//new CorrelationData(UUID.randomUUID().toString())传的唯一ID在消息确认机制会用到
} else {
OrderTest entity = new OrderTest();
entity.setOrder("OrderTest==>" + i);
rabbitTemplate.convertAndSend("hello-java-direct-exchange", "hello.java", entity,new CorrelationData(UUID.randomUUID().toString()));
}
}
return "ok";
}
@RabbitHandler
public void recieveMessage(UserTest o) {
System.out.println("消息体UserTest====>" + o.toString());
}
@RabbitHandler
public void recieveMessage2(OrderTest o) {
System.out.println("消息体OrderTest====>" + o.toString());
}
@Data
public static class UserTest implements Serializable {
private String username;
}
@Data
public static class OrderTest implements Serializable {
private String order;
}
}
在分布式系统中有很多的微服务,微服务连接MQ服务器并监听队列消息,但是由于网络的原因或者服务器宕机等原因造成消息的丢失,所以在生产者发送消息,消费者获取消息这一些列操作一定要保证消息的不丢失,可靠抵达,可以使用事务消息,但是如果使用事务,按照官方文档说的性能会下降250倍。所以在分布式高并发状态,为了快速的确认消息是否成功,而引入了消息确认机制
确认机制分为两端操作,生产者端的确认模式 和 退回模式,消费者端的ack机制,相关说明可以参考官方文档(传送门)
确认模式:ConfirmCallback,消息只要被broker消息代理接收到就会执行ConfirmCallback,如果是cluster集群模式,需要所有的broker接收到才会调用ConfirmCallback。被broker接收到只能表示message已经到达服务器,并不能保证消息一定会被投递到目标queue里,所以还需要调用ReturnCallback
退回模式:confirm确认模式只能保证消息到达broker,不能保证消息准确投递到目标queue里,所以还需要用到return退回模式,这样如果未能投递到目标queque里将调用returnCallback,可以记录下详细投递数据
spring:
rabbitmq:
publisher-confirm-type: correlated # 发送端消息抵达服务器确认 可选值:correlated,none(默认值),simple
publisher-returns: true # 发送端消息抵达队列确认
template:
mandatory: true # 只要抵达队列,以异步发送优先回调这个returnConfirm
/**
* 配置生产者端确认机制
*/
@PostConstruct //对象创建完成后执行这个方法,保证这个方法在系统启动后生效
public void initRabbitTemplate() {
// 配置消息抵达服务器的确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* @param correlationData 当前消息的唯一关联数据(消息的唯一ID)
* @param ack 消息是否成功收到,只要消息抵达服务器就为true
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println(correlationData);//在发送消息时需要指定唯一ID才有值
System.out.println(ack);
System.out.println(cause);
}
});
// 配置消息抵达队列的消息回调,没有抵达队列才会进行回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* @param message 投递失败的消息的详细信息
* @param replyCode 回复的状态码
* @param replyText 回复的文本内容
* @param exchange 消息发给的交换机
* @param routingKey 消息发送时的路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
}
});
}
Ack机制:消费者一旦获取了消息,就会给服务器回复获取成功,然后服务器就会将此消息进行删除,并且默认是自动确认的,但是如果消费者端在处理消息的时候服务器突然宕机或其他原因导致消息没有处理完,自动ack机制就会将所有的消息自动回复然后服务器就会将消息全部删除,就会出现消息丢失的现象。为了保证消息不丢失就可以使用手动确认
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual # 配置回复模式,可选值:auto(默认自动回复),none,manual(手工)
@RabbitHandler
public void recieveMessage2(Message message, OrderTest o, Channel channel) throws IOException {
System.out.println("消息体OrderTest====>" + o.toString());
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag, false);//给服务器回复已处理这个消息,第二个参数代表是否批量操作
//处理消息失败并退回到服务器中
//long deliveryTag, boolean multiple(批量拒绝), boolean requeuevar4(重新入队)
//channel.basicNack(deliveryTag,false,true);
//long deliveryTag, boolean requeue(重新入队)
//channel.basicReject(deliveryTag,true);
}