Kafka详细总结

1 消息队列

1.1 基本定义

  用于两个系统或者两个模块之间的消息传递,用于缓存消息的队列,就叫消息队列。传统的消息传递在数据传输的过程中可能会出现数据丢失。如果基于消息队列进行数据的传递的话是不会出现数据丢失的,适用于异步模式。

1.2 消息传递的两种模式
1.2.1 同步模式

  用户需要等待,必须等待服务器真正的完成,才能看到结果,结果必须为处理完成后的结果。

1.2.2 异步模式

  用户不需要等待,数据传递之后不用等待真正的处理结果。

1.3 消息传递的应用场景
1.3.1 应用解耦

  实现各个系统之间或者模块之间的解耦。

1.3.2 限流削峰

  在高并发环境下,避免高并发带来的业务的峰值,提高系统的稳定性和健壮性。

1.3.3 消息驱动系统

  可以使用消息队列构建多个系统之间的连接。

1.4 消息队列的两种模式
1.4.1 点对点的数据传递

  1. 几乎不会使用到。
  2. 只允许一个消费者进行消费。
  3. 消费成功后,这条数据就会被删除。
  4. 适合于多个生产者、单个消费者的模式。

1.4.2 订阅发布模式

  1. 多个生产者生产数据,可以生产到不同的主题,也可以生产到相同的主题。
  2. 多个消费者可以共享数据,数据不会被删除。
  3. 随意的添加生产者和消费者。
  4. 一个消费者可以消费多个主题的数据。
  5. 一个主题可以被多个消费者消费,每个消费者会拿到一份完整的数据。

2 CAP理论

2.1 一致性(Consistency)

  在分布式结构中,我们通过其中一个节点来实现写或者更改操作,在分布式其他的节点中,也能读到对应的更改。

2.2 可用性(Availability)

  如果分布式结构中,某一个节点发生故障,其他任何一个没有发生故障的节点,必须在一定的时间内能返回合理的结果【能正常提供读写所有的数据】。

2.3 分区容错性(Partition Tolerance)

  如果任何一个分区故障,必然要有对应的容错机制来保证分区数据的正常提供。
Kafka中的分区的主副本与从副本是存在数据差的,如果leader副本故障,从副本不一定能提供对应的数据

3 Kafka中的CAP

  Kafka满足CA,不满足P,C:在kafka的任意节点上做对应的修改操作,在其他任意节点上都能看到对应的更改。A:机器宕机,其他机器上还有对应的副本。P:kafka中主副本与从副本之间存在数据差,甚至可能会没有可用的从副本。

4 Kafka的介绍

  分布式的流平台,但是在实际使用中,大部分情况下都是把它做为一个高可靠的分布式的可扩展的高吞吐量的消息队列系统。

5 Kafka的特点

  1. 可靠性。分布式结构存储数据,构建多台KafkaServer集群。
  2. 可扩展。横向扩展,机器不够可以通过加机器扩大集群规模。
  3. 耐用性。Kafka中的数据分区存储在每台机器的磁盘上,数据存储在Kafka中不易丢失。
  4. 性能。Kafka使用顺序读写磁盘的策略,不用磁盘寻址,并且还具有零拷贝的特性。
  5. 安全。Kafka数据是分区存储的,并且支持副本机制。

6 Kafka架构

  Kafka中央控制器,在Kafka的所有broker启动的时候会通过zookeeper选举出一个broker作为中央控制器,负责监督和管理整个Kafka集群中broker和分区的状态。选举规则为:抢注原则,谁先在zookeeper中创建了临时节点,谁就是中央控制器,如果这个节点故障,其他所有正常的broker重新抢注,产生新的中央控制器。

7 Kafka涉及的概念

7.1 Topic主题

  主题,Kafka中用来对数据进行分类的概念,相当于数据库中的表。

7.2 producer

  生产者,负责往Kafka的Topic中生产数据,一个Topic可以有多个生产者。

7.3 consumer

  消费者,负责从Kafka的Topic中消费数据。消费者和Kafka的broker是一个多对多的关系,任何一个消费者都可以消费Kafka中多个主题,任何一个Topic都可以被Kafka中的多个消费者消费。

