转自ios
http://cmsblogs.com/?p=3846git
在IM这种讲究高并发、高消息吞吐的互联网场景下,MQ消息中间件是个很重要的基础设施,它在IM系统的服务端架构中担当消息中转、消息削峰、消息交换异步化等角色。程序员
固然,MQ消息中间件的做用远不止于此,它的价值不只仅存在于技术上,更重要的是改变了以往同步处理消息的思路(好比进行IM消息历史存储时,传统的信息系统做法多是收到一条消息就立刻同步存入数据库,这种做法在小并发量的状况下能够很好的工做,但互联网大并发环境下就是灾难)。数据库
MQ消息中间件能够理解为一个水池,水池的这头是消息生产者,水池的那头是消息消费者,生产者和消息者无需直接对接,这将带来不少好处:业务解耦、架构分布式化等,生产者和消费者互相彻底透明。编程
但市面上的MQ消息中间件产品不少,做为IM系统中必不可少的一环,咱们该如何选型?缓存
消息队列中间件(简称消息中间件)是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通讯来进行分布式系统的集成。安全
经过提供消息传递和消息排队模型,它能够在分布式环境下提供应用解耦、弹性伸缩、冗余存储、流量削峰、异步通讯、数据同步等等功能,其做为分布式系统架构中的一个重要组件,有着举足轻重的地位。性能优化
目前开源的消息中间件可谓是琳琅满目,能让你们耳熟能详的就有不少,好比ActiveMQ、RabbitMQ、Kafka、RocketMQ、ZeroMQ等,无论选择其中的哪一款,都会有用的不趁手的地方,毕竟不是为你量身定制的。服务器
可能有些大厂在长期的使用过程当中积累了必定的经验,加上其消息队列的使用场景也相对稳定固化,或者目前市面上的消息中间件没法知足自身需求,同时它也具有足够的精力和人力而选择自研来为本身量身打造一款消息中间件。网络
可是绝大多数公司仍是不会选择重复造轮子,那么选择一款合适本身的消息中间件显得尤其重要。就算是前者,那么在自研出稳定且可靠的相关产品以前也会经历这样一个选型过程。
在总体架构中引入消息中间件,势必要考虑不少因素,好比成本及收益问题,怎么样才能达到最优的性价比?
虽然消息中间件种类繁多,可是各自都有各自的侧重点,选择合适本身、扬长避短无疑是最好的方式。若是你对此感到无所适从,本文或许能够参考一二。
ActiveMQ
Apache出品的、采用Java语言编写的彻底基于JMS1.1规范的面向消息的中间件,为应用程序提供高效的、可扩展的、稳定的和安全的企业级消息通讯。
不过因为历史缘由包袱过重,目前市场份额没有后面三种消息中间件多,其最新架构被命名为Apollo,号称下一代ActiveMQ,有兴趣的同窗可自行了解。
RabbitMQ
采用Erlang语言实现的AMQP协议的消息中间件,最初起源于金融系统,用于在分布式系统中存储转发消息。RabbitMQ发展到今天,被愈来愈多的人承认,这和它在可靠性、可用性、扩展性、功能丰富等方面的卓越表现是分不开的。
Kafka
起初是由LinkedIn公司采用Scala语言开发的一个分布式、多分区、多副本且基于ZooKeeper协调的分布式消息系统,现已捐献给Apache基金会。
它是一种高吞吐量的分布式发布订阅消息系统,以可水平扩展和高吞吐率而被普遍使用。目前愈来愈多的开源分布式处理系统如Cloudera、Apache Storm、Spark、Flink等都支持与Kafka集成。
RocketMQ
是阿里开源的消息中间件,目前已经捐献给Apache基金会,它是由Java语言开发的,具有高吞吐量、高可用性、适合大规模分布式系统应用等特色,经历过双11的洗礼,实力不容小觑。
ZeroMQ
号称史上最快的消息队列,基于C语言开发。ZeroMQ是一个消息处理队列库,可在多线程、多内核和主机之间弹性伸缩,虽然大多数时候咱们习惯将其纳入消息队列家族之中,可是其和前面的几款有着本质的区别,ZeroMQ自己就不是一个消息队列服务器,更像是一组底层网络通信库,对原有的Socket API上加上一层封装而已。
目前市面上的消息中间件还有不少,好比腾讯系的PhxQueue、CMQ、CKafka,又好比基于Go语言的NSQ,有时人们也把相似Redis的产品也看作消息中间件的一种。
固然,它们都很优秀,可是本文篇幅限制没法穷其全部,下面会针对性地挑选RabbitMQ和Kafka两款典型的消息中间件来作分析,力求站在一个公平公正的立场来阐述消息中间件选型中的各个要点。
衡量一款消息中间件是否符合需求,须要从多个维度进行考察。
首要的就是功能维度,这个直接决定了你可否最大程度上地实现开箱即用,进而缩短项目周期、下降成本等。
若是一款消息中间件的功能达不到想要的功能,那么就须要进行二次开发,这样会增长项目的技术难度、复杂度以及增大项目周期等。
一、功能维度
功能维度又能够划分个多个子维度,大体能够分为如下这些。
优先级队列:
优先级队列不一样于先进先出队列,优先级高的消息具有优先被消费的特权,这样能够为下游提供不一样消息级别的保证。
不过这个优先级也是须要有一个前提的:若是消费者的消费速度大于生产者的速度,而且消息中间件服务器(通常简单的称之为Broker)中没有消息堆积,那么对于发送的消息设置优先级也就没有什么实质性的意义了,由于生产者刚发送完一条消息就被消费者消费了,那么就至关于Broker中至多只有一条消息,对于单条消息来讲优先级是没有什么意义的。
延迟队列:
当你在网上购物的时候是否会遇到这样的提示:“三十分钟以内未付款,订单自动取消”,这个是延迟队列的一种典型应用场景。
延迟队列存储的是对应的延迟消息,所谓“延迟消息”是指当消息被发送之后,并不想让消费者马上拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
延迟队列通常分为两种:基于消息的延迟和基于队列的延迟。
死信队列:
因为某些缘由消息没法被正确投递,为了确保消息不会被无端丢弃,通常将其置于一个特殊角色的队列,这个队列称为死信队列。
与此对应的还有一个“回退队列”的概念,试想若是消费者在消费时发生了异常,那么就不会对这一次消费进行确认(Ack), 进而发生回滚消息的操做以后消息始终会放在队列的顶部,而后不断被处理和回滚,致使队列陷入死循环。
为了解决这个问题,能够为每一个队列设置一个回退队列,它和死信队列都是为异常的处理提供的一种机制保障。实际状况下,回退队列的角色能够由死信队列和重试队列来扮演。
重试队列:
其实能够当作是一种回退队列,具体指消费端消费消息失败时,为防止消息无端丢失而从新将消息回滚到Broker中。
与回退队列不一样的是重试队列通常分红多个重试等级,每一个重试等级通常也会设置从新投递延时,重试次数越多投递延时就越大。
举个例子:消息第一次消费失败入重试队列Q1,Q1的从新投递延迟为5s,在5s事后从新投递该消息;若是消息再次消费失败则入重试队列Q2,Q2的从新投递延迟为10s,在10s事后再次投递该消息。以此类推,重试越屡次从新投递的时间就越久,为此须要设置一个上限,超过投递次数就入死信队列。
重试队列与延迟队列有相同的地方,都是须要设置延迟级别,它们彼此的区别是:延迟队列动做由内部触发,重试队列动做由外部消费端触发;延迟队列做用一次,而重试队列的做用范围会向后传递。
消费模式:
消费模式分为推(push)模式和拉(pull)模式:
广播消费:
消息通常有两种传递模式——点对点(P2P,Point-to-Point)模式和发布/订阅(Pub/Sub)模式:
RabbitMQ是一种典型的点对点模式,而Kafka是一种典型的发布订阅模式。可是RabbitMQ中能够经过设置交换器类型来实现发布订阅模式而达到广播消费的效果,Kafka中也能以点对点的形式消费,你彻底能够把其消费组(Consumer Group)的概念当作是队列的概念。不过对比来讲,Kafka中由于有了消息回溯功能的存在,对于广播消费的力度支持比RabbitMQ的要强。
消息回溯:
通常消息在消费完成以后就被处理了,以后不再能消费到该条消息。消息回溯正好相反,是指消息在消费完成以后,还能消费到以前被消费掉的消息。
对于消息而言,常常面临的问题是“消息丢失”,至因而真正因为消息中间件的缺陷丢失仍是因为使用方的误用而丢失,通常很难追查。若是消息中间件自己具有消息回溯功能的话,能够经过回溯消费复现“丢失的”消息进而查出问题的源头所在。
消息回溯的做用远不止与此,好比还有索引恢复、本地缓存重建,有些业务补偿方案也能够采用回溯的方式来实现。
消息堆积+持久化:
流量削峰是消息中间件的一个很是重要的功能,而这个功能其实得益于其消息堆积能力。从某种意义上来说,若是一个消息中间件不具有消息堆积的能力,那么就不能把它看作是一个合格的消息中间件。
消息堆积份内存式堆积和磁盘式堆积:
通常来讲,磁盘的容量会比内存的容量要大得多,对于磁盘式的堆积其堆积能力就是整个磁盘的大小。从另一个角度讲,消息堆积也为消息中间件提供了冗余存储的功能。援引《纽约时报》的案例,其直接将Kafka用做存储系统。
消息追踪:
对于分布式架构系统中的链路追踪(Trace),你们必定不陌生。对于消息中间件,消息的链路追踪(如下简称消息追踪)一样重要,最通俗来理解,就是要知道消息从哪来,存在哪里以及发往哪里去。基于此功能,咱们能够对发送或者消费完的消息进行链路追踪服务,进而能够进行问题的快速定位与排查。
消息过滤:
消息过滤是指按照既定的过滤规则为下游用户提供指定类别的消息。
就以Kafka而言,彻底能够将不一样类别的消息发送至不一样的Topic中,由此能够实现某种意义的消息过滤,或者Kafka还能够根据分区对同一个Topic中的消息进行分类。
不过,更加严格意义上的消息过滤,应该是对既定的消息采起必定的方式按照必定的过滤规则进行过滤。
一样以Kafka为例,能够经过客户端提供的Consumer Interceptor接口或者Kafka Stream的Filter功能进行消息过滤。
多租户:
也能够称为多重租赁技术,是一种软件架构技术,主要用来实现多用户的环境下公用相同的系统或程序组件,而且仍能够确保各用户间数据的隔离性。
RabbitMQ就可以支持多租户技术,每个租户表示为一个VHost,其本质上是一个独立的小型RabbitMQ服务器,又有本身独立的队列、交换器及绑定关系等,而且它拥有本身独立的权限。
VHost就像是物理机中的虚拟机同样,它们在各个实例间提供逻辑上的分离,为不一样程序安全保密地容许数据,它既能将同一个RabbitMQ中的众多客户区分开,又能够避免队列和交换器等命名冲突。
多协议支持:
消息是信息的载体,为了让生产者和消费者都能理解所承载的信息(生产者须要知道如何构造消息,消费者须要知道如何解析消息),它们就须要按照一种统一的格式描述消息,这种统一的格式称之为消息协议。
有效的消息必定具备某种格式,而没有格式的消息是没有意义的。
通常消息层面的协议有AMQP、MQTT、STOMP、XMPP等(消息领域中的JMS更多的是一个规范而不是一个协议),支持的协议越多其应用范围就会越广,通用性越强,好比RabbitMQ可以支持MQTT协议就让其在物联网应用中得到一席之地。还有的消息中间件是基于其自己的私有协议运转的,典型的如Kafka。
跨语言支持:
对不少公司而言,其技术栈体系中会有多种编程语言,如C/C++、JAVA、Go、PHP等,消息中间件自己具有应用解耦的特性,若是可以进一步的支持多客户端语言,那么就能够将此特性的效能扩大。跨语言的支持力度也能够从侧面反映出一个消息中间件的流行程度。
流量控制:
针对的是发送方和接收方速度不匹配的问题,提供一种速度匹配服务抑制发送速率使接收方应用程序的读取速率与之相适应。一般的流控方法有Stop-and-Wait、滑动窗口以及令牌桶等。
消息顺序性:
顾名思义,是指保证消息有序。这个功能有个很常见的应用场景就是CDC(Change Data Chapture),以MySQL为例,若是其传输的Binlog的顺序出错,好比本来是先对一条数据加 1,而后再乘以2,发送错序以后就变成了先乘以2后加1,形成数据不一致。
安全机制:
在Kafka0.9版本以后就开始增长了身份认证和权限控制两种安全机制:
对于RabbitMQ而言,其一样提供身份认证(TLS/SSL、SASL)和权限控制(读写操做)的安全机制。
消息幂等性:
确保消息在生产者和消费者之间进行传输,通常有三种传输保障(Delivery Guarantee):
对于大多数消息中间件而言,通常只提供At most once和At least once两种传输保障,对于第三种通常很难作到,由此消息幂等性也很难保证。
Kafka自0.11版本开始引入了幂等性和事务,Kafka的幂等性是指单个生产者对于单分区单会话的幂等,而事务能够保证原子性地写入到多个分区,即写入到多个分区的消息要么所有成功,要么所有回滚,这两个功能加起来可让Kafka具有EOS(Exactly Once Semantic)的能力。
不过若是要考虑全局的幂等,还须要与从上下游方面综合考虑,即关联业务层面,幂等处理自己也是业务层面所须要考虑的重要议题。
如下游消费者层面为例,有可能消费者消费完一条消息以后没有来得及确认消息就发生异常,等到恢复以后又得从新消费原来消费过的那条消息,那么这种类型的消息幂等是没法有消息中间件层面来保证的。若是要保证全局的幂等,须要引入更多的外部资源来保证,好比以订单号做为惟一性标识,而且在下游设置一个去重表。
事务性消息:
事务自己是一个并不陌生的词汇,事务是由事务开始(Begin Transaction)和事务结束(End Transaction)之间执行的全体操做组成。
支持事务的消息中间件并不在少数,Kafka和RabbitMQ都支持,不过此二者的事务是指生产者发生消息的事务,要么发送成功,要么发送失败。消息中间件能够做为用来实现分布式事务的一种手段,但其自己并不提供全局分布式事务的功能。
下表是对Kafka与RabbitMQ功能的总结性对比及补充说明:
二、性能
功能维度是消息中间件选型中的一个重要的参考维度,但这并非惟一的维度,有时候性能比功能还要重要,何况性能和功能不少时候是相悖的,鱼和熊掌不可兼得。
Kafka在开启幂等、事务功能的时候会使其性能下降;RabbitMQ在开启rabbitmq_tracing插件的时候也会极大影响其性能。
性能指什么?
消息中间件的性能通常是指其吞吐量。虽然从功能维度上来讲,RabbitMQ的优点要大于Kafka,可是Kafka的吞吐量要比RabbitMQ高出1至2个数量级,通常RabbitMQ的单机QPS在万级别以内,而Kafka的单机QPS能够维持在十万级别,甚至能够达到百万级。
拓:消息中间件的吞吐量始终会受到硬件层面的限制。就以网卡带宽为例,若是单机单网卡的带宽为1Gbps,若是要达到百万级的吞吐,那么消息体大小不得超过(1Gb/8)/100W,即约等于134B,换句话说若是消息体大小超过134B,那么就不可能达到百万级别的吞吐。这种计算方式一样能够适用于内存和磁盘。
性能的指标是什么?
时延做为性能维度的一个重要指标,却每每在消息中间件领域所被忽视,由于通常使用消息中间件的场景对时效性的要求并非很高,若是要求时效性彻底能够采用RPC的方式实现。
消息中间件具有消息堆积的能力,消息堆积越大也就意味着端到端的时延也就越长,与此同时延时队列也是某些消息中间件的一大特点。
那么为何还要关注消息中间件的时延问题呢?
消息中间件可以解耦系统,对于一个时延较低的消息中间件而言,它可让上游生产者发送消息以后能够迅速的返回,也可让消费者更加快速的获取到消息,在没有堆积的状况下,可让总体上下游的应用之间的级联动做更加高效,虽然不建议在时效性很高的场景下使用消息中间件,可是若是所使用的消息中间件的时延方面比较优秀,那么对于总体系统的性能将会是一个不小的提高。
三、可靠性+可用性
消息丢失是使用消息中间件时所不得不面对的一个同点,其背后消息可靠性也是衡量消息中间件好坏的一个关键因素。尤为是在金融支付领域,消息可靠性尤其重要。
然而说到可靠性必然要说到可用性,注意这二者之间的区别:
从狭义的角度来讲,分布式系统架构是一致性协议理论的应用实现,对于消息可靠性和可用性而言也能够追溯到消息中间件背后的一致性协议:
多副本能够保证在Master节点宕机异常以后能够提高Slave做为新的Master而继续提供服务来保障可用性。
Kafka设计之初是为日志处理而生,给人们留下了数据可靠性要求不高的不良印象,可是随着版本的升级优化,其可靠性获得极大的加强,详细能够参考KIP101。
就目前而言,在金融支付领域使用RabbitMQ居多,而在日志处理、大数据等方面Kafka使用居多,随着RabbitMQ性能的不断提高和Kafka可靠性的进一步加强,相信彼此都能在之前不擅长的领域分得一杯羹。
同步刷盘是加强一个组件可靠性的有效方式,消息中间件也不例外,Kafka和RabbitMQ均可以支持同步刷盘,可是笔者对同步刷盘有必定的疑问:绝大多数情景下,一个组件的可靠性不该该由同步刷盘这种极其损耗性能的操做来保障,而是采用多副本的机制来保证。
这里还要说起的一个方面是扩展能力,这里我狭隘地将此概括到可用性这一维度,消息中间件的扩展能力可以加强其用可用能力及范围,好比前面提到的RabbitMQ支持多种消息协议,这个就是基于其插件化的扩展实现。
还有从集群部署上来说,归功于Kafka的水平扩展能力,其基本上能够达到线性容量提高的水平,在LinkedIn实践介绍中就说起了有部署超过千台设备的Kafka集群。
四、运维管理
在消息中间件的使用过程当中不免会出现各式各样的异常状况,有客户端的,也有服务端的,那么怎样及时有效的进行监测及修复?业务线流量有峰值又低谷,尤为是电商领域,那么怎样前进行有效的容量评估,尤为是大促期间?脚踢电源、网线被挖等事件层出不穷,如何有效的作好异地多活?
这些都离不开消息中间件的衍生产品——运维管理。
运维管理也能够进行进一步的细分,好比申请、审核、监控、告警、管理、容灾、部署等。
申请、审核 很好理解,在源头对资源进行管控,既能够进行有效校订应用方的使用规范,配合监控也能够作好流量统计与流量评估工做。通常申请、审核与公司内部系统交融性较大,不适合使用开源类的产品。
监控、告警 也比较好理解,对消息中间件的使用进行全方位的监控,既能够为系统提供基准数据,也能够在检测到异常的状况配合告警,以便运维、开发人员的迅速介入。除了通常的监控项(好比硬件、GC等)以外,消息中间件还须要关注端到端时延、消息审计、消息堆积等方面:
无论是扩容、降级、版本升级、集群节点部署、仍是故障处理都离不开管理工具的应用,一个配套完备的管理工具集能够在遇到变动时作到事半功倍。
故障可大可小,通常是一些应用异常,也能够是机器掉电、网络异常、磁盘损坏等单机故障,这些故障单机房内的多副本足以应付。
若是是机房故障就要涉及异地容灾了,关键点在于如何有效的进行数据复制,Kafka能够参考MirrorMarker、uReplicator等产品,而RabbitMQ能够参考Federation和Shovel。
五、社区力度及生态发展
对于目前流行的编程语言而言,如Java、Python,若是你在使用过程当中遇到了一些异常,基本上能够经过搜索引擎的帮助来获得解决,由于一个产品用的人越多,踩过的坑也就越多,对应的解决方案也就越多。
消息中间件也一样适用,若是你选择了一种“生僻”的消息中间件,可能在某些方面运用的驾轻就熟,可是版本更新缓慢、遇到棘手问题也难以获得社区的支持而越陷越深;相反若是你选择了一种“流行”的消息中间件,其更新力度大,不只能够迅速的弥补以前的不足,并且也能顺应技术的快速发展来变动一些新的功能,这样可让你以“站在巨人的肩膀上”。
在运维管理维度咱们说起了Kafka和RabbitMQ都有一系列开源的监控管理产品,这些正是得益于其社区及生态的迅猛发展。
选型误区
在进行消息中间件选型以前能够先问本身一个问题:是否真的须要一个消息中间件?
在搞清楚这个问题以后,还能够继续问本身一个问题:是否须要本身维护一套消息中间件?不少初创型公司为了节省成本会选择直接购买消息中间件有关的云服务,本身只须要关注收发消息便可,其他的均可之外包出去。
不少人面对消息中间件有一种自研的冲动,你彻底能够对Java中的ArrayBlockingQueue作一个简单的封装,你也能够基于文件、数据库、Redis等底层存储封装而造成一个消息中间件。
消息中间件作为一个基础组件并无想象中的那么简单,其背后还须要配套的管理运维整个生态的产品集。自研还有会交接问题,若是文档不齐全、运做不规范将会带给新人噩梦般的体验。
是否真的有自研的必要?
若是不是KPI的压迫能够先考虑下面这两个问题:
不少人在作消息中间件选型时会参考网络上的不少对比类的文章,可是其专业性、严谨性、以及其政治立场问题都有待考证,须要带着怀疑的态度去审视这些文章。好比有些文章会在没有任何限定条件及场景的状况下直接定义某款消息中间件最好;还有些文章没有指明消息中间件版本及测试环境就来作功能和性能对比分析,诸如此类的文章均可以唾弃之。
消息中间件犹如小马过河,选择合适的才最重要。这须要贴合自身的业务需求,技术服务于业务,大致上能够根据上一节所说起的功能、性能等6个维度来一一进行筛选。更深层次的抉择在于你可否掌握其魂。
笔者鄙见:RabbitMQ在于Routing,而Kafka在于Streaming,了解其根本对于本身可以对症下药选择到合适的消息中间件尤其重要。
消息中间件选型切忌一味的追求性能或者功能,性能能够优化,功能能够二次开发。若是要在功能和性能方面作一个抉择的话,那么首选性能,由于整体上来讲性能优化的空间没有功能扩展的空间大。然而看长期发展,生态又比性能以及功能都要重要。
可靠性误区
不少时候,可靠性方面也容易存在一个误区:想要找到一个产品来保证消息的绝对可靠,很不幸的是这世界上没有绝对的东西,只能说尽可能趋于完美。想要尽量的保障消息的可靠性也并不是单单只靠消息中间件自己,还要依赖于上下游,须要从生产端、服务端和消费端这3个维度去努力保证。
消息中间件选型还有一个考量标准就是尽可能贴合团队自身的技术栈体系,虽说没有蹩脚的消息中间件,只有蹩脚的程序员,可是让一个C栈的团队去深挖PhxQueue总比去深挖Scala编写的Kafka要容易的多。
消息中间件大道至简:一发一存一消费,没有最好的消息中间件,只有最合适的消息中间件。
在IM这种讲究高并发、高消息吞吐的互联网场景下,MQ消息中间件是个很重要的基础设施,它在IM系统的服务端架构中担当消息中转、消息削峰、消息交换异步化等角色。
固然,MQ消息中间件的做用远不止于此,它的价值不只仅存在于技术上,更重要的是改变了以往同步处理消息的思路(好比进行IM消息历史存储时,传统的信息系统做法多是收到一条消息就立刻同步存入数据库,这种做法在小并发量的状况下能够很好的工做,但互联网大并发环境下就是灾难)。
MQ消息中间件能够理解为一个水池,水池的这头是消息生产者,水池的那头是消息消费者,生产者和消息者无需直接对接,这将带来不少好处:业务解耦、架构分布式化等,生产者和消费者互相彻底透明。
但市面上的MQ消息中间件产品不少,做为IM系统中必不可少的一环,咱们该如何选型?
消息队列中间件(简称消息中间件)是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通讯来进行分布式系统的集成。
经过提供消息传递和消息排队模型,它能够在分布式环境下提供应用解耦、弹性伸缩、冗余存储、流量削峰、异步通讯、数据同步等等功能,其做为分布式系统架构中的一个重要组件,有着举足轻重的地位。
目前开源的消息中间件可谓是琳琅满目,能让你们耳熟能详的就有不少,好比ActiveMQ、RabbitMQ、Kafka、RocketMQ、ZeroMQ等,无论选择其中的哪一款,都会有用的不趁手的地方,毕竟不是为你量身定制的。
可能有些大厂在长期的使用过程当中积累了必定的经验,加上其消息队列的使用场景也相对稳定固化,或者目前市面上的消息中间件没法知足自身需求,同时它也具有足够的精力和人力而选择自研来为本身量身打造一款消息中间件。
可是绝大多数公司仍是不会选择重复造轮子,那么选择一款合适本身的消息中间件显得尤其重要。就算是前者,那么在自研出稳定且可靠的相关产品以前也会经历这样一个选型过程。
在总体架构中引入消息中间件,势必要考虑不少因素,好比成本及收益问题,怎么样才能达到最优的性价比?
虽然消息中间件种类繁多,可是各自都有各自的侧重点,选择合适本身、扬长避短无疑是最好的方式。若是你对此感到无所适从,本文或许能够参考一二。
ActiveMQ
Apache出品的、采用Java语言编写的彻底基于JMS1.1规范的面向消息的中间件,为应用程序提供高效的、可扩展的、稳定的和安全的企业级消息通讯。
不过因为历史缘由包袱过重,目前市场份额没有后面三种消息中间件多,其最新架构被命名为Apollo,号称下一代ActiveMQ,有兴趣的同窗可自行了解。
RabbitMQ
采用Erlang语言实现的AMQP协议的消息中间件,最初起源于金融系统,用于在分布式系统中存储转发消息。RabbitMQ发展到今天,被愈来愈多的人承认,这和它在可靠性、可用性、扩展性、功能丰富等方面的卓越表现是分不开的。
Kafka
起初是由LinkedIn公司采用Scala语言开发的一个分布式、多分区、多副本且基于ZooKeeper协调的分布式消息系统,现已捐献给Apache基金会。
它是一种高吞吐量的分布式发布订阅消息系统,以可水平扩展和高吞吐率而被普遍使用。目前愈来愈多的开源分布式处理系统如Cloudera、Apache Storm、Spark、Flink等都支持与Kafka集成。
RocketMQ
是阿里开源的消息中间件,目前已经捐献给Apache基金会,它是由Java语言开发的,具有高吞吐量、高可用性、适合大规模分布式系统应用等特色,经历过双11的洗礼,实力不容小觑。
ZeroMQ
号称史上最快的消息队列,基于C语言开发。ZeroMQ是一个消息处理队列库,可在多线程、多内核和主机之间弹性伸缩,虽然大多数时候咱们习惯将其纳入消息队列家族之中,可是其和前面的几款有着本质的区别,ZeroMQ自己就不是一个消息队列服务器,更像是一组底层网络通信库,对原有的Socket API上加上一层封装而已。
目前市面上的消息中间件还有不少,好比腾讯系的PhxQueue、CMQ、CKafka,又好比基于Go语言的NSQ,有时人们也把相似Redis的产品也看作消息中间件的一种。
固然,它们都很优秀,可是本文篇幅限制没法穷其全部,下面会针对性地挑选RabbitMQ和Kafka两款典型的消息中间件来作分析,力求站在一个公平公正的立场来阐述消息中间件选型中的各个要点。
衡量一款消息中间件是否符合需求,须要从多个维度进行考察。
首要的就是功能维度,这个直接决定了你可否最大程度上地实现开箱即用,进而缩短项目周期、下降成本等。
若是一款消息中间件的功能达不到想要的功能,那么就须要进行二次开发,这样会增长项目的技术难度、复杂度以及增大项目周期等。
一、功能维度
功能维度又能够划分个多个子维度,大体能够分为如下这些。
优先级队列:
优先级队列不一样于先进先出队列,优先级高的消息具有优先被消费的特权,这样能够为下游提供不一样消息级别的保证。
不过这个优先级也是须要有一个前提的:若是消费者的消费速度大于生产者的速度,而且消息中间件服务器(通常简单的称之为Broker)中没有消息堆积,那么对于发送的消息设置优先级也就没有什么实质性的意义了,由于生产者刚发送完一条消息就被消费者消费了,那么就至关于Broker中至多只有一条消息,对于单条消息来讲优先级是没有什么意义的。
延迟队列:
当你在网上购物的时候是否会遇到这样的提示:“三十分钟以内未付款,订单自动取消”,这个是延迟队列的一种典型应用场景。
延迟队列存储的是对应的延迟消息,所谓“延迟消息”是指当消息被发送之后,并不想让消费者马上拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
延迟队列通常分为两种:基于消息的延迟和基于队列的延迟。
死信队列:
因为某些缘由消息没法被正确投递,为了确保消息不会被无端丢弃,通常将其置于一个特殊角色的队列,这个队列称为死信队列。
与此对应的还有一个“回退队列”的概念,试想若是消费者在消费时发生了异常,那么就不会对这一次消费进行确认(Ack), 进而发生回滚消息的操做以后消息始终会放在队列的顶部,而后不断被处理和回滚,致使队列陷入死循环。
为了解决这个问题,能够为每一个队列设置一个回退队列,它和死信队列都是为异常的处理提供的一种机制保障。实际状况下,回退队列的角色能够由死信队列和重试队列来扮演。
重试队列:
其实能够当作是一种回退队列,具体指消费端消费消息失败时,为防止消息无端丢失而从新将消息回滚到Broker中。
与回退队列不一样的是重试队列通常分红多个重试等级,每一个重试等级通常也会设置从新投递延时,重试次数越多投递延时就越大。
举个例子:消息第一次消费失败入重试队列Q1,Q1的从新投递延迟为5s,在5s事后从新投递该消息;若是消息再次消费失败则入重试队列Q2,Q2的从新投递延迟为10s,在10s事后再次投递该消息。以此类推,重试越屡次从新投递的时间就越久,为此须要设置一个上限,超过投递次数就入死信队列。
重试队列与延迟队列有相同的地方,都是须要设置延迟级别,它们彼此的区别是:延迟队列动做由内部触发,重试队列动做由外部消费端触发;延迟队列做用一次,而重试队列的做用范围会向后传递。
消费模式:
消费模式分为推(push)模式和拉(pull)模式:
广播消费:
消息通常有两种传递模式——点对点(P2P,Point-to-Point)模式和发布/订阅(Pub/Sub)模式:
RabbitMQ是一种典型的点对点模式,而Kafka是一种典型的发布订阅模式。可是RabbitMQ中能够经过设置交换器类型来实现发布订阅模式而达到广播消费的效果,Kafka中也能以点对点的形式消费,你彻底能够把其消费组(Consumer Group)的概念当作是队列的概念。不过对比来讲,Kafka中由于有了消息回溯功能的存在,对于广播消费的力度支持比RabbitMQ的要强。
消息回溯:
通常消息在消费完成以后就被处理了,以后不再能消费到该条消息。消息回溯正好相反,是指消息在消费完成以后,还能消费到以前被消费掉的消息。
对于消息而言,常常面临的问题是“消息丢失”,至因而真正因为消息中间件的缺陷丢失仍是因为使用方的误用而丢失,通常很难追查。若是消息中间件自己具有消息回溯功能的话,能够经过回溯消费复现“丢失的”消息进而查出问题的源头所在。
消息回溯的做用远不止与此,好比还有索引恢复、本地缓存重建,有些业务补偿方案也能够采用回溯的方式来实现。
消息堆积+持久化:
流量削峰是消息中间件的一个很是重要的功能,而这个功能其实得益于其消息堆积能力。从某种意义上来说,若是一个消息中间件不具有消息堆积的能力,那么就不能把它看作是一个合格的消息中间件。
消息堆积份内存式堆积和磁盘式堆积:
通常来讲,磁盘的容量会比内存的容量要大得多,对于磁盘式的堆积其堆积能力就是整个磁盘的大小。从另一个角度讲,消息堆积也为消息中间件提供了冗余存储的功能。援引《纽约时报》的案例,其直接将Kafka用做存储系统。
消息追踪:
对于分布式架构系统中的链路追踪(Trace),你们必定不陌生。对于消息中间件,消息的链路追踪(如下简称消息追踪)一样重要,最通俗来理解,就是要知道消息从哪来,存在哪里以及发往哪里去。基于此功能,咱们能够对发送或者消费完的消息进行链路追踪服务,进而能够进行问题的快速定位与排查。
消息过滤:
消息过滤是指按照既定的过滤规则为下游用户提供指定类别的消息。
就以Kafka而言,彻底能够将不一样类别的消息发送至不一样的Topic中,由此能够实现某种意义的消息过滤,或者Kafka还能够根据分区对同一个Topic中的消息进行分类。
不过,更加严格意义上的消息过滤,应该是对既定的消息采起必定的方式按照必定的过滤规则进行过滤。
一样以Kafka为例,能够经过客户端提供的Consumer Interceptor接口或者Kafka Stream的Filter功能进行消息过滤。
多租户:
也能够称为多重租赁技术,是一种软件架构技术,主要用来实现多用户的环境下公用相同的系统或程序组件,而且仍能够确保各用户间数据的隔离性。
RabbitMQ就可以支持多租户技术,每个租户表示为一个VHost,其本质上是一个独立的小型RabbitMQ服务器,又有本身独立的队列、交换器及绑定关系等,而且它拥有本身独立的权限。
VHost就像是物理机中的虚拟机同样,它们在各个实例间提供逻辑上的分离,为不一样程序安全保密地容许数据,它既能将同一个RabbitMQ中的众多客户区分开,又能够避免队列和交换器等命名冲突。
多协议支持:
消息是信息的载体,为了让生产者和消费者都能理解所承载的信息(生产者须要知道如何构造消息,消费者须要知道如何解析消息),它们就须要按照一种统一的格式描述消息,这种统一的格式称之为消息协议。
有效的消息必定具备某种格式,而没有格式的消息是没有意义的。
通常消息层面的协议有AMQP、MQTT、STOMP、XMPP等(消息领域中的JMS更多的是一个规范而不是一个协议),支持的协议越多其应用范围就会越广,通用性越强,好比RabbitMQ可以支持MQTT协议就让其在物联网应用中得到一席之地。还有的消息中间件是基于其自己的私有协议运转的,典型的如Kafka。
跨语言支持:
对不少公司而言,其技术栈体系中会有多种编程语言,如C/C++、JAVA、Go、PHP等,消息中间件自己具有应用解耦的特性,若是可以进一步的支持多客户端语言,那么就能够将此特性的效能扩大。跨语言的支持力度也能够从侧面反映出一个消息中间件的流行程度。
流量控制:
针对的是发送方和接收方速度不匹配的问题,提供一种速度匹配服务抑制发送速率使接收方应用程序的读取速率与之相适应。一般的流控方法有Stop-and-Wait、滑动窗口以及令牌桶等。
消息顺序性:
顾名思义,是指保证消息有序。这个功能有个很常见的应用场景就是CDC(Change Data Chapture),以MySQL为例,若是其传输的Binlog的顺序出错,好比本来是先对一条数据加 1,而后再乘以2,发送错序以后就变成了先乘以2后加1,形成数据不一致。
安全机制:
在Kafka0.9版本以后就开始增长了身份认证和权限控制两种安全机制:
对于RabbitMQ而言,其一样提供身份认证(TLS/SSL、SASL)和权限控制(读写操做)的安全机制。
消息幂等性:
确保消息在生产者和消费者之间进行传输,通常有三种传输保障(Delivery Guarantee):
对于大多数消息中间件而言,通常只提供At most once和At least once两种传输保障,对于第三种通常很难作到,由此消息幂等性也很难保证。
Kafka自0.11版本开始引入了幂等性和事务,Kafka的幂等性是指单个生产者对于单分区单会话的幂等,而事务能够保证原子性地写入到多个分区,即写入到多个分区的消息要么所有成功,要么所有回滚,这两个功能加起来可让Kafka具有EOS(Exactly Once Semantic)的能力。
不过若是要考虑全局的幂等,还须要与从上下游方面综合考虑,即关联业务层面,幂等处理自己也是业务层面所须要考虑的重要议题。
如下游消费者层面为例,有可能消费者消费完一条消息以后没有来得及确认消息就发生异常,等到恢复以后又得从新消费原来消费过的那条消息,那么这种类型的消息幂等是没法有消息中间件层面来保证的。若是要保证全局的幂等,须要引入更多的外部资源来保证,好比以订单号做为惟一性标识,而且在下游设置一个去重表。
事务性消息:
事务自己是一个并不陌生的词汇,事务是由事务开始(Begin Transaction)和事务结束(End Transaction)之间执行的全体操做组成。
支持事务的消息中间件并不在少数,Kafka和RabbitMQ都支持,不过此二者的事务是指生产者发生消息的事务,要么发送成功,要么发送失败。消息中间件能够做为用来实现分布式事务的一种手段,但其自己并不提供全局分布式事务的功能。
下表是对Kafka与RabbitMQ功能的总结性对比及补充说明:
二、性能
功能维度是消息中间件选型中的一个重要的参考维度,但这并非惟一的维度,有时候性能比功能还要重要,何况性能和功能不少时候是相悖的,鱼和熊掌不可兼得。
Kafka在开启幂等、事务功能的时候会使其性能下降;RabbitMQ在开启rabbitmq_tracing插件的时候也会极大影响其性能。
性能指什么?
消息中间件的性能通常是指其吞吐量。虽然从功能维度上来讲,RabbitMQ的优点要大于Kafka,可是Kafka的吞吐量要比RabbitMQ高出1至2个数量级,通常RabbitMQ的单机QPS在万级别以内,而Kafka的单机QPS能够维持在十万级别,甚至能够达到百万级。
拓:消息中间件的吞吐量始终会受到硬件层面的限制。就以网卡带宽为例,若是单机单网卡的带宽为1Gbps,若是要达到百万级的吞吐,那么消息体大小不得超过(1Gb/8)/100W,即约等于134B,换句话说若是消息体大小超过134B,那么就不可能达到百万级别的吞吐。这种计算方式一样能够适用于内存和磁盘。
性能的指标是什么?
时延做为性能维度的一个重要指标,却每每在消息中间件领域所被忽视,由于通常使用消息中间件的场景对时效性的要求并非很高,若是要求时效性彻底能够采用RPC的方式实现。
消息中间件具有消息堆积的能力,消息堆积越大也就意味着端到端的时延也就越长,与此同时延时队列也是某些消息中间件的一大特点。
那么为何还要关注消息中间件的时延问题呢?
消息中间件可以解耦系统,对于一个时延较低的消息中间件而言,它可让上游生产者发送消息以后能够迅速的返回,也可让消费者更加快速的获取到消息,在没有堆积的状况下,可让总体上下游的应用之间的级联动做更加高效,虽然不建议在时效性很高的场景下使用消息中间件,可是若是所使用的消息中间件的时延方面比较优秀,那么对于总体系统的性能将会是一个不小的提高。
三、可靠性+可用性
消息丢失是使用消息中间件时所不得不面对的一个同点,其背后消息可靠性也是衡量消息中间件好坏的一个关键因素。尤为是在金融支付领域,消息可靠性尤其重要。
然而说到可靠性必然要说到可用性,注意这二者之间的区别:
从狭义的角度来讲,分布式系统架构是一致性协议理论的应用实现,对于消息可靠性和可用性而言也能够追溯到消息中间件背后的一致性协议:
多副本能够保证在Master节点宕机异常以后能够提高Slave做为新的Master而继续提供服务来保障可用性。
Kafka设计之初是为日志处理而生,给人们留下了数据可靠性要求不高的不良印象,可是随着版本的升级优化,其可靠性获得极大的加强,详细能够参考KIP101。
就目前而言,在金融支付领域使用RabbitMQ居多,而在日志处理、大数据等方面Kafka使用居多,随着RabbitMQ性能的不断提高和Kafka可靠性的进一步加强,相信彼此都能在之前不擅长的领域分得一杯羹。
同步刷盘是加强一个组件可靠性的有效方式,消息中间件也不例外,Kafka和RabbitMQ均可以支持同步刷盘,可是笔者对同步刷盘有必定的疑问:绝大多数情景下,一个组件的可靠性不该该由同步刷盘这种极其损耗性能的操做来保障,而是采用多副本的机制来保证。
这里还要说起的一个方面是扩展能力,这里我狭隘地将此概括到可用性这一维度,消息中间件的扩展能力可以加强其用可用能力及范围,好比前面提到的RabbitMQ支持多种消息协议,这个就是基于其插件化的扩展实现。
还有从集群部署上来说,归功于Kafka的水平扩展能力,其基本上能够达到线性容量提高的水平,在LinkedIn实践介绍中就说起了有部署超过千台设备的Kafka集群。
四、运维管理
在消息中间件的使用过程当中不免会出现各式各样的异常状况,有客户端的,也有服务端的,那么怎样及时有效的进行监测及修复?业务线流量有峰值又低谷,尤为是电商领域,那么怎样前进行有效的容量评估,尤为是大促期间?脚踢电源、网线被挖等事件层出不穷,如何有效的作好异地多活?
这些都离不开消息中间件的衍生产品——运维管理。
运维管理也能够进行进一步的细分,好比申请、审核、监控、告警、管理、容灾、部署等。
申请、审核 很好理解,在源头对资源进行管控,既能够进行有效校订应用方的使用规范,配合监控也能够作好流量统计与流量评估工做。通常申请、审核与公司内部系统交融性较大,不适合使用开源类的产品。
监控、告警 也比较好理解,对消息中间件的使用进行全方位的监控,既能够为系统提供基准数据,也能够在检测到异常的状况配合告警,以便运维、开发人员的迅速介入。除了通常的监控项(好比硬件、GC等)以外,消息中间件还须要关注端到端时延、消息审计、消息堆积等方面:
无论是扩容、降级、版本升级、集群节点部署、仍是故障处理都离不开管理工具的应用,一个配套完备的管理工具集能够在遇到变动时作到事半功倍。
故障可大可小,通常是一些应用异常,也能够是机器掉电、网络异常、磁盘损坏等单机故障,这些故障单机房内的多副本足以应付。
若是是机房故障就要涉及异地容灾了,关键点在于如何有效的进行数据复制,Kafka能够参考MirrorMarker、uReplicator等产品,而RabbitMQ能够参考Federation和Shovel。
五、社区力度及生态发展
对于目前流行的编程语言而言,如Java、Python,若是你在使用过程当中遇到了一些异常,基本上能够经过搜索引擎的帮助来获得解决,由于一个产品用的人越多,踩过的坑也就越多,对应的解决方案也就越多。
消息中间件也一样适用,若是你选择了一种“生僻”的消息中间件,可能在某些方面运用的驾轻就熟,可是版本更新缓慢、遇到棘手问题也难以获得社区的支持而越陷越深;相反若是你选择了一种“流行”的消息中间件,其更新力度大,不只能够迅速的弥补以前的不足,并且也能顺应技术的快速发展来变动一些新的功能,这样可让你以“站在巨人的肩膀上”。
在运维管理维度咱们说起了Kafka和RabbitMQ都有一系列开源的监控管理产品,这些正是得益于其社区及生态的迅猛发展。
选型误区
在进行消息中间件选型以前能够先问本身一个问题:是否真的须要一个消息中间件?
在搞清楚这个问题以后,还能够继续问本身一个问题:是否须要本身维护一套消息中间件?不少初创型公司为了节省成本会选择直接购买消息中间件有关的云服务,本身只须要关注收发消息便可,其他的均可之外包出去。
不少人面对消息中间件有一种自研的冲动,你彻底能够对Java中的ArrayBlockingQueue作一个简单的封装,你也能够基于文件、数据库、Redis等底层存储封装而造成一个消息中间件。
消息中间件作为一个基础组件并无想象中的那么简单,其背后还须要配套的管理运维整个生态的产品集。自研还有会交接问题,若是文档不齐全、运做不规范将会带给新人噩梦般的体验。
是否真的有自研的必要?
若是不是KPI的压迫能够先考虑下面这两个问题:
不少人在作消息中间件选型时会参考网络上的不少对比类的文章,可是其专业性、严谨性、以及其政治立场问题都有待考证,须要带着怀疑的态度去审视这些文章。好比有些文章会在没有任何限定条件及场景的状况下直接定义某款消息中间件最好;还有些文章没有指明消息中间件版本及测试环境就来作功能和性能对比分析,诸如此类的文章均可以唾弃之。
消息中间件犹如小马过河,选择合适的才最重要。这须要贴合自身的业务需求,技术服务于业务,大致上能够根据上一节所说起的功能、性能等6个维度来一一进行筛选。更深层次的抉择在于你可否掌握其魂。
笔者鄙见:RabbitMQ在于Routing,而Kafka在于Streaming,了解其根本对于本身可以对症下药选择到合适的消息中间件尤其重要。
消息中间件选型切忌一味的追求性能或者功能,性能能够优化,功能能够二次开发。若是要在功能和性能方面作一个抉择的话,那么首选性能,由于整体上来讲性能优化的空间没有功能扩展的空间大。然而看长期发展,生态又比性能以及功能都要重要。
可靠性误区
不少时候,可靠性方面也容易存在一个误区:想要找到一个产品来保证消息的绝对可靠,很不幸的是这世界上没有绝对的东西,只能说尽可能趋于完美。想要尽量的保障消息的可靠性也并不是单单只靠消息中间件自己,还要依赖于上下游,须要从生产端、服务端和消费端这3个维度去努力保证。
消息中间件选型还有一个考量标准就是尽可能贴合团队自身的技术栈体系,虽说没有蹩脚的消息中间件,只有蹩脚的程序员,可是让一个C栈的团队去深挖PhxQueue总比去深挖Scala编写的Kafka要容易的多。
消息中间件大道至简:一发一存一消费,没有最好的消息中间件,只有最合适的消息中间件。