分布式消息系统kafka该怎么理解

31次阅读
没有评论

共计 6238 个字符,预计需要花费 16 分钟才能阅读完成。

今天就跟大家聊聊有关分布式消息系统 kafka 该怎么理解,可能很多人都不太了解,为了让大家更加了解,丸趣 TV 小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

kafka:一个分布式消息系统

1. 背景

最近因为工作需要,调研了追求高吞吐的轻量级消息系统 Kafka,打算替换掉线上运行的 ActiveMQ,主要是因为明年的预算日流量有十亿,而 ActiveMQ 的分布式实现的很奇怪,所以希望找一个适合分布式的消息系统。

以下是内容是调研过程中总结的一些知识和经验,欢迎拍砖。

2. 基础知识

2.1. 什么是消息队列

首先,我们来看看什么是消息队列,维基百科里的解释翻译过来如下:

队列提供了一种异步通信协议,这意味着消息的发送者和接收者不需要同时与消息保持联系,发送者发送的消息会存储在队列中,直到接收者拿到它。

一般我们把消息的发送者称为生产者,消息的接收者称为消费者;注意定义中的那两个字“异步”,通常生产者的生产速度和消费者的消费速度是不相等的;如果两个程序始终保持同步沟通,那势必会有一方存在空等时间;如果两个程序一持续运行的话,消费者的平均速度一定要大于生产者,不然队列囤积会越来越多;当然,如果消费者没有时效性需求的话,也可以把消息囤积在队列中,集中消费。

说到这里,我们再来谈谈队列的分类,一般我们根据生产者和消费者的不同,可以把队列分为三类:

第一类是在一个应用程序内部(进程之间或者线程之间),相信大家学多线程时都写过“生产者消费者”程序,生产者负责生产,将生产的结果放到缓冲区(如共享数组),消费者从缓冲区取出消费,在这里,这个缓冲区就可以称为“消息队列”。

第二类其实也算在第一类的特例,就像我们喜欢把操作系统和应用程序区别对待来看,操作系统要处理无数繁杂的事物,各进程、线程之间的数据交换少不了消息队列的支持。

第三类是更为通用意义上的“消息队列”,这类队列主要作用于不同应用,特别是跨机器、平台,这令数据的交换更加广泛,一般一款独立的队列产品除了实现消息的传递外,还提供了相应的可靠性、事务、分布式等特性,将生产者、消费者从中解耦。常见的消费队列产品根据开源与否又可分为两类:

专有软件:IBM WebSphere MQ,MSMQ…

开源软件:ActiveMQ、RabbitMQ、Kafka…

2.2.JMS 与 AMQP

好了,对于上述第三类“消息队列”,要在不同的机器中提供消息队列的功能,那势必要有统一的规范,这时候 SUN 就跳出来了,作为跨平台的 JAVA 势必也要支持跨平台的消息传递,基于此,SUN 提供了一套消息标准:Java Message Service,缩写 JMS,但是这套规范定义的是 API 层面的标准,在 JAVA 体系中可以很方便的交换,但对于其他平台就需要,可能需要消息队列产品本身支持多协议(如 OpenWire、STMOP)。

而 AMQP 定义的比 JMS 更加底层,从名字就能看出来(Advanced Message Queuing Protocol),它定义的是 Wire-level 的协议,天然具有跨平台、跨语言的特性,基于此实现的消息队列可以与任何支持该协议的平台交互。

一种是 JAVA 层面的 API,一种是 Wire-level 协议,这是 JMS 和 AMQP 最本质的区别;同时两种标准还有两个比较明显的差异:

一是消息传递模型;JMS 比较简单,支持两种最通用的 Peer-2-Peer、publisher/subscriber;通俗点就是点对点和广播模式;而 AMQP 定义的更为复杂,其定义了一种 exchange binding 机制,由此支持五种模型:direct exchange、fanout exchange、topic exchange、headers exchange、system exchange,本质上与 P2P、PUB/SUB 一样,但是更加细致些。

二是支持的消息类型,JMS 支持多种消息模型:TextMessage、MapMessage、BytesMessage、StreamMessage、ObjectMessage、Message 等;而 AMQP 只有 byte 数组。

2.3.ActiveMQ

ActiveMQ 是基于 JMS 实现的 Provider(可以理解为队列),它支持多种协议,如 OpenWire,Stomp,AMQP 等,基于此,支持多平台;支持事务,支持分发策略、还有上面的多种消息模型。这里我们不细谈 ActiveMQ 的各特性,我们着重来看 ActiveMQ 的分布式模型。

ActiveMQ 支持分布式,它支持 Master-Slave 提供高可用,也支持 Broker-Cluster 提供负载均衡,但是它的负载基于一种 Forwarding Bridge 机制。

在这种机制下,任意时刻一条消只会被一个 broker 持有,producer 发送的消息,可能会经过多个 broker 转发最终才会到达 consumer,可以想象,当 broker 越来越多时,几乎每次消费都要经过转发,效率会明显下降;并且在这种复杂逻辑下,任一 broker 的加入和移除都显得十分复杂;这两点是我不建议使用 ActiveMQ 分布式集群的根本原因。