7.4 consumer group

  消费者组,一个消费者组可以有一个消费者或者多个消费者,但是一个消费者组是一个整体,对于Topic来说,这整个消费者组就是一个消费者,一个消费者组只消费一份数据。Kafka会记录每个消费者组所消费的情况。

7.5 partition分区

  一个Topic可以有多个分区来实现分布式存储,类似于Hbase中的region的概念,创建Topic的时候可以指定这个Topic有几个分区。数据写入Topic,要根据分区规则来写入对应的分区。

7.6 repartition副本

  分区的副本机制,每个分区都可以有多个副本。将分区数据复制多份,每份都是一份完整的数据。创建Topic的时候,也可以指定副本的个数,副本本来就是分区,如果某个正在使用的分区出现故障,副本可以代替出现故障的分区提供服务。副本的个数不能超过Kafka集群机器的总个数,最多相等。保证每个机器只有一份相同的数据。每个分区的多个副本之间是有主从关系的,一个主副本,多个从副本,提供服务的一直是主副本。

7.7 kafka中ISR、AR、OSR、HW、LEO的关系
7.7.1 AR

  在Topic创建的时候某个分区的所有副本,包括主副本和从副本。

7.7.2 ISR

  in sync replication所有与主副本不断同步的副本集合。

7.7.3 OSR

  outsync replication不可用副本,没有及时与主副本进行同步数据的副本,和主副本的数据相差太多。

7.7.4 HW

  最高水位。在Kafka对消费者提供服务的时候,支持的最新可消费的offset为HW-1。

7.7.5 LEO

  log end offset,当前每个分区的待写入的offset。这个offset其实当前是没有消息的。

7.7.6 关系

  AR = ISR+OSR,HW是当前所有的ISR(包括主副本)的最新offset的下一个offset,可提供消费者消费的offset是最高水位之前的所有offset总和。理想情况下HW应该和LEO相等的,但是因为Kafka的主副本和副本之间不能保证实时同步,中间会有误差,所有正常情况下HW应该小于LEO。可能会出现所有的副本都是OSR,然后主副本故障,没有副本可选。如果没有ISR,只要AR中任何一个可用的副本都可以。

7.8 Segment分区段/文件段

  kakfa的数据是以文件的形式存储在硬盘上的,任何一个segment分区段都会有两个文件,log和index文件,他们是成对出现,分别为.log和.index文件。.log存储的生产者生产的数据,.index存储的.log文件对应的索引信息。一般情况下会把index文件加载到内存中,用于以后查找数据的时候,需要读取文件查找,可以从index索引中找到要查找的数据在文件的什么位置。

7.9 offset

  偏移量,Kafka会记录每个消费者已经消费的偏移量,用于下次消费的时候,返回下一条数据,不在返回之前已经消费过的数据。通过offset来保证消费者消费Kafka的数据不丢失不重复,只消费处理一次。segment文件段的划分就跟offset有关。offset在各个分区是有序的,生产者提交一次数据,记录一次offset,每个分区都是从0开始。在实际使用中要手动控制offset的提交,如果让程序自动提交offset的话,可能会出现数据丢失和数据重复。

8 Kafka的安装与部署

  本次安装的版本为Kafka2.8.2版本,下载,上传到Linux服务器上解压

tar -zxvf kafka_2.13-2.8.2.tgz -C /opt/cloudera/
cd /opt/cloudera/kafka_2.13-2.8.2
#用于记录kafka的日志和kafka存储的数据
mkdir log
vim config/server.properties
#唯一的服务端id,同一个集群中id,不能相同,类似于zookeeper的myid
broker.id=0
#指定kafka的日志及数据【segment【.log,.index】】存储的位置
log.dirs=/opt/cloudera/kafka_2.13-2.8.2/log
#指定zookeeper的地址
zookeeper.connect= #填写自己的zookeeper的集群ip:port
#分发到其他机器上面并修改其他机器的服务端id和主机名
scp -r kafka_2.13-2.8.0 ...
broker.id=...
host.name=#自己的主机名
#先启动zookeeper
#在启动Kafka server
nohup bin/kafka-server-start.sh config/server.properties 2>&1 &
关闭命令:bin/kafka-server-stop.sh
#默认端口为9092

