-
C#队列学习笔记:RabbitMQ基础知识
一、引言
RabbitMQ是Rabbit Message Queue的简写,但不能仅仅理解其为消息队列,消息代理更合适。RabbitMQ是一个由 Erlang 语言开发的AMQP(高级消息队列协议)的开源实现,其内部结构如下:
RabbitMQ作为一个消息代理,主要和消息打交道,负责接收并转发消息。RabbitMQ提供了可靠的消息机制、跟踪机制和灵活的消息路由,支持消息集群和分布式部署。适用于排队算法、秒杀活动、消息分发、异步处理、数据同步、处理耗时任务、CQRS等应用场景。
二、基础概念
讲解基础概念之前,先来构造一个整体结构图,以方便我们更好地去理解RabbitMQ的基本原理。
通过上面这张结构图,能够清晰地了解到Send Message到Receive Message的一个大致流程。
2.1、Queue
Queue(队列)在RabbitMQ中的作用是存储消息,队列的特性是先进先出。上图可以清晰地看到Client A和Client B是生产者,生产者生产消息最终被送到RabbitMQ的内部对象Queue中去,而消费者则是从Queue中取出数据,可以简化表示为:
生产者Send Message,“A”被传送到Queue中,消费者发现消息队列Queue中有订阅的消息,就会将这条消息A读取出来进行一系列的业务操作。这里是一个消费者对应一个队列Queue,也可以多个消费者订阅同一个队列Queue,此时就会将Queue里面的消息平分给其他的消费者,但是会存在一个问题就是如果每个消息的处理时间不同,就会导致某些消费者一直在忙碌中,而有的消费者处理完消息后一直处于空闲状态。这时我们可以使用prefetchCount来限制每次发送给消费者消息的个数,详情见下图所示:
这里的prefetchCount=1是指每次从Queue中发送一条消息,等消费者处理完这条消息后,Queue会再发送一条消息给消费者。
2.2、Exchange
在本节开头的结构图中我们留下了一个坑:消费者Client A和消费者Client B,是如何知道要发送的消息是给Queue1还是给Queue2的呢?下面让我们来解开这个面纱。首先要明确的一点是:生产者生产的消息并不是直接发送给消息队列Queue的,而是要经过Exchange(交换器)将消息路由到一个或多个Queue,当然这里还会对不符合路由规则的消息进行丢弃,这个涉及到后面要讲的Exchange Type。那么Exchange是怎样将消息准确地推送到对应的Queue的呢?功劳最大的当属Binding。RabbitMQ通过Binding将Exchange和Queue链接在一起,这样Exchange就知道如何将消息准确地推送到Queue中去了,简单示意图如下所示:
生产者将消息发送给Exchange的时候,一般会产生一个Routing Key,而通过Binding链接Exchange和Queue的时候,一般会指定一个Binding Key。当Routing Key与Binding Key对应得上的时候,消息就会发送到对应的Queue中去。
2.3、Exchange Type
Exchange Type有4种,不同的类型有着不同的策略,也就意味着类型的不同将决定着绑定的Queue的不同。换言之,生产者发送了一个消息,Routing Key的规则是A,那么生产者会将Routing Key=A的消息推送到Exchange中,这时候Exchange根据自己的规则去筛选生产者发来的消息。
- fanout
fanout类型的Exchange,路由规则非常简单:它会把所有发送到该Exchange的消息,路由到所有与它绑定的Queue中。
上图所示,生产者(P)将消息1推送到Exchange,由于Exchange Type=fanout,这时候会遵循fanout的规则将消息推送到所有与它绑定的Queue中(上图的两个Queue),最后给两个消费者消费。
- direct
direct类型的Exchange,路由规则也很简单:它会把消息路由到那些Routing Key与Binding Key完全匹配的Queue中。
当生产者(P)发送消息时Rotuing key=booking,Exchange获取到生产者发送过来的消息后,会根据自身的规则进行匹配相应的Queue,这时发现Queue1和Queue2都符合,就会将消息传送给这两个队列。如果我们以Rotuing key=create和Rotuing key=confirm发送消息时,这时消息只会被推送到Queue2队列中,其它Routing Key的消息将会被丢弃。
- topic
如果说前面的direct规则是严格意义上的匹配(即Routing Key必须与Binding Key相匹配的时候才将消息传送给Queue),那么topic这个规则就是模糊匹配,可以通过通配符满足一部分规则就可以传送,它的约定是:
1)Routing Key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”。
2)Binding Key与Routing Key一样也是句点号“. ”分隔的字符串。
3)Binding Key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。
当生产者发送消息Routing Key=F.C.E的时候,这时候只满足Queue1,所以会被路由到Queue1中;如果Routing Key=A.C.E这时候会被同时路由到Queue1和Queue2中;如果Routing Key=A.F.B时,这时只会路由到Queue2中。
- headers
headers类型的Exchange,不依赖于Routing Key与Binding Key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
在绑定Exchange与Queue时指定一组键值对,当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),并对比其中的键值对是否完全匹配Exchange与Queue绑定时指定的键值对,如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。该类型的Exchange没有用到过(不过也应该很有用武之地),所以不作介绍。
下面对4种Exchange类型进行简要的表格整理:
类型名称 | 类型描述 |
fanout | 把所有发送到该Exchange的消息路由到所有与它绑定的Queue中 |
direct | Routing Key==Binding Key |
topic | 简称模糊匹配 |
headers | Exchange不依赖于Routing Key与Binding Key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。 |
2.4、补充说明
ConnectionFactory、Connection、Channel:都是RabbitMQ对外API中最基本的对象。
ConnectionFactory:Connection的制造工厂。
Connection:RabbitMQ的socket链接,它封装了socket协议相关的逻辑。Connection建立一个TCP连接,生产者和消费者通过TCP连接到RabbitMQ Server。
Channel:
1)Channel是与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作都是通过Channel这个接口来完成的,包括定义Exchange与Queue、绑定Exchange与Queue、发布消息等。
2)Channel虚拟连接,是建立在上面TCP连接的基础上。数据流动都是通过Channel来进行的,为什么不是直接在TCP连接上进行数据流动呢?是因为建立和关闭TCP连接是有代价的。频繁地建立与关闭TCP连接对于系统的性能有很大的影响,而且TCP的连接数也有限制,这也限制了系统处理高并发的能力,而在TCP连接中建立Channel是没有上述代价的。