RabbitMQ 知识点
1.什么是消息中间件?
MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。
在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
MQ的主要优点:
应用解耦——增加整个系统的隔离性和扩展性,让各个系统之间耦合性降低,可以分别维护。
异步提速——不使用MQ前,各个系统互相同步调用,需要将全部运行完毕后才可以响应前端,而使用MQ作为中间件相当于一个阻塞队列,实现异步响应,大大提高前端的响应时间,将后台数据异步处理。
削峰填谷——活动期间,大量请求接入服务器将对服务器造成很大压力,而采用MQ作为缓冲器,让服务器从中持续处理请求,可以平均服务器压力。
主要缺点:
引入了MQ中间件,降低了系统的稳定性,如果MQ挂了,业务也会停掉
提高了复杂度,需要处理详细重复消费,丢失,顺序性等幂等性问题
一致性问题,同时通过MQ发向多个消费者,如果一个消费者挂了,其他成功了,怎么保证消息的一致呢?
总结:MQ适用于:生产者不需要消费者的及时反馈,即消息对自己本身不造成影响、同时允许不一致性,允许各系统数据短暂不一致。
2.生产者和消费者
生产者:
1.创建ConnectFactory 并设置地址和账户参数
2.使用ConnectFactory对象创建一个频道Channel
3.设置channel参数:队列名称,是否定义持久化队列,是否独占本次连接,是否在不使用的时候自动删除队列,队列其它参数(map)
4. channel.basicPublish方法 生产消息 参数:交换机名称,如果没有指定则使用默认Default Exchage,路由key,简单模式可以传递队列名称,消息其它属性,消息内容
消费者
1.用同样的队列建立channel对象
2.建立一个消费者对象,用于配置消费者
3.重写 handleDelivery方法用于处理消息
4.使用channel.basicConsume方法启动消费者监听
//建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.146.129");
factory.setPort(5672);
factory.setVirtualHost("/jiaxinger");
factory.setUsername("wannibaba172");
factory.setPassword("wannibaba172");
//建立频道
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
AMQP.Queue.DeclareOk queue = channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//生产消息
channel.basicPublish("",QUEUE_NAME,null,"hey!guys!".getBytes());
System.out.println("消息已发送");
channel.close();
connection.close();
try {
Channel channel = RabbitMqConnectionUtil.getChannel();
channel.queueDeclare("simple_queue", true, false, false, null);
//创建一个消费者对象
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
String s = channel.basicConsume("simple_queue", consumer);
System.out.println(s);
3.工作模式
-
Work queues工作队列模式
多个消费者消费一个队列中的消息,用于分发工作,提高任务处理速度。 -
订阅模式类型
订阅模式中开始启用交换机exchange了,生产者对队列发送消息,再由交换机进行转发处理,交换机通常分为三种类型:
-
Fanout:广播,将消息交给所有绑定到交换机的队列
-
Direct:定向,把消息交给符合指定routing key 的队列
-
Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
交换机负责对消息进行转发,如果没有标的,将会丢失信息。实际上如果不指定交换机的情况下,会使用默认交换机的。
//声明和绑定示例
Channel channel = RabbitMqConnectionUtil.getChannel();
channel.exchangeDeclare("simple_exchange", BuiltinExchangeType.FANOUT);
channel.queueDeclare("simple_queue2", true, false, false, null);
channel.queueBind("simple_queue2","simple_exchange","");
-
Routing路由模式
在绑定交换机的时候绑定routing key就代表路由模式,在路由模式下Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key
进行判断,只有队列的Routingkey
与消息的Routing key
完全一致,才会接收到消息 -
Topics通配符模式
Topic
类型与Direct
相比,都是可以根据RoutingKey
把消息路由到不同的队列。只不过Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符!
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
.:每个单词用.来进行分割
模式总结
RabbitMQ工作模式:
1、简单模式 HelloWorld
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)
2、工作队列模式 Work Queue
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)
3、发布订阅模式 Publish/subscribe
需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
4、路由模式 Routing
需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
5、通配符模式 Topic
需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
4.消息的可靠投递
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
confirm 确认模式
return 退回模式
rabbitmq 整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer
消息从 producer 到 exchange 则会返回一个 confirmCallback 。
消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。
在rabbitMQ的参数中设置开启可靠投递
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
调用RabbitMQTemplate里的setConfirmCallback和setReturnCallback设置回调函数
4.消息的消费确认ack
自动确认:acknowledge=“none”
手动确认:acknowledge=“manual”
根据异常情况确认:acknowledge=“auto”
使用手动接受:
设置ack模式为手动:
listener:
direct:
acknowledge-mode: none
在编写的RabbitListener类中调用channel.basicAck方法设置确认函数
调用channel.Nack方法设置返回函数
实例:
try {
System.out.println("Listener get message"+message.getBody());
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
System.out.println("消息接受成功");
} catch (IOException e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
System.out.println("消息被返回");
}
5.消费者限流
为了避免消费者拉取过多消息而挂掉,我们应该设置拉取消息的最大值
而配置prefetch: 属性则可以设置消费者在确认先可以拉取多少条消息,避免消费者压力过大。
6.TTL 存活时间
在配置queue时设置ttl属性或设置ttl方法可以添加消息存活时间,到底定时过期消息的目的
设置队列存活时间,整个队列的消息会在时间内同时过期
public Queue getQueue1(){
return QueueBuilder.durable("queue3").ttl(10000).build();
单独设置消息过期时间:
rabbitTemplate.convertAndSend("Exchange", "first", "运行测试中..." + i, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("5000");
return message;
}
7.DLX 死信队列
死信队列就是一个专门用来回收消息的队列,它本质上和普通的队列没有区别,只是用途上有所不同。
消息成为死信的三种情况:
- 队列消息长度到达限制;
- 消费者拒接消费消息,并且不重回队列;
- 原队列存在消息过期设置,消息到达超时时间未被消费;
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
8.延迟队列
延迟队列的消息只有到达指定时间才能被消费
比如:30分钟后未支付取消订单
使用TTL+死信队列实现延迟队列
即设置一个TTL队列,如果消息超时进入死信队列,则由死信监听器进行业务回滚。
9.rabbitMQ消息追踪
开启消息追踪后会将生产的消息进行一个备份转发,发送到默认的trace,可以用来追踪消息的传递情况
rabbitmqctl trace_on:开启Firehose命令
rabbitmqctl trace_off:关闭Firehose命令
也可以启用插件:rabbitmq-plugins enable rabbitmq_tracing,在rabbitmq的控制台网页使用