当前位置: 首页 > 科技观察

卡夫卡的生死系列11问

时间:2023-03-14 18:22:42 科技观察

该做什么,该做什么,看小说第一名。不,我写的文章很好。最近在整理文章目录,因为很久之前有个师兄跟我说之前的文章找不到了,懒得整理了。自己编的,以前没写过,可能是很难想起来从哪里收集这么多问题???看文章目录的话,可以在公众号菜单栏中间看到,方便大家以后看。先说说你对kafka的理解。Kafka是一个流式数据处理平台。它具有消息系统的能力和实时流式数据处理分析能力,但我们更愿意将其作为消息队列系统来使用。如果按照简单理解来分层的话,大致可以分为三层:第一层是Zookeeper,相当于注册中心。它负责Kafka集群元数据的管理和集群的协调。是时候连接到Zookeeper并在Zookeeper中注册自己了。第二层是Kafka的核心层,包含了很多Kafka的基本概念:record:代表消息topic:topic,消息会按照topic来组织,可以理解为消息的分类。producer:生产者,负责发送消息consumer:消费者,负责消费消息顺序读取,不同分区不能保证顺序。分区就是我们常说的数据分片sharding机制。主要目的是提高系统的可扩展性。通过分区,可以将消息的读写负载均衡到多个不同的分区。节点上的Leader/Follower:分区的副本。为了保证高可用,分区都会有一些副本。每个分区都会有一个Leader主副本负责读写数据。Follower从副本只负责与Leader副本保持数据同步,不对外提供任何服务。offset:偏移量,在partition中每条消息都会有一个按照时间顺序递增的序号。这个序列号就是偏移量。MessageCoordinator:协调器主要为消费者组分配分区和rebalanceRebalance操作Controller:控制器实际上只是一个broker,用于协调和管理整个Kafka集群。他将负责分区Leader选举、主题管理等,在Zookeeper中创建的第一个临时节点/控制器将成为控制器。第三层是存储层,用来保存Kafka的核心数据,它们最终会以日志的形式写入磁盘。你知道消息队列模型吗?kafka是如何支持这两种模型的呢?对于传统的消息队列系统,支持两种模式:点对点:即消息只能被一个消费者消费。消费后,消息被删除、发布和订阅:相当于广播模式,消息可以被所有消费者消费。上面说了,Kafka其实是通过ConsumerGroup来支持这两种模型的。如果所有的消费者都属于一个组,并且消息只能被同一个组中的一个消费者消费,那就是点对点模型。如果每个消费者都是一个单独的Group,那么就是发布-订阅模型。事实上,Kafka通过消费者分组灵活支持这两种模式。能说说kafka通信过程的原理吗?首先,kafkabroker启动时,会向Zookeeper注册自己的ID(创建一个临时节点)。此ID可以配置或自动生成。同时会订阅Zookeeper的brokers/ids路径。当有新经纪商加入或退出时,您可以获取当前所有经纪商信息。当生产者启动时,它会指定bootstrap.servers。通过指定的broker地址,Kafka会与这些broker建立TCP连接(通常我们不需要配置所有broker的服务器地址,否则kafka会与所有配置好的broker建立TCP连接)连接到任意一个broker后,然后发送请求获取元数据信息(包括有哪些topic,topic有哪些partitions,partition有哪些replicas,以及partitionTheLeadercopy等信息)然后会和所有broker建立TCP连接,然后会是发送消息的过程。消费者和生产者还将指定引导程序。leader所在的broker,然后与coordinatorbroker建立TCP连接,获取元数据。根据分区中leader节点所在的broker节点,与这些broker建立连接,最后开始消费消息。发送消息时如何选择分区?主要有两种方式:轮询,消息按顺序随机发送到不同的分区,随机发送到一个分区。如果消息指定了key,则根据消息的key进行hash,然后对partition分区数取模,确定落在哪个partition上。因此,相同key的消息总会被发送到同一个分区,也就是我们常说的消息分区顺序。一个很常见的场景是,我们希望订单消息和支付消息是有序的,这样以订单ID为key发送消息就达到了分区顺序的目的。如果不指定key,则执行默认的round-robin负载均衡策略,比如第一条消息落在P0上,第二条消息落在P1上,第三条消息落在P1上。另外,针对一些特定的业务场景和需求,也可以通过实现Partitioner接口,重写configure和partition方法来达到自定义分区的效果。好的,那你为什么认为需要分区?有什么好处?这个问题很简单。如果没有分区,我们在发送消息和写入数据时,只能将数据保存在一个节点上。在这种情况下,无论这个服务器节点的性能再好,最终也撑不住了。事实上,分布式系统都面临着这个问题。他们要么在收到消息后对数据进行划分,要么提前进行划分。Kafka选择前者,数据可以通过分区均匀分布到不同的节点上。分区带来了负载平衡和横向扩展的能力。发送消息时,可以根据分区数落在不同的Kafka服务器节点上,提高了并发写入消息的性能。消费消息时,它们与消费者绑定。可以从不同节点的不同分区消费消息,提高了读取消息能力的性能。另一个是分区引入了副本。冗余副本保证了Kafka的高可用和高持久性。详细说说消费者群体和消费者再平衡?Kafka中的消费者组订阅主题主题消息。一般来说,consumer的个数应该和所有topic分区的个数一致(比如用一个topic,其实当然可以订阅多个topic)。当消费者数量小于分区数量时,一定有一个消费者消费多个分区的消息。当消费者数量超过分区数量时,必然会出现没有分区消费的消费者。所以,一方面上面说了消费组的好处,可以支持多种消息模型,另一方面支持根据消费者和分区之间的消费关系进行横向扩缩容。当我们知道消费者是如何消费分区的时候,显然会有一个问题,消费者消费的分区是怎么分配的,有消费者先加入怎么办?老版本的rebalancing过程主要由ZKserver监听触发,每个consumerclient自己执行分区分配算法。新版本由协调员完成。每次有新的消费者加入,都会向协调器发送请求,获取分区的分配情况。本次分区分配的算法逻辑由协调器完成。再平衡是指新消费者加入的情况。比如一开始只有消费者A在消费消息。一段时间后,消费者B和C加入进来,此时需要重新分配分区。这就是再平衡。平衡也可以称为重平衡,但是重平衡的过程很像我们GC时的STW,会导致整个消费者组停止工作,在重平衡期间不能发送任何消息。此外,这并不是唯一发生再平衡的情况,因为消费者和分区总数之间存在绑定关系。如上所述,消费者的数量最好与所有主题的分区总数相同。只要消费者数量、主题数量(例如定期订阅的主题数量)和分区数量中的任何一项发生变化,就会触发重新平衡。再说说再平衡的过程。再平衡机制依赖于消费者和协调者之间的心跳来维持。消费者将有一个独立的线程定期向协调器发送心跳。这个可以通过参数heartbeat.interval.ms来控制发送心跳的时间间隔。当每个消费者第一次加入该组时,它会向协调器发送一个JoinGroup请求。第一个发送这个请求的消费者将成为“群主”,协调器将群成员列表返回给群主进行分区分配。Strategy,然后通过SyncGroup请求将分配结果发送给协调器。协调器收到分区分配结果,组内其他成员也将SyncGroup发送给协调器。协调器分别响应每个消费者给他们的分区分配。你我再详细说说分区分配策略?主要有三种分配策略:Range不知道如何翻译,这是默认策略。大致意思是对分区进行排序,排序越高的分区可以分配给更多的分区。比如有3个分区,消费者A排名靠前,可以分配到两个分区P0\P1,而消费者B只能分配到一个P2。如果有4个partition,那么它们会恰好分配给2个。但是这个分配策略有点问题。它是按照topic来分配的,所以如果consumergroup订阅了多个topic,可能会导致partition分布不均。比如下图中的两个topic的P0\P1都分配给了A,这样A有4个partition,而B只有2个,如果这样的topic越多,不平衡会越严重。RoundRobin也就是我们常说的轮询。这个比较简单,不用画图也能很容易理解。这样会根据所有主题进行循环分配,不会出现Range中主题越多分区分配越不均衡的问题。P0->A,P1->B,P1->A。..以此类推,Sticky字面意思就是粘性策略,大概就是这个意思。主要考虑是在均衡分布的前提下,对分区的分布做较小的改动。比如之前P0\P1分配给消费者A,下次再尝试分配给A。这样做的好处是可以重用连接。要使用消息,始终需要连接到代理。如果能保留上次分配的分区,就没有必要频繁销毁和创建连接。快点!如何保证消息的可靠性?报文可靠性的保证,基本上从三个方面来说明(这个比较全面,无懈可击)。生产者发送消息并丢失。Kafka支持3种发送消息的方式,也就是常规的3种方式,不管发送后结果如何,同步发送,异步发送,基本上所有的消息队列都是这么玩的。sendandforget,直接调用send方法,不管结果如何,虽然可以开启自动重试,但是肯定会有消息丢失的可能。同步发送,同步发送返回一个Future对象,我们可以知道发送结果,然后处理异步发送,发送消息,同时指定一个回调函数,根据结果进行相应的处理。为了保险起见,我们一般采用异步发送的方式,通过回调的方式来发送消息,然后设置参数在发送消息失败的时候不停的重试。acks=all,这个参数可以配置0|1|all。0表示生产者不管服务器的响应如何都写入消息,消息可能还在网络缓冲区中,服务器根本收不到消息,消息当然会丢失。1表示至少有一个副本收到消息并认为成功。一个副本必须是集群的领导副本。但是如果leader副本所在的节点恰好挂了,follower没有同步消息,消息还是丢失了。如果配置all,则表示所有ISR都写入成功,除非所有ISR中的copy都挂掉,否则消息会丢失。retries=N,设置一个非常大的值可以让生产者在发送消息失败后重试Kafka自身的消息丢失。Kafka因为消息是通过PageCache异步写入磁盘的,所以还是有丢失消息的可能。因此,针对Kafka自身可能存在的丢失,设置参数:replication.factor=N,设置一个比较大的值,保证至少有2个或更多的副本。min.insync.replicas=N,表示如何认为消息写入成功,设置一个大于1的数,保证至少写入一份或多份才算消息写入成功。unclean.leader.election.enable=false,这个设置意味着没有完全同步的partitionreplicas不能成为leaderreplicas。如果为true,那些与leader没有完全同步的replicas成为leader,存在消息丢失的风险。如果消费者消息丢失了,那么丢失消费者可能会比较简单。把位移的自动提交关掉就好了,改成业务流程成功的手动提交。因为当rebalancing发生的时候,consumer会去读取上次提交的offset,而默认自动提交是5秒一次,会导致重复消费或者消息丢失。enable.auto.commit=false,设置为手动提交。我们可能还需要考虑另一个参数:auto.offset.reset=earliest。该参数表示当没有offset提交或者broker上没有offset时,consumer如何处理。earliest表示从分区的开头读取,消息可能会被重复读取,但不会丢失。一般来说,消费者必须自己保证幂等性。另一种最新的意思是从分区末尾读取,有丢失信息的概率。结合这些参数设置,我们可以保证消息不会丢失,可靠性有保障。OK,说说replica及其同步原理?前面说过,Kafka副本分为Leader副本和Follower副本,即Master副本和Slave副本。与Mysql等其他的不同,Kafka中只有Leader副本会对外提供服务,而Follower副本只是简单的与Leader保持数据同步,起到数据冗余和容灾的作用。在Kafka中,我们统称所有副本的集合为AR(AssignedReplicas),与Leader副本同步的副本集合称为ISR(InSyncReplicas)。ISR是一个动态集合。维护这个集合会被replica.lag.time.max.ms参数控制,这个参数代表滞后Leader副本的最长时间。默认值是10秒,所以只要Follower副本不落后于Leader副本超过10秒或更多,就可以认为是与Leader同步了(简单的可以认为是同步时间差).副本间同步还有两个关键概念:HW(HighWatermark):Highwatermark,也叫复制点,表示副本间同步的位置。如下图,0~4绿色表示已提交的消息,这些消息已经在副本之间同步,消费者可以看到这些消息并消费,4~6黄色表示未提交的消息,可能还没有副本之间的同步,这些消息对消费者来说是看不见的。LEO(LogEndOffset):下一条要写入的消息的位移hw。副本之间的同步过程依赖于HW和LEO的更新。副本之间同步消息的过程通过它们的值变化来展示。绿色代表Leader副本,黄色代表Follower副本。首先,生产者不断向领导者写入数据。这个时候leader的LEO可能已经到了10,但是HW还是0,两个follower向leader请求同步数据,他们的值都是0。然后,消息继续写入,Leader的LEO值又变了。两个Follower也拉取了自己的消息,更新了自己的LEO值,但是此时Leader的HW没有变化。这个时候Follower又从Leader那里拉取数据。此时Leader会更新自己的HW值,取Follower中最小的LEO值进行更新。随后,Leader将自己的HW响应给Follower,Follower更新自己的HW值。因为又拉取了消息,所以又更新了LEO,过程以此类推。你知道为什么新版Kafka放弃了Zookeeper吗?这个问题我觉得可以从两个方面来回答:第一,从运维的复杂度来看,Kafka本身就是一个分布式系统,它的运维已经很复杂了,另外,还需要严重依赖另一个ZK,这在成本和复杂性方面都是很多工作。其次,应考虑性能问题。比如之前提交位移的操作都是存储在ZK中,但是ZK其实并不适合这种高频的读写更新操作,会严重影响ZK集群。在性能方面,在新版本中,Kafka还以消息的形式处理了提交和存储位。此外,Kafka严重依赖ZK来实现元数据管理和集群协调。如果集群规模大,topic和partition数量多,ZK集群的元数据过多,集群压力过大,直接影响很多Watch的延迟。时间或丢失。OK,大家问的最后一个问题,Kafka为什么快?嘿,这是我的费用,我背了很多遍!主要有三个方面:SequentialIOkafka使用append的方式将消息写入分区,也就是顺序写入磁盘,而不是随机写入,这个速度比普通的随机IO快很多,几乎可以媲美网络IO的速度。PageCache和零拷贝Kafka在写入消息数据时使用mmap内存映射。它们不是立即写入磁盘,而是使用操作系统的文件缓存异步写入PageCache,从而提高写入消息的性能。消费消息时,通过sendfile实现零拷贝。我写过mmap和sendfile零拷贝,你可以在这里阅读:阿里二面:什么是mmap?批处理和压缩Kafka在发送消息时并不是一条一条发送消息,而是将多条消息合并成一批处理发送,消息的消费也是一样的。一次拉取一批消息进行消费。此外,Producer、Broker、Consumer都使用了优化的压缩算法。发送和message消息使用压缩节省了网络传输的开销,Broker存储使用压缩减少了磁盘存储空间。我是艾小仙,过两天就要喷人了。