RabbitMQ消息队列

来源:互联网 发布:清华大数据产业联合会 编辑:程序博客网 时间:2024/06/05 03:13

RabbitMQ是一个消息代理。它的核心原理是接收和发送消息。

RabbitMQ和消息,有一些专有名词,如下:

生产(Producing)的意思是发送,发送消息的程序就是一个生产者(producer)。

队列(queue)的意思是名称,消息通过你的应用程序和RabbitMQ进行传输,它们能够只存储在一个队列(queue)中。队列(queue)没有任何限制,你要存储多少消息都可以——基本上是一个无限的缓冲。多个生产者(producers)能够把消息发送给同一个队列,同样,多个消费者(consumers)也能够从一个队列(queue)中获取数据。

消费(Consuming)和获取消息是一样的意思。一个消费者(consumer)就是一个等待获取消息的程序。


一般生产者,消费者和代理不必部署在同一台机子上。

要发送消息,我们必须首先定义一个queue(队列),然后我们才能把消息发送给queue;

queue 的定义具有幂等性(一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相等),因此定义的queue已经存在,不会重复定义,且不能修改。

Work Queues(别名:Task Queue)是为了避免立即做一个资源密集型任务,而不得不等待它完成。我们可以把这个耗时的任务封装提取起来作为message,发送给一个queue。一个Worker 后台进程会获取task,然后执行他。当有多个Workers 时,他们平分这些task。

Round-robin dispatching 循环调度

使用Task Queue的一个优点就是可以很容易的平均分配任务。如果queue里有堆积过多的任务,我们可以添加更多的Worker就行了。

默认,RabbitMQ会顺序的,平均的把任务发给每个consumer,到最后每个Consumer会得到相同数量的任务。

Message acknowledgment

执行一个耗时的任务,你可能会想知道任务的执行情况。是否有Consumer开始执行任务了?是否任务执行到一半死机了?一旦RabbitMQ分发message给Custoerm,它就会立刻从内存删除。这种情况下,如果你关闭一个Worker,我们就会丢失他正在执行的消息。同样,我们也会丢失之前分发给他,还没有来的及执行的消息。

但是我们不想丢失任何 task。如果一个Worker死了,我们想把任务分发给其他的Worker。

为了确保message不丢失,RabbitMQ 提供了 message acknowledgments。Ack是consumer 发送给RabbitMQ的,告诉它,task 已经接受,并处理了,RabbitMQ 可以删除它了。

如果一个consumer死机了(channel closed,connection closed or Tcp connection lost),没有返回ack,RabbitMQ就会知道task 没有处理完,该task就会重新排队。如果这时候有另外一个Consumer在线,RabbitMQ 就会把它分发给他。

默认Message acknowLedgments 是打开的。

当consumer 死亡的时候,task 不会丢失。但是如果RabbitMQ服务停了,task 仍然会丢失。
这里我们就要持久化 task的信息了。

首先,我们确保RabbitMQ不会丢失我们的queue;

queue 持久化的修改,producer 和consumer的代码都要修改。

如果我们设置message持久化了,但是这也不能完全保证message不会丢失。
这是由于RabbitMQ保存message到硬盘是需要时间的,如果再此期间RabbitMQ服务挂了,message就丢失了。不过对于一般的程序已经足够了。如果要一个更强壮的方案,你可是使用publisher confirms;

Fair dispatch 公平调度

例如,假设有两个Worker,所有的奇数的message都是耗时的操作,而偶数的message都是很简单的。你会发现一个Worker很空闲,而另一个Woker累死累活的。然而RabbitMQ不知道,还是不停的给他发任务。

这个情况的发生,是由于RabbitMQ 不看 the number of unacknowledged message,只要message进入队列就分发message。他只是盲目的分发message。

为了解决上面的问题,我们可以使用 basicQos方法 设置 prefetchCount=1。这个设置会告诉RabbitMQ 每次给Workder只分配一个task,只有当task执行完了,才分发下一个任务。

注意queue的size
如果所有的Worker都busy,你的queue会填满,因此你需要监测queue的情况,添加更多的worker 或者采用其他的策略。

RabbitMQ的消息模型的核心思想是,生产者没有直接向队列发送任何消息。实际上,经常生产者甚至不知道一个消息将传递给任何队列。事实上,Producer只能发送message给exchange。exchange 很简单,一方面它从producers 接收messages,另一方面,它把messages 推送给queues。因此exchange要知道怎么处理接收到的message。是把message发给一个特定的队列?还是发给多个队列?或者丢弃?这个规则是由 exchange type 定义的。

exchange 有以下几种:direct, topic, headers 和 fanout。

当你想在生产者和消费者之间共享队列,给queue命名是至关重要的。

但是 这种情况对于我们的日志系统是不适合的。我们想看到所有的日志信息,而不是仅仅是他的一个子集。我们也只对当前流动的感兴趣而不是旧的消息。
为了解决这个问题我们需要做两件事:
1、我们需要一个新的,空的队列,不管什么时候我们连接到Rabbit。这就需要,我们每次连接rabbti都要创建一个名字随机的队列,或者让服务器选择一个名字随机的队列给我们。
2、一旦我们consumer断开与queue的连接,queue应该自动删除。

在.NET client 我们提供了一个无参的 queueDeclare()方法,使用它,我们可以创建一个 不持久化,名字唯一,自动删除的队列。

var queueName = channel.QueueDeclare().QueueName;

这里queueName是随机生成的队列的名字。例如amq.gen-JzTY20BRgKO-HjmUJj0wLg

binding是exchange与queue之间的关系,简单的来说就是。queue对message来自指定的exchange的感兴趣。

Binding可以指定routingKey参数。为了避免和BasicPublish参数疑惑,我们可以叫它 binding key。因此我们可以创建一个带key的bingding;

bingding key的意义取决于 exchange的类型。fanout类型的exchange会忽略这个值。

如果我们只想记录重要的错误日志信息到FILE,其他的不记录到文件。

但是fanout类型exchange 不够灵活,它只能盲目的分发。
因此这里我们使用 direct类型的exchange来替代。direct 类型exchange背后的算法很简单——一个消息只会发送给queue的bingding key 完全匹配message的routing key的队列。

Topic exchange
message 发送到一个topic类型的exchange不包含任意的routing_key——而是一系列".“分隔的word。这些word可以任意,但是通常是一些连接message的一些特性。例如"stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”。但是最多255个字节。

A note on RPC
尽管RPC 是一种很常见的模式,但是它是有争议的。主要问题是:程序不知道函数调用是本地还是一个缓慢的RPC。

Callback queue
通常通过该RabbitMQ 实现RPC是很简单的。一个Client发送一个请求信息,一个Server返回响应信息。为了接收这个反馈信息,我们应该发送请求时包含一个callback queue地址。