RabbitMQ 学习兼实战

RabbitMQ 学习兼实战

在之前已经有写过部分RMQ的代码了,这段时间重新学


更新日志

  • 2019-02-11
    • 22:24
      • 添加了持久化的方式,还是不完善
  • 2019-01-27
    • 23:16
      • 重新看了文档的内容,帮助真的大啊
      • 明天将exchange的相关内容加上,都写在草稿本上了
      • demo会重新写,结合Spring的,题材会跟官网上的log分级相同,会有变化这是肯定的,但是需求还没想好
  • 2019-01-28
    • 09:28
      • 添加了exchange的内容,内容文档挺多的,添加的挺少,部分都记在脑子里了,不理理直接记录的话有点乱

基本角色

  • producer: 生产者: 既发送消息的对象
  • consumer: 消费者: 既消费消息的对象
  • queue: 队列: 存放消息的场所
  • exchange: 消息的中间站;在producer-queue模型中,msg会被明确的发送给指定的queue,而在exchange模型中,消息是发送给exchange的,exchange的作用就是一端接收数据,另外一端将数据发送给指定的队列 当然,其实producer-queue模型中,msg是发送给默认的""exchange的
  • 注意点:
    • 无论是producer,还是consumer,都需要声明queue,声明重复的queue并不会有问题,但是如果声明的queue具有不同的配置,则会出错,切记

Message

Consumer端:通过manual ackonwledgement mode实现消息的消费

  • RabbitMQ消息的确认提供了如下几种:
    • automatic acknowledgement mode 自动确认模式:既消息通过MQ delivery之后会立即设置为成功发送,因而这条消息就会立马移除

    In automatic acknowledgement mode, a message is considered to be successfully delivered immediately after it is sent,This mode trades off higher throughput (as long as the consumers can keep up) for reduced safety of delivery and consumer processing.

    • Manually sent acknowledgements 手动确认模式: 但是手动确认又提供了不同的方式:

      • channel.basicAck(deliveryTag, true);: 消息被处理成功(broker会丢弃消息) 注意第二个参数是:是否批量确认 ,这个批量确认其实与tcp的延迟确认差不多
      • channel.basicNAck(deliveryTag, requeue,mulitiACK): 表示consumer处理失败,需要mq再进行处理,至于是重新入队还是直接删除取决consumer调用这个方法时是否设置requeue为true 注意第二个参数是 是否重新入队,第三个参数是 是否批量确认
      • channel.basicReject(deliveryTag, requeue): 表示consumer处理失败,需要mq再进行处理,至于是重新入队还是直接删除取决consumer调用这个方法时是否设置requeue为true,与nack的区别在于,reject无法批量reject,而nack可以 注意第二个参数是 是否重新入队

      Positive acknowledgements simply instruct RabbitMQ to record a message as delivered and can be discarded. Negative acknowledgements with basic.reject have the same effect. The difference is primarily in the semantics: positive acknowledgements assume a message was successfully processed while their negative counterpart suggests that a delivery wasn’t processed but still should be deleted.

    • 两者的区别在于:

      • 手动ack效率比自动ack效率低
      • 手动ack比自动ack更安全
      • 手动ack的时候会与一个channel保持连接,意味着通道上的消息是一定的,不会发生oom,而自动ack对消息投递的数率没有限制,因而可能会崩溃,所以当设置为auto ack的时候最好额外监控内存压力
      • auto ack适合于当consumer有效且稳定的消费消息的时候
    • 额外注意点:

      • 当消息重新入队的时候,它会处于其原先的位置处或靠近队头处
      • 对于因为consumer 消费失败入队然后出队又失败而导致的循环,
        consumer可以设置在本地设置失败次数,判断并且跟踪,达到一定次数直接discard

