参照本人RabbitMQ笔记:项目写完会发布
[root@hgwtencent ~]# docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
[root@hgwtencent ~]# docker update rabbitmq --restart=always
rabbitmq
登录 ip:15672 访问 web管理后台端口:
账号: guest
密码: guest
这里讲师的意图就是让大家理解明白 交换机的三种模式,不做记录。
Direct(直接)[点对点] :(消息中的Routing key)==(交换器和队列绑定的队列中的Routing key)
Fanout (扇出)[发布订阅] :消息发送给交换器绑定的所有队列
Topic(主题)[发布订阅] :
使用RabbitMQ
1、引入amqp场景启动器;RabbitAutoConfiguration就会自动生效
2、给容器中自动配置了RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate
所有的属性都是在这绑定的:spring.rabbitmq
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {}
3、给配置文件中配置 spring.rabbitmq 信息
4、@EnableRabbit 开启功能
1、导入amqp场景启动器依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
2、添加配置
spring:
rabbitmq:
host: 124.222.223.222
username: guest
port: 5672
virtual-host: /
password: guest
3、主启动类添加 @EnableRabbit
注解
@EnableRabbit
@SpringBootApplication
public class GulimallOrderApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallOrderApplication.class, args);
}
}
使用AmqpAdmin创建Exchange、Queue、Binding
/**
* 创建交换机
* TopicExchange
* FanoutExchange
* DirectExchange
*/
@Test
public void createExchange(){
/**
* String name 交换机名字
* boolean durable 是否持久化
* boolean autoDelete 是否自动删除
* Map arguments 参数
*/
DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
log.info("Exchange[{}]创建成功",directExchange.getName());
}
/**
* 创建队列
*/
@Test
public void createQueue(){
/**
* Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)
* String name,
* boolean durable, 是否持久化
* boolean exclusive, 是否排他:只能同时被一个交换器连接
* boolean autoDelete 是否自动删除
* Map arguments 携带参数
*/
Queue queue = new Queue("hello-java-queue",true,false,false);
amqpAdmin.declareQueue(queue);
log.info("Queue[{}]创建成功",queue.getName());
}
/**
* 创建绑定
*/
@Test
public void createBinding() {
/**
* Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, Map arguments)
* String destination, 目的地(队列或主题)
* Binding.DestinationType destinationType, 目的地类型(队列或主题)
* String exchange, 交换机
* String routingKey, 路由键
* Map arguments
* 将exchange指定的交换机和destination目的地进行绑定,使用routingKey作为指定的路由键
*/
Binding binding = new Binding("hello-java-queue",
Binding.DestinationType.QUEUE,
"hello-java-exchange",
"hello.java",
null);
amqpAdmin.declareBinding(binding);
log.info("Binding创建成功");
}
使用RabbitTemplate 发送、接收消息。
/**
* convertAndSend(String exchange, String routingKey, Object object)
* String exchange, 交换器
* String routingKey, 路由值
* Object object 消息,如果发送的消息是对象,我们会使用序列化机制将对象写出去。
*/
@Test
public void sendMsgTest(){
OrderReturnApplyEntity orderReturnApplyEntity = new OrderReturnApplyEntity();
orderReturnApplyEntity.setId(1L);
orderReturnApplyEntity.setCreateTime(new Date());
orderReturnApplyEntity.setReturnName("哈哈哈");
//1、发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出去。对象必须实现Serializable
String msg = "Hello,World!";
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderReturnApplyEntity);
log.info("消息[{}]发送成功!",msg);
}
注意:
配置MyRabbitConfig,让发送的对象类型的消息,可以是一个json
添加“com.atguigu.gulimall.order.config.MyRabbitConfig”类,代码如下:
package com.atguigu.gulimall.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;
@Configuration
public class MyRabbitConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
配置MyRabbitConfig后,发送接收到的数据被序列化为JSON
@RabbitListener: 类+方法上(监听哪些队列即可)
@RabbitHandler: 标在方法上(重载区分不同的消息)
监听消息:使用@RabbitListener; 主启动类必须有@EnableRabbit
@interface RabbitListener
String[] queues() default {}; 监听的队列
参数列表:
2、T<发送的消息类型> :如:OrderReturnReasonEntity content; 指定接收到的消息的类型
3、Channel channel :当前传输数据的信道
/**
* @interface RabbitListener
* String[] queues() default {}; 监听的队列
* 参数列表:
* 1、Message message:原生消息详细信息。头+体
* 2、T<发送的消息类型> :如:OrderReturnReasonEntity content
* 3、Channel channel :当前传输数据的信道
*/
@RabbitListener(queues = {"hello-java-queue"})
public void recieveMessage(Message message,
OrderReturnReasonEntity content,
Channel channel){
// 消息头属性
MessageProperties properties = message.getMessageProperties();
// 消息体属性
byte[] body = message.getBody();
System.out.println("接收到消息:" + message +"内容:"+content);
}
采用在类上加 @RabbitListener 注解,标识监听哪个消息队列。在方法上添加@RabbitHandler注解,重载区分不同的消息。
@RabbitListener: 加在类上监听哪个消息队列
@RabbitHandler: 标在方法上(重载区分不同的消息)
添加“com.atguigu.gulimall.order.controller.RabbitController”类,代码如下:
@Slf4j
@RestController
public class RabbitController {
@Autowired
RabbitTemplate rabbitTemplate;
@GetMapping("/sendMq")
public String sendMq(@RequestParam(value = "num",defaultValue = "10") Integer num){
for (int i = 0; i < num; i++) {
if (i%2 == 0) {
OrderReturnApplyEntity orderReturnApplyEntity = new OrderReturnApplyEntity();
orderReturnApplyEntity.setId(1L);
orderReturnApplyEntity.setCreateTime(new Date());
orderReturnApplyEntity.setReturnName("哈哈哈");
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderReturnApplyEntity);
} else {
OrderEntity entity = new OrderEntity();
entity.setOrderSn(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", entity);
}
}
return "ok";
}
}
修改“com.atguigu.gulimall.order.service.impl.OrderItemServiceImpl”类,代码如下:
@Service("orderItemService")
@RabbitListener(queues = {"hello-java-queue"})
public class OrderItemServiceImpl extends ServiceImpl<OrderItemDao, OrderItemEntity> implements OrderItemService {
@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<OrderItemEntity> page = this.page(
new Query<OrderItemEntity>().getPage(params),
new QueryWrapper<OrderItemEntity>()
);
return new PageUtils(page);
}
/**
* @interface RabbitListener
* String[] queues() default {}; 监听的队列
* 参数列表:
* 1、Message message:原生消息详细信息。头+体
* 2、T<发送的消息类型> :如:OrderReturnReasonEntity content
* 3、Channel channel :当前传输数据的信道
*
* Queue:可以很多人来监听,但一条消息只能被一个消费者接收。
* 场景:
* 1)、订单服务启动多个:同一个消息,只能有一个客户端收到
* 2)、只有一个消息完全处理完,方法运行结束,我们就可以接收到下一个消息
*/
@RabbitHandler
public void recieveMessage(OrderReturnApplyEntity content){
System.out.println("接收到的消息:" + content);
}
@RabbitHandler
public void recieveMessage2(OrderEntity content){
System.out.println("接收到的消息:" + content);
}
}
第一步、修改配置文件
spring:
rabbitmq:
publisher-confirms: true # 开启发送端确认
publisher-returns: true # 开启发送端消息抵达队列的确认
template:
mandatory: true # 只要抵达队列以异步方式优先回调这个returnconfirm
第二步、定制 RabbitTemplate
package com.atguigu.gulimall.order.config;
@Configuration
public class MyRabbitConfig {
@Autowired
RabbitTemplate rabbitTemplate;
/**
* 使用JSON序列化机制,进行消息转换
* @return
*/
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
/**
* 定制 RabbitTemplate
* 1、服务器收到消息就会回调
* 1)、spring.rabbitmq.publisher-confirms=true
* 2)、设置确认回调 ConfirmCallback
* 2、消息正确抵达队列进行回调
* 1)、spring.rabbitmq.publisher-returns: true
* 2)、spring.rabbitmq.template.mandatory: true
* 3)、设置确认回调 ReturnCallback
* 3、消费端确认(保证每个消息被正确消费,此时才可以Broker删除这个消息)
*/
@PostConstruct //在 MyRabbitConfig 对象创建完成以后,执行这个方法
public void initRabbitTemplate() {
// 设置消息确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 1、只要消息抵达Broker就会返回ack=true
* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
* @param b 消息是否成功还是失败
* @param s 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("confirm...[correlationData:"+correlationData+"];[ack="+b+"];[cause="+s+"]");
}
});
/**
* 设置消息抵达队列的确认回调
*/
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("Fail Message["+message+"]==>i["+i+"]==>s["+s+"]==>s1["+s1+"]==>s2["+s2+"]");
}
});
}
}
消费端确认(保证每个消息被正确消费,此时才可以保证Broker删除这个消息)
1)、默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息
1、收到消息,但没成功处理自动回复给服务器ACK,发生消息丢失。
2、我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了。发生消息丢失
手动确认收货(ack)
**第一步、**修改配置文件,修改为:手动确认
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual # 开启手动ack消息模式
**第二步、**业务代码中进行手动签收
@RabbitHandler
public void recieveMessage(Message message,OrderReturnApplyEntity content, Channel channel){
System.out.println("接收到的消息:" + content);
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("消息处理完成!");
try {
if (deliveryTag%2==0) {
// 签收,返回Ack;业务成功完成就应该签收
channel.basicAck(deliveryTag,false);
System.out.println("签收了货物..."+deliveryTag);
} else {
/**
* deliveryTag:当前消息派发的标签,一串数字
* multiple:是否批量处理
* requeue:
* true:重新入队
* false: 丢弃消息
*/
channel.basicNack(deliveryTag,false,true);
System.out.println("拒签了货物..."+deliveryTag);
}
} catch (IOException e) {
// 网络中断
}
}