引子

我们在设计实施基于数据请求的系统时,对于那些 实时性要求不高,但耗时、数据完整度、保证处理或者巨大量级 的一般都会采用 消息队列 1 系统。此外对于基于 发布者-订阅者* 2 或者 *数据总线 3 获取消息模式的,会更进一步使用 AMQP4协议,以及引入AMQP协议的服务端软件——RabbitMQ5

问题范围

笔者在这里仅针对 *RabbitMQ服务端软件系统本身可以做到的事情* 。例如客户端自身支持多个服务端节点轮询、在TCP/IP网络层面进行扩展甚至业务系统上进行横向以及纵向分层在本文中并不作详细介绍。

为什么要分布式

将系统软件设计成分布式一般出于以下几点需求

  • 提高系统容量(可处理的请求、数据数量)

  • 便于横向扩展

  • 提升系统性能(请求的响应时间、处理速度等指标)

  • 加强整体可用性,解决单点故障引起的系统瘫痪

  • 负载均衡(写入或读取)

RabbitMQ目前的稳定版本3.6分支已经具有满足以上需求的能力,笔者借工作之机总结一下解决业务问题的思路与实施方案。

问题描述

这里笔者设置一个情景来帮助说明后面的使用手法。现有业务在使用一台单节点的RabbitMQ服务端,均使用同一个exchange,不同的消息内容按照不同的routing\_key分开,均使用topic形式发送。消息的消费者自身进行队列的申明以及绑定(或者由管理者预先分配、设置好)。现有如下重要、影响服务端性能以及可用性的重大变化

  • 消息量本身 增大了N倍

  • 消息内容中 有一种消息的大小相当大 ;真正需要用到体积巨大的字段的消费者较少, 而大部分消费者不需要这个很大的字段内容

  • 重要和非重要业务的消费者混用

  • 开启ack确认的消费者 一旦处理速度拖慢,引起整个服务端宕机

问题分析

分层与分类

首先这个问题中有和AMQP协议以及RabbitMQ服务端机制有关的部分,也有一些业务使用上常见的通用部分。我们分开进行看待。

比如针对通用的方法:

  • 设计高可用方案,提供多个节点同时服务进行备份

  • 对消息的读写进行分离,视实际情景需求,按照写入、转发、读取多层提供访问服务

针对RabbitMQ本身的特性:

  • 由于ack机制或者durable的队列会在发生消费者拥堵时引起占用大量的服务端内存引起性能的雪崩效应6,应该对消费者进行分类

    • 区分重要与非重要的业务,分开节点提供访问

    • 对于重要的业务如确实使用ack确认机制,应该在消费者程序本身调优做好的情况下多拆开节点提供访问,降低服务端宕机对业务的风险

几种分布式的手法

官方文档:

官方文档:

适用于局域网延迟低,可靠性高的环境。多台RabbitMQ节点共同构成一个集群,所有节点和其他节点都有双向绑定关系。对于高可用策略的配置可以做到任何一个节点可以访问到任何exchange与queue。除了queue内容以外所有集群的信息均在集群内部进行复制,即可靠性和节点数成正比。

针对queue不复制的情况,还有镜像队列(Mirrored Queue)7可供使用;其设计是主从模式。

同时笔者 需要特别提及 的是,RabbitMQ的集群模式中的通信访问采用的是Erlang语言自身的分布式功能,即需要了解分布式Erlang8以及epmd9的知识。尤其是两台主机上的Erlang进行通信时需要确保Erlang Cookie是一致的10 11

Federation

官方文档

适用于广域网(局域网自然也可以用);可将多个节点进行“宽松”的结合,vhost与授权用户可以在连接上游时配置,不需要双向绑定,即上游可以不打开federation插件。同时,federation手法中,结合的exchange与queue的含义是不一样的

federated exchange

  • 从上游流向下游,采用的是publish形式

    • 可以从一个上游拉往多份,相当于 内容复制

    • 从多个上游拉到同一个下游节点,相当于 汇聚

  • 对于上游来说下游只是一个消费者

    • 上游不知晓下游的消费情况

/images/2016/0008-01-federated-exchange.png

federated queue

  • 下游来上游进行拉取消息,采用的是consume形式

    • 上游消费者来不及消费(即有堆积)时,下游才能收到消息

  • 如果故意设置成上游不消费,那么可以分散到多个下游去,类似与多个并发的worker

/images/2016/0008-02-federated-queue.png

Shovel

官方文档

笔者还未仔细研究和把玩过,就不多言了。

/images/2016/0008-03-shovel.png

手法的应用与问题的解决方案

笔者目前所处的阶段是需要一种短期可行(实际情况是一周),实施成本低,降低 由于业务上不重要的消费者接入同一个节点危害服务端整体可用性 以及 少部分消费者在共用的消息体中传输巨大的字段引起的流量问题 这两大风险进行改造。

方案如下:

使用Ferdration将现有的exchange接出来到一台新的节点上,将不重要的消费者都移到这个新节点上,出现问题或宕机将不影响主要节点。只需要修改消费者的配置信息。

通用消息去掉巨大的字段,再新发一个消息,采用不同的routing\_key;根据实际情况将其中一部分消费者改成新的routing\_key进行消费,降低服务端出站流量,同时降低网络压力,降低消息的延迟性。

长期的改造方案

  • 写入消息和提供消息读取的节点使用集群模式,提供高可用

  • 将写入消息和提供消息的节点使用Federation进行分层

  • 撰写一个消息中间件

    • 降低发送者的成本(是否有RabbitMQ客户端,保持连接、重连、心跳等等细节问题)

    • 基于运维和业务层面,更灵活的访问控制

    • 降低RabbitMQ服务端的连接数

  • 针对消息量的提升,大到一定量时首要瓶颈很可能是网络带宽、硬件资源;除了在业务端方面进行裁剪,可以考虑按内容拆分、压缩等等与RabbitMQ自身无关的方面入手。

真实案例中的数据

下面是消息队列所在主机的网络流量近一个月的变化趋势

/images/2016/0008-04-bandwidth.png

可以看到出站流量比入站要多很多倍,并且某一天开始急剧增加。这不仅说明原本的消息体内容和使用方式值得商榷,更需要对出现变化的那天进行审视。

参考资料

\_\_END\_\_