消息中间件RabbitMQ(一)
1、消息中间件
消息队列中间件是指利用高效可靠地消息传递机制传递消息。有两种传递模式:点对点模式、发布/订阅模式。流行的消息中间件有RabblitMQ、Kafka、RockerMQ。它们都提供了基于存储和转发的应用程序之间的异步数据发送,即应用程序彼此不直接通信,而是与作为中介的消息中间件通信。
2、组成部分
RabbitMQ的整体模型架构如图。RabbitMQ的组成由 生产者、交换器、绑定、队列、消费者组成。
2.1 Server(broker)
接受客户端连接,实现AMQP消息队列和路由功能的进程。
2.2 Virtual Host
其实是一个虚拟概念,类似于权限控制组,一个Virtual Host里面可以有若干个Exchange和Queue,但是权限控制的最小粒度是Virtual Host
2.3 连接
生产者和消费者都需要和RabbitMQ Broker建立连接,连接是TCP连接。一旦TCP连接建立起来,客户端紧接着创建一个AMQP信道(Channel)。信道是建立在TCP Connection之上的虚拟连接,RabbitMQ处理每条AMQP指令都是通过信道完成的。因为建立和销毁TCP连接开销大,所以选择TCP连接复用,减少开销。
2.4 生产者和消费者
生产者:创造消息,发布到RabbitMQ中。消息包含两个部分:标签和消息体。标签是为了描述这条消息,生产者把消息交由RabbitMQ之后会根据标签把消息发送给感兴趣的消费者。在消息路由的过程中,消息的标签会丢弃,存入到队列中的消息只有消息体,
消费者:接受消息。消费者连接到RabbitMQ 服务器,并订阅到队列上。消费者只能得到消息体,也就不知道消息的生产者是谁。
2.5 绑定和路由键
路由键:生产者将消息发送给交换器的时候,指定RoutingKey.
绑定键:通过绑定键将交换器和队列联系起来。如下图
2.6 交换器
生产者将消息发送到交换器,由交换器将消息路由到一个或多个对列中。如果路由不到,直接丢弃消息或者返回给生产者。队列是生产者和消费者传递消息的一个中介,所有消息都必须通过交换器将消息放入队列中,不能直接将消息放到队列中。
交换器类型:
Fanout:会将所有发送到交换器的消息路由到所有与改交换器绑定的队列中。这种情况BingKey和RoutingKey相当于不起作用。
Direct:会把消息路由到BindingKey和RoutingKey完全匹配的队列中。如下图 消息只会进入队列一中。
Topic:会按照一定规则将BindingKey和RoutingKey相匹配的队列中。BindingKey可以存在两种特殊字符串"*"和"#"。“#”号用于匹配一个单词,“*”匹配多个单词。
Headers:Headers类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
路由键、交换器类型、绑定键三者共同决定了消息进入哪些队列中。
2.7 队列
用于存储消息。RabbitMQ中消息只能存储在队列后中。多个消费者可以订阅同一个队列,队列中的消息会被平均分摊给多个消费者。RabbitMQ 不支持队列层面的广播消费。
附上一张完整的结构图:
3、消息确认
在生产者发送消息到消费者消费消息的流程中,有两个地方需要消费确认:
- 生产者要确认发出的消息到达RabbitMQ。
- 消息从队列到达消费者的过程。队列要确认发出的消息被消费者消费,才会将消息从队列中删除。
为了保证,生产者的消息到达RabbitMQ,可以通过事务机制和发送方确认机制实现。
事务实现:
Channel.TxSelect 将当前信道设置成事务模式
Channel.TxCommit 提交事务
Channel.TxRollback 事务回滚
发送方确认机制:
生产者将信道设置成confirm(确认)模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从l开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ 就会发送一个确认(Basic.Ack) 给生产者(包含消息的唯一ID) ,这就使得生产者知晓消息已经正确到达了目的地了(如上图的流程1)。如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出。
生产者调用channel.ConfirmSelect将信道设置为confirm模式,事务机制和Publisher confirm机制确保的是消息能够正确地发送至RabbitMQ,这里的“发送至RabbitMQ”的含义指消息被正确地发送到交换器。
事务机制在一条消息发送之后会使发送端阻塞,以等待RabbitMQ 的回应,之后才能继续发送下一条消息。相比之下, 发送方确认机制最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用程序便可以通过回调方法来处理该确认消息。
为了保证,队列中发出的消息被消费者消费,RabbitMQ提供了消息确认机制。
消费者订阅队列时,可以指定autoAck参数,autoAck等于false,RabbitMQ会等待消费者显示地回复确认信号才能从队列后中删除(如上图的流程2)。autoAck等于true,会在消息发送去后删除,不管消费者是否真正消费到这条消息。当autoAck 参数置为false ,对于RabbitMQ 服务端而言,队列中的消息分成了两个部分:一部分是等待投递给消费者的消息、一部分是己经投递给消费者,但是还没有收到消费者确认信号的消息。
如果RabbitMQ 一直没有收到消费者的确认信号,并且消费此消息的消费者己经断开连接,则RabbitMQ 会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。RabbitMQ 不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否己经断开。
4、API
4.1 创建连接
ConnectionFactory factory = new ConnectionFactory { UserName = "admin", Password = "admin", HostName = "118.21.96.213" }; var connection = factory.CreateConnection();
var channel = connection.CreateModel();
4.2 声明交换器
void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete, IDictionary<string, object> arguments); //声明交换器后,不需要等待交换器返回。但如果服务器未完成创建,而客户端使用了这个交换器,会发生异常。 void ExchangeDeclareNoWait(string exchange, string type, bool durable, bool autoDelete, IDictionary<string, object> arguments);
- exchange:交换器名称
- type:交换器的类型
- durable:是否持久化
- autoDelete:是否自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定, 之后所有与这个交换器绑定的队列或者交换器都与此解绑
- internal:设置是否内置。如果为true,客户端无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式
- arguments:参数设置。
4.3 删除交换器
void ExchangeDelete(string exchange, bool ifUnused); void ExchangeDeleteNoWait(string exchange, bool ifUnused);
- ifUnused:为true,表示没有被使用的情况下才会被删除。设置为false,无论如何都要被删除
4.4 声明队列
QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments);
void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments);
- Queue:队列名称
- Durable:是否持久化。持久化的队列会存盘,在服务器重启的时候可以保证不丢失相关信息。
- Exclusive:设置是否排他。
- AutoDelete:设置是否自动删除。
如果队列为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。排他队列是基于连接(Connection)可见的,同一个连接的不同信道(Channel)是可以同事访问同一连接创建的排他队列。 “首次”是指如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的。即使该队列是持久化的,一旦连接关闭或者客户端退出,改排他队列都会被自动删除。
自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。
4.5 删除队列
//返回队列删除期间清除的消息数
uint QueueDelete(string queue, bool ifUnused, bool ifEmpty); void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty);
- ifUnused:为true,表示没有被使用的情况下才会被删除。设置为false,无论如何都要被删除
- ifEmpty:为true,表示在队列为空(队列里面没有任何消息堆积)的情况下才能够删除。
4.6 队列绑定
void QueueBind(string queue, string exchange, string routingKey, IDictionary<string, object> arguments); void QueueBindNoWait(string queue, string exchange, string routingKey, IDictionary<string, object> arguments);
4.7 交换器绑定/解除绑定
void ExchangeBind(string destination, string source, string routingKey, IDictionary<string, object> arguments); void ExchangeBindNoWait(string destination, string source, string routingKey, IDictionary<string, object> arguments);
void ExchangeUnbind(string destination, string source, string routingKey, IDictionary<string, object> arguments);
void ExchangeUnbindNoWait(string destination, string source, string routingKey, IDictionary<string, object> arguments);
- destination:交换器名称
- source:交换器名称
消息从source交换器发送到destination交换器中。
4.8 发送消息
void BasicPublish(string exchange, string routingKey, bool mandatory, IBasicProperties basicProperties, byte[] body);
- exchange:消息发送到的交换器
- routingKey:路由键
- mandatory:当为true时,交换机无法根据自身类型和路由键找到符合条件的队列,消息会返回给生产者。当为false时,消息直接丢弃。
- basicProperties:其他参数设置
- body:消息字节数组