Kafka(架构深入理论)

Kafka工作流程

下图左边是一个生产者,右边是一个消费者组,生产者于消费者面向的都是一个Topic,在Kafka中,即使我们不手动创建Topic,有生产和消费了,也会自动生成Topic,而且自动创建的这个Topic默认是一个分区,一个副本,我们这里创建的是一个Topic三个分区,对应三个副本
在这里插入图片描述
生产者给Topic发消息(数据),是怎么发的呢?一个小方块就代表一条数据,一条消息,它不是一个一个进来的,是一批一批进来的,也就是说,生产者向Kafka中的Topic输送数据,是成批次进去的。如果是一条一条的发送,那么肯定是每发送一条数据都会和Kafka建立一次连接,这不仅仅是IO的问题,还涉及到了网络传输问题,按道理讲生产者这个应用与Kafka这个集群应该不在一个节点信息上的,所以会有网络传输。成批次发送回提高效率,减少连接。
在这里插入图片描述
Leader是接收数据的,而Follower是从Leader上主动拉取同步数据。自己拉取自己Leader的数据,而消费者消费数据的时候,指挥去找Leader,不会去找Follower。
在这里插入图片描述
在这里给大家引出一个新的概念就是offset,这个offset在Kafka中非常重要,我们标记的数字都放在了独立的分区中,他们是在Kafka中真实存在的,而offset就是偏移量的意思,如果在正常的情况下,生产者生产数据向Topic中发送了,消费者这边也正常消费了,是没毛病的,但是我们考虑到一点,就是如果消费者挂了,会不会对Kafka造成影响?首先它挂了,我们肯定是要重新启动,让它再次能够进行消费的,但是它面临一个问题,从哪里开始消费呢?offset就会记录它上次消费的位置,offset就是给消费者用的,消费者每次消费完数据,都要更新一下offset,更新它的目的很简单,就是怕消费者挂了,再回来不知道从哪里消费,那么offset储存在什么地方呢?在0.8之前的Kafka,包括0.8,offset都是存储在zk中的,如果所有的消费者的offset都存储在zk中,那么对zk的压力还是有点大的,显得Kafka太过于依赖zk了,后来kafka将它的架构变了,将offset存储在自己内置的一个Topic中了,它自己的这个Topic晚点我们可以一起看一看。
在这里插入图片描述
我们思考一个问题,消费者第一次消费的时候它有offset么?那我从哪里消费呢?这就牵扯出两个策略,一,从最开始消费,二从最新的消费,Kafka默认从最新的开始消费,所以我们敲命令的时候,那个–from-beginning,代表的就是从0开始消费了。就是这个原理

Kafka文件存储机制

我们已经清楚了,生产者生产数据,回向Kafka发下哦那个数据,那么,数据会放在什么地方呢?数据会落盘的,落盘落在了什么地方呢,就落在了前面配置的data.logs中。我们是怎么存的呢?存储的文件是什么结构呢?
我们下图是一个Topic,带着三个分区,三个分区对应着三个副本,加起来就是六个副本,每一个副本都对应着我们之前配置的logs一个文件夹,他们名命是怎么名命的呢?就是Topic的名字横杠0,1,2,我们一会可以去看一下,我们log存储的就是真实存在的文件了,它这个文件是什么文件呢?是一个log文件,日志文件特点都是追加进去的,追加的好处是什么?是顺序写入

在这里插入图片描述
我们都知道机械硬盘的内部结构大概是什么样的
在这里插入图片描述
我们向磁盘写入数据都是顺序写入,当然,旁边的那个磁针是不会动的,知识磁盘在疯狂的旋转,这样写的画速度是非常快的,随机写的话效率很低,存在一个磁针寻址的时间,所以顺序写入比随机写要快的多。就好比我们读取一个大文件的时候非常快,因为是顺序读写,而读取一大堆小文件的时候是随机读写,要找地址,所以说很慢。为什么要将这个呢,因为我们生产者向Kafka发送数据写入的过程,都是顺序写入。效率非常快。这个对比高Kafka的吞吐量是由非常大的帮助的。有些硬盘顺序写入的速度甚至超过了内存读写速度,是非常恐怖的。由的时候我们会假想,Kafka的数据都存在一个落盘的过程,是不是会影响它的效率呢?放心,不会的,Kafka是顺序写入,速度非常的快

