RabbitMQ提供了6种模式:Direct
简单模式(点对点),work模式,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式(通配符匹配),RPC远程调用模式。
生产者发消息给Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
工作原理:
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 -p 61613:61613 -p 61614:61614 -p 1883:1883 -p 8883:8883 rabbitmq:management
docker update rabbitmq --restart=always
4369,25672 (Erlang发现&集群端口)
5672,5671(AMQP端口)
15672 (Web管理后台端口)
61613, 61614 (STOMP协议端口)
1883,,8883, (MQTT协议端口)
默认登录地址: http://192.168.56.10:15672 guest/guest
Exchange有常见以下3种类型:
Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向(点对点),把消息交给符合指定routing key 的队列(注意:如果绑定的时候不指定routingKey为空,交换机发送消息时(不指定routingKey为空)会广播发送到所有绑定(routingKey为空)的Queue)
Topic:通配符(部分广播),把消息交给符合routing pattern(路由模式) 的队列
Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
通配符规则:exchange 与 queue 绑定的时候可以使用通配符;发送消息的时候不能使用通配符
#:匹配一个或多个词 eg: item.#:能够匹配item.insert.abc 或者 item.insert
*:匹配1个词 eg: item.*:只能匹配item.insert
其他设置: Type- Durable 持久化 Auto delete-NO 不允许自动删除; Iternal -No
Queue队列设置: Type Durability是否持久化
Bindings 绑定设置- To queue - Routing key-可以使用通配符
发消息是发给exchanges交换机;监听的是queues队列
创建Queue时注意到可配置为持久化(Durable)和非持久化(Transient),当Consumer不在线时,持久化的Queue会暂存消息,非持久化的Queue会丢弃消息。
--------------------------------------------------------------------------------------------------------------
生产者发送消息
生产者创建连接(Connection),开启一个信道(Channel),连接到RabbitMQ Broker;
声明队列并设置属性;如是否排它,是否持久化,是否自动删除;
将路由键(空字符串)与队列绑定起来;
发送消息至RabbitMQ Broker;
关闭信道;
关闭连接;
消费者接收消息
消费者创建连接(Connection),开启一个信道(Channel),连接到RabbitMQ Broker
向Broker 请求消费相应队列中的消息,设置相应的回调函数;
等待Broker回应闭关投递响应队列中的消息,消费者接收消息;
确认(ack,自动确认)接收到的消息;
RabbitMQ从队列中删除相应已经被确认的消息;
关闭信道;
关闭连接;
---------------------------------------------------------------------------------------------------------------
RabbitMQ消息确认机制--可靠抵达-保证消息不丢失
- publisher confirmCallback 确认模式 P--->Exchange
- publisher returnCallback 未投递到queue退回模式 Exchange--->queue
- consumer ack 机制 queue--->Consumer
P -------> Exchange ---------> queue --------->Consumer
#开启发送端消息确认机制-回调函数
#rabbitMq服务器地址
spring.rabbitmq.addresses=192.168.191.35
#使用的Virtual host名称
spring.rabbitmq.virtual-host=test
#开启发送端确认-到达服务器Exchange
#spring.rabbitmq.publisher-confirms=true
#高版本代替上面配置,确认消息已发送到交换(Exchange)选择确认消息类型为交互
spring.rabbitmq.publisher-confirm-type=correlated
#开启发送端确认-消息抵达队列Queue的确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步方式优先回调returnConfirm回调函数
spring.rabbitmq.template.mandatory=true
#客户端消费-手动ack确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
rabbit配置序列化json--消息确认机制
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.connection.CorrelationData;
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;
import java.util.HashMap;
@EnableRabbit
@Configuration
public class MyRabbitmqConfig {
// 使用json序列化机制,进行消息转换
@Bean
public MessageConverter messageConverter() {
//在容器中导入Json的消息转换器
return new Jackson2JsonMessageConverter();
}
@Autowired
RabbitTemplate rabbitTemplate;
/**定制rabbitTemplate 具有消息确认机制回调函数
* A,服务器收到消息就回调ConfirmCallback
* 1,spring.rabbitmq.publisher-confirms=true 开启发送端确认
#高版本代替上面配置,确认消息已发送到交换(Exchange)选择确认消息类型为交互
spring.rabbitmq.publisher-confirm-type=correlated
* 2,设置确认回调--服务端收到消息就会自动触发回调函数--只要消息抵达服务器Exchange其中ack=true
* B, 消息正确抵达队列进行回调
* 1,spring.rabbitmq.publisher-returns=true #开启发送端确认-消息抵达队列Queue的确认
* spring.rabbitmq.template.mandatory=true #只要抵达队列,以异步方式优先回调returnConfirm回调函数
* 2,失败才回调returnCallback
* C,客户端消费端确认(保证每个消息被正确消费,此时才可以删除Queue队列中这个消息)
* 1,默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息
* 问题: 收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了,发生消息丢失
* 需要手动确认-执行完成一个确认一个;只要没有ack,消息一致是unacked转态,即使Consumer宕机,消息也不丢失,
* 会重新变为ready转态,下一次Consumer连接继续执行
* spring.rabbitmq.listener.simple.acknowledge-mode=manual #客户端消费-手动ack确认
*2,如何确认签收:
* long deliveryTag = message.getMessageProperties().getDeliveryTag();//channel中按顺序自增的
* channel.basicAck(deliveryTag,false);//成功-手动确认签收 false-只签收当前的, true-批量确认签收
* channel.basicReject(deliveryTag,true);//失败-拒绝签收
* //long deliberyTag, boolean multiple,boolean requeue //第3个参数:requeue=false丢弃;requeue=true发回服务器重新入队
* channel.basicNack(deliveryTag,false,true);//失败-拒签
*/
@PostConstruct //对象创建完成之后执行
public void initRabbitTemplate(){
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {//设置确认回调
/**
* 服务器代理Exchange 收到消息之后会自动调用
* @param correlationData 当前消息的唯一关联数据(消息的唯一Id)
* @param ack 消息是否成功收到: ack=true 表示成功 ack=false 表示失败
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 消息抵达队列Queue的确认回调,只要消息没有投递到指定的队列就触发这个失败回调
* @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("失败执行--fail---message="+message+"--replyCode="+replyCode+"--replyText="+
replyText+" --exchange"+exchange+"--routingKey="+routingKey);
}
});
}
//创建交换机-主题类型的change
@Bean
public Exchange topicExchange() {
/**
* String name,
* boolean durable, 是否持久化
* boolean autoDelete, 是否允许自动删除
* Map<String, Object> arguments
*/
return new TopicExchange("test.exchange", true, false);
}
/**
* 创建Queue时注意到可配置为持久化(Durable)和非持久化(Transient),
* 当Consumer不在线时,持久化的Queue会暂存消息,非持久化的Queue会丢弃消息。
* 普通队列
* @return
*/
@Bean
public Queue dataCreateQueue() {
Queue queue = new Queue("test.hys.data", true, false, false);
return queue;
}
/**
* 创建用户队列
* @return
*/
@Bean
public Queue userCreateQueue() {
Queue queue = new Queue("test.hys.user", true, false, false);
return queue;
}
/**
* 交换机绑定队列
* 如果绑定的时候不指定routingKey为空,交换机发送消息时(不指定routingKey为空)会广播发送到所有绑定(routingKey为空)的Queue
*
* 创建binding
* @return
*/
@Bean
public Binding dataCreateBinding() {
/**
* String destination, 目的地(队列名或者交换机名字) --test.hys.data
* DestinationType destinationType, 目的地类型(Queue、Exhcange)
* String exchange, --test.exchange
* String routingKey, --test.*.data
* Map<String, Object> arguments
* */
return new Binding("test.hys.data", Binding.DestinationType.QUEUE, "test.exchange", "test.*.data", null);
}
//交换机与队列 绑定
@Bean
public Binding userCreateBinding() {
/**
* String destination, 目的地(队列名或者交换机名字)
* DestinationType destinationType, 目的地类型(Queue、Exhcange)
* String exchange,
* String routingKey,
* Map<String, Object> arguments
* */
return new Binding("test.hys.user", Binding.DestinationType.QUEUE, "test.exchange", "test.#", null);
}
}
从队列中接受消息:
1,一个队列中同一消息可以很多人都监听,只能有一个客户端收到之后队列删除消息
2,只有一个消息完全处理完成之后,方法运行结束,才可以接收下一个消息
/**
* 1、多个人监听同一个队列,谁收到消息?
* 只有一个人能收到消息。(消息重复消费)
*
* 2、service监听消息队列里面的内容,方法参数能写哪些?
* Map<String,Object> content:自定义对象;将队列的内容自动的转为这个对象
* Message message:可以获取到当前消息的详细信息;(消息的id,什么时候发送的....)
* Channel channel:通道,可以ack数据
* 参数不分先后顺序,不一定全要或者要哪些
* 3、切换到手动ack模式
* 4、我们如果没有回复消息;
* 1)、我们一直在线:此消息不会被再发给别人;
* 2)、一旦掉线,未回复的消息又会变成ready;又可以发给别人
* @param content
*/
@RabbitListener(queues = "myqueue")
public void hello(Message message,Map<String,Object> content, Channel channel) throws IOException {
System.out.println("hello..."+content);
/**
* long deliveryTag,消息的标签
* boolean multiple,是否批量拒绝多个消息
* boolean requeue,是否重新将消息放回队列,false【discarded【丢弃】/dead-lettered[死信]】
*/
//1、拒绝(unack)不入队,消息直接被丢弃
//2、拒绝(unacke)入队。消息会被立即重新投递。
//3、以前的不回复。消息是unacked状态,如果consumer断了。unacked变成ready成能下一次重新投递
// channel.basicNack(message.getMessageProperties().getDeliveryTag(),
// false,
// true);
//4、拒绝(reject)不入队,和1一样
//5、拒绝(reject)入队。消息会被立即重新投递。
// channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
//6、消息会被移除
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
//没响应的回复
//我不要的回复
}
标注注解的组件必须在spring容器中
@EnableRabbit 监听队列消息的时候必须加上这个注解
@RabbitListener :类+方法上,
@RabbitHandler 标在方法上,(重载区分不同的消息-队列中存放不同的对象数据);
import com.rabbitmq.client.Channel;
import io.niceseason.gulimall.order.entity.OrderEntity;
import io.niceseason.gulimall.order.service.OrderService;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@RabbitListener(queues = {"order.release.order.queue"})
public class OrderCloseListener {
@Autowired
private OrderService orderService;
/**
* @param orderEntity 发送的消息类型
* @param message 原生消息详细信息:头+体
* @param channel 当前传输的通道
* @throws IOException
*/
@RabbitHandler
public void listener(OrderEntity orderEntity, Message message, Channel channel) throws IOException {
System.out.println("收到过期的订单信息,准备关闭订单" + orderEntity.getOrderSn());
long deliveryTag = message.getMessageProperties().getDeliveryTag();//channel中按顺序自增的
try {
orderService.closeOrder(orderEntity);
channel.basicAck(deliveryTag,false);//手动确认签收 false-只签收当前的, true-批量确认签收
} catch (Exception e){
channel.basicReject(deliveryTag,true);//拒绝签收
//long deliberyTag, boolean multiple,boolean requeue //第3个参数:requeue=false丢弃;requeue=true发回服务器重新入队
channel.basicNack(deliveryTag,false,true);//拒签二
}
}
@RabbitHandler
public void handleStockLockedRelease(StockLockedTo stockLockedTo, Message message, Channel channel) throws IOException {
log.info("************************收到库存解锁的消息********************************");
try {
wareSkuService.unlock(stockLockedTo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
@Autowired
RabbitTemplate rabbitTemplate;
/**
* 向rabbitMq中发送数据
* 交换机名称:test.topic.exchange
* 路由键: test.hys.data
* 发送的消息数据:answer
*
* queue名称 binding-key通配符
* test.hys.data test.hys.data 匹配
* test.hys.org test.*.org 不匹配
* test.hys.user test.hys.# 匹配
*/
@Test
public void sendMessageToMq(){
AnswerEntity answer=new AnswerEntity();
answer.setUserId(707L);
answer.setQuestionId(101L);
answer.setAnswer("A,B,D");
answer.setRight("正确-yes");
rabbitTemplate.convertAndSend("test.topic.exchange", "test.hys.data", answer);
}