RabbitMQ阅读笔记-----理解Rabbit MQ的消息通信
理解消息通信
RabbitMQ的消息通信是基于AMQP(高级消息队列协议)模式消息模式。AMQP隐去了消息的发送方和接收方。AMQP消息能以一对多的广播方式进行路由, 也可以选择一对一的方式路由。
消费者和生产者
如果在工作中使用过网络软件, 脑海中应该有客户端和服务端的概念,不管是浏览器和服务器,还是应用程序和MySQL服务器, 都是其中一方发送请求, 而另一方服务这些请求。你可以视为快餐车模式。该模型就是我们平时如何尝试理解应用程序和服务器之间发送的一切。因此对于这个新的消息通信机制,你可能会问:哪个是顾客, 哪个是快餐车,还有我怎么样下订单呢?
这确实是个问题。RabbitMQ不是快餐车而是消息投递服务。应用程序从RabbitMQ获得的数据并不是由RabbitMQ产生的, 就如同你收到的快递包裹不是由快递公司生产的一样。因此, 我们把RabbitMQ当作一种投递服务。应用程序可以发送和接收包裹。而数据所在的服务器也可以发送和接收。RabbitMQ在应用程序和服务器之间扮演者路由器的角色。所以当应用程序链接到RabbitMQ时, 它就必须做个决定: 我是在发送还是接收呢?从AMQP的角度思考,我是一个生产者还是消费者呢?
生产者(producer)创建消息,然后发布到代理服务器(RabbitMQ)。什么是消息呢?消息包含两个部分内容:有效载荷(payload)和标签(label)。有效载荷就是你想要传输的数据。它可以是任何内容, 一个JSON数组或者你喜欢的图片等。RabbitMQ不会在意这些。标签就更有趣了。它描述了有效载荷, 并且RabbitMQ用它来决定谁将获取消息的拷贝。举例来说, 不同于TCP协议, 当你明确指定发送方和接收方时, AMQP只会用标签表述这条消息(一个交换器的名称和可选的主体标记),然后把消息交给RabbitMQ。RabbitMQ会根据标签把消息发送给感兴趣的接收方。这种通信方式是一种“发后就忘”的单向方式。
图1 从生产者到消费者的消息流
消费者很容易理解, 它们连接到代理服务器上, 并订阅到队列(queue)上。把消息队列想象称一个具名邮箱。每当消息到达特定的邮箱时, RabbitMQ会将其发送给其中一个订阅的/监听的消费者。当消费者接收到消息时, 它只得到消息的一部分: 有效载荷。在消息的路由过程中,消息的标签并没有随着有效载荷一同传递。RabbitMQ甚至不会告诉你是谁生产/发送了消息。就好比你拿起信件,却发现所有的信封都是空白的。想知道这条消息是否从姑妈发来的唯一方式就是她在信里签了名。同理,如果需要明确知道是谁生产的AMQP消息的话, 就要看生产者是否把发送方信息放入有效载荷中。
整个过程其实很简单:生产者创建消息, 消费者接收这些消息。你的应用可以作为生产者,向其他应用程序发送消息。或者作为一个消费者, 接收消息。也可以在两者之间进行切换。不过在此之前,它必须先建立一条信道(channel)。
什么是信道?
你必须首先连接到Rabbit MQ,才能消费或者发布消息。你在应用程序和Rabbit MQ代理服务器之间创建一条TCP连接。一旦TCP连接打开(你通过了认证), 应用程序就可以创建一条AMQP信道。信道是建立在真实的TCP连接内的虚拟连接。AMQP命令都是通过信道发送出去的。每条信道都会被指派一个唯一ID(AMQP库会帮你记住ID的)。不论是发布消息、订阅队列或是接收消息, 这些动作都是通过信道完成的。你也许会问为什么我们需要信道呢?为什么不直接通过TCP连接发送AMQP命令呢? 主要原因是对于操作系统来说建立和销毁TCP会话是非常昂贵的开销。假设应用程序从队列消费信息,并根据服务需求合理调度线程。假设你只进行TCP连接, 那么每个线程都需要自行连接到RabbitMQ。也就是说高峰期由每秒成百上千的连接。这不仅仅造成TCP连接的巨大浪费,而且操作系统每秒也就只能建立这点数量的连接。因此,你可能很快就碰到性能瓶颈了。如果我们为所有线程只使用一条TCP连接以满足性能方面的要求,但又能确保每个线程的私密性,就像拥有独立连接一样的话,那不是非常完美?这就是要引入信道概念的原因。线程启动后, 会在现成的连接上创建一条信道, 也获得了连接到RabbitMQ的私密通信路径, 而不会给操作系统的TCP栈造成额外负担。因此可以每秒成百上千地创建信道而不影响操作系统。在一条TCP连接上创建多少信道是没有限制地。可以把信道想象称一束光纤电缆就可以了。
每条电缆地光纤束都可以传输(就像一条信道)。一条电缆有絮叨光纤束,允许所有连接地线程通过多条光纤束同时进行传输和接收。TCP连接就像电缆, 而AMQP信道就像一条条独立光纤束。
我们来举个例子吧。假设你正在编写一个服务来跟踪代客泊车。所有人都和RabbitMQ进行交流。服务必须要完成两个任务:
1)存储代客票ID以及对应的车辆停放泊车位。
2)返回指定代客票ID对应得泊车位。
就第一个任务而言, 你提供的服务将扮演消费者角色。它订阅了RabbitMQ队列,等待“存放票”消息。该消息包含票ID和泊车位号码。对于第二个任务,你提供的服务即是消费者也是生产者。它需要接收消息来获取特定代客票ID,然后它需要发布一个包含对应泊车位号码的应答消息。
为了实现第二个任务,应用程序要扮演生产者的角色。一旦建立到RabbitMQ代理服务器的连接,应用程序将创建多条信道:chan_recv信道用于服务接收消息的线程, chan_sendX(X就是线程号)信道用于服务每一个应答线程。你使用chan_recv信道设置了队列的订阅, 用来接收包含“票查询”请求消息。当应用程序通过chan_recv信道收到一条查询消息时, 它检查消息中包含的票ID。一旦确认了对应的泊车位号, 应用程序将创建线程发送应答。然后新的应答线程创建包含泊车位号的消息。最终,新线程为应答消息设置标签并通过chan_sendX信道将其发送给RabbitMQ。如果只有一条信道, 新应答线程将无法分享TCP连接。你有两个选择。其一, 每个线程启用一个连接。这意味着你的应用程序在响应当前请求前无法处理新的票查询请求。其二,为每个发送线程都分配TCP连接,这样会浪费TCP资源。使用多个信道, 线程可以同时共享连接。这意味着对请求应答不会阻塞消费新的请求,而且不会浪费TCP连接。有时,你可能会选择仅使用一条信道, 但是有了AMQP,你可以灵活地使用多个信道来满足应用程序地需求,而不会有众多TCP连接地开销。
重要地是记住消费者和生产者是消息接收和消息发送概念地体现。而非客户端和服务器端。从总体来说, 消息通信, 特别是AMQP,可以被当作加强版地传输层。使用信道, 你能根据应用需要, 尽可能地创建并行地传输层, 而不会被TCP连接约束所限制。当你理解了这些概念,就能把RabbitMQ看作软件地路由器了。