高并发下,如何让你的数据库再快一点?

334 阅读11分钟

消息队列(Message Queue)是一种使用高效可靠的数据传输机制来进行平台无关的数据通信的技术。消息队列拥有消息传递、消息生产、消息消费、优先级消息等功能,为我们的分布式系统提供了数据通信、功能解耦、弹性伸缩、数据冗余、限流削峰、异步消息等丰富能力,是分布式系统的一个重要组件。

当前开源的消息队列的组件种类繁多,在Github上搜索Message Queue,就有4K+的资源。如此众多的消息队列的开源项目中,我们耳熟能详的有 RabbitMQ、Kafka、RocketMQ、ActiveMQ、Pulsar等等,很多公司也根据业务需求,定制了自己的消息队列中间件,比如腾讯的CMQ等。这些消息队列组件各自都有各自的特性与侧重点,适合自己的才是最好的。

那么如何选择一款最合适的消息队列组件呢?本文选择了RabbitMQ以及Kafka这两款最为广泛使用的消息中间,来探讨一下如何综合考虑各种因素,比如消费模式、性能、语言支持、社区生态等来选择一款称手的消息队列组件。

概述

一、RabbitMQ

RabbitMQ是一个历史比较悠久的消息队列中间件,最早可以追溯到2007年,它是使用Erlang语言开发的一个AMQP(Advanced Message Queue Protocol 高级消息队列协议)实现。AMQP是一个应用层协议的开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。RabbitMQ最初起源于金融系统,它在可靠性、可用性、扩展性、消息持久化、高并发等方面的有着卓越的表现。

二、Kafka

Kafka最早由LinkedIn公司开发,它是一个使用Scala语言开发的支持多分区,多副本并且基于Zookeeper协调的分布式消息系统。它是一种高吞吐、低延迟、可容错的分布式发布订阅消息系统,凭借其可水平扩展的高吞吐率而被广泛使用。在大数据以及流式数据处理方面,Kafka的周边生态也是其一大优势,越来越多的开源分布式处理系统如 Cloudera、Apache Storm、Spark、Flink等都支持与 Kafka 集成。

对比

一、消费模式

消费模式是指消息队列消费消息是时候的策略,分为两种,一种是Push模式,一种是Pull模式。

Push模式是指消息队列的服务器端收到新的消息时主动将消息推送(Push)到消费端,这种消费模式相比如Pull模式会有更好的实时性,但是当服务器端消息较多时,可能出现消费端来不及消费消息从而压垮消费端的情况,需要一定的策略来避免这种情况的发生。

Pull模式是指消息队列的服务器不主动将消息推送给消费端,而是有消费端主动地去从服务器拉取(Pull)消息,一般都是定时拉取或者定量拉取的方式。Pull模式相比与Push模式实时性会差一些,其优势在于消费端可以根据自身消费消息的能力去拉取消息,不会出现消息消费不过来的情况。

RabbitMQ既支持Push模式同时也支持Pull模式,而Kafka仅支持Pull模式。

二、消息持久化

消息持久化是指将消息队列中的消息保存至磁盘中,以防止在发生异常或者服务器宕机等突发情况时发生数据丢失的情况。消息持久化是保证消息队列消息可靠性的关键技术之一。

RabbitMQ在默认情况下是不开启持久化操作,exchange、queue、message等数据都是存储在内存中的,这意味着如果 RabbitMQ 重启、关闭、宕机时所有的信息都将丢失。我们可以在使用RabbitMQ的时候显式地将exchange、queue、message 等这些数据对象声明为持久化,这样一来,即便服务器宕机或者故障了,我们也可以将这些数据从硬盘中进行恢复。不过需要注意的是,如果RabbitMQ将所有的这些对象都进行持久化操作,会严重地影响RabbitMQ的性能,因为同步写入磁盘的速度会比写内存慢很多,因此需要在可靠性与性能之间进行权衡。

相比与RabbitMQ需要显式地指定数据类型的持久化,Kafka在设计之初就是依赖磁盘上的文件系统来进行消息的存储。从传统的观念来讲,磁盘的读写速度总是比内存慢很多,但是实际上磁盘读写速度的快慢取决于我们的使用方式:

“一块SATA RAID-5阵列磁盘的线性写速度可以达到几百M_s,而随机写的速度只能是100多KB_s,线性写的速度是随机写的上千倍”

Kafka的数据存储设计是建立在对磁盘文件进行追加写的基础上实现的,数据读取也是顺序访问,这样的数据存储设计带来了非常大的优势:

1. 读操作不会阻塞写操作与其他操作,并且数据大小不会对性能产生影响;

2. 磁盘的容量相比与内存来说会大很多,消息队列的容量大,并且可以存储任意时间,不用担心故障导致数据丢失;

综上所述,RabbitMQ和Kafka均支持消息持久化,但是RabbitMQ需要显式地开启持久化,并且开启持久化可能影响消息队列性能。而Kafka从设计之初便支持消息持久化,并且通过优秀的设计保证了高效的消息读写从而保证了较高的吞吐量。

三、性能

