理解Apache kafka 的设计元素和原则(一)

前言

本文参考apachekafka文档提炼精要所写,英文好的同学可以直接从文末的参考文档传送至官方文档。

设计思路

kafka的设计开发致力于提供一个处理各种大型实时数据流的统一平台。正因如此,它需要满足一些特性:

  1. 具备高吞吐量以支持高容量的事件流如实时日志
  2. 可以优雅的处理大数据积压以支持离线系统的定期数据加载
  3. 能够处理低延迟交互以满足传统消息应用场景
  4. 支持分区、分布式和实时处理出具功能(因此设计了分区和消费者模型)
  5. 高容错性

基于上述的特性需求,kakfa被设计成更接近一个数据库日志系统而非一个传统的消息系统。

数据持久化
基于文件系统

Kafka的数据持久化和消息缓极度依赖于文件系统, 通常大家认为磁盘很慢的观点导致人们怀疑文件系统持久化是否能够提供高性能。磁盘的快慢取决于使用方法,一个设计合适的磁盘结构常常能够比网络更加快速。

很重要的一点是现在的磁盘吞吐量已经和过去十年的磁盘搜索速度天差地别。在一个jBOD配置(表示一个没有控制软件提供协调控制的磁盘集合)的带有6个7200转的SATA RAID-5的磁盘阵列的线性写速度能够达到600MB/s, 当然,如果是随机写入,其速度会骤降到100KB/s。线性读写是所有模式中最可预测的,同时,操作系统也对此进行的大量的优化。现代操作系统采用与读取和后写入技术以大块倍预取数据同时将多个小逻辑写入组合成一个大的物理写入。

PS :上面说那么多主要是说明一点,顺序读写在一些情况下会比随机内存访问更快。

为了平衡性能差异,现代操作系统越来越倾向使用主存储器来做磁盘缓存(也就是文件系统的页缓存,pagecache)。当内存回收时,操作系统将会很乐意将所有的空闲内存作为磁盘的缓存从而使得性能损失很小。所有的硬盘读写都需要通过这个统一的缓存(个人理解意思是,硬盘的读写都会先进行缓存,执行写入磁盘操作时当缓存达到一定时间或者数量再写入磁盘,读取时则优先先读取缓存中的数据)。这个特性只能通过 direct I/O 方式关闭,因此即使进程维护数据的进程内缓存,该数据也可能会在操作系统页面缓存中复制,从而有效地存储所有内容。

进一步的说,我们是构建在jvm之上,了解一些java 内存的人都知道:

  • 对象的内存开销特别高,通常回事存储的数据大小加倍
  • 随着堆中的数据量增大,java垃圾收集将变得越来越繁琐和缓慢

根据上述的多个特点,使用文件系统并依赖于页面缓存由于维护内存中的缓存或其他结构–通过自动访问空闲缓存,我们
至少可以将可用缓存加倍。并且可能通过存储紧凑的字节机构而不是独立单个的对象再次翻倍。在这种情况下,没有GC处罚的时候可以做到内存32G的机器中磁盘缓存达到28-30G。与此同时,即使系统重启,此缓存仍然保留缓存的数据;而进程内缓存将需要在内存中重建(10G缓存约莫需要10分钟重建)否则需要从一个完全冷却的缓存(cold cache 以为这 cache is empty)开始。采用文件系统和页面缓存也大大简化了代码,因为所有保持高速缓存和文件系统的一致性的逻辑全部在操作系统内部。如果你的磁盘使用情况偏向于线性读取,那么预读取将高效的把从各个磁盘读取的有用数据填充到此缓存中。

因此,相对于尽可能的维护内存然后在空间不足的时候将其写入磁盘,我们反过来维护数据。所有的数据都立刻写入文件系统的永久日志中,而不必冲刷到磁盘中,实际上,这只是意味着它被转移动到内核的页面缓存中。

满足时间复杂度不变 (Constant time suffices)

