原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。
看完本文,你将明白为什么一个简单的消息队列,能够有那么多的知识点;能够了解到Kafka的主要功能和应用场景;能够了解到Kafka的主要技术术语。了解到什么叫本分!
作为一个分布式消息系统,Kafka
要有本分思想。它要搞清楚自己的定位,明白是为谁创造什么样的价值,依赖谁活着,自己的职责又是什么。
很少有系统在这么有压迫力的连环问下保持冷静,不过Kafka
顶住了,它是真的勇士。
Kafka
的本分核心,就是当作消息队列用。那么消息队列是什么呢?如果这个问题没法搞懂,就证明Kafka
的思想觉悟并不是很高,还需要继续去思考、去深造。
为了弄清楚这个问题,我们采访了一位送牛奶的工人。
1. 送奶工的故事
牛奶好喝而且有营养,不管是牛奶子里捏出来的新鲜牛奶还是合成的牛奶,所以小区里有很多人订。
每天清晨,送奶工人都拉着一车牛奶开始送奶。刚开始,他按照本子上的门牌号,一家家的敲门,然后把牛奶塞进客户手里。有时候,客户不在家,他只好翻出通讯录找到客户的电话号码进行沟通。但过了不久,随着业务做的越来越大,送奶工对这份工作的评价只有一句话:*费力不讨好。
有的客户睡眼朦胧的开门,投诉他打扰生活;有的女客户披着睡衣就来接奶,投诉他的眼光猥琐;有的客户上班比较早,但在送奶工的路线规划上,却是奶最后送到的,于是投诉他配送不及时。
好在送奶工以前是个程序员,稍一思考,他说服老板:给每一家客户,配备一个奶箱。他的工作,只需要定时把鲜奶放入箱子里即可。至于客户什么时候去拿,拿去洗脸了还是搓手了,他并不关心。
从此,他再也没看到睡衣下若隐若现的胴体。
我们注意到,上面的场景,有两个主要的参与方:送奶工和客户。在加入奶箱之前,他们的交互是阻塞的,信息处理是低效的,而且存在严重的耦合问题,以至于送奶工看了不该看的东西。
当然,加入奶箱之后,交互逻辑就发生了变化,这是需要适应的;而且,奶箱是有成本的,如果业务量并不是很大,加这个玩意反而会增加成本。
我们来稍微see一下下:上面的奶箱,就是消息系统。每一个奶箱,就是一条
消息队列。牛奶工,就是生产者;客户,就是消费者;而牛奶,就是消息。客户一直不取走奶,就是消息积压。客户和你发消息,确认奶已经收到,就是ACK...
2. 最简单的广义消息系统
消息系统!就是提供一个中间层,生产者只需要把消息提交到特定的中间层,消费者只需要从中间层去拿信息就可以了。
所以,它最简单的表现形式,就是数据库。
上图是一些小系统的典型架构。考虑订单的业务场景,有大量的请求指向我们的业务系统,如果直接经过复杂的业务逻辑进入业务表,将会有大量请求超时失败。所以我们加入了一张中间缓冲表,用来承接用户的请求。然后,有一个定时任务,不断的从缓冲表中获取数据,进行真正的复杂的业务逻辑处理。
不要怀疑,这其实就是最简陋的消息系统
,只不过它存在不少问题。
- 定时任务的轮询间隔不好控制。业务处理容易延迟。
- 无法横向扩容处理能力,且会引入分布式锁、顺序性保证等问题。
- 当其他业务也需要这些订单数据的时候,业务逻辑就必须要加入到定时任务里。
当访问量增加、业务逻辑复杂化的时候,更高的消息模型就呼之欲出了。
3. 消息系统的基本要求
我们对消息系统的本分要求有下面这些:
- 性能要高 包含消息投递和消息消费,都要快。一般通过增加分片数获取并行处理能力。数据库显然是有瓶颈的。
- 消息要可靠 在某些场景,不能丢消息。生产、消费、MQ端都不能丢消息。一般通过增加副本,强制刷盘来解决。数据库显然也要通过主从来做备份的。
- 扩展性要好 能够陪你把项目做大,陪你到天荒地老。增加节点集群增大后,不能降低性能。数据库的扩展性肯定是存疑的,你可能会引入一些复杂的分库分表组件。
- 生态成熟 监控、运维、多语言支持、社区的活跃。这决定了你用的消息队列值不值得你信赖。
甚至有更多,xjjdog有另外一篇文章去说明它:分布式消息系统,设计要点。画龙画虎难画骨
要求这么多,但模型又如此简单,它的难点到底在哪里呢?为什么有些同学看到Kafka就头疼呢?
4. 要你本分,到底多难
既然消息系统的模型就是一个简单的生产者消费者模型,那为什么现在的消息系统都那么的复杂呢?其实,它的复杂性,主要体现在分布式
这三个字上,和消息队列的关系不大,它需要处理一些所有分布式系统都要面临的问题。
4.1 副本
单机上的任何数据都是不可信的,因为硬盘会坏,会断电,会被挖光缆。所以一般通过冗余多个副本来保证数据的安全。副本的另外一个作用,就是提供额外的计算能力,比如某些请求,会落到副本上。副本越多,可用性越高。
而加入副本以后,就涉及到数据的同步问题。即使是最快的局域网,也会存在延迟,更不用说机器性能差异引起的同步延迟。这就存在一个问题,读副本的请求读到的数据,可能不是最新的,这就是数据的一致性发生了改变。当然有些手段能保证数据的一致性,但副本越多,延迟越大。
副本的加入还会引入主从的问题。主节点死掉以后,要有副本节点顶上去,这个过程的协调需要时间,其间部分不可用。
所有的消息系统,需要有大量的代码去处理这些异常情况。
4.2 分区
而当一类数据足够大(比如说某张表),在其上的操作已经非常耗时的情况下,就需要对此类数据进行切割,将其分布到多台机器上。这个切割过程就是Sharding,通过一定规则的分片来减少单次查询数据的规模,增加集群容量。
针对一个分片的数据,只能有一个写入的地方,这就是master
,其他副本都是从master
复制数据。
副本能够增加读操作的并行读,但会读到脏数据。如果你想要读到的数据是一致的,可以采用同步写副本的方式,比如KAFKA的ack=-1
,只有全部同步成功了,才认为本次提交成功。
但如果你的副本太多,这个过程会非常的慢。你可能想要通过分配写入和读取的副本个数来协调写入和读取的效率,Quorum
的R W>N
就是一个权衡策略。
5. Kafka名词解释
我们反过来再看Kafka的名词定义,就简单的多了。
Kafka是一个分布式消息(存储)系统。分布式系统通过分片增加并行度;通过副本增加可靠性,kafka也不例外。它的结构逃不出我们上面介绍的基本分布式理论。如果你把副本、分区、主题通道,生产者、消费者这些名词放在一块的话,图就可以变得非常大。
你在一台机器上安装了Kafka,那么这台机器就叫Broker
,KAFKA集群包含了一个或者多个这样的实例。这只是一个命名而已,并没有什么特定含义。
负责往KAFKA写入数据的组件就叫做Producer
,消息的生产者一般写在业务系统里。和我们的送奶工是一个维度。
发送到KAFKA的消息可能有多种,如何区别其分类?就是Topic
的概念。一个主题分布式化
后,可能会存在多个Broker上。
将Topic拆成多个段,增加并行度后,拆成的每个部分叫做Partition
,分区一般平均分布在所有机器上。
那些消费Kafka中数据的应用程序,就叫做Consumer
,我们给某个主题的某个消费业务起一个名字,这么名字就叫做Consumer Group
再看一下Kafka Server的配置文件,最重要的两个参数:partitions
和replication.factor
,其实就非常好理解了。
再来说一个最重要的概念。Kafka解决副本之间的同步,采用的是ISR
,这是一个面试Kafka必考的点之一。
ISR全称"In-Sync Replicas",是保证HA和一致性的重要机制。副本数对Kafka的吞吐率是有一定的影响,但极大的增强了可用性。一般2-3个为宜。
副本有两个要素,一个是数量要够多,一个是不要落在同一个实例上。ISR是针对与Partition的,每个分区都有一个同步列表。N个replicas中,其中一个replica为leader,其他都为follower, leader处理partition的所有读写请求,其他的都是备份。与此同时,follower会被动定期地去复制leader上的数据。
如果一个flower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其从ISR中移除。
当ISR中所有Replica都向Leader发送ACK时,leader才commit。
6. 消息系统的作用
说了这么多,是时候把消息队列的作用,使用计算机的术语解释一下了:
- 削峰 用于承接超出业务系统处理能力的请求,使业务平稳运行。这能够大量节约成本,比如某些秒杀活动,并不是针对峰值设计容量。
- 缓冲 在服务层和缓慢的落地层作为缓冲层存在,作用与削峰类似,但主要用于服务内数据流转。比如批量短信发送。
- 解耦 项目伊始,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。只需要遵守约定,针对数据编程即可获取扩展能力。
- 冗余 消息数据能够采用一对多的方式,供多个毫无关联的业务使用。
- 健壮性 消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。
但是,由于Kafka是个优秀的小伙,它内卷的非常可以,就能做更多的事情。它的本分范围更加大,包括但不限于:
- 传递业务消息
- 用户活动日志 • 监控项等
- 日志
- 流处理,比如某些聚合
- Commit Log,作为某些重要业务的冗余
- Event Source,实践溯源,DDD中的概念
下面是一个日志方面的典型使用场景。
7. KAFKA为什么快
一般用到Kafka,都是奔着它的速度去的,这一度让人认为它只能处理一些日志类的消息。事实上,Kafka就连最复杂的事务消息都支持,也算是被它的速度所掩盖的一个光彩。
那么,它为什么那么快呢?总结下来有以下几点原因:
- Cache Filesystem Cache PageCache缓存
- 顺序写 由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快
- Zero-copy 零拷⻉,少了一次内存交换
- Batching of Messages 批量量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限
- Pull 拉模式 使用拉模式进行消息的获取消费,与消费端处理能力相符
主要就这5点,至于什么压缩,JVM性能优化之类的,都是小儿科,上不了台面。
End
可以看到,Kafka是一个全能的选手,既能做消息处理,又能做数据存储。它无怨无悔的工作,虽然效率奇高,也要一刻不停歇的工作,体现了打工人最悲催的命运。
它的分布式系统设计也是非常棒的,这是它的设计者为它量身定做的一套体系:一台Kafka节点倒下了,会有更多的Kafka节点顶上来,经过十几秒的阵痛,然后就可以彻底的忘掉它的牺牲。
Kafka是一个本分的分布式消息系统,但也不要无限的压迫它。只给它分配了1核cpu、512M的内存,这是要磨练它在艰苦环境下不屈不挠的本分意志,啊!
丢了数据还骂ta不稳定,你的良心不会痛么?
作者简介:小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。