一、RMQ相关配置
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、启动类开启RMQ注解
/**
使用RabbitMQ
1、引入amqp场景;RabbitAutoConfiguration就会自动生效
2、给容器中自动配置了
RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate
@ConfigurationProperties(prefix="spring.rabbitmq")
public class RabbitProperties
3、给配置文件中配置 spring.rabbitmq 信息
4、@EnableRabbit (开启RMQ注解)
**/
@EnableRabbit // 开启消息队列
// 添加注册发现功能
@EnableDiscoveryClient
@MapperScan("com.atguigu.gulimall.order.dao")
@SpringBootApplication
public class GulimallOrderApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallOrderApplication.class, args);
}
}
3、 配置文件增加RMQ属性
# RabbitMQ配置
spring.rabbitmq.host=192.168.10.10
spring.rabbitmq.port=5672
# 虚拟主机配置
spring.rabbitmq.virtual-host=/
# 开启发送端消息抵达Broker确认
spring.rabbitmq.publisher-confirms=true
# 开启发送端消息抵达Queue确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达Queue,就会异步发送优先回调returnfirm
spring.rabbitmq.template.mandatory=true
# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
4, 配置序列化
package com.kun.order.config;
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;
/**
* @author zhoukun 86547462@qq.com
* @version 1.0
* @date 2020/12/28 17:34
*/
@Configuration
public class MyRabbitConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
二、RMQ使用
1、单元测试
package com.kun.order;
import com.kun.order.entity.OrderReturnReasonEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.UUID;
@SpringBootTest
@Slf4j
class GulimallOrderApplicationTests {
@Autowired
private AmqpAdmin amqpAdmin; // 创建交换机名,创建队列,创建绑定
@Autowired
private RabbitTemplate rabbitTemplate; // 发送消息相关
/**
* 1、如何创建交换机Exchange、队列Queue、绑定关系Binding
* 1)、使用AmqpAdmin进行创建
* 2、如何收发消息
*/
//创建交换机Exchange
@Test
public void createExchange() {
/**
* DirectExchange 指定队列模式
* FanoutExchange 广播模式
* TopicExchange 主题模式
*/
Exchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
log.info("Exchange[{}]创建成功:","hello-java-exchange");
}
//创建队列Queue
@Test
public void testCreateQueue() {
Queue queue = new Queue("hello-java-queue",true,false,false);
amqpAdmin.declareQueue(queue);
log.info("Queue[{}]创建成功:","hello-java-queue");
}
//创建绑定Binding
@Test
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");
}
@Test
public void sendMessageTest() {
OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
reasonEntity.setId(1L);
reasonEntity.setCreateTime(new Date());
reasonEntity.setName("reason");
reasonEntity.setStatus(1);
reasonEntity.setSort(2);
String msg = "Hello World";
//1、发送消息,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable接口
//2、发送的对象类型的消息,可以是一个json
rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",
reasonEntity,new CorrelationData(UUID.randomUUID().toString()));
log.info("消息发送完成:{}",reasonEntity);
}
}
三、RMQ队列监听
监听消息
使用 @RabbitListener
:必须有 @EnableRabbit
@RabbitListener
:类+方法上(监听那些队列即可)@RabbitHandler
:标在方法上(重载区分不同的消息)
/**
* 监听队列
* 参数可以写类型
* 1、Message message:原生消息详细信息。头+体
* queues:声明需要监听的所有队列
* channel:当前传输数据的通道
*
* Queue:可以很多人来监听。只要收到消息,队列删除消息,而且只能有一个收到消息(分布式场景)
* 场景:
* 1)、订单服务启动多个:同一个消息,只能有一个客户端收到
*/
@RabbitListener(queues = {"hello-java-queue"})
public void revieveMessage(Message message,
OrderReturnReasonEntity content) {
//拿到主体内容
byte[] body = message.getBody();
//拿到的消息头属性信息
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("接受到的消息...内容" + message + "===内容:" + content);
}
消息队列-可靠投递
保证消息不丢失,可靠抵达,可以使用事务消息,性能下降250倍,为此引入确认机制。
- publisher confirmCallback 确认模式
- publisher returnCallback 未投递到 queue 退回模式
- consumer ack机制
一、发送端确认
修改 application.yml
文件:
#rabbitmq配置
rabbitmq:
host: 106.55.168.234
port: 5672
virtual-host: / # 虚拟主机配置
publisher-returns: true # 开启发送端消息抵达Broker确认
publisher-confirm-type: correlated #必须配置这个才会确认回调
创建配置文件:MyRabbitConfig.java
package com.kun.order.config;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* @author zhoukun 86547462@qq.com
* @version 1.0
* @date 2020/12/28 17:34
*/
@Configuration
public class MyRabbitConfig {
@Autowired
RabbitTemplate rabbitTemplate;
/**
序列化
**/
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
/**
* 定制RabbitTemplate
* 1、服务收到消息就会回调
* 1、spring.rabbitmq.publisher-confirms: true
* 2、设置确认回调
* 2、消息正确抵达队列就会进行回调
* 1、spring.rabbitmq.publisher-returns: true
* spring.rabbitmq.template.mandatory: true
* 2、设置确认回调ReturnCallback
* <p>
* 3、消费端确认(保证每个消息都被正确消费,此时才可以broker删除这个消息)
*/
@PostConstruct //MyRabbitConfig对象创建完成以后,执行这个方法
public void initRabbitTemplate() {
/**
* 1、只要消息抵达Broker就ack=true
* correlationData:当前消息的唯一关联数据(这个是消息的唯一id)
* ack:消息是否成功收到
* cause:失败的原因
*/
//设置确认回调
rabbitTemplate.setConfirmCallback((correlationData,ack,cause) -> {
System.out.println("confirm...correlationData["+correlationData+"]==>ack:["+ack+"]==>cause:["+cause+"]");
});
/**
* 只要消息没有投递给指定的队列,就触发这个失败回调
* message:投递失败的消息详细信息
* replyCode:回复的状态码
* replyText:回复的文本内容
* exchange:当时这个消息发给哪个交换机
* routingKey:当时这个消息用哪个路邮键
*/
rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey) -> {
System.out.println("Fail Message["+message+"]==>replyCode["+replyCode+"]" +
"==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]");
});
}
}
二、消费端确认
可靠抵达-Ack消息确认机制说明:
1、消费者获取到消息,成功处理,可以回复Ack给Broker
- basic.ack 用于肯定确认;broker将移除此消息
- basic.nack 用于否定确认;可以指定broker是否丢弃此消息,可以批量
- basic.reject 用于否定确认;同上,但不能批量
2、默认,消息被消费者收到,就会从broker的queue中移除
3、queue无消费者,消息依然会被存储,直到消费者消费
4、消费者收到消息,默认会自动ack。但是如果无法确定此消息是否被处理完成,或者成功处理。我们可以开启手动ack模式。
- 消息处理成功,ack(),接受下一个消息,此消息broker就会移除。
- 消息处理失败,nack()/reject(), 重新发送给其他人处理,或者容错处理后ack。
- 消息一直没有调用ack/nack方法,broker认为此消息正在被处理,不会投递给别人,此时* 客户端断开,消息不会被broker移除,会投递给别人。
配置修改
#rabbitmq配置
rabbitmq:
host: 106.55.168.234
port: 5672
virtual-host: / # 虚拟主机配置
publisher-returns: true
publisher-confirm-type: correlated #必须配置这个才会确认回调
listener:
direct:
acknowledge-mode: manual # # 手动ack消息,不使用默认的消费端确认
测试
package com.kun.order.service.impl;
import com.kun.order.entity.OrderReturnReasonEntity;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kun.common.utils.PageUtils;
import com.kun.common.utils.Query;
import com.kun.order.dao.OrderDao;
import com.kun.order.entity.OrderEntity;
import com.kun.order.service.OrderService;
@RabbitListener(queues = {"hello-java-queue"})
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {
@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<OrderEntity> page = this.page(
new Query<OrderEntity>().getPage(params),
new QueryWrapper<OrderEntity>()
);
return new PageUtils(page);
}
@RabbitHandler
public void revieveMessage(Message message,
OrderReturnReasonEntity content, Channel channel) throws IOException {
System.out.println("接收到消息..." + content);
//拿到主体内容
byte[] body = message.getBody();
//拿到的消息头属性信息
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("接受到的消息...内容" + message + "===内容:" + content);
//消息是否是第二次派发过来的
Boolean redelivered = message.getMessageProperties().getRedelivered();
// Thread.sleep(3000);
// Channel内按顺序自增
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag===>" + deliveryTag);
try {
// 签收货物, v2非批量模式
channel.basicAck(deliveryTag, false);
//退货 v2非批量模式,v3 true:重新加入队列,false:丢弃
channel.basicNack(deliveryTag,false,true);
} catch (Exception e) {
// 网络中断(突然)
}
}
@RabbitHandler
public void revieveMessage2(Message message,
OrderEntity entity, Channel channel) throws IOException {
System.out.println("2接收到消息..." + entity);
//拿到主体内容
byte[] body = message.getBody();
//拿到的消息头属性信息
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("2接受到的消息...内容" + message + "===内容:" + entity);
// Thread.sleep(3000);
// Channel内按顺序自增
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("2deliveryTag===>" + deliveryTag);
try {
// 签收货物,非批量模式
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 网络中断(突然)
}
}
}