RabbitMQ学习笔记

使用任务队列一个优点是能够轻易地并行处理任务。当处理大量积压的任务,只要增加“Worker”,通过这个方式,能够实现轻易的缩放。

Round-robin dispatching:
  默认地,RabbitMQ会逐一地向下一个“Consumer”发放消息,每一个“Consumer”会得到数目相同的消息。
  这种发放消息的方式叫Round-ronbin dispaching。

Message acknowledgment:
  当“Consumer”接受到一个消息并作长时间处理时,有可能发生意外状况,如运行“Consumer”的机器突然关闭,这时这个消息所要执行的任务可能没有得到正确处理。
  我们不希望有任务丢失或者没有正确处理,RabbitMQ支持Message acknowledgment来解决这个问题。
  当“Consumer”接收到消息、处理任务完成之后,会发送带有这个消息标示符的ack,来告诉RabbitMQ这个消息接收到并处理完成。
  RabbitMQ会一直等到处理某个消息的“Consumer”的链接失去之后,才确定这个消息没有正确处理,从而RabbitMQ重发这个消息。

  Message acknowledgment是默认关闭的。
  初始化“Consumer”时有个auto参数,如果设置为true,这个Consumer在收到消息之后会马上返回ack。
  我们的应用应该是在消息的任务处理完之后再ack,因此初始化“Consumer”时这个参数应该置为false。实例代码:
  QueueingConsumer consumer = new QueueingConsumer(channel);
  boolean autoAck = false;
  channel.basicConsume("hello", autoAck, consumer);

  while (true) {
  QueueingConsumer.Delivery delivery = consumer.nextDelivery();
  //我们的任务处理
  channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
  }

  忘记ack是一个容易犯并且后果很严重的错误。RabbitMQ会侵占越来越多的内存,因为它不会释放没有被ack的消息。
  可以使用rabbitmqctl去debug这个错误。实例:
  rabbitmqctl list_queues name message_rady message_unacknowleded

Message durability:
  Message acknowledgment解决了如果“Consumer”异常之后,任务得到保证。但是这并不能保证服务器异常之后任务能不丢失。Message durability正是用来解决这个问题。

  为了实现Message durability,我们应该做两个设置:消息队列和消息本身。
  声明消息队列时应该将durable参数置为true,消息的properties做设置,一个实例:
  //channel设置
  boolean durable = true;
  channel.queueDeclare("hello", durable, false, false, null);

  //publish的消息properties设置
  channel.basicPublish("", "task_queue",
  MessageProperties.PERSISTENT_TEXT_PLAIN,
  message.getBytes());

  这里有一个有意思的提示,RabbitMQ不能重复声明队列名称相同但是属性不一样的队列。属性如durable。

  关于message持久性的note:
  即使做了上述的几步来确保消息和任务不丢失,但是这样依然有几率丢失消息。
  RabbitMQ没有为每一个消息做fsync(2)(不知道这个是什么),消息可能只被保存在cache中,而没有真正写入硬盘,这时服务器突然异常就可能产生丢失。即使这个概率发生很小,但是如果需要更强大的保障,考虑用事件去包装发布消息的过程。

Fair dispatch:
  RabbitMQ默认只会用Round-robin的方式盲目地将消息派发给下一个“Consumer”,它不顾及“Consumer”还有多少个消息没有ack。
  为解决这个问题,我们可以使用basicQos方法来限制“Consumer”没有ack的消息数目。实例:
  int prefetchCount = 1;
  channel.basicQos(prefetchCount);

  如果这样做也有个问题,如果消息无法派发(“Consumer”的unack消息太多),消息多的可能使消息队列溢出。

exchange:
  在实际的情况下,消息发送者不会直接将Message发入queue,而是发入exchange,exchange能通过指定的规则将message放入指定的queue中(public/subscribe模型)。exchange有direct,topic,headers和fanout等几种类型。
  声明exchange的方法如下:
  channel.exchangeDeclare(String name, String type);

  fanout是一种广播模式,将消息派发到所有的绑定到该exchange的queque中。

  查看rabbitMQ中的exchange可以使用rabbitmqctl list_exchanges,查看exchange和queue的绑定关系可以使用rabbitmqctl list_bindings

  basicPublish()方法中,如果exchange置为"",将使用默认的exchange,这时将消息放入名字为routingKey参数的值的队列。

  channel.queueDeclare()不带参数的方法将生产一个non-durable(服务器重启后,这个queue不将存在), exclusive(这个queue只能被这个链接使用), autodelete(这个queue不再使用的时候会被删除)并且名字随机生成的queue,这种queue非常适合做广播接收queue

direct exchange:
  这种exchange的routing-key只有一个单词,exchange根据这个key将消息派发到对应的queue中。

topic exchange:
  topic exchange的routing-key不能是任意,它必须是一个一些词的数组,用"."隔开,每个词可以是任意的,但通常是代表某些含义的。这个词的数组不能超过255个字节。
  "*"可以代表任意一个词
  "#"可以代表任意数目的词(0个或更多)。

  topic exchange是非常强大的,它几乎能实现其他exchange的routing规则。

自己的note:
  一个exchange能用n条routing-key规则去指向一个queue。
  如果一个Message符合n条规则去往同一个queue,这个Message只会被投放到这个queue一次。

 

RPC:
  Remote Procedure Call(远程过程调度)。Client向远程的计算机请求一个耗时较长的任务,并等待其返回结果。
  RPC尽管是非常常见的一种模式,但是它有许多不足之处。RPC会带来一些不可预知的错误并增加了调试的复杂性。开发人员可能会困惑哪个是远程调用哪个是本地调用,或者说哪个RPC是耗时较长的调用。
  开发RPC时,记住以下的建议:
  1)将远程调用和本地调用清楚的区别
  2)为系统编写文档,将组件的依赖关系清楚的分离。
  3)着手准备处理意外事件,要考虑到远程调用的服务器意外关闭之后该如何处理。

  用RabbitMQ做RPC是非常容易的,客户端将Message发送到服务器,在Message里应该带上一个回调queue的地址,服务器通过这个地址将结果返回给客户端。
  Message一共有14种属性,常见的有这么几个:
  1)deliveryMode:标识这个Message是持久性的还是短暂性,如果是持久性并且保持这个Message的queue是durable的,服务器重启之后这个消息如果未被Consumer处理,就会恢复这个Message并加入到queue中。
  2)contentType: 标识消息内容的MIME,例如JSON用application/json
  3)replayTo: 标识回调的queue的地址
  4)correlationId:用于request和response的关联,确保消息的请求和响应的同一性。

 

posted on 2012-07-30 00:24  answer1991  阅读(...)  评论(...编辑  收藏

统计