Producer端:Producer发送消息的时候,消息也可能会因为各种原因丢失,如何避免:

  • 确保消息成功发送有两种方式
    • 第一种方式是通过事务的方式,既使得channel 变为transactional,
      然后每个消息就得以确保成功发送了,但这种方式会严重降低吞吐量
    • 第二种方式是Publish-Confirms的方式:原理与TCP的三次握手类似,broker会返回ack(seq+1)确保当前的message发送成功,若broker无法处理消息的时候,会通过basic.nack将消息返回,然后publisher做re-publish还是其他随业务而定
    • 注意:
      • channel若处于publish-confirm时无法再更改状态为transactional,同理互换也是;
      • 同时要考虑网络通信的因素,broker可能不会立马就返回的,
      • 消息确认可能会乱序()
      • 当消息模式不一样的时候,confirm的时机也是不一样的:当message的模式为persistent的时候,只有当消息写入到disk了才会confirm,而如果为trisient的时候则入队之后直接confirm,切记
  • 一个消息可能会遇到如下情况:
    • 消息丢失:
      • consumer 消费了消息,mq不知道
        • 解决方法: 通过手动ack的方式,manaual acknowledge默认是开启的,当RabbitMQ delivery一个message之后并不会立马移除,而是会放入一个待确认队列中,当consumer端消费完毕之后可以通过basic.AckKnowledgement确认消息已经被消费了,RabbitMQ则会移除消息
      • mq宕机了:
        • 解决方法: 当创建队列的时候,RabbitMQ提供了这么一个参数:Druable,既是否持久,然后每个消息的模式改为persistent
    • 消息重复消费: 这个需要我们本地业务进行管控

消息的持久化:

  • 说到消息的持久化不得不先提及队列几个相关属性:
    • durable: 代表是否持久化
    • exclusive: 是否是排它队列,代表仅当前申明它的连接可见,当连接断开时自动删除,与durable互斥
    • autoDelete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。
  • 消息幸免于重启的几个必要条件:
    • 消息自身的属性为:persistent
    • exchange的durable属性为:true
    • queue的durable属性为true
  • 消息何时持久化:
    • persistent消息入队的时候,并会append到磁盘中,当append完毕之后才会publish-confirm
    • trisient的消息持久化的条件为:当内存不足的时候,部分trisient的消息会被持久化到disk中
  • 持久化的原理:
    • RMQ启动的时候通过两个进程实现持久化:msg_store_persistent, msg_store_tranisent 前者用于对消息属性为persistent的消息持久化到文件中,后者在内存不足时将部分消息持久化到文件中
    • 添加通过append的形式写到文件中,超过限定大小的时候会创建一个新的文件并写入,命名为*.rdq的形式,从0开始递增
    • 删除:当删除的时候并不会从disk中立即删除,而是会先记录,当达到一定比例(似乎是50%)的时候会垃圾回收,同时会将逻辑相连的文件中的数据合并到一起,

总结:

  • publisher 可以通过publish-confirm确保消息被投递到了消息队列中(但是当需要为消息持久化的时候并不会立即通知,而是持久化到disk之后才会通知)
  • consumer 通过manaul ackonwledgement确保消息被消费成功
  • RabbitMQ并不能确保消息一定会被持久化 ,只能够strongker guarantee

额外的引申点

  • RMQ提供了类似于TCP的滑动窗口机制,developer可以通过basic.qos初始化设置,这个值定义了在一个consumer中最多有多少个unacknowledged的个数,一旦到达阈值则会停止delivery,(但是如果设置了话得注意queue的size) 可以先设置为100-300保持吞吐量再尝试修改

Values in the 100 through 300 range usually offer optimal throughput and do not run significant risk of overwhelming consumers. Higher values often run into the law of diminishing returns.

  • 消息有两种属性选择:
    • persistent: 用于持久化消息
    • transient: 短暂的消息,既用完就扔的消息

Exchange

  • Exchange: 在producer-queue模型中,消息都是发送给特定的queue的,而exchange的作用就是一端接收数据而另一端将数据发送给指定的queue,默认情况下,producer-queue模型其实是把msg发送给默认的""exchange的,因而exchange必须知道该如何对message处理(是发送给某个队列还是某些队列,又或者是直接抛弃),取决于Exchange的type

  • Routing

    • Exchange中还有一个重要的概念,叫做路由,Routing使得某个message得以发送给某个queue
    • Routing key 和Binding key 其实是等同的概念,但对于queue而言是Binding Key 代表着这个queue通过key与Exchange绑定在一起,而对于Message而言则是Routing Key代表着这个消息会被Exchange路由发送到哪个队列中