1

3.Kafka

好,我们最后来谈今天的主角 Kafka,这个奇特的名字我始终没有找到典故,也许是开发者暗恋女孩(基友)的名字吧 ^_^,Kafka 由 linkin 开发,最初的目的是为了应对 linkin 庞大的活动流数据(登录、浏览、点击、分享、喜欢等),这部分数据容量庞大,但是可靠性要求不高,故而通过牺牲一部分可靠性(这并不是说我们的数据会按百分比丢,我们后面再谈)来提升吞吐量;它砍掉了很多复杂的特性,如事务、分发策略、多种消息模型等;通过自身独特的设计将消息持久化到磁盘上,以此同时支持在线和离线消费;并且其天生为分布式而设计,压根就没有单机模式(或者说单机模式是分布式的特例),能够很好的扩展。实际应用中,Kafka 可以用来做消息队列、流式处理(一般结合 storm)、日志聚合等。

3.1. 架构

2

我们先宏观的看看 Kafka 的架构,Producer 集群通过 zookeeper(实际中写的是 broker list)获取所写 topic 对应的 partition 列表,然后顺序发送消息(支持自己实现分发策略),broker 集群负责消息的存储和传递,支持 Master Slaver 模型,可分布式扩展;Consumer 集群从 zookeeper 上获取 topic 所在的 partition 列表,然后消费,一个 partition 只能被一个 consumer 消费。Name Server 集群(一般是 zookeeper)提供名称服务等协调信息。至于什么是 topic,什么是 partition,我们接下来看。

3.2.Topic

Topic 是生产者生产、消费者消费的队列标识。一个 Topic 由一个或多个 partition 组成,每个 partition 可以单独存在一个 broker 上,消费者可以往任一 partition 发送消息,以此实现生产的分布式,任一 partition 都可以被且只被一个消费者消息,以此实现消费的分布式;因此 partition 的设计提供了分布式的基础。

3

同时,从上图我们也能发现这种设计还有一个优点,因为每个 partition 内的消息是有序的,而一个 partition 只能被一个消费者消费,因此 Kafka 能提供 partition 层面的消息有序,而传统的队列在多个 consumer 的情况下是完全无法保证有序的。

3.3. 消息传递模型

传统的消息队列最少提供两种消息模型,一种 P2P,一种 PUB/SUB,而 Kafka 并没有这么做,巧妙的,它提供了一个消费者组的概念,一个消息可以被多个消费者组消费,但是只能被一个消费者组里的一个消费者消费,这样当只有一个消费者组时就等同与 P2P 模型,当存在多个消费者组时就是 PUB/SUB 模型。

4

3.4. 消息持久化

很多系统、组件为了提升效率一般恨不得把所有数据都扔到内存里,然后定期 flush 到磁盘上;可实际上,现代操作系统也是这样,所有的现代操作系统都乐于将空闲内存转作磁盘缓存(页面缓存),想不用都难;对于这样的系统,他的数据在内存中保存了一份,同时也在 OS 的页面缓存中保存了一份,这样不但多了一个步骤还让内存的使用率下降了一半;因此,Kafka 决定直接使用页面缓存;但是随机写入的效率很慢,为了维护彼此的关系顺序还需要额外的操作和存储,而线性的写入可以避免这些,实际上,线性写入(linear write)的速度大约是 300MB/ 秒,但随即写入却只有 50k/ 秒,其中的差别接近 10000 倍。这样,Kafka 以页面缓存为中间的设计在保证效率的同时还提供了消息的持久化,每个消费者自己维护当前读取数据的 offser(也可委托给 zookeeper),以此可同时支持在线和离线的消费。

3.5.Push vs. Pull

对于消息的消费,ActiveMQ 使用 PUSH 模型,而 Kafka 使用 PULL 模型,两者各有利弊,对于 PUSH,broker 很难控制数据发送给不同消费者的速度,而 PULL 可以由消费者自己控制,但是 PULL 模型可能造成消费者在没有消息的情况下盲等,这种情况下可以通过 long polling 机制缓解,而对于几乎每时每刻都有消息传递的流式系统,这种影响可以忽略。

3.6. 可靠性

刚刚说 Kafka 牺牲了一些可靠性来提升吞吐量,很多同学可能担心消息的丢失,那么我们现在来看看各种情况下的可靠性。

5

对于如上的模型,我们分开来看,

先来看消息投递可靠性,一个消息如何算投递成功,Kafka 提供了三种模式,第一种是啥都不管,发送出去就当作成功,这种情况当然不能保证消息成功投递到 broker;第二种是对于 Master Slave 模型,只有当 Master 和所有 Slave 都接收到消息时,才算投递成功,这种模型提供了最高的投递可靠性,但是损伤了性能;第三种模型,即只要 Master 确认收到消息就算投递成功;实际使用时,根据应用特性选择,绝大多数情况下都会中和可靠性和性能选择第三种模型。