思考一个问题,我们生产者如果向log文件一直追加数据,log文件是不是会越来越大,大了会有什么影响呢?首先我们知道Kafka的保存数据时间默认为7天,如果7天内,这个文件变的超级大,那么我们读取这个文件的时候是不是会变的很慢。这是一点,这还不是最主要的一点,最主要的一点是Kafka的消费者挂了,它会通过offset来寻址上一次读写的位置,那么它会去哪里找这个位置,还是会去log文件里去找,因为我们的数据都在log文件里存着,这个文件超级大,我们拿offset来找的时候,也是会非常慢的。这也是大文件的一个缺点,为了解决这个缺点Kafka怎么做的呢?他采用了两种机制,一肿是分片,一种是索引,分片是将一个大的log文件分成了多个Segment(片),每个Segment由自己的大小,大概10个G,开始的时候我这个log只有一个文件,这个Partition下的log文件达到10G的时候我就关闭了,不追加写入了,我就开启一个新的文件进行追加写入,还有一个就是如果我这个第一个文件迟迟到不了10G,它会有一个时间点,到这个时间点我也关闭这个文件,生成一个新的文件。总而言之,两者达到一个条件,就会生效,开启一个新文件

总结:一个大log文件真实的在Kafka种是分成多个小文件存在的

现在存在一个问题,我一个Segment极限10G,数据量也不小啊,我消费者拿着offset来寻址的时候也是比较慢的,我们要提高查询效率怎么办?答:加索引。我们的Segment会生成两个文件,其中.log文件,是真真实实的存储数据的文件,而.index文件就是log文件的索引,我们消费者拿着offset定位log文件中数据位置的时候,是借助index文件来定位log文件中的想要的位置的。

在这里插入图片描述
总结:我们在Kafka的分区下面存储数据,这个路径下真实存在的文件应该是什么呢?图中的log是虚拟的,没有,有Segment么?没有,Segment只是一个概念,.log和.index文件合起来才叫一个Segment,真实存在的其实就是很多很多个.log和.index文件。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那我们看到它前面都是一堆0,这都是什么玩意儿?这个我们也需要很好的掌握,我们看图描述。下图中是一个log中有两个Segment,而在每个Segment中都有.log文件存储的是真实数据。图中Message - 0代表的是第一条消息,以此类推,后面的0,1,2,3,4,5其实就是offset,每个分区中的offset都是从0开始的。下面的是从6到11,是接着上一个文件的offset,每个文件的名命规律,如下图所示中描述:

在这里插入图片描述
我们观察一下index文件的结构,我们会发现index文件分为两列,左边都是从0开始,代表的是什么意思呢,代表的是我这个Segment中的第0条,1条,2条,第n条数据,后面的数字0,237,526等代表的是index对应的log文件中真实的物理偏移量,我们在log文件中的数据都是顺序追加进去的,每一条消息都有自己的大小,我每一个消息从第几个字节开始呢,就是在index中存着呢,也就是index中的右侧的一列数字。以index中的1为例,它代表的是当前的Segment中的第一条消息,第一条消息指的是谁?它对应的是log文件中的Massage - 1,那Massage - 1这条消息,我在log文件中的第几个字节开始呢?从第237个字节开始,那言外之意,我Massage - 0这条数据多长呢?是236个字节
在这里插入图片描述

思考题:如何找到offset=3的Message?

offset=3,也就是意味着Message - 3,怎么定位这个文件呢,第一,首先确定是哪个Segment,找到了对的Segment之后,怎么确定Segment呢?我们可以根据文件名来确定,文件名末尾已经标记好了对应的offset,因为Segment-1的log文件名是00000000000000000006.log,代表的是offset从6开始,那么3肯定是在Segment - 0中,确定了Segment之后,我们开始找它对应的index文件,找到index文件之后,去里面找对应的哪条消息,怎么定位呢?我们知道offset已经等于3了,我们可以用3减去文件名最后一个数字,就可以得到它的文件起始位置,就可以往后读取数据了
在这里插入图片描述