性能是我们进行技术选型的一个重要的参考维度,对于消息队列来讲,我们最为关注的一个性能指标是其吞吐量。在吞吐量这个性能指标上,Kafka基于其优秀的存储以及读写设计,相比于RabbitMQ拥有更高的性能。一般来说RabbitMQ的单机QPS在万这个级别左右,而Kafka的单机QPS可以达到十万级甚至百万级。

这里我没有进行单机的吞吐量性能测试,援引网上的其他团队进行的一次消息队列单机吞吐量性能测试,让大家感受一下RabbitMQ与Kafka在吞吐量性能上的差距到底有多少。这次测试对比的是服务端发送小消息(124Byte)的性能,测试的策略是不断增加发送端的压力,直到系统的吞吐量不再上升,并且系统响应时间增长,这时候服务器端可判断已出现性能瓶颈,这时候的吞吐量即为系统的最高吞吐量。

测试的结果如下:

1. Kafka的单机吞吐量为17.3w/s,达到这个吞吐量时其Broker磁盘IO已经达到了瓶颈,Kafka能达到如此之高的单机吞吐量主要还是得益于其优秀的设计。

2. RabbitMQ的单机吞吐量为5.95w/s,并且CPU资源消耗较高,主要原因是其支持AMQP协议,实现地非常重量级,在消息的可靠性与吞吐量上做了取舍。

由此可见,在吞吐量这个性能指标上,Kafka相比与RabbitMQ是具有明显的优势的。

说完了吞吐量,让我们来讨论一下另一个性能指标:时延。其实在使用消息队列的场景下讨论时延是有些矛盾的,因为使用消息队列就代表了可以允许较高的时延,因为使用消息队列就可能产生消息的堆积,并且消息堆积的越多,从消息生产到消息消费的时延就越高,在对时延要求比较高的场景下使用消息队列是不合适的,我们应该使用时延更低的解决方案比如RPC远程过程调用。

四、可靠性

消息队列消息的可靠性也是我们进行技术选型的关键考虑因素之一,尤其涉及到金融、支付、安全等领域,消息的可靠性就显得尤为重要。保证消息可靠性的关键技术之一消息持久化,上文已经讨论过,RabbitMQ以及Kafka均支持,我们这里不做赘述。这里我们来讨论一下消息队列可靠性的另一个方面,消息投递(消费)的三种不同的保证:

1. 至多一次投递:消息最多会被投递一次,但是可能丢失

2. 至少一次投递:消息至少会被投递一次,但是可能重复消费

3. 精确一次投递:保证只会被投递一次,有且仅有一次

RabbitMQ以及Kafka都支持至多一次投递以及至少一次投递的保证,同时Kafka在0.11.0.0版本之后,通过事务机制支持了精确一次投递的这种保证。

五、可用性

可靠性是保证消息不会丢失或者重复消费,而这里的可用性则是指系统正常运行时间占总运行时间的百分比,高可用性也就对应着低故障率,那么RabbitMQ与Kafka分别有什么机制来保证系统的高可用呢?

RabbitMQ采用镜像集群的策略来保证系统的高可用性,在镜像集群模式下,无论是消息队列还是消息都会存储在集群中的多个实例上。也就是说,对于集群中的每个queue来说,集群中的每个节点都有这个queue的完整镜像,这样一来,即使某个节点宕机了,也不会影响整个集群的功能。并且即使是某个主节点宕机了,RabbitMQ集群也可以通过选主算法选举新的主节点从而恢复正常服务。

Kafka的高可用性主要源自于其健壮的副本(Replication)策略。其采用的是类似 PacificA 的一致性协议,通过 ISR(In-Sync-Replica)来保证多副本之间的同步,并且支持强一致性语义(通过 acks 实现)。

六、社区生态及语言支持

从长远的角度来讲,社区生态是我们选择解决方案考虑的关键因素之一,一个开源组件,使用的人越多,社区越活跃,则说明别人踩过的坑也就越多,在我们开发过程中遇到问题时就更加容易找到解决方案。同时,如果一个开源组件更新迭代很快,那么它就可以迅速修复以前旧版本的问题,同时快速地迭代开发新功能。

在社区生态这方面,总体来说,Kafka的生态以及周边环境相比于RabbitMQ更加的成熟和丰富,Kafka拥有更多的开源的客户端、负载均衡组件,同时像Kubernetes、Spark等知名的开源项目也对Kafka有较好的支持。这可能也是得益于在大数据处理方面,Kafka的流式数据的概念和大数据处理更为的契合。

相比于Kafka,RabbitMQ的社区规模可能会较小一些,但是毕竟也是一个久经考验的开源消息队列组件,总的来说我们在使用过程中一般也不会遇到社区解决不了的疑难问题。

在语言支持方面,RabbitMQ和Kafka支持的语言都非常的多,Kafka支持大约17种语言,RabbitMQ支持大约22中语言,主流的变成语言如Java、PHP、C++等等两者均支持,相信在使用的编程语言方面不会有太大的问题。

结语

本文就两种主流的消息队列组件RabbitMQ和Kafka进行了对比和探讨,希望给大家在消息队列技术选型方面提供一定的思路。大家在选择的时候要注意结合自己的业务需求,团队的技术栈体系选择最为合适的消息队列组件!

本文由博客一文多发平台 OpenWrite 发布!