9 Kafka生产者的ACK机制

  生产者的ACK机制是生产者不丢失数据的一种保证。

 props.put("acks", "all");

  取值有三种,分别是0、1、-1/all
  1. 0代表生产者将数据提交给Kafka以后,就立即返回,生产下一条,不管Kafka有没有真正存储,最快,但是最不安全,数据会丢失。
  2. 1是Kafka默认的选项,生产者将数据提交给Kafka,Kafka写入对应分区的主副本,返回一个ack确认主副本已写入,然后生产者生产下一条。只要对应分区的主副本写入,就发送下一条,数据相对安全,但也可能存在数据丢失。生产者写入数据到一个主分区中,然后立马故障了,副本分区还没有来的及同步数据,然后主分区的所有分区进行抢注选举,新的副本分区上位,之前的那条数据就丢失了。
  3, all生产者将数据提交给Kafka,Kafka写入对应分区的主副本,等到所有从副本与主副本同步完成,kafka返回ack给生产者,生产者生产发送下一条,最安全,但是性能最低。虽然几乎是最完美的方案,牺牲了效率来保证数据的一致性的,但是不是绝对安全的。

10 分区规则

  分区规则和生产者生产的数据有关,如果在生产者往broker生产数据的时候带了key和value,那么分区规则为key的hash取余,这种方式可以保证相同数据在同一个分区中有序。如果在生产者往broker生产数据的时候只传递了value,那么则会使用轮询机制。当然也可以自定义分区规则,自定义分区规则可用通过生产者的partitioner.class参数来配置。

public class KafkaCustomPartitioner implements Partitioner {
    @Override
    public void configure(Map<String, ?> configs) {
    }
    @Override
    public int partition(String topic, Object arg1, byte[] keyBytes, Object arg3, byte[] arg4, Cluster cluster) {
        List<PartitionInfo> partitions =
            cluster.partitionsForTopic(topic);
        int partitionNum = partitions.size();
        Random random = new Random();
        int partition = random.nextInt(partitionNum);
        return partition;
    }
    @Override
    public void close() {
    }
}

11 Kafka为什么这么快的原因

  1. Pagecache,Kafka通过页缓存,在写入的时候会先把数据写入到页缓存中,后台通过线程不断地把数据写入segment文件中。
  2. Zero copy,零拷贝,属于一种硬件机制,在数据交换过程中,少了用户态和内核态的两次数据拷贝。
  3. 顺序读写磁盘,在数据写入磁盘的时候,会消耗大量的时间进行寻址,顺序写入避免了寻址使用顺序读写来提高读写速度。

12 数据安全

12.1 消息队列消费语义

  数据一次性语义:所有的消息队列都在解决这个问题。
  1. at most once :最多一次,0次或者1次,消息可能会丢失,但不会重复。
  2. at least once :至少一次,1次或者N次,消息可能会重复,但不会丢失。
  3. Exactly Once:只且一次,消息不丢失不重复,只消费成功一次

12.2 Kafka保证消费数据一次性语义

  手动分区提交offset保证消费者在消费数据的时候不重复不丢失。

13 segment分段规则

  每个segment都有两个文件:.log和对应的.index文件,这两个文件名处理后缀不同,其他都是相同的。
  分段的规则有两种方式,根据文件大小log.segment.bytes=1073741824、根据时间长度log.roll.hours=168/24 = 7天
  分段文件的命名规则:
  第一个segment的文件名称以0标记,不足20位左补0。
  第二个segment的文件名称时第一个文件的最大offset+1,不足20位左补0。
  思考:为什么要将一个分区的数据划分为多个segment呢?
  不分:
  1. 这个分区的所有数据全部写在一个.log文件中,这个.log文件会非常大,对于存储就会非常麻烦。
  2. 消息队列:临时的缓存数据,如果数据永远不再需要的了,可以删除,kafka可以配置自动删除。
  3. 如果一个分区的所有数据都在一个文件中,怎么删除过期的数据?牺牲的代价就比较高,以数据如果超过7天,就过期了 ,希望自动删除。
  分:
  1. 多个segment之间是有序的,同一时间只有一个读写的segment,其他的segment只提供读
  2. 写的segment比较小,容易操作。
  3. 如果数据过期,我们可以以segment为单位,删除过期数据。
  index与log文件成对出现,用于记录对应的log文件数据的位置索引,是稀疏索引,不是完整索引。
  完整索引:log文件中的每一条数据都在索引中有记录。
  稀疏索引:log文件中的数据只有部分数据有索引存储在index中。
  为什么要构建稀疏索引:数据量特别大,如果要用全量索引,索引所占用的内存会比较高,检索索引的性能也会下降
  index中记录的是这条数据在log文件中的第几条,和行对应的偏移位置,而不是全局的偏移量。
  为什么不记录偏移量,不就不用转换计算了嘛!
  如果写了偏移量,索引的检索性能会差。