我们再来看消息在 broker 上的可靠性,因为消息会持久化到磁盘上,所以如果正常 stop 一个 broker,其上的数据不会丢失;但是如果不正常 stop,可能会使存在页面缓存来不及写入磁盘的消息丢失,这可以通过配置 flush 页面缓存的周期、阈值缓解,但是同样会频繁的写磁盘会影响性能,又是一个选择题,根据实际情况配置。

接着,我们再看消息消费的可靠性,Kafka 提供的是“At least once”模型,因为消息的读取进度由 offset 提供,offset 可以由消费者自己维护也可以维护在 zookeeper 里,但是当消息消费后 consumer 挂掉,offset 没有即时写回,就有可能发生重复读的情况,这种情况同样可以通过调整 commit offset 周期、阈值缓解,甚至消费者自己把消费和 commit offset 做成一个事务解决,但是如果你的应用不在乎重复消费,那就干脆不要解决,以换取最大的性能。

最后,我们再来看 zookeeper 的可靠性,很明显,他要挂了,一切都完了,地球就毁灭了,人类就灭绝了,星级穿越也挽救不了了……所以增强可靠性的方式就是把 zookeeper 也部署成集群。

3.7. 性能

好了,说了那么多,我们实际来测试下 Kafka 在各种情况下的性能,为了对比我也测了下单机模式下 ActiveMQ 的性能,不过由于懒,没有搭建 ActiveMQ 集群进行测试,但是基于其恶心的 Forwarding Bridge 模型,我也持悲观态度。

首先,测试环境如下:

Kafka:3 broker;8 核 /32G;默认配置

ActiveMQ:1 broker;8 核 /32G;默认配置

Producer: 一台机器通过多线程模拟多 producer;8 核 /32G;默认配置,异步发送

Consumer: 一台机器通过多线程模拟多 consumer;8 核 /32G;默认配置

除了特殊说明,生产和消费同时进行。

然后,我使用如下字符表示各种测试条件:

1T-1P3C-1P1C-1KW-1K:

1T:1 个 toipc

1P3C:1 个 partition 3 个 replication

1P1C:1 个 producer 1 个 consumer

1KW:1 千万条消息

1K:每个消息 1K

我先对 ActiveMQ 在单机多 Producer、多 consumer 的情况下的测试,结果比我想象中的好,官方的给出的一个数据是 1 -2K 的数据,每秒 10-20K 个,这样算下来大概 30-40MB/S,而测试的结果在多线程的情况下会更好些。

ActiveMQ-thread Produce Consume 1T-XXX-1P1C-1KW-1K 28.925MB/S 28.829MB/S 1T-XXX-3P3C-1KW-1K 43.711MB/S 41.791MB/S 1T-XXX-8P8C-1KW-1K 52.426MB/S 52.383MB/S

然后我又对 Kafka 进行了相应的测试,用一个 partition 模拟单机模式,结果和预想的一样,在单机模型下,两者差异不大;而官方给的数据说生产者能达到 50MB/S,消费者能达到 100MB/S,生产者符合官方数据,而消费者我始终没有压到那么高的速度。

Kafka- thread Produce Consume 1T-1P1C-1P1C-1KW-1K 29.214MB/S 29.117MB/S 1T-1P1C-3P3C-1KW-1K 46.168MB/S 43.018MB/S 1T-1P1C-8P8C-1KW-1K 52.140MB/S 51.975MB/S

接下来的对于 Kafka 集群,我想同样数量的消息会不会因为 topic 数目的增多而影响,测试结果如下,表明 topic 越多,速度会有所下降,也符合预期。

Kafka-topic Produce Consume 1T-3P3C-3P3C-1.2KW-1K 49.255MB/S 49.204MB/S 3T-3P3C-3P3C-0.4KW*3-1K 46.239MB/S 45.774MB/S

然后为了测试 partition 对性能的影响,进行了如下测试,可以看到 partition 数量越多,总的生产和消费速度越快;但是意外的是 Only produce 情况下生产效率没有明显提升反而略慢,这里怀疑和 page cache 有关,没有深入研究。

Kafka-partition Produce Consume Only Produce Only Consume 1T-1P3C-1P1C-1KW-1K 29.213MB/S 29.117MB/S 28.941MB/S 34.360MB/S 1T-3P3C-3P3C-1KW-1K 47.103MB/S 46.966MB/S 46.540MB/S 66.219MB/S 1T-8P3C-8P8C-1KW-1K 61.522MB/S 61.412MB/S 60.703MB/S 72.701MB/S

综上,我们可以看到 Kafka 的性能和吞吐是可以扩展的。

3.8. 风险点

对于我们来说,Kafka 主要有两个风险点,第一,要深入使用必须要熟读源码,而 kafka 源码是用 scala 写的,我们并没有相应的技术储备,需要学习;第二,kafka 技术较新,目前的版本是 0.8.1.1,看起来还不太成熟。

看完上述内容,你们对分布式消息系统 kafka 该怎么理解有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注丸趣 TV 行业资讯频道,感谢大家的支持。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计6238字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)