目录
概述
消息队列目的地形式
RabbitMQ简介
核心概念
Docker安装RabbitMQ
SpringBoot整合RabbitMQ
RabbitMQ消息确认机制-可靠抵达
RabbitMQ消息可靠性
消息丢失
消息重复
消息积压
RabbitMQ延时队列(实现定时任务)
消息TTL(Time To Live)
Dead Letter Exchage(DLX)
RabbitMQ延时队列模拟定时关单&库存自动解锁
JMS(Java Message Service) | AMQP(Advanced Message Queuing Protocol) | |
定义 | Java api 基于JVM消息代理的规范 |
高级消息队列协议,也是消息代理的规范,网络协议通信,兼容JMS |
跨语言 | 否 | 是 |
跨平台 | 否 | 是 |
Model | 提供两种消息模型:
|
提供五种消息模型:
本质来讲,后四种和JMS的pub/sub模型没有太大差别,但是在路由机制上做了更详细的划分 |
支持消息类型 | 多种消息类型:
|
byte[] 实际应用有复杂的消息,可以将消息序列化后发送 |
综合评价 | JMS定义了java api层面的标准,在java体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是对其跨平台的支持差 | AMQP定义wire-level层的协议标准,天然具有跨平台、跨语言特性 |
Spring支持 |
|
|
SpringBoot自动配置 | JmsAutoConfiguration | RabbitAutoConfiguration |
实现 | ActiveMQ、HornetMQ | RabbitMQ |
Message
消息,消息是不具名的,它由消息头和消息体组成,消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其它消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
Publisher
消息生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列,有四种类型:
消息中的路由键(routing key)如果和Binding中binding key一致,交换器就将消息发送到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发routing key标记为“dog”的消息,不会转发给“dog.puppy”,它是完全匹配,单播模式。
每个发到fanout类型交换器的消息都会分发到有绑定的队列中,fanout交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout类型转发消息是最快的。
通过匹配模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开,它同样也会识别两个通配符(#、*)。#:匹配0个或者多个单词,* 匹配一个单词。
header匹配AMQP消息的header,而不是路由,headers交换器和direct交换器完全一致,但性能差很多。
Queue
消息队列,用来保存消息知道发送给消费者。它是消息的容器,也是消息的终点,一个消息可以投入一个或者多个队列,消息一直在队列里面,等待消费者连接到这个队列将其取走。
Binding
绑定,用于消息队列和交换器之间的关联,一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
Exchange和Queue的绑定可以是多对多的关系
Connection
网络连接,比如一个TCP连接。
Channel
信道,多路复用连接中的一条独立的双向数据通道,信道是建立在真是TCP连接内的虚拟连接,AMQP命令都是通过信道发出去的,不管是发布消息,订阅队列还是接收消息,这些动作都是通过信道完成的。因为对于操作系统来说建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,已复用一条TCP连接。
Consumer
消息消费者,表示一个从消息队列中取得消息的客户端应用程序。
Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同身份和加密环境的独立服务器。每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在连接是指定,RabbitMQ默认的vhost是 / 。
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
4369、25672(Erlang发现&集群端口)
5671、5672(AMQP端口)
15672(web管理后台端口)
61613、61614(STOMP协议端口)
1883、8883(MQTT协议端口)
http://www.rabbitmq.com/networking.html
RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate
org.springframework.boot
spring-boot-starter-amqp
spring.rabbitmq.addresses=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
# 开启发送端消息抵达服务(Broker)的确认
spring.rabbitmq.publisher-confirm-type=correlated
# 开启发送端消息抵达队列(Queue)的确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达队列,以异步发送优先回调 returnConfirm
spring.rabbitmq.template.mandatory=true
# 开启手动ack机制
spring.rabbitmq.listener.direct.acknowledge-mode=manual
@EnableRabbit 开启功能
监听消息:@RabbitListener、@RabbitHandler
保证消息不丢失,可靠抵达,可以使用事务消息,性能下降250倍,为此引入确认机制
publisher confirmCallback 确认模式
publisher returnsCallback 未投递到 queue 退回
consumer ack 机制
@EnableRabbit
@Configuration
public class RabbitmqConfig {
/**
* 使用JSON序列化机制进行消息转换
*
* @return
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
@Primary
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter());
initRabbitTemplate(rabbitTemplate);
return rabbitTemplate;
}
/**
* 定制RabbitTemplate
* 1、服务(Broker)收到消息回调
* 1)spring.rabbitmq.publisher-confirm-type=correlated
* 2)设置ConfirmCallback
* 2、消息正确抵达队列(Queue)回调
* 1)spring.rabbitmq.publisher-returns=true
* spring.rabbitmq.template.mandatory=true
* 2)设置ReturnCallback
* 3、消费端确认(默认自动确认)
* 设置手动签收,没有ack,消息就会一直处于Unacked状态,即使consumer宕机,消息也不会丢失,会重新变为Ready,下次连接重新发送
* spring.rabbitmq.listener.direct.acknowledge-mode=manual
* channel.basicAck(deliveryTag, false) 签收
* channel.basicNack(deliveryTag, false, false) 拒签
* long deliveryTag: channel内自增
*/
public void initRabbitTemplate(RabbitTemplate rabbitTemplate) {
// 设置消息抵达服务(Broker)回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 确认回调机制
*
* @param correlationData 当前消息的唯一关联数据(消息唯一ID)
* @param b 消息是否成功收到
* @param s 失败原因
*/
@Override
public void confirm(@Nullable CorrelationData correlationData, boolean b, @Nullable String s) {
System.out.println("ConfirmCallback==>correlationData["+correlationData+"]==>b["+b+"]==>s["+s+"]");
}
});
// 设置消息抵达队列(Queue)确认回调
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
/**
* 只要消息没有投递成功,就会触发这个失败回调
*
* @param returnedMessage
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println("ReturnsCallback==>"+returnedMessage.toString());
}
});
}
@Bean
public Queue orderDelayQueue() {
Map map = new HashMap<>();
map.put("x-dead-letter-exchange", OrderConstant.ORDER_EVENT_EXCHANGE);
map.put("x-dead-letter-routing-key", OrderConstant.ORDER_RELEASE_ORDER_ROUTING_KEY);
map.put("x-message-ttl", OrderConstant.ORDER_MESSAGE_TTL);
Queue queue = new Queue(OrderConstant.ORDER_DELAY_QUEUE, true, false, false, map);
return queue;
}
@Bean
public Queue orderReleaseQueue() {
Queue queue = new Queue(OrderConstant.ORDER_RELEASE_QUEUE, true, false, false);
return queue;
}
@Bean
public Exchange orderEventExchange() {
return new TopicExchange(OrderConstant.ORDER_EVENT_EXCHANGE, true, false);
}
@Bean
public Binding orderCreateBinding() {
return new Binding(OrderConstant.ORDER_DELAY_QUEUE,
Binding.DestinationType.QUEUE,
OrderConstant.ORDER_EVENT_EXCHANGE,
OrderConstant.ORDER_CREATE_ORDER_ROUTING_KEY,
null);
}
@Bean
public Binding orderReleaseBinding() {
return new Binding(OrderConstant.ORDER_RELEASE_QUEUE,
Binding.DestinationType.QUEUE,
OrderConstant.ORDER_EVENT_EXCHANGE,
OrderConstant.ORDER_RELEASE_ORDER_ROUTING_KEY,
null);
}
@Bean
public Binding orderReleaseOtherBinding() {
return new Binding(WareConstant.STOCK_RELEASE_QUEUE,
Binding.DestinationType.QUEUE,
OrderConstant.ORDER_EVENT_EXCHANGE,
OrderConstant.ORDER_RELEASE_STOCK_ROUTING_KEY,
null);
}
}
rabbitTemplate.convertAndSend(OrderConstant.ORDER_EVENT_EXCHANGE, OrderConstant.ORDER_CREATE_ORDER_ROUTING_KEY, orderResult);
@Slf4j
@Service
@RabbitListener(queues = OrderConstant.ORDER_RELEASE_QUEUE)
public class OrderListener {
@Autowired
OrderService orderService;
@RabbitHandler
public void listener(OrderResultVO orderResult, Channel channel, Message msg) throws Exception {
log.info("订单过期,收到订单关闭消息====>{}", msg);
try {
orderService.closeOrder(orderResult);
//TODO 如果定时关单先于支付宝响应,手动调用支付宝收单
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicReject(msg.getMessageProperties().getDeliveryTag(), true);
}
}
}
rabbitTemplate.convertAndSend(OrderConstant.ORDER_EVENT_EXCHANGE, OrderConstant.ORDER_CREATE_ORDER_ROUTING_KEY, orderResult);
@Slf4j
@Service
@RabbitListener(queues = WareConstant.STOCK_RELEASE_QUEUE)
public class StockListener {
@Autowired
WareSkuService wareSkuService;
@RabbitHandler
public void unLockStock(StockLockedVO lockedVO, Channel channel, Message msg) throws Exception {
log.info("订单过期,收到库存解锁消息====>{}", msg);
try {
wareSkuService.unLockStock(lockedVO);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicReject(msg.getMessageProperties().getDeliveryTag(), true);
}
}
@RabbitHandler
public void unLockStock(OrderTO orderTO, Channel channel, Message msg) throws Exception {
log.info("订单关闭,收到库存解锁消息====>{}", msg);
try {
wareSkuService.unLockStock(orderTO);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicReject(msg.getMessageProperties().getDeliveryTag(), true);
}
}
}