Kafka消费模式

消费方式有两种,一种是主动的拉取,另一种是被动的接收,下图是Topic主动向消费者推送数据。

在这里插入图片描述
还有一种情况,就是消费者Consumer从Topic中拉取数据
在这里插入图片描述
那么我们Kafka在消费时,采用的是哪种方式获取数据呢?Kafka采用的时拉取的方式,consumer采用pull(拉)模式从broker中读取数据。

那么推数据,和拉取数据,有什么优缺点呢?我们分析一下。

我们先看一下Kafka的Topic主动给Consumer放数据。Push有优点么?其实时有的,我的主动权在Topic,我有数据才会给你推数据,没有就不推了,这个时它的优点,那么缺点呢?我不管你Consumer能不能承受的了,反正我就一个劲儿的推。那么相对而言,Pull的模式它的优缺点是什么呢?主动权在Consumer这边,我就会考虑自身的消费能力,我能拉多少就拉多少,缺点就是我也不知道Topic里面有没有数据。它会不断的去轮询,一直拉取,Kafka最终还是选择了拉取数据。我能拉多少就拉多少,能消费多少就拉多少,那我们说了,它有缺陷,Topic中没有数据会一直拉取的问题,Kafka也做了一定的优化,针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout,也就是说,kafka的Consumer会等待一段时间再返回,如果有数据了,我就拿回来了。

分区分配策略

分区分配策略,与前面说的分区策略不是一个事儿,生产者分区策略是生产者生产数据后,将数据发往哪个分区。而分区分配策略是和消费者有关,我们前面说过了,一个Topic有多个Partition,一个消费者组里面有多个消费者。

在这里插入图片描述
那么消费者与Partition之间的关系是谁来定的呢?谁给谁消费呢?就是分区分配策略。它是怎么定的呢?我们一起来看一下,它有两种策略。

第一种:roundrobin策略。

首先我假设有7个分区,三个消费者。

在这里插入图片描述
然后我将第0个分区给第一个消费者0,第1个给第二个消费者1,第三个给消费者2以此类推的轮询。这就是roundrobin策略。这样一来,我就均匀的将分区分给三个消费者。我最多的Partition的个数,比最少的Partition多一,就是均匀的,大二就不是均匀的了。

在这里插入图片描述

第二种:range策略

Range其实就是范围,将一个范围的数据直接给消费者,我们还是以7个分区,三个消费者为例子。

在这里插入图片描述

然后稳直接将三个分区给一个消费者挪过去,还是一个范围的分区。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
效果其实和我们之前roundrobin的效果是一样的,只不过消费者获得的分区号可能不同了。他俩虽然效果一样,但是还是有些区别的,这个range稍微有点局限,是按照主题Topic分的。而这个roundrobin就打破了这个界限了。

为了让大家看到更好的效果,我们假如说现在有两个Topic,每个Topic有7个分区,消费者还是三个,我们再看看Range是怎么分的呢。

在这里插入图片描述
他会先分一个Topic的数据,进入到Consumer中,然后再分第二个Topic的数据,一起来看一下。
在这里插入图片描述
然后再分第二个Topic的数据,也是按照批次进入到Consumer中。

在这里插入图片描述
总结:其实我一个消费者组是可以订阅多个Topic的。

但是我们会发现一个问题,按照之前的分区数322去分,我们Range是均匀的,但是Topic一旦多了,他就不均匀了,它就会出现数据倾斜。而我们的roundrobin就不会出现这种情况,因为是轮询。

我们还是两个Topic,每个中各有7个分区,准备一个消费者组,组里面三个消费者。

在这里插入图片描述
它会一口气均匀的分布完成的,最终形成554的状态。我们一起来看一下。

在这里插入图片描述
最终变成这样的,会发现非常的均匀。
在这里插入图片描述
结论:如果我们一个消费者组,只订阅一个Topic的话,我们选谁都行。如果要订阅多个Topic的话,我们就要选择Roundrobin,而我们Kafka中默认是Range策略,这个要记住。为什么默认是Range呢?我们如果翻阅源码会发现,这个Range效率会高一些,在不考虑多个Topic的情况下,我们默认选择它。