14 Kafka的Java Api实现

14.1 生产者示例
/**
 * @ClassName UserKafkaProudcer
 * @Description TODO 用户自定义的Kafka的生产者,往Topic中生产数据
 */
public class UserKafkaProudcer {
    public static void main(String[] args) {
        //构建properties对象用于配置一些Kafka的属性,类似于在Hadoop或者Hbase中学的Configuration【Map集合】对象的作用
        Properties props = new Properties();
        //指定Kafkaserver的地址,可以写一个,最好写多个,用于故障转移
        props.put("bootstrap.servers", "node-01:9092,node-02:9092,node-03:9092");
        props.put("acks", "all");
        //如果发送失败,重试的次数
        props.put("retries", 0);
        //每个批次发送的数据大小
        props.put("batch.size", 16384);
        //每个批次发送的时间间隔
        props.put("linger.ms", 1);
        //如果数据量比较大,Kafka的负载比较高,可以将数据进行本地缓存,再批量发送给Kafka
        props.put("buffer.memory", 33554432);
        //Kafka是分布式的 ,数据以keyvalue形式存储写入Kafka,所以必然会有对key和value的序列化,指定序列化的类
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        //构建了一个Kafka的生产者对象,并且加载Kafka的用户自定义的属性
        Producer<String, String> producer = new KafkaProducer<>(props);
        for (int i = 1; i < 10; i++){
            //将数据写入某个topic,没有key,key为null
//            producer.send(new ProducerRecord<String, String>("bigdata1803", "itcast"+i));
            //将数据写入某个topic,有key和value
//            producer.send(new ProducerRecord<String, String>("bigdata1803", ""+i, "itcast"+i));
            //将数据写入某个topic的某个分区中,有key和value
            producer.send(new ProducerRecord<String, String>("bigdata", 0,""+i, "test"+i));
        }
        //关闭生产者
        producer.close();
    }
}
14.2 消费者示例
/**
 * @ClassName UserKafkaConsumer
 * @Description TODO 用于自定义一个kafka的消费者,订阅Topic,消费数据,手动管理每个分区的offset
 */
public class UserKafkaConsumerManaulPartitionOffset {
    public static void main(String[] args) {
        //构建Properties对象用于配置怎么从Kafka中读取数据
        Properties props = new Properties();
        //定义kafka的地址
        props.put("bootstrap.servers", "node-01:9092,node-02:9092,node-03:9092");
        //所有的消费者都是以消费者 组的形式来访问kafka消费topic,每个消费者必然属于某个消费者组,指定当前消费者属于哪个组
        props.put("group.id", "test01");
        //关闭自动提交
        props.put("enable.auto.commit", "false");
        //定义消费者读取数据时对key和value进行反序列化的列,类型必须与序列化的时候类型一致
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        //构建了一个kafka的消费者对象,并加载了对应的配置
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        //订阅消费哪个topic的数据,可以消费多个topic,默认消费这个topic的所有分区
        consumer.subscribe(Arrays.asList("bigdata1804"));
        //消费指定的分区的数据:实现只消费0和1这两个分区
//        TopicPartition bigdata18040 = new TopicPartition("bigdata1804", 0);
//        TopicPartition bigdata18041 = new TopicPartition("bigdata1804", 1);
//        consumer.assign(Arrays.asList(bigdata18040,bigdata18041));
        //工作中也是死循环,因为消费者要一直运行,不断消费数据
        while (true) {
            //通过调用消费者对象的poll方法去消费Kafka的Topic中的数据,参数代表,多长时间获取一次数据,如果为0,数据一产生就立马被消费
            ConsumerRecords<String, String> records = consumer.poll(100);
            //将获取这次获取到的数据对应的每个分区
            Set<TopicPartition> partitions = records.partitions();
            for (TopicPartition partition : partitions) {
                //对每个分区进行操作,每个分区操作成功,就提交一次该分区的最新偏移量
                //从这次的所有数据中获取属于这个分区的数据
                List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
                long offset = 0;
                for (ConsumerRecord<String, String> partitionRecord : partitionRecords){
                    //迭代取出这个分区的每一条数据
                    String topic = partitionRecord.topic();//取出这条数据属于哪个topic
                    int part = partitionRecord.partition();//这条数据是这个topic的哪个分区的数据
                    offset = partitionRecord.offset();//这条数据在这个topic的这个分区中的偏移量
                    String key = partitionRecord.key();//这条数据的key
                    String value = partitionRecord.value();//这条数据的value
                    //读取到数据以后进行处理:处理逻辑
                    System.out.println(topic+"\t"+part+"\t"+offset+"\t"+key+"\t"+value);
                }
                //执行到这一步:表示这个分区的数据已经消费成功 了,可以提交这个分区的偏移量
                //Map集合的第一个参数:这个分区
                //Map集合的第二个参数 :这个分区最新的偏移量,就是下一次要消费的起始位置
                Map<TopicPartition, OffsetAndMetadata> offsets = Collections.singletonMap(partition, new OffsetAndMetadata(offset+1));
                consumer.commitSync(offsets);
            }
        }
    }
}

