kafka分布式架构原理以及数据重复消费、数据丢失、消息顺序性、消息积压等的解决方案

本文详细介绍了kafka分布式架构的变迁,包括0.8版本前后的变化,并探讨了kafka如何解决消费幂等性、数据丢失、数据顺序性以及消息积压等问题。通过设置副本机制、调整配置参数和优化消费策略,确保了kafka的高可用性和数据可靠性。

一、kafka消息系统架构的变迁

  kafka是一个分布式架构的系统,集群可以部署在多台机器上,每台机器可以启动一个或多个borker进程,每个broker进程是kafka集群中的一个节点,当创建topic时,可以把topic划分为一个或多个partition,每个partition存放在不同的broker上,且每个partition可以(生产者可以指定partition进行存储)存放所属topic的一部分数据。

1.1、kafka0.8版本前的架构

  在0.8版本前的架构如果某个broker宕机了,该broker中的存放topic的partition中的数据都无法进行读取,也就是没有HA(高可用)机制。如下图所示:

kafka0.8版本前的架构

1.2、kafka0.8版本之后增加了HA副本机制

在这里插入图片描述

  A、在创建topic是可以通过--replication-factor 3的方式指定每个partition的副本数为3个,kafka会把该partition的副本均匀的分布到不同的broker上,提高容错性

  B、partition的多个副本通过选举选出leader,所有的生产者、消费者都与这个leader 副本进行交互,然后再同步到其他副本

  C、如果某个broker宕机了,该broker的partition在其他broker上副本会选举出一个新leader负责新的读写任务,这个就是kafka的高可用机制

  D、可通过设置生产者客户端request.required.acks参数的方式,实现消息的同步反馈机制:

    -1:生产者消息发送后,leader副本写入本地磁盘,然后成功同步给ISR (In Sync Replicas)中所有副本,leader副本才返回成功给生产者,可以达到最强可靠性

    0:生产者消息发送后,无需等待响应,如果此时leader宕机或者写入异常,生产者无法得知导致消息丢失,但能达到最大吞吐量

    1:生产者消息发送成功后,只要leader副本写入磁盘成功即返回成功给生产者客户端,这是在可靠性以及吞吐量之间的一个折中方案

  E、生产者发送的数据只有在ISR列表中所有的副本都写入成功后才会被消费者消费到

  F、topic的partition副本并不是任何时候都会存在ISR中,在副本消息滞后过多(replica.lag.max.messages=4000)、宕机无响应(replica.lag.time.max.ms=10000)、或者新加入的副本不会存在ISR中,只有消息同步趋于一致的情况下才会存在于ISR中

  G、每个partition副本中文件分段进行存储,可以通过设置文件分段大小,超过段的大小,会生成一个新的文件段。每个段分实际数据文件*.log和索引文件*.index。每个新段的文件名都是上一个段中记录的最后一条消息的索引值offset

kafka数据文件

  H、kafka通过顺序写盘配合索引的方式,提高性能,性能趋近于随机写内存

二、kafka常见系统性问题

2.1、kafka如何解决消费幂等性,也就是保证消息不被重复消费

  A、kafka为每条消息都分配一个offset,代表这条消息的顺序;

  B、消费者消费数据后提交offset确认消息已被消费,kafka有两种提交offset的方式:

    自动提交:在消费者客户端设置enable.auto.commit=true和auto.commit.interval.ms完成自动提交,fetch到消息后就可以更新offset,无论消费逻辑是否处理成功;可能会造成数据丢失,消费者逻辑处理失败,但kafka消费组offset已改变。

    手动提交:在消费者客户端设置enable.auto.commit=false,fetch到消息并处理消费逻辑成功后,调用consumer.commitSync()提交offset,如果消费逻辑处理失败,不提交offset,消息会被重新消费

  C、消息被消费并处理业务逻辑后,在提交offset之前系统重启了,下次再消费时消息会被重复消费,此时需要消费者增加相应的逻辑保证数据消费幂等性,如:

    假如拿到数据后只写库,可通过唯一键更新插入的方式解决

    如果是写入redis可以不用考虑,直接set,redis天然具有幂等性

    假如不是以上两种逻辑,可通过在消息中增加全局唯一键的方式,在数据消费处理前从redis查询是否消费过,如果没有消费过就处理并写入redis,如果已经消费过了,就不做任何操作,直接提交offset即可

  D、在kafka0.9版本以前,是通过zookeeper来存储offset的,数据消费成功后会把offset提交到zookeeper,但这里也会存在网络传输问题,offset写入效率较低,且zk与kafka的offset变化确认也需要网络IO,这就导致了offset的维护存在不确定性和低效;在kafka0.9版本之后,kafka把offset维护在broker上,offset交由__consumer_offsets函数来处理

2.2、kafka如何确保数据不丢失

  从kafka架构图可知,当kafka的broker宕机,重新给partition副本选举leader时,其他的follower还有数据没有同步完成,leader选举完成后会有部分为同步完成的数据丢失掉,可通过以下配置解决服务端数据丢失问题:

    A、创建topic时,指定topic的每个partition至少拥有两个副本,通过设置replication.factor参数设置partition副本数量

    B、在kafka服务端设置min.insync.replicas大于1,也就是partition在ISR中最小副本数,只有request.required.acks=-1时发生效用,此时如果ISR中副本数小于设定值客户端会报NotEnoughReplicasExceptoin的异常

    C、在生产者端设置request.required.acks=-1,要求每条消息都必须成功写入ISR中的所有副本才返回给生产端

    D、在生产端设置retries的值多次重试,生产端写入失败通过重试重新发送,多次重试依然失败的情况下,建议在发送回调中写入到本地磁盘或者写入数据库通过定时任务重新发送,防止数据丢失

2.3、如何确保数据的顺序性

  topic在单partition的情况下消息肯定是顺序的,但性能较差;在多partition的情况下如需保证消息的顺序性需要做一些特殊处理

  A、生产端指定partition发送,由于同个partition内消息是顺序的,在生产端可根据某条数据的id(特定规则)进行hash然后指定发送到固定的partition中,保持有序性。主要是通过实现Partitioner类,重写partition方法实现:

package com.zcj.study.mq.kafka;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.record.InvalidRecordException;
import java.util.List;
import java.util.Map;

/**
 * @description 根据key进行hash发送到特定的partition中
 * @author zcj
 * @date 2020/10/12 21:56
 */
public class IdHashPartitioner implements Partitioner {
   
   

    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
   
   
        List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值