Kafka拆解,kafka里都有什么

216 阅读12分钟

写在最前面

大家好,我是傻鱼,这篇文章主要是拆解了kafka的组成构建,以及告诉大家每一个构建在kafka中所述角色,和提供了什么功能.

实际上对于中间件,最佳的了解手段就是去访问他的官方主页:kafka.apache.org/intro. 有能力的同学可以自行阅读英文文档

kafka的设计

首先大家可以看一下kafka在整体服务架构中负责的角色. kafka架构.png 了解kafka你就要知道kafka其中的内容,下面是一些你需要注意的名词我们会在后续中对其一一解释:

  • Topic 主题
  • Provider 生产者
  • Consumer 消费者
  • Broker 服务
  • Cluster 集群

kafka的流程

准备流程:

准备设置topic备份的数量,broker集群的数量进行管理->zk进行注册->选举集群领导broker作为整体管理->zk节点管理注册完毕,等待被使用

生产过程:

provider进行消息生产->发送至topic->topic进行消息批量接收并进行ack备份->consumer拉取消息消费->offset进行偏移kafka自动,或者我们手动提交->消息消费完毕

什么是topic

对于kafka你需要掌握的第一个名次就是topic.所有的kafka数据都将被存储于topic当中,你可以理解为topic就是一台电脑中的一个文件夹,文件夹用来区分不同的文件,每个消息发送存储的管道,就可以理解为一个topic.

每个topic中又有无数多个event来组成,这些event你可以理解为这个文件夹中的数据文件.kafka可以设置数据的过期策略,这样所有在kafka中的数据都可以按照你的意愿进行处理.

另一个关于topic的重要概念是topic是分区的\分块的(segment),这意味着这些topic是一段一段的保存在brokers中.这个概念对kafka的可拓展性非常重要,这意味着你可以对复数级别的brokers同时进行读写操作.

image.png

为了足够的容灾处理,所有的topic都能被复制,这意味着你的每次写入,都有多个brokers会记录你的写入,这样保证当一个topic崩溃的时候,能够有其余的topic能够顶上.

Provider生产者

生产者直接将数据发送到作为分区领导的代理,中间没有任何路由层。为了帮助生产者做到这一点,所有Kafka节点都可以回答一个元数据请求,关于哪些服务器是活的,在任何给定的时间,topic分区的leader在哪里,以允许生产者适当地引导它的请求。

kakfa使用了批处理技术,Kafka生产者将尝试在内存中累积数据,并在单个请求中发送更大的单次数据。我们可以配置:批处理积累的消息数量,等待时间不超过某个固定延迟上限(比如64k或10ms)。这允许一次交互积累更多的字节来发送,并且很少在服务器上进行更大的I/O操作。这种缓冲是可配置的,并且提供了一种机制,可以牺牲少量的额外延迟以获得更好的吞吐量。

保证provider不丢消息:

1.我们可以使用get方法来获得这次消息的发送情况,但这会让kafka的send()方法变成一个同步方法.

SendResult<String, Object> sendResult = kafkaTemplate.send(topic, o).get();
if (sendResult.getRecordMetadata() != null) {
  logger.info("生产者成功发送消息到" + sendResult.getProducerRecord().topic() + "-> " + sendRe
              sult.getProducerRecord().value().toString());
}

2.好的办法是使用kafka提供的回调函数,并进行一定次数的重试.另外建议重试时间中间有一定的延迟,这样可以防止网络抖动带来的发送失败.对于必须保证消息生产发送成功的消息,建议对发送失败的回调进行数据补偿,如果发送不成功进行记录,找到发送失败的原因,进行手动补偿发送.

ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, o);
        future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),
                ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));

kafka基本架构.png

Consumer消费者

Kafka消费者通过向代理发出“fetch”请求来处理它想要使用的分区。消费者在每个请求中指定其在日志中的偏移量,并接收从该位置开始的日志块。因此,用户对这个位置有很大的控制权,并可以在需要时倒带它以重新使用数据。

kafka采用的是pull的形式进行消息消费的,优点:1.当消费端消费能力不够的时候,往往对push的消息会直接进行拒绝.2.pull的形式消费者可以选择进行批处理的方式拉取数据,这样可以最程度上减少不必要的延时.缺点:1.基于拉数据的方式,如果长时间没有数据,消费者可能会进行长时间的轮询,为了避免这个情况,kafka设置了pull请求的一个参数,允许消费者进行长轮询阻塞,直到数据到达

在讨论偏移量消费之前让我回忆一下常用的消息中间件对数据的处理方式.对消息的消费会进行元数据mark.在消费完成后,会对消息进行消费完毕的mark.但是这种情况会带来一个问题,如果元数据消费完毕

Kafka对消息有不同的处理方式。我主题被划分为一组完全有序的分区,每个分区在任何给定时间都恰好由每个订阅消费者组中的一个消费者使用。这意味着消费者在每个分区中的位置只是一个整数,即下一个要消费的消息的偏移量。这使得被消耗的资源的状态非常小,每个分区只有一个数字。可以定期检查该状态。这使得等价的消息确认非常便宜。这就意味着kafka的consumer只需要对偏移量进行负责,而不需要对具体的消息负责.另一方面说明kafka的消息没有元数据这个概念

如何不丢消费消息我们在后面会进行详解

Broker服务

kafka中每一个topic都需要有人进行管理,你可以理解为topic就是一个线程,线程的启动依赖于一个服务.所以Broker可以理解为包含topic运行的一个服务. 对下Broker负责管理自身当中所有topic的运行.当topic崩溃的时候broker还负责在多个topic当中进行topic选举.

除此之外,kafka还有特殊的acks同步,也是经由broker进行管理.在上一节讲过的consumer对于偏移量的管理,实际上就是由broker进行管理.kafka特有的消息消费机制需要broker进行配合标记偏移量来达成.