15 Kafka常用配置

15.1 生产者配置
属性含义
bootstrap.servershostname:9092KafkaServer端地址
poducer.typesync同步或者异步发送,0,1,all
min.insync.replicas3如果为同步,最小成功副本数
buffer.memory33554432配置生产者本地发送数据的 缓存大小
compression.typenone配置数据压缩,可配置snappy
partitioner.classPartition指定分区的类
acks1指定写入数据的保障方式
request.timeout.ms10000等待ack确认的时间,超时发送失败
retries0发送失败的重试次数
batch.size16384批量发送的大小
metadata.max.age.ms300000更新缓存的元数据【topic、分区leader等】
15.2 消费者配置
属性含义
bootstrap.servershostname:9092指定Kafka的server地址
group.idid消费者组的名称
consumer.id自动分配消费者id
auto.offset.resetlatest新的消费者从哪里读取数据latest,earliest
auto.commit.enabletrue是否自动commit当前的offset
auto.commit.interval.ms1000自动提交的时间间隔
15.3 broker配置
属性含义
broker.idint类型Kafka服务端的唯一id,用于注册zookeeper,一般一台机器一个
host.namehostname绑定该broker对应的机器地址
port端口Kafka服务端端口
log.dirs目录kafka存放数据的路径
zookeeper.connecthostname:2181zookeeper的地址
zookeeper.session.timeout.ms6000zookeeper会话超时时间
zookeeper.connection.timeout.ms6000zookeeper客户端连接超时时间
num.partitions1分区的个数
default.replication.factor1分区的副本数
log.segment.bytes1073741824单个log文件的大小,默认1G生成一个
log.index.interval.bytes4096log文件每隔多大生成一条index
log.roll.hours168单个log文件生成的时间规则,默认7天一个log
log.cleaner.enablefalsefalse表示删除过期数据,如果为true,进行compact
log.cleanup.policydelete,compact默认为delete,删除过期数据,compact:表示合并,相同key的合并在一起。
log.retention.minutes分钟值segment生成多少分钟后删除
log.retention.hours小时值segment生成多少小时候删除
log.flush.interval.messagesLong.MaxValue消息的条数达到阈值,将触发flush缓存到磁盘
log.flush.interval.msLong.MaxValue隔多长时间将缓存数据写入磁盘
auto.create.topics.enablefalse是否允许自动创建topic,不建议开启
delete.topic.enabletrue允许删除topic
replica.lag.time.max.ms10000可用副本的同步超时时间
replica.lag.max.messages4000可用副本的同步记录差,该参数在0.9以后被删除
unclean.leader.election.enabletrue允许不在ISR中的副本成为leader
num.network.threads3接受客户端请求的线程数
num.io.threads8处理读写硬盘的IO的线程数
background.threads4后台处理的线程数,例如清理文件等
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值