rabbitmq学习入门

rabbitmq阅读笔记

当我们再讨论消息中间件时,我们要想要什么

消息模型本身的概念已经超出了消息中间件的范畴,我们再使用面向对象建模的时候,对象之间信息的传递就是通过消息的方式,因此方法调用可以算是一种消息模型的一类;rpc调用的方式同理,在两个系统(对象)之间传递一些信息;消息中间件的方式显然和以上都不同,它的基础编程模型是一个FIFO的队列(当然不一定,也有可能有优先级等等),“水管”的双方互相不关心对方,一边在扔消息,一遍在消费消息,通过这个模型可以看出,它从根本上就是一个异步的模型。

我们想要消息中间件的原因有很多,削峰、解耦、最终一致性。前面说过,消息中间件基本的模型就是一个队列,再这之上增加了很多特性,有对消息本身的增强,有有消息控制的增强等。

那么下图是根据我浏览文档总结的一幅图,想要说明消息中间件关心什么:

可靠性是我们很关注的一个话题,因为不同场景对丢消息的容忍程度是不一样的,可靠性在某种程度与性能也是相悖的,因此大部分时候需要再特定场景上对二者进行取舍,或者做额外的工作。

rabbitmq的一些特性

Queue

每个队列有一个名字,amq.开头是保留名字,最大支持255个utf-8字符
传入""名字,服务端会生成一个名字,整个channel会复用这个名字(channel会记住这个服务端生成的名字)

属性: 强制与非强制

  • Durable 持久化
  • Exclusive 只允许一个连接,连接断开后自动删除
  • Auto-delete 最后一个消费者取消订阅后删除
  • Arguments 可选参数,很有用
  • message和queue的ttl
  • 队列长度限制
  • 镜像设定
  • 优先级
  • 消费属性
  • 死信处理

设置队列属性,两种方式:使用policies(推荐)或者客户端声明

对于持久化,当服务器宕机,只有持久化的队列会恢复,并且只有持久化的消息会被恢复。 => 持久化队列中的非持久化不会恢复

临时队列:队列自动删除机制:Exclusive、TTL、Auto-delete

镜像复制队列: todo

time-to-live和队列长度: "x-max-length" 和 "x-message-ttl"属性。

内存、持久化消息: 客户端决定,在AMQP 0-9-1协议中,通过消息的delivery_mode属性来决定。其次尽管是非持久化消息,有内存压力的时候也会存入磁盘,持久化队列中的持久化消息必须是批量持久化或者定时持久化

优先级: 设置了优先级属性的队列才拥有这个概念,publisher发送消息的时候需要制定消息的优先级(通过priority属性),建议使用1-10,越多的优先级会消耗越多的资源

消费者应答机制: 自动应答机制能够提供高吞吐(忽略网络带宽),同时也最小保证失败。通常来说,有限考虑手动应答机制。

prefetch和消费者负载: 自动应答机制会快速增加消费者消费的负担,手动应答机制可以设置一个QoS(prefetch),来限制消费者消费速度

消费状态: ready和not acknowledged by consumer

TTL特性

队列、队列中的消息、每条消息都可以有TTL特性。

队列中的消息TTL:

  • 队列中消息存放时间可能比TTL时间要长,相同消息发往不同队列可以拥有不同的TTL时间,此外不同队列的相同消息存活不会互相干扰
  • 服务端确保不会投递过期消息到消费端,或者不会发送basic.get-ok回应给客户端(猜测是拉模型,不会发送响应)
  • 声明队列使用x-message-ttl。

每条消息都可以拥有各自TTL时间,publish的时候设置属性expiration

注意事项:

  • 只有过期消息到达队列头才会被清理(或者死信投递)
  • 消费过期和消费行为存在天然条件竞争
  • 过期消息前面有没有过期的消息时候,会一直待在队列,所有队列统计特性都会统计到它
  • 启用该特性最后保证有消费者一直在消费它

消费者确认以及投递确认

消费端确认

每一次投递都有一个唯一标识符。rabbitmq每一次投递消息,都会在消息中携带delivery tag,唯一标识在一个channel里的投递。注意的是delivery tag的作用域是在channel。

delivery tag是一个单调递增的数字,客户端确认消费必须在消息中携带上这个tag。因为它是channel作用域的,因此投递确认必须在相同的channel中,否则会抛出unknown delivery tag协议异常。

消费者注册队列的配置决定应答机制。取决于应答机制,rabbitmq会对成功投递有不同的认识: 是发送完(写入tcp socket)就认为成功,还是客户端手动应答成功接收后认为成功。手动应答机制分为positive和negative应答,使用不同的方法。