关于Kafka的Offset

首先我们得知道Kafka中的Offset作用是什么?它其实就是在Consumer挂了之后,再次恢复过来后,知道上一次消费到哪里了,去哪里继续消费,加如说,我的消费者永远不会出现问题,那么我的Offset其实是没用的,但是不会不出现问题,哪有那么绝对的事。

Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始
consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets。当然,我们也可以选择继续将这个Offset存储在zk当中,看自己选择。我们其实不建议这么做,还是存储在Kafka中比较合适的。我们也有可能将Kafka的offset存储在自己的存储系统中,比如Mysql,Redis都是有可能的

Kafka高速读写数据

就这个Kafka架构而言,谁和高效读写挂钩?分区的设计就能够极大的提高Kafka的吞吐量,生产者,或者消费者发送消息,它都是成批次的,这个也是可以提高效率的。那么Kafka还有两个比较重要的点,可以提高读写速度。

第一种:顺序写磁盘

我们之前就说过了,顺序写入比随机读写要快的多,原因就是类似磁盘的方式去处理。在我们追加数据到.log文件的时候,我们这个操作可以省去浪费资源的环节

第二种:零复制技术

它指的是我们Linux系统的一个技术,我们Kafka只是里用了Linux这个0复制技术,Linux系统分为内核层,硬件层,用户层,在Linux中用户层是不能直接操作我硬件的,我们今天想操作硬件层,只能通过内核层去操作。直接上图,看图说话:

在这里插入图片描述
它今天如果用户层想访问硬件层只能通过内核层去访问。
在这里插入图片描述
这个零拷贝在Linux里面是将数据从磁盘里面通过网络发送的一个技术,从磁盘里面读,然后通过网络发送,这个零拷贝能够减少我这个数据复制的次数,能够提高我数据发送的速率。它既然是读取后发送,我们什么时候使用的这个零拷贝技术呢?只有在消费的时候才会从磁盘文件中读取数据然后发送给消费者。

如果我们不用零拷贝技术,我们数据走一遍流程看看怎么走的。我们的File相当于一个硬件,我们看图说。

在这里插入图片描述
正常我们想通过网络读取操作是不是要经过一些序列化的操作,这些序列化的算法等等等等这些在哪里呢?是不是应该在我们用户的应用层呢,所以我们应该将我们的数据拷贝到我们的内核层。内核层中有一个缓存叫做Page Cache,也就是说,我硬件会将数据读到内核的缓存中

在这里插入图片描述
然后再从内核缓存将数据拷贝到用户应用缓存当中

在这里插入图片描述
来到了应用缓存中我才能够进行一系列的序列化等等的一系列操作处理,那这个数据处理完之后,最终要通过谁发送?通过网络肯定要通过网卡发送嘛,又要操作硬件了,那么我们还得经过一次内核层。我们要经过一个Socket Cache,这个是不是我们的网络常用连接的那些东西。它只是内核层中的另一个缓存

在这里插入图片描述

经过了内核层,最后才能通过我们的网卡进行发送。

在这里插入图片描述
总体下来,我们的数据拷贝了四次。。。四次才做完这个事,那么我们Kafka怎么作到的零拷贝呢?零拷贝指的又是什么呢?
我们的Kafka是这样操作的,直接将文件读到Page Cache,然后直接将Page Cache中的数据发给网卡,上图说话:

在这里插入图片描述

我们不用经过用户层去做了,那么为什么Kafka可以这么做呢?我们的生产者、Borker、消费者,他们几个的序列化都是统一的。意思是我生产者这边传输数据的时候,会把消息进行了序列化,序列化之后呢,会把消息通过网络会给Kafka,在Kafka中,我们直接将序列化的数据存到刚才图中的File里,我消费者这边再次获取数据的时候就不需要序列化了,也就不用走用户层了,直接拿数据就可以。它说的有点夸张了,也拷贝了一次不是?只是省去了几次拷贝

猜你喜欢

转载自blog.csdn.net/weixin_45284133/article/details/106871047
今日推荐