ack:同步集群数量,当有多个topic备份集群的时候,你可以确认有多少个集群被同步之后就算本次消息发送成功,如果有2个备份topic当ack设置为1时至少要有一个备份topic同步了来主topic的消息才能算是本次消息发送成功. kafka内部.png

Cluster集群

cluster可以理解为整体管理多个broker的集群服务,他负责管理整个kafka的集群运作,也是将其注册到zooker上的重要角色.实际上你可以理解cluster就是一个抽象意义,实际上是由无数多个broker一起组成cluster.

在实际过程中,会有很多broker记录在zookeeper中,有一个broker会被通知到进行管理的职能,当有一个broker异常了,负责管理的broker就会去检查他是否有副本broker,并且去便利其他broker看是否能够选举成新的leader.

什么铸成了kafka的高效

零拷贝技术

kafka的建立之初其实是基于大家对磁盘应用的不足,一磁盘的存储实际上在数据库中的空间内容都是不连续的,这会造成随机存储过程中有很大部分的时间都花在对数据定位以及对磁盘的io上.仔细研究磁盘过后可以发现,磁盘有非常多的io都是以字节为单位进行操作的,这些操作会频繁的消耗我们整体的效率.(可以类比为mysql,b+树对磁盘的查询)

其次就是硬盘技术在发展过程中,出现了缓存这么个东西,数据交互都需要从磁盘copy到缓存,再在缓存进行数据处理,再发送到服务socket进行发送,kafka借助缓存技术,将数据直接在缓存进行处理,这使java能够快速的对数据进行交互处理.而kafka的设计会保证数据都整理成连续的数据段,这样与磁盘保存落库交互过程中,就可以把数据全部都放在一个连续的空间中进行处理.

为了理解这个过程,给大家描述一下理解数据从文件传输到套接字的公共数据路径:

1.操作系统将数据从磁盘读取到内核空间的页缓存中 2.应用程序将数据从内核空间读入用户空间缓冲区 3.应用程序将数据写回内核空间到套接字缓冲区中 4.操作系统将数据从套接字缓冲区复制到NIC缓冲区,在那里数据通过网络发送

可以看到这里进行了4次的copy操作,和两次的系统之间调用. 实际上,只需要一次copy就能搞定这个过程 要看怎么操作的可以看这个文章developer.ibm.com/articles/j-…

数据的批处理

批处理导致了更大的网络数据包,更大的顺序磁盘操作,连续的内存块.而以上的这些信息保证了kafka能够将碎机消息写入改变为顺序消息写入,将这个给到消费者.

而很多时候系统的交互瓶颈来自于网络贷款,一旦kafka将各种数据进行整合之后,kafka就可以更高效的将数据进行压缩处理.

消息的消费传输

我们已经知道了kafka中生产者和消费者的部分知识,现在我们需要知道,消息在传输过程中是怎么进行的. 为了保证消息的传输,kafka提供了三种消费的形式:

  • At most once-在大多数情况下,消息可能会丢失,但永远不会重新发送。
  • At least once-至少有一次——消息永远不会丢失,但可以重新发送。
  • Exactly once-正是这样——这是人们真正想要的,每条信息只传递一次。

kafka使用offset进行控制保证了这三个等级的消费形式.

  • 当你想要的是至多一次的消费,在你的消费者消费失败,或者出现offset记录错误的时候,kafka会永远从记录的offset的后面开始进行消费,这样保证被标记的offset不会被重复消费.
  • 当你想要的是至少有一次的消费情况的时候,kafka会从记录offset的那一次开始从头消费这样,消费的过程会对标记的offset进行再次消费,保证达到至少有一次的消费情况.
  • 为了保证每条消息只消费一次的情况,在一个系统执行的时候,我们通常对offset标记和消费进行事务mark.将offset标记结束作为本次消费事件的结尾,这样就能保证每个消息被正确的消费一次.在多系统执行的过程中,可以使用两阶段提交的方式来完成对offset的标记,这和分布式事务的处理方式一致. kafka默认是保证至少一次传输的,如果使用者有需要,可以根据自己需要去调整这个策略.

消息的复制

kafka是一个由zk管理主从节点的高可用中间件,其丛节点会从主节点的partition log中复制相应的数据内容和offset到自己的节点上,其获取的方式和消费者的获取方式一致.

在节点消费的过程中,zk会监控每个从节点的消费情况,对消费落后的从节点进行控制,一旦发现从节点消费失败,或者消费过于落后,zk就会标记这个节点为不可用节点,这样出现问题的时候也不会从这个节点替换为主节点.

不可避免的是,kafka仍旧不可避遍在主节点异常的时候,每个从节点能够保证其里面的所有内容都和主节点一致,但是可以肯定的是只要是提交完毕的log消费,从节点是可以100%同步过的.

为什么kafka比RocketMQ吞吐量更高,MQ选型

kafka性吞吐量更高主要是由于Producer端将多个小消息合并,批量发向Broker。kafka采用异步发送的机制,当发送一条消息时,消息并没有发送到broker而是缓存起来,然后直接向业务返回成功,当缓存的消息达到一定数量时再批量发送。

此时减少了网络io,从而提高了消息发送的性能,但是如果消息发送者宕机,会导致消息丢失,业务出错,所以理论上kafka利用此机制提高了io性能却降低了可靠性。 zhuanlan.zhihu.com/p/163246737

名词解释

事件流:事件流类似于数据库,他把数据按照时间的形式平凑成一个数据流,你可以理解为这是一个链表结构的数据,当你消费了上一个数据后,下一个数据就会实时的传输过来,他可能不想database一样可以提供数据的查询之类的.但是他的好处就是能够实时的将数据流反馈到你的业务表达上.