应答分为三种

  1. basic.ack => positive acknowledgements

  2. basic.nack => negative acknowledgements

  3. basic.reject => negative acknowledgements,同nack应答有些细微差别

positive acknowledgements指示rabbitmq消息已经被投递了可以扔掉了。Negative acknowledgements中basic.reject也有相同作用。差别在于第一个语义是成功消费,第二个语义是没有被成功消费,但是还是应该丢弃。

自动确认机制中,消息在发送之后就认为成功投递了,这种模式可以有很高的吞吐,这种模式被称为"fire-and-forget". 毫无疑问,这种方式在连接断开等情况会丢消息,适用于需要高吞吐、容许丢消息的场景。

当然手动确认机制还可以控制消费的速率,通过设置Qos来设置prefetch数量

Positively Acknowledging在java中使用:

 channel.BasicAck(ea.DeliveryTag, false);

改方法支持批量确认,第二个参数设为true就是批量确认。批量确认的机制是: 确认时候回一次性把低于delivery_tag的值的消息全部确认。

Negative Acknowledgement有两个协议方法,basic.reject和basic.nack。在java中方法对应为:

channel.basicReject(deliveryTag, false);
channel.basicReject(deliveryTag, true);

对于reject而言,第二个参数为false,则会被直接丢弃,若为true,消息会重新入队。假如重新入队,消息会尽可能被放置在队列头(或者离队列头很近的位置)。对于basic.nack,支持批量确认:

channel.basicNack(deliveryTag, true, true);

该方法会使得所有未确认的tag重新入队。

消费确认模式、prefetch和吞吐量取舍。增加prefetch会改进消息投递的速度,同时也会增大已投递但未确认的消息数量,增加消费者RAM的负担。寻找一个合适的prefetch值是一个经验之谈,通常100-300会有理想的吞吐量也不会对消息者负担过重。prefetch为1的情况是非常极端保守的情况。

考虑消费失败或者连接断开的情况,对于手动确认机制,投递未应答的消息会自动重新入队。重新入队会在以下情况发生: tpc连接断开、消费处理失败和通道协议异常。因此,消费端必须保证幂等性。重新投递的消息属性上的redeliver会被设置为true。注意:对于探测不可达的客户端,需要一定的时延。

投递确认

网络探测失败需要一定的时间,因此客户端在socket写入协议帧后,不能假定消息成功抵达了服务端并且被成功处理。

在标准的AMQP 0-9-1协议中,保证消息不丢的方式只有使用事务,然而事务的开销非常大,需要等待服务端确认,处理,回应,非常影响性能。大部分时候,消费端仅仅只需要处理下没有发送成功的消息,因此引入一种拓展:confirm模式。

服务端处理完之后会发送basic.ack消息,发送端可以异步处理,同理假如无法处理成功,会返回basic.nack,客户端可以选择重新发送等。

一旦开启了confirm模式,所有的消息都会被ack或者nack,但是没有任何保证,消息被确认的需要多久。ack可能会乱序,客户端不应该依赖应答的顺序。

broker发送ack的时机:

  • 对于无法路由的消息,一旦确认无法路由,就会发送。
  • 对于可路由的消息,发送ack意味着,持久化消息持久化到磁盘中或者已经被队列消费了,镜像队列而言所有的镜像已经接受了该消息。

可靠性分析

如何获得可靠性,首先分析失败的原因:网络原因失败、防火墙阻拦、broker和client服务器硬件失效或者软件原因挂了、客户端逻辑错误导致通道或者连接关闭。

对于通道关闭的错误,java提供ShutdownListener回调处理这种情况。

确认机制:保证connection失效的时候,处在中间态(broker已发送,未处理)会丢失,确认机制能保证双方有各自应对措施。tcp本身也包含一个确认机制,但是仅仅是在传输层,而rabbitmq的确认机制意味着所有权的转移,消息从生产者转移到了接受者。同样消费端也有确认机制,代表消费端处理完成,broker可以忘掉这条消息了。

确认机制能保证至少投递一次,没有确认机制则能保证至多投递一次。

心跳机制、集群高可用

对于生产端,刚从连接关闭或者失败的生产者会重新发送已经发送但是未收到确认的消息,增加了消息重复的可能性,因此必须在消费端保证消息的幂等性或者去重。

消息者取消通知:队列被取消或者失效转移,这种情况增加消息重复被消费的可能性。

猜你喜欢

转载自www.cnblogs.com/snailLx/p/9164130.html