Fanout Exchange

  • 定义: 消息会广播出去,在这种模式下,routing key不再起作用

Direct Exchange

  • 定义: 消息会发送给指定的queue,当然一个queue也是能绑定多个routing key的

Topic Exchange

  • 定义: 消息会发送给复合某个条件的queue,最强大的一种模式,本质上可以实现Fanout和Direct

Exchange总结

  • 几种模式的选择:
    • Fanout毫无疑问适用于广播的数据,但是适用情况很少
    • Direct虽然它可以绑定多个routing key达到类似于fanout的效果,但是与topic相比,无法优雅的通过多个标准来获取消息,如若原先向通过log 的级别来判断,direct完全可以(debug,info,warn,error),但是当想再添加一个分割的条件如:日志的源地址的时候,direct虽说可以再添加相关的routing key,但是若需求再变,这是不优雅的,而topic因为其通配符*或者#的存在,使得可以一个routing key即可将消息绑定到多种queue上

RabbitMQ 和RPC

如何从consume中获取消费的结果

  • 通过RPC的方式,consumer端消费完毕之后,将结果通过rpc的方式回复给producer
  • consumer消费完了之后将结果入队给某个结果集队列中,producer监听处理,但是这种方法无法得知这个结果respose属于哪个request
  • prefer每个消息发送之前设置这2个参数replyTo和correlationId,replyTo使得consumer消费完毕之后往replyTo的queue中发送结果,同时也携带上correlationId,producer端或者是第三方结果端监听这个callback 队列,获取消息之后通过correlationId判断是否有对应的request请求,有则处理,无则记录 注意这里的queue只需要默认的即可,druable为false,用完自动清除,至于exchange 可以为direct

疑问点

  • 到底是消费者主动去拉消息,还是RabbitMQ主动推送

    • 答: 是RabbitMQ主动推送消息,通过basic.deliver会主动将消息发给消费者
  • 当有多个消费者的时候,消费不均如何避免:

    • 答: 有的worker可能会busy而有的可能idle,为了避免这种情况,可以使用channel.basicQos(prefetchCount) 来控制,这种方法使得每个consumer最多有prefetchCOunt个未消费的消息,这样就避免了消费不均的问题
  • 当消息发送出去的时候如何确认消费者接收到了消息,或者说是如何确认消费者处理完了消息:

    • 答: 通过channel.basicAck 可以手动确认
    • 生产者如何确认消息是发送成功了的:
      • 答: 通过publish-confim 机制,当mq-server收到消息的时候,它会返回一个udp提示收到了(confirm)
  • 当consumer手动ack或者reject或者nack的时候,怎么确保是对这个消息的ack/nack/reject呢,会不会导致其他消息ack了

    • 答: 不会,因为broker发送消息的时候会为每个消息都绑定一个delivery tag,这个delivery tag唯一标识了在这个channel中的消息,既作用范围只在这个channel,而consumer是当处理的时候是与这个channel相连的,因而只需要回复的时候带上这个delivery tag即可 另外,当consumer没有执行完毕的时候是会一直与broker保持连接的,若consumer退出的时候忘记ack,则会使得消息重新requeue,若一直保持连接,并且无ack,则会消耗mq服务器的内存
  • 如何持久化数据,以及何时会持久化

    • 答: 初始化队列的时候有durable这个属性,代表持久化,首先设置为true,这样这个队列就会被持久化,然后消息如何持久化? ,通过为消息设置delivery_mode为2 既persistent的形式,这样消息也得以持久化(但是并不能保证)
    • 什么时候会触发持久化: 每隔几百毫秒(文档不是这么说的,但是可以这么认为),或者是当queue空闲的时候

Marking messages as persistent doesn’t fully guarantee that a message won’t be lost. Although it tells RabbitMQ to save the message to disk, there is still a short time window when RabbitMQ has accepted a message and hasn’t saved it yet. Also, RabbitMQ doesn’t do fsync(2) for every message – it may be just saved to cache and not really written to the disk. The persistence guarantees aren’t strong, but it’s more than enough for our simple task queue. If you need a stronger guarantee then you can use publisher confirms.

你可能感兴趣的:(MQ,MQ,消息队列)