快到起飞的Kafka,是如何设计的?
消息队列是分布式系统中重要的组件,在很多生产环境如商品抢购等需要控制并发量的场景下都需要用到。
图片来自 Pexels
消息队列主要解决了应用耦合、异步处理、流量削锋等问题,当前使用较多的消息队列有 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ 等。
而部分数据库如 Redis,MySQL 以及 phxsql 也可实现消息队列的功能。
消息队列在实际应用场景:
应用解耦:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败。
异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间。
限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况。
今天分享一篇经典的 Kafka 设计剖析文章给大家,Kafka 作为顶级消息中间件,据 Confluent 称,超过三分之一的财富 500 强公司使用 Apache Kafka。
Kafka 架构
Kafka 是一个被精心设计的东西,我只能这样说。我这里所谓的精心不是说它很完备的实现了某种规范。
像个学生那般完成了某个作业,比如 JMS,恰恰相反,Kafka 突破了类似 JMS 这种规范性的束缚,它是卓越的,乃 yet another JMS。
当我用 yet…如此称呼一个技术的时候,意味着这玩意儿已经进入了我的视野。好了,现在是 Kafka 和 Storm 时间,本文先谈 Kafka。
Kafka 是什么?
参见官方文档,它是 Apache 的一个项目。它是一个消息队列。
消息队列若何:消息队列是生产者和消费者之间的信使,避免了二者之间直接的接触。
在效果上,它可能和缓存所起的作用一样,平滑了生产者和消费者之间的代谢速率差,但是在其根本目的上,它是为了解除生产者和消费者之间的耦合。如果你觉得有点费解,那么简单点说。
fire and forget,这句话的意思再简单点说,就是真男人从不看爆炸,烟头往油箱里一丢,把风衣的领子一竖,手插裤兜里,径直走开,决不不回头。
消息队列,以下简称 MQ,就是造就这种真男人的。它能让生产者把消息扔进 MQ 就不管了,然后消费者从 MQ 里取消息即可,不用和生产者交互。
下面的篇幅,我将逐步用我的方式演化出 Kafka 的原型,为了掌握整体脉络,难免会隐掉很多细节。
当然这些细节可以随便在其官方文档以及别人的博客里搜到,我的目的只是希望能整理出一个脉络,在设计类似的系统的时候,见招拆招以备参考。
MQ 朝着“正确”方向的演化
Kafka 就一定正确吗?客观讲,肯定不,但是它是本文的主角,所以它就一定正确。
这是很显然的想法,就是是在消息入队处区分消息的 Topic,然消费者从取自己感兴趣的消息队列取消息即可。
如果采用广播,那么就仍然会出现冗余传播问题,如果单播,那么一个消费者取出消息后,这条消息该不该删除呢?如果删除了,另一个消费者怎么办?广播会浪费带宽,不广播也不行…
回到 UNIX 哲学,遇到新问题的时候,要新编一个程序,而不是为已有的程序添加一个功能。
本着这个思路,为什么不把这件因为消费者而导致复杂化的事情完全交给消费者呢?
有点往 Kafka 上靠了啊。如果把 MQ 里面的数据全部持久化存储,消费者不就可以各取所需了吗?
这是一个根本的转变,如果以前的方式是限量商务套餐-套餐强行推给你,不想要的自己扔掉,那么现在的方式就是无限量自助餐-想要什么自己去拿即可。
消息自取,消息永远都在 MQ,消费者随便取,取哪个消息都行,什么时候取都行。
然而自助餐也有打烊的时候,部分也会限制就餐时长,这是 Kafka 策略化存储的问题,详见文档。
一切 OK 了。嗯,是的,这就是 Kafka 的原始模型。然而 Kafka 远不仅此而已。且看下文继续演化。
集群化,容错
这是在逻辑上一个 Kafka 类似的 MQ 应有的结构。但是在物理实现上,它又如何呢?
常听人说,Kafka 一开始就是为分布式而生的,这话怎么理解呢?我们只需要先理解它如何扩容,然后再理解它如何将扩容作用于不同的机器即可。先看扩容。
类似高速公路,一般当你听到广深高速的时候,我们知道这是从广州到深圳的一条高速公路,这是逻辑上的说法,类似到目前为止我们讨论的 MQ 的 Topic。
然而这条高速公路到底长什么样子,沿途怎么路由,这就是物理实现了。此外,所有的道路都会分多个车道用于并行。
严格来讲,每一个车道都会被细分,比如小型车道,客车道,大货车道,超车道等等,所有这些车道上的车都是到达同一个目的地(属于同一个 Topic),然而它们确实是细分的不同种类。
把一个叫做 partition 的概念类比为车道,如下图:
注意这个 key hash 模块,这里就是区分车子要进入哪个车道的逻辑。在 Kafka 的术语中,车道就是 partition,即分区。
在同一个 Topic 中分发消息的时候,你要自己设计 hash 函数,该 hash 函数就是一个分发策略,决定把消息按序放到哪一个分区中去。
温州皮鞋厂老板说类比和举例不好,但这是技术散文,不是技术文档,多半是给自己看,所以还要类比。
Topic Routing 做的事是决定从哪条高速公路到哪里,而 key hash 则是决定你是坐轿车,客车还是卡车过去。
值得注意的是,Kafka 只保证同一 Topic 内同一 partition 内消息的有序性,无法做到全局有序性。
这并不是一个缺陷,这是两全不能齐美的。完全的顺序就需要串行化,然而串行化就无法并行,这简直就是废话!
现在,在 Topic 之下,我们又有了一个新的单位,叫做 partition,这个叫做 partition 的就是 Kafka 中最基本的部署单位,这一点务必要记住,它关乎到如何组织你的集群。
以上是花开两朵,各表一枝,现在该说说消费者了。
消费者面对 MQ 本身进化到如此细粒度,该如何应对呢?其实消费者也有横向扩展的需求,如果说消费者对应 partition,那么对应 Topic 的就是消费者的上级了。
从 CPU cache 到 Kafka,设计思路殊途同归,这就是一个典型的全方位组相联结构:
到此为止,全部图景已经完全绘制完毕,是时候展示集群的部署了。我们知道所谓的 Kafka 集群,就是将各个 Topic 的 partition 部署在不同的机器上,达到两个目的。
持久化存储/查询机制
上面的两个小节,我已经展示了 Kafka 是如何一步一步地肚子里面的勾当内外有别的,虽然我不知道作者怎么去设计,但如果是我自己,我肯定就是上面这个思路了…
前面的叙述终究是概览,不甚过瘾。本节将给出半点细节,瑾阐释一下 Kafka 存储的半景。
我们知道,Kafka 为了卸载 MQ 本身的复杂性,为了其真正无状态的设计,它将状态维护机制这口锅完全甩给了消费者。
因此取消息的问题就转化成了消费者拿着一个 offset 索引来 Kafka 存储器里取消息的问题,这就涉及到了性能。But 如何能查的更快?How?
还是先给出一个最简单的场景。假设 Kafka 的每一个 partition 都一个完整独立的文件,那么如果这个文件非常大,事实上也确实非常大(有可能到达 T 级别甚至 P 级别…)。
那么在大文件中检索一个特定的消息本身就是一个头疼的问题,并且该文件还在磁盘中,这更是雪上加霜,我们都知道磁盘的随机读写是硬伤,顺序读写也好不到哪去,这怎么办?
面对这个遍历问题,一般的解决方案就是建立索引,并且把索引数据常驻内存,很多数据库就是这么干的,Kafka 当然也可以这么干。
Kafka 比较帅的一点就是它并不借助任何特殊的文件系统,它的数据就存在一般的文件中。
然而它把一个 partition 分成了等大小的一系列小文件,因此在物理上,并不存在一个完整的 partition 文件,partiotion 只是表现为一个目录。
以上的例子中,一个 partiton 被分成了 100M 大小的文件,这种小文件叫做分段。
在 Kafka 存储的时候,每一个段文件存满为止再开辟下一个,由于消息的长度并不一定统一,因此每一个小段文件里面包含的消息数量并不一定一样多。
通过这个区间查找树,很快就能定位到特定的段文件,但是事情并没有结束。
最终,经过两次区间树查找之后,最多再经历一次简单的遍历即可完成 offset 定位工作。
诚然,最终的遍历可能是少不了的,但是 Kafka 尽可能地避免了大长段耗时的遍历计算,而是将遍历压缩到一个很小的量级,这是一个权衡!跟谁权衡呢?为什么不把段文件所有消息的索引均建立起来呢?
很简单,建立全部的索引会造成索引非常大,这样如果你还想其常驻内存的话,内存占用会很大,这确实又是一个时间和空间之间的权衡了。
稀疏索引闲谈
稀疏索引很有用,除了本文列举的 Kafka 的 segment index 稀疏索引之外,还有两个更为常见的例子(我不是应用编程的,我是搞内核网络协议栈的,所以在我看来 Kafka 更不常见)
索引整个内存地址空间,稀疏化的做法就是分页,即采用规则的方式将内存划分为等大小的块,叫做内存页,然后索引这些内存页即可,页表而不是地址表稀疏化索引,减小索引的大小。
另外,IP 地址具有地域聚集性,因此对于路由器物理设备而言,对于每一个接口引出的方向,其 IP 地址集在很大程度上是可以聚集的。
路由表一开始采用地址分类的方法,后来采用了前缀匹配的方法稀疏化索引,地址分类有点像内存地址分页,只是页面有多种大小而不仅仅是一种。
而这里的地址前缀则比较像 Kafka 使用的两种索引,第一种是段索引,这是规则的,第二种是消息索引,这是不规则的。因为消息并不定长。
两种说法总结如下:
OS 内存页表:在从虚拟地址定位物理地址的时候,需要一一对应定位到每一个地址吗?
假如真是这样子,那么光页表项这种管理内存就要耗多少你算过吗?虚拟地址和物理地址将会是全相联结构。
UNIX 哲学的出路
作者:极客重生
编辑:陶家龙
出处:转载自公众号极客重生(ID:geek__coding)
请输入标题