MQ:Message Queue,消息队列
MOM:Message Oriented Middleware,面向消息的中间件
主流实现方式:
JMS:Java Message Service,java消息服务。java规范;只能使用java实现;只有两种消息模型(点对点和发布订阅)。(ActiveMQ)
AMQP:Advanced Message Queueing Protocol,高级消息队列协议。本质上是协议,只规定了数据格式;任何语言都可以实现;可以有任意多种消息模型。(RabbitMQ)
三个主要作用:
1.异步
2.解耦
3.削峰填谷
主流产品:
1.ActiveMQ:Apache,基于JMSjava
2.RabbitMQ:独立社区(spring) 基于AMQP实现 ,用erlang语言实现(erlang面向并发的编程语言)
3.RocketMQ:阿里开源 贡献给apache
4.Kafka:Apache 追求吞吐量
RabbitMQ:
-
Connection:连接
-
Channel:管道 信道 默认一个连接只有一个信道
-
Exchange:交换机,接受消息并转发消息
Fanout:转发消息给所有队列 Direct:指定RK(RoutingKey),符合RK的队列可以获取消息 Topic:通配符 *(匹配一个单词) #(匹配任意多个)
-
Queue:队列,接受 转发 存储消息
-
RoutingKey:路由键
-
Producer:生产者,发送消息的应用程序
-
Consumer:消费者,接受并消费消息的应用程序
五种消息模型:
1.简单模型(Simple)
一个生产者 一个队列 一个消费者
2.工作模型(work queue)
一个生产者 一个队列 多个消费者 消息只能被一个消费者消费
能者多劳(Fair Dispatch 公平分发):channel.basicQos(1)
3.发布订阅(publish/subscribe)
一个生产者 一个交换机 多个队列 每个队列有自己的消费者
4.路由模型(route)
多了一个routingKey
5.通配模型(Topic)
两个通配符
*:匹配一个单词
#:匹配 零个或多个单词(任意多个)
简单面试题
1.怎么避免消息堆积?
-
搭建消费者集群(工作队列) 配合能者多劳、公平分发
能者多劳:channel.basicQos(prefetch: 1)spring.rabbitmq.listener.simple.prefetch=1
-
多线程并发消费:concurrency。container启动的时候会根据设置的concurrency的值创建n个BlockingQueueConsumer。注意当队列的Exclusive(默认false)为true时,concurrency参数只能是1
spring.rabbitmq.listener.simple.concurrency=4
2.怎么避免消息丢失?
-
生产者确认机制:确保消息到达MQ、
spring.rabbitmq.publisher-confirm-type=none/simple/correlated spring.rabbitmq.publisher-returns=true
-
消息持久化:确保不会因为MQ服务器宕机导致消息丢失,交换机持久化、队列持久化、消息持久化
@Queue("名称") //默认持久化
-
消费者确认机制:确保消息被消费者正确无误的消费,手动确认basicAck
spring.rabbitmq.listener.simple.acknowledge-mode=none/auto/manual
生产者:
1.依赖 AMQP启动器
2.yml配置:
spring.rabbitmq.host/port/vitual-host/username/password
spring.rabbitmq.publisher-confirm-type=none/simple/correlated
spring.rabbitmq.publisher-returns=true
3.java配置
rabbitTemplate.setConfirmCallback();
rabbitTemplate.setReturnCallback();
4.代码
发送消息
rabbitTemplate.convertAndSend(交换机, RK, 消息内容)
RabbitConfig:
//交换机
@Bean
public Exchange exchange(){
return new FanoutExchange/DirectExchange/TopicExchange("名称", durable, autoDelete, null);
return ExchangeBuilder.fanoutExchange/directExchange/topicExchange().durable(true/false).autoDelete().withArguments().build();
}
//队列
@Bean
public Queue queue(){
return new Queue("名称", durable, autoDelete, exclusive, arguments);
// x-message-ttl x-dead-letter-exchange x-dead-letter-routing-key
return QueueBuilder.durable(名称)/nonDurable("名称").autoDelete().exclusive().deadLetterExchange().deadLetterRoutingKey().ttl().build()
}
//队列绑定到交换机
@Bean
public Binding binding(){
return new Binding("队列名称", DestinationType.QUEUE, "交换机", "RK", arguments);
return BindingBuilder.bind(队列对象).to(交换机).with(RK).and()/noargs()
}
消费者:
1.个性化配置
spring.rabbitmq.listener.type=simple/direct
spring.rabbitmq.listener.simple.prefetch=1
spring.rabbitmq.listener.simple.acknowledge-mode=none/auto/manual
spring.rabbitmq.listener.simple.concurrency=4
2.代码
@RabbitListener(queues={})
@RabbitListener(bindings = @QueueBinding(
value = @Queue("名称"),
exchange = @Exchange(value="名称", ignoreDeclaretionExceptions="true", type=ExchangeTypes.TOPIC/DIRECT/FANOUT)
key = {RK列表}
))
//手动确认
channel.baskAck(message.getMessageProperties().getDeliveryTag(), multipe)
//是否重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(), multipe, requeue)
channel.basicReject(message.getMessageProperties().getDeliveryTag(), requeue)
例:ConsumerListener消费者代码
@Component
public class ConsumerListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "SPRING_RABBIT_QUEUE", durable = "true"),
exchange = @Exchange(value = "SPRING_RABBIT_EXCHANGE", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC),
key = {"a.b"}
))
public void listener(String msg, Channel channel, Message message) throws IOException {
try {
System.out.println(msg);
// 手动确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
// 是否已经重试过
if (message.getMessageProperties().getRedelivered()){
// 已重试过直接拒绝
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
} else {
// 未重试过,重新入队
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
}
死信队列:普通的队列,只是存放的是死信消息而已
死信交换机:普通交换机,只是转发的是死信消息而已
Dead Letter:死信,有三类消息会变成死信消息:
1.basicNack、basicRejected,requeue参数为false
2.超时消息
3.队列已满,依然入队
延时队列:
x-message-ttl:延时时间
x-dead-letter-exchange:死信交换机
x-dead-letter-routing-key:死信routingkey
创建交换机的两种方式:
①注解方式(如上消费者所示)
②代码方式(如下所示)
new Queue("队列名称", durable, autoDelete, exclusive, argments)
QueueBuilder.nonDurable()/durable().autoDelete().exclusive().withArguments().build()
new Topic/Direct/FanoutExchange("交换机名称", durable, autoDelete, argments);
ExchangeBuilder.fanout/direct/topicExchange("名称").durable().autoDelete().withArguments().build()
new Binding("队列名称", DestinationType.Queue, "交换机名称", RK, arguments);
BindingBuilder.bind(队列对象).to(交换机对象).with(RK).noargs();