消息系统中的持久数据结构通常是采用一个基于相关的BTree或者其他通用随机访问数据结构的消费者队列来维护消息的元数据。BTree是消息系统中最通用的数据结构,它使得消息系统支持各种事务性和非事务性语义成为可能。然而,BTree结构确实带来了高昂的成本:BTree的操作复杂度是O(log N),通常来讲,O(log N)复杂度被认为等价于常量(实际上不是,只不过是随着N的增大缓慢增大),但是对于磁盘操作来讲则不然,磁盘查找在10ms后弹出,且每张磁盘同时只能查找一个,不允许并行操作。因此,即使是少数磁盘搜索也消耗高昂的成本。由于存储系统将高速缓存操作和低速物理磁盘操作混在一起,因此观测到的BTree结构性能通常是超线性的。两倍的数据量会导致性能下降不止两倍。
直观的说,持久化队列可以基于简单的读取并添加到文件上,这类似于日志解决方案。这个方案的优势在于其复杂度为O(1)且读取不会阻塞到写入数据和其他的读取,这样,性能和数据的大小完全分离,这样一台服务器现在可以充分利用大量廉价,低转速1 + TB SATA驱动器,尽管这些磁盘的搜寻性能较差,但是对于大型数据的读取和写入性能是可以接受的(3倍的容量和1/3的价格)。
能够在没有任何性能损失的情况下访问近乎无限制的磁盘空间意味着我们可以提供一些在消息系统中不常见的特性。比如说在kafka中,我们可以保留较长时间的数据,而非倾向于删除已消费的消息。这对于消费者来说更加灵活

—– 待续 (太长了)

高效率

kafka最基本的使用场景之一是处理WEB活跃数据,WEB数据吞吐量相当大:每个页面可能会导致几十个写入。此外,我们假设每条消息都至少被一个消费者读取(通常更多),因此,我们努力是的消费成本更低。
根据大量build 和 run 的类似系统,我们发现,效率是有效的多租户运营的关键。如果应用下游基建服务会很容易的被使用过程中的小问题导致性能瓶颈,那么这些小问题将会经常导致问题。当尝试运行支持集中式群集上数十或数百个应用程序的集中式服务时,这一点尤其重要,因为使用模式的变化几乎每天都会发生
我们在上一节讨论了磁盘的效率。一旦消除了磁盘访问模式不佳的情况,这种类型系统的常见的导致低效原因如下:过多的小I/O和过多的字节复制。
小I/O问题在客户端、服务器和服务器本地的持久化操作中都存在。为了避免这些情况,我们的协议是基于一个叫“消息集”的抽象概念基础上的,这样可以自然的将众多消息分组合。这允许网络请求将消息打包并分摊网络往返开销而不是每次单独发送一条消息。对应的,服务器一次性将大块的消息追加到日志后面,消费者也一次性拉取大块的消息消费。
这个简单的优化极大的提升了执行速度,批量化操作导致了更大的网络包,更大的顺序磁盘操作和连续的内存块等等,这些使得kafka能够将随机消息写入的突发流转化为流向消费者的线性写入。
另一个导致影响效率的原因是字节复制,在低消息速率的情况下这没什么,但是在高吞吐量的情况下对性能的影响非常显著,为了避免这个问题,我们采用了标准化的二进制消息格式,在生产者、broker 和 消费者中都采用这种格式,所以数据库可以不经修改的在他们之间传输。
broker维护的消息日志本身就是一个文件目录。每个文件由一系列已经被以标准化二进制格式写入磁盘的消息集填充。保持这种通用格式可以优化最终要的操作:持久化日志的网络传输。现代Unix操作系统提供了一个高度优化的代码路径,用来将数据从页缓存传输至socket(套接字)。在Linux系统中,这些通过sendfile 系统调用来完成。

端对端批量压缩

在一些情况下,性能瓶颈往往不在CPU或者磁盘,而是源自于网络的带宽限制。对于需要通过广域网在数据中心之前发送消息的数据通道而言,尤其如此。当然,用户可以随时压缩一条消息而不需要kafka的支持,但是,这样的压缩比率往往很差,因为大量的冗余通常源自于消息之间相同类型数据的重复(例如:消息中的用户信息,JSON的字段名称等)。高效的压缩需要一起压缩大量的消息,而非独立的压缩每条消息。
kafka通过高效的批处理格式提供上述功能,一批消息可以一并压缩并以这种形式发送给服务器。这批消息将会被以压缩的形式写入,并在日志中保持压缩,仅仅有消费者解压消费。

后续诸如生产者、消费者等元素将在下一篇介绍
参考文档

apache kafka官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值