RabbitMQ学习之一:理解RabbitMQ

RabbitMQ:一款基于Erlang的分布式消息队列,采用AMQP(高级消息队列协议)对队列和交换器等资源进行配置。


AMQP消息路由必须有三个部分: 队列、交换器和绑定。

    生产者把消息发布到交换器上;

    消息最终到达队列,并等待消费者接收;

    绑定决定了消息如何从路由器路由到特定的队列。


首先,一个生产者、消费者、RabbitMQ的关系图:

image.png


一、信道(Channel)

应用程序和Rabbit服务器之间建立一条TCP连接,一旦TCP连接打开,应用程序就可以创建一条AMQP信道。

信道是建立在“真实的”TCP连接内的虚拟连接。AMQP命令都是通过信道发送出去的,在一条TCP连接上创建多少条信道是没有限制的,每条信道都会被指派一个唯一的ID。

信道的引入避免了创建和销毁TCP连接带来的开销。


二、关于消息(Message)

1、消费者如何接收消息:

    第一种,通过basic.consume命令订阅。这样会将信道置为接收模式,持续获取消息,直到取消对队列的订阅;

    第二种,通过basic.get从队列中获取单条消息。如果需要获得更多的消息,需要再次发送basic.get命令。

当一个队列有多个消费者时,队列收到的消息将以循环(round-robin)的方式发送给消费者,每条消息只会发送给一个订阅的消费者。消费者确认(basic.ack)接收到消息后,消息就会从队列中删除。或者订阅队列时将auto_ack参数设置为true,一旦消费者接收消息,RabbitMQ会自动认为其确认接收了消息。

2、消息重发:

    如果消费者在确认接收消息之前,从Rabbit服务器断开连接,RabbitMQ会认为这条消息没有分发,消息重新进入队列,然后分发给下一个订阅的消费者。

    如果消费者忘记确认消息,RabbitMQ会认为消费者还没有做好准备接收下一条消息,将不会给该消费者发送更多消息了。

3、拒绝消息:

    消费者可以直接从Rabbit断开连接,以拒绝消息。

    也可以使用AMQP的basic.reject命令。如果reject命令的requeue参数设置成true的话,RabbitMQ会让消息重新进入队列,发送给下一个订阅的消费者。如果设置成false,RabbitMQ会把消息从队列中移除。


三、关于队列(Queue)

1、创建队列:

    生产者和消费者都能使用AMQP的queue.declare命令来创建队列。

    队列是AMQP消息通信的基础模块:

        为消息提供了处所,消息在队列中等待消费;

        队列是Rabbit中消息的终点。

2、消息如何到达队列(交换器和绑定):

    队列通过路由键绑定到交换器。想要将消息投递到队列时,先把消息发送给交换器,交换器根据确定的规则(路由键routing key)决定将消息投递到哪个队列。

    

3、死信队列(Dead letter):

    用来存放被消费者拒绝而不会重新进入队列的消息。通过检测拒绝/未送达的消息来发现问题。

    如果消息被basic.reject,并且requeue参数设置成false,该消息会进入死信队列。



四、关于交换器(Exchange)

1、四种交换器:

    headers交换器允许匹配AMQP消息的header而非路由键。几乎不会被用到了;

    direct交换器:如果路由键匹配,消息就被投递到对应的队列。服务器必须有一个direct类型的默认交换器。当声明一个队列时,会自动绑定到默认交换器,并以队列名称作为路由键;

    fanout交换器:会将接收到的消息广播到绑定的队列上。

    topic交换器:使得来自不同源头的消息能够到达同一个队列。

2、topic交换器:

    举个例子。

    1)如果应用程序想要给RabbitMQ报告一个error日志的话,可以编写以下代码:

$channel->basic_publish($msg, 'logs-exchange', 'error.msg-inbox');

        $msg:要发送的消息

        logs-exchange:指定的交换器的名称

        error.msg-inbox:路由键

    2)将“msg-inbox-errors”队列绑定到“logs-exchange”交换器上:

$channel->queue_bind('msg-inbox-errors', 'logs-exchange', 'error.msg-inbox');

    3)如果要让“msg-inbox-errors”队列监听所有error级别的日志:

$channel->queue_bind('msg-inbox-errors', 'logs-exchange', '*.msg-inbox');

    4)如果要让“msg-inbox-errors”队列监听所有日志:

$channel->queue_bind('msg-inbox-errors', 'logs-exchange', '#');

    符号“#”匹配所有规则。


五、关于虚拟主机(vhost)

每一个vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器和绑定,拥有自己的权限控制机制。RabbitMQ以vhost为单位进行权限控制,各个vhost之间是绝对隔离的。

RabbitMQ包含了一个默认的vhost:“/”。

在创建一个RabbitMQ用户时,用户通常会被指派至少一个vhost。

vhost创建与使用:

# rabbitmqctl add_vhost [vhost_name]
# rabbitmqctl delete_vhost [vhost_name]
# rabbitmqctl list_vhosts


六、持久化策略

默认情况下,当重启RabbitMQ服务器后,队列和交换器都消失了。原因在于每个队列和交换器的durable属性,该属性默认情况为false。它决定了RabbitMQ是否需要在崩溃或者重启之后重新创建队列(或者交换器)。将durable的值设置为true,重启RabbitMQ之后队列和交换器就不会消失了。

durable能让队列和交换器持久化,对于消息却不能。

能从AMQP服务器崩溃中恢复的消息,称为持久化消息。在消息发布前,通过把它的投递模式(delivery mode)设置为“persistence”。

持久化的消息必须被发布到持久化的交换器并到达持久化的队列中才能实现持久化。如果在Rabbit崩溃重启后,未发布到持久化的交换器和队列的消息将会成为孤儿。

RabbitMQ确保消息持久化的方式是,将它们写入磁盘上的一个持久化日志文件。当RabbitMQ重启之后,服务器会自动重建交换器和队列,重新将持久性日志文件中的消息发送到对应的交换器或者队列上,这取决于RabbitMQ服务器宕机时,消息处在路由过程的哪个环节。

因此,如果想实现消息的持久化,则必须满足三个条件,且缺一不可:

    1)把消息的投递模式设置为“persistence”;

    2)发送到持久化的交换器;

    3)到达持久化的队列。

 但是如果对所有的消息都启用持久化策略,因为写入磁盘的速度要比写入内存中慢不止一点点,从而导致吞吐量大大降低。所以需要权衡取舍。


七、保证消息投递

由于发布操作并不会返回任何信息给生产者,那生产者如何知道服务器是否已经成功将消息送达队列,或者持久化了消息呢?

采用发送方确认模式。告诉Rabbit将信道设置成confirm模式,这时只能通过重新创建信道来关闭该设置。一旦信道进入confirm模式,所有在信道上发布的消息都会被指派一个唯一的ID号(从1开始)。当消息被投递给所有匹配的队列后,信道会发送一个确认信息给生产者应用程序(包含消息的唯一ID),这使得生产者知道消息已经安全到达目的队列了。如果消息和队列是持久化的,那么确认信息只有在队列将消息写入磁盘后才会发出。

生产者发布一条消息后,会在等待确认的同时继续发送下一条消息。如果Rabbit发生错误导致消息丢失,Rabbit会发送一个nack(not acknowledged)消息。


猜你喜欢

转载自blog.51cto.com/13568014/2495857