kafka

目录

一、kafka安装与部署

1、环境准备

(1)ubuntu离线安装jdk1.8

(2)Linux安装jdk1.8

(3)Win安装jkd1.8

2、kafka安装(单机版)

(1)Win 安装

(2)Linux 安装

(3)ubuntu安装

3、启动与停止

二、总结

1、kafka消费最新数据实现

2、kafka真实环境部署规划

3、redis和kafka订阅区别

4、关于spring中kafka的参数配置

5、获取kafka topic指定时间段的数据

6、设置topic数据过期时间

三、问题

1、Kafka 中重要的组件

 2、Kafka 性能高原因

3、Kafka文件高效储存的设计原理

4、Kafka 的优缺点

5、Kafka 的应用场景

6、Kafka 中分区的概念

7、Kafka 中分区的原则

8、Kafka 中生产者运行流程

9、Kafka 的 consumer 如何消费数据

10、Kafka 中 Zookeeper 的作用

11、kafka中Producer的ack机制

12、kafka判断节点是否活着的条件

13、Kafka 的Topic中 Partition 数据是怎么存储到磁盘的

14、Kafka 的日志保留期与数据清理策略

15、Kafka 中什么情况下会出现消息丢失/不一致的问题

16、Kafa 中如何保证顺序消费

17、数据储存设计

18、常用术语

19、Kafka中分区器、序列化器、拦截器

20、Kafka 生产者客户端的整体结构

21、最新的消息时offset还是offset+1

22、重复消费和消息遗漏

23、Kafka如何处理大量积压消息

24、kafka消息丢失原因及解决方案


一、kafka安装与部署

1、环境准备

(1)ubuntu离线安装jdk1.8

  • 在/usr/local下创建java文件夹,并解压jdk
cd /usr/local
mkdir java
  • 然后把下载好的jdk文件,一般结尾是tar.gz,或者tar,或者zip,都可以,不过使用解压命令的时候要注意。
  • 解压完毕之后,修改一个环境文件:
vim /etc/environment  ##打开这个这个文件

##打开之后把光标移动到文件的末尾,进行添加下面的命令:

export JAVA_HOME=/usr/local/java/jdk1.8.0_231
export JRE_HOME=/usr/local/java/jdk1.8.0_231/jre
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
  • 做完上面之后保存退出,然后修改profile文件:
vim /etc/profile     ##打开profile文件

##打开之后把光标移动到文件的末尾,进行添加下面的命令:

export JAVA_HOME=/usr/local/java/jdk1.8.0_231
export JRE_HOME=/usr/local/java/jdk1.8.0_231/jre
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH:$HOME/bin
  • 做完上面的操作,保存退出,然后刷新,使环境变量马上生效,命令:
  • 最后输入 以下命令 来查看是否生效
source /etc/profile

 java -version

注意:如果遇到权限不够的话,在命令前面加上sudo ,上面编辑文件的命令 vim是一个比较灵活的文本编辑器,也可以用vi 命令。

(2)Linux安装jdk1.8

  •  在/usr/local下创建java文件夹,并解压jdk
cd /usr/local
mkdir java
  • 然后把下载好的jdk文件,一般结尾是tar.gz,或者tar,或者zip,都可以,不过使用解压命令的时候要注意。
  • 解压完毕之后,修改一个环境文件:
vim /etc/profile ##打开这个这个文件

##打开之后把光标移动到文件的末尾,进行添加下面的命令:

export JAVA_HOME=/usr/local/java/jdk1.8.0_231
export JRE_HOME=/usr/local/java/jdk1.8.0_231/jre
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
  • 做完上面的操作,保存退出,然后刷新,使环境变量马上生效,命令:
  • 最后输入 以下命令 来查看是否生效
source /etc/profile

 java -version

(3)Win安装jkd1.8

  • 具体自行百度安装jdk,配置好 JAVA_HOME和path,
  • 下载地址:   Java Downloads | Oracle,
  • 注意,  选择1.8的版本,然后环境变量JAVA_HOME,不要选择默认的"C:\Program Files\Java\jdk1.8.0_151" , 因为文件夹路径不能有空格,后面可能启动kafka服务出错.

2、kafka安装(单机版)

(1)Win 安装

  • 修改hosts (C:\Windows\System32\drivers\etc)

 为了安全起见,只有配置了hosts才能进行消费数据

  • 解压kafka_2.12-2.6.0.tgz
  • 修改\conf\zookeeper.properties文件

  •  修改conf\server.properties文件

PLAINTEXT://wjKafka  为hosts配置

(2)Linux 安装

  • 修改hosts (/etc/hosts)

  • 在usr/locak的目录下创建kafka文件夹

  • 解压kafka_2.12-2.6.0.tgz (tar -zxvf)

  • 在config目录下修改配置

zookeeper.properties

service.properties

 PLAINTEXT://wjKafka  为hosts配置

(3)ubuntu安装

 与Linux相同,

 注意:如果遇到权限不够的话,在命令前面加上sudo ,上面编辑文件的命令 vim是一个比较灵活的文本编辑器,也可以用vi 命令。

3、启动与停止

先启动zookeeper,在启动kafka。先停止kafka,在停止zookeeper。

  • Win(start.bat)
@echo off

start cmd /k "D:\Software\kafka\kafka_2.12-2.6.0\bin\windows\zookeeper-server-start.bat D:\Software\kafka\kafka_2.12-2.6.0\config\zookeeper.properties"

timeout /T 20 /NOBREAK

start cmd /k "ping 127.1 -n "4">null && D:\Software\kafka\kafka_2.12-2.6.0\bin\windows\kafka-server-start.bat D:\Software\kafka\kafka_2.12-2.6.0\config\server.properties"

exit
  • Centos(start-zk-kafka.sh)修改kafka的安装目录

启动脚本:

#!/bin/bash

cd /home/kafka_2.13-2.6.0

# 首先启动 zookeeper
echo "启动 zookeeper..."
nohup bin/zookeeper-server-start.sh config/zookeeper.properties >/dev/null 2>&1 &
echo "zookeeper 启动完成"

echo "休眠6秒..."
sleep 6

# 启动 kafka
echo "启动 kafka..."
nohup bin/kafka-server-start.sh config/server.properties >/dev/null 2>&1 &
echo "kafka 启动完成"

 后台启动:

在kafka的bin目录下执行:

#启动zookeeper
./zookeeper-server-start.sh config/zookeeper.propertie &

#启动kafka
./kafka-server-start.sh config/server.propertie &
  • Centos(stop-zk-kafka.sh)修改kafka的安装目录

停止脚本:

#!/bin/bash

cd /home/kafka_2.13-2.6.0

# 关闭 kafka
echo "关闭 kafka..."
bin/kafka-server-stop.sh
echo "kafka 关闭完成"

echo "休眠6秒"
sleep 6

# 关闭 zookeeper
echo "关闭 zookeeper..."
bin/zookeeper-server-stop.sh
echo "zookeeper 关闭完成"
  • ubuntu

在启动时需要加  sudo  

后台启动:

sudo bin/zookeeper-server-start.sh config/zookeeper.properties &


sudo bin/kafka-server-start.sh config/server.properties &

停止:

sudo bin/zookeeper-server-stop.sh


sudo bin/kafka-server-stop.sh

启动zookeeper、kafka报错,sudo java未找到

原因:
使用了sudo命令
尝试执行 sudo java -version 命令,发现sudo找不到java命令
sudo: java: command not found

解决,让sudo能找到java命令

  • 方法1:
# 在/usr/bin/中生成java命令软链接,记得替换成自己的java路径
# java和javac都要,如果只有java没有javac,sudo还是无法执行java命令
ln -s /usr/local/java/jdk1.8.0_291/bin/java /usr/bin/
ln -s /usr/local/java/jdk1.8.0_291/bin/javac /usr/bin/
  • 方法2
# 直接修改sudo的配置文件 /etc/sudoers
sudo vim /etc/sudoers
# 在 secure_path 后面直接加上java路径 :/usr/local/java/jdk1.8.0_291/bin
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/usr/local/java/jdk1.8.0_291/bin"

二、总结

1、kafka消费最新数据实现

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>kafkalearn</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer-seektoend</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.6.0</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
        </dependency>
    </dependencies>


</project>
package com.zhang;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;

import java.util.Arrays;
import java.util.List;
import java.util.Properties;

public class ConsumerSeekToEnd {

    public static void main(String[] args) {
        //1. 创建配置对象
 Properties props = new Properties();
        //执行kafka集群的位置
 props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"10.100.12.54:9092");


        //重置offset : earliest(最早) latest(最后)
 //满足两个条件: 1. 当前的消费者组在kafka没有消费过所订阅的主题 2.当前消费者组使用的offset在kafka集群中已经被删除
 props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"latest");
        //1-20 40
 //指定消费者组
 props.put(ConsumerConfig.GROUP_ID_CONFIG,"test2");

        //指定kv的反序列化器
 props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");

        //2. 创建消费者对象
 KafkaConsumer<String,String> consumer = new KafkaConsumer<String, String>(props);

        //3. 订阅主题

 TopicPartition topicPartition = new TopicPartition("test", 0);
        List<TopicPartition> topics = Arrays.asList(topicPartition);
        consumer.assign(topics);
        //consumer.subscribe(Arrays.asList("test"));
 consumer.seekToEnd(topics);

        //4. 消费数据
 while(true){
            ConsumerRecords<String, String> records  = consumer.poll(1000);

            for (ConsumerRecord<String, String> record : records) {
                System.out.println(record.topic() + " -- " + record.partition() + " -- " + record.offset() +" -- " +
                        record.key() +" -- " + record.value());
            }

        }
    }
}

2、kafka真实环境部署规划

操作系统选型

因为kafka服务端代码是Scala语言开发的,因此属于JVM系的大数据框架,目前部署最多的3类操作系统主要由Linux ,OS X 和Windows,但是部署在Linux数量最多,为什么呢?因为I/O模型的使用和数据网络传输效率两点。

  • 第一:Kafka新版本的Clients在设计底层网络库时采用了Java的Select模型,而在Linux实现机制是epoll,感兴趣的读者可以查询一下epoll和select的区别,明确一点就是:kafka跑在Linux上效率更高,因为epoll取消了轮询机制,换成了回调机制,当底层连接socket数较多时,可以避免CPU的时间浪费。
  • 第二:网络传输效率上。kafka需要通过网络和磁盘进行数据传输,而大部分操作系统都是通过Java的FileChannel.transferTo方法实现,而Linux操作系统则会调用sendFile系统调用,也即零拷贝(Zero Copy 技术),避免了数据在内核地址空间和用户程序空间进行重复拷贝。

磁盘类型规划

  • 机械磁盘(HDD) 一般机械磁盘寻道时间是毫秒级的,若有大量随机I/O,则将会出现指数级的延迟,但是kafka是顺序读写的,因此对于机械磁盘的性能也是不弱的,所以,基于成本问题可以考虑。
  • 固态硬盘(SSD) 读写速度可观,没有成本问题可以考虑。
  • JBOD (Just Bunch Of Disks ) 经济实惠的方案,对数据安全级别不是非常非常高的情况下可以采用,建议用户在Broker服务器上设置多个日志路径,每个路径挂载在不同磁盘上,可以极大提升并发的日志写入速度。
  • RAID 磁盘阵列 常见的RAID是RAID10,或者称为(RAID 1+0) 这种磁盘阵列结合了磁盘镜像和磁盘带化技术来保护数据,因为使用了磁盘镜像技术,使用率只有50%,注意,LinkedIn公司采用的就是RAID作为存储来提供服务的。那么弊端在什么地方呢?如果Kafka副本数量设置为3,那么实际上数据将存在6倍的冗余数据,利用率实在太低。因此,LinkedIn正在计划更改方案为JBOD.

磁盘容量规划

我们公司物联网平台每天大约能够产生一亿条消息,假设副本replica设置为2 (其实我们设置为3),数据留存时间为1周,平均每条上报事件消息为1K左右,那么每天产生的消息总量为:1亿 乘 2 乘 1K 除以 1000 除以 1000 =200G磁盘。预留10%的磁盘空间,为210G。一周大约为1.5T。采用压缩,平均压缩比为0.5,整体磁盘容量为0.75T。 关联因素主要有:

  • 新增消息数
  • 副本数
  • 是否启用压缩
  • 消息大小
  • 消息保留时间

 内存容量规划

kafka对于内存的使用,并不过多依赖JVM 内存,而是更多的依赖操作系统的页缓存,consumer若命中页缓存,则不用消耗物理I/O操作。一般情况下,java堆内存的使用属于朝生夕灭的,很快会被GC,一般情况下,不会超过6G,对于16G内存的机器,文件系统page cache 可以达到10-14GB。

  • 怎么设计page cache,可以设置为单个日志段文件大小,若日志段为10G,那么页缓存应该至少设计为10G以上。
  • 堆内存最好不要超过6G。

CPU选择规划

kafka不属于计算密集型系统,因此CPU核数够多就可以,而不必追求时钟频率,因此核数选择最好大于8。

网络带宽决定Broker数量

带宽主要有1Gb/s 和10 Gb/s 。我们可以称为千兆位网络和万兆位网络。举例如下: 我们的物联网系统一天每小时都要处理1Tb的数据,我们选择1Gb/b带宽,那么需要选择多少机器呢?

  • 假设网络带宽kafka专用,且分配给kafka服务器70%带宽,那么单台Borker带宽就是710Mb/s,但是万一出现突发流量问题,很容易把网卡打满,因此在降低1/3,也即240Mb/s。因为1小时处理1TTB数据,每秒需要处理292MB,1MB=8Mb,也就是2336Mb数据,那么一小时处理1TB数据至少需要2336/240=10台Broker数据。冗余设计,最终可以定为20台机器。

典型推荐

  • cpu 核数 32
  • 内存 32GB
  • 磁盘 3TB 7200转 SAS盘三块
  • 带宽 1Gb/s

3、redis和kafka订阅区别

  • Kafka与Redis PUB/SUB之间较大的区别在于Kafka是一个完整的系统,而Redis PUB/SUB只是一个套件(utility)——没有冒犯Redis的意思,毕竟它的主要功能并不是PUB/SUB。

  • Redis 消息推送(基于分布式pub/sub)多用于实时性较高的消息推送,并不保证可靠。(推荐学习:Redis视频教程)其他的mq和Kafka保证可靠但有一些延迟(非实时系统没有保证延迟)。redis-pub/sub断电就清空,而使用redis-list作为消息推送虽然有持久化,但是又太弱智,也并非完全可靠不会丢。

  • Redis 发布订阅除了表示不同的topic 外,并不支持分组,比如Kafka中发布一个东西,多个订阅者可以分组,同一个组里只有一个订阅者会收到该消息,这样可以用作负载均衡。

  • Redis,它首先是一个内存数据库,其提供的PUB/SUB功能把消息保存在内存中(基于channel),因此如果你的消息的持久性需求并不高且后端应用的消费能力超强的话,使用Redis PUB/SUB是比较合适的使用场景。比如官网说提供的一个网络聊天室的例子:模拟IRC,因为channel就是IRC中的服务器。用户发起连接,发布消息到channel,接收其他用户的消息。这些对于持久性的要求并不高,使用Redis PUB/SUB来做足矣。而Kafka是一个完整的系统,它提供了一个高吞吐量、分布式的提交日志(由于提供了Kafka Connect和Kafka Streams,目前Kafka官网已经将自己修正为一个分布式的流式处理平台,这里也可以看出Kafka的野心:-)。除了p2p的消息队列,它当然提供PUB/SUB方式的消息模型。而且,Kafka默认提供了消息的持久化,确保消息的不丢失性(至少是大部分情况下)。另外,由于消费元数据是保存在consumer端的,所以对于消费而言consumer被赋予极大的自由度。consumer可以顺序地消费消息,也可以重新消费之前处理过的消息。这些都是Redis PUB/SUB无法做到的。

  • Redis PUB/SUB使用场景:

1. 消息持久性需求不高

2. 吞吐量要求不高

3. 可以忍受数据丢失

4. 数据量不大

  • Kafka使用场景:

上面以外的其他场景:

1. 高可靠性

2. 高吞吐量

3. 持久性高

4. 多样化的消费处理模型

4、关于spring中kafka的参数配置

具体参数配置如下:深入理解Kafka的核心调优参数 | Applied Research. Big Data. Distributed Systems. Open Source.

spring:
  kafka:
    #如果consumer和product的地址一致,可以在这里统一设置,在下面就不用单独设置
    bootstrap-servers:
      - 192.168.2.88:9092
      - 192.168.2.88:9093

    #########consumer的配置参数(开始)############
    consumer:
      #如果'enable.auto.commit'为true,则消费者偏移自动提交给Kafka的频率(以毫秒为单位),默认值为5000。
      auto-commit-interval:
      #当Kafka中没有初始偏移量或者服务器上不再存在当前偏移量时该怎么办,默认值为latest,表示自动将偏移重置为最新的偏移量
      #可选的值为latest, earliest, none
      auto-offset-reset:
      #用于建立与Kafka群集的初始连接
      bootstrap-servers:
        - 192.168.2.88:9092
        - 192.168.2.88:9093
      #ID在发出请求时传递给服务器;用于服务器端日志记录
      client-id:
      #如果为true,则消费者的偏移量将在后台定期提交,默认值为true
      enable-auto-commit:
      #如果没有足够的数据立即满足“fetch.min.bytes”给出的要求,服务器在回答获取请求之前将阻塞的最长时间(以毫秒为单位)
      #默认值为500
      fetch-max-wait:
      #服务器应以字节为单位返回获取请求的最小数据量,默认值为1,对应的kafka的参数为fetch.min.bytes。
      fetch-min-size:
      #用于标识此使用者所属的使用者组的唯一字符串
      group-id:
      #心跳与消费者协调员之间的预期时间(以毫秒为单位),默认值为3000
      heartbeat-interval:
      #密钥的反序列化器类,实现类实现了接口org.apache.kafka.common.serialization.Deserializer
      key-deserializer: org.apache.kafka.common.serialization.StringSerializer
      #值的反序列化器类,实现类实现了接口org.apache.kafka.common.serialization.Deserializer
      value-deserializer: org.apache.kafka.common.serialization.StringSerializer
      #一次调用poll()操作时返回的最大记录数,默认值为500
      max-poll-records:

    #########producer的配置参数(开始)############
    producer:
      #procedure要求leader在考虑完成请求之前收到的确认数,用于控制发送记录在服务端的持久化,其值可以为如下:
      # 0 如果设置为零,则生产者将不会等待来自服务器的任何确认,该记录将立即添加到套接字缓冲区并视为已发送。在这种情况下,无法保证服务器已收到记录,并且重试配置将不会生效(因为客户端通常不会知道任何故障),为每条记录返回的偏移量始终设置为-1。
      # 1 这意味着leader会将记录写入其本地日志,但无需等待所有副本服务器的完全确认即可做出回应,在这种情况下,如果leader在确认记录后立即失败,但在将数据复制到所有的副本服务器之前,则记录将会丢失。
      # all 这意味着leader将等待完整的同步副本集以确认记录,这保证了只要至少一个同步副本服务器仍然存活,记录就不会丢失,这是最强有力的保证,这相当于acks = -1的设置。
      #可以设置的值为:all, -1, 0, 1
      acks:
      #每当多个记录被发送到同一分区时,生产者将尝试将记录一起批量处理为更少的请求,
      #这有助于提升客户端和服务器上的性能,此配置控制默认批量大小(以字节为单位),默认值为16384
      batch-size:
      #用于建立与Kafka群集的初始连接
      bootstrap-servers:
        - 192.168.2.88:9092
        - 192.168.2.88:9093
      #生产者可用于缓冲等待发送到服务器的记录的内存总字节数,默认值为33554432
      buffer-memory:
      #ID在发出请求时传递给服务器,用于服务器端日志记录
      client-id:
      #生产者生成的所有数据的压缩类型,此配置接受标准压缩编解码器('gzip','snappy','lz4'),
      #它还接受'uncompressed'以及'producer',分别表示没有压缩以及保留生产者设置的原始压缩编解码器,
      #默认值为producer
      compression-type:
      #密钥的反序列化器类,实现类实现了接口org.apache.kafka.common.serialization.Deserializer
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      #值的反序列化器类,实现类实现了接口org.apache.kafka.common.serialization.Deserializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      #如果该值大于零时,表示启用重试失败的发送次数
      retries:

    #########listener的配置参数(开始)############
    listener:
      #侦听器的AckMode,参见https://docs.spring.io/spring-kafka/reference/htmlsingle/#committing-offsets
      #当enable.auto.commit的值设置为false时,该值会生效;为true时不会生效
      ack-mode:
      #在侦听器容器中运行的线程数
      concurrency:
      #轮询消费者时使用的超时(以毫秒为单位)
      poll-timeout:
      #当ackMode为“COUNT”或“COUNT_TIME”时,偏移提交之间的记录数
      ack-count:
      #当ackMode为“TIME”或“COUNT_TIME”时,偏移提交之间的时间(以毫秒为单位)
      ack-time: 

以下是一个去掉了一些默认参数后的配置:

spring: 
  kafka:
    bootstrap-servers: 10.102.1.113:9092
    # bootstrap-servers: 33.67.0.18:9093,33.67.0.19:9093,33.67.0.20:9093
    #=============== provider  =======================
    listener:
      missing-topics-fatal: false
    producer:
      retries: 0
      # 每次批量发送消息的数量
      batch-size: 102400
      buffer-memory: 33554432
      # 指定消息key和消息体的编解码方式,StringSerializer支持Json对象的序列化
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    #=============== consumer  =======================
    # 指定默认消费者group id
    consumer:

      group-id: test-group-hh-6
      auto-offset-reset: earliest
      enable-auto-commit: true
      auto-commit-interval: 1000
      # 指定消息key和消息体的编解码方式,StringDeserializer支持Json对象的反序列化
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      maxPollRecords: 50

5、获取kafka topic指定时间段的数据

public static void main(String[] args) {
        //kafkaConsumer
        Properties props = new Properties();
        props.put("bootstrap.servers", "10.102.1.113:9092");  //连接地址
        props.put("group.id", "test16");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        KafkaConsumer<String, String> consumer = new KafkaConsumer(props);
        consumerOnTimeBatch(consumer, "radar.alarm", 0, startTime, endTime);
    }


/**
     * 消费某时间范围内的一批数据
     *
     * @param consumer
     * @param topic
     * @param partition 分区号
     * @param startTime 消费起始时间
     * @param endTime 消费结束时间
     */
    public void consumerOnTimeBatch(KafkaConsumer<String, String> consumer, String topic, int partition,
                                    Long startTime, Long endTime) {
        TopicPartition topicPartition = new TopicPartition(topic, partition);
        // 指定主题分区
        List<TopicPartition> topicPartitions = Collections.singletonList(topicPartition);
        consumer.assign(topicPartitions);
        long startOffset = 0L;
        try {
            startOffset = getOffsetByDateTime(consumer, topic, partition, startTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //判断startOffset是否大于kafka中的beginningOffset。若小于,则将startOffset替换成beginningOffset
        Long beginningOffset = consumer.beginningOffsets(topicPartitions).get(topicPartition);
        if (startOffset < beginningOffset) {
            startOffset = beginningOffset;
        }
        consumer.seek(topicPartition, startOffset);
        boolean flag = true;
        while (flag) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));//设置超时时间,一般设置为100ms
            for (ConsumerRecord<String, String> record : records) {
                logger.info("消费kafka数据:,时间:{},偏移量:{},消息体:{} ",new Date(record.timestamp()),record.offset(),record.value());
                //判断消费的offset是否到了结束的时间
                if (record.timestamp() >= endTime) {
                    flag = false;
                    break;
                }
            }
        }
        consumer.close();
    }   



    /**
     * 根据时间戳获取偏移量
     *
     * @param consumer
     * @param topic
     * @param partition 分区号
     * @param timestamp 消息时间
     * @return
     * @throws ParseException
     */
    public Long getOffsetByDateTime(KafkaConsumer consumer, String topic, int partition, Long timestamp) {

        Map<TopicPartition, Long> map = new HashMap();
        TopicPartition topicPartition = new TopicPartition(topic, partition);
        map.put(topicPartition, timestamp);
        Map<TopicPartition, OffsetAndTimestamp> offset = null;
        try {
            offset = consumer.offsetsForTimes(map, Duration.ofSeconds(1000));//设置时长(一般设置为1000ms)
        } catch (Exception e) {
            return null;
        }
        return offset.get(topicPartition).offset();
    }



  • kafka在通过startTime和endTime消费数据时,会通过startTime查询offset,如果该startTime的时间戳没有对应的offset则会找到离startTime最近的下一个offset作为开始的偏移量。

  • 在消费前还需要获取到kafka中最新的beginningOffset,并判断startoOfset与beginningOffset的值,如果startOffset小于beginningOffset则将其替换。为防止消费不到数据。

  • consumer通过poll方法获取到ConsumerRecords对象,并通过该对象获取timestamp,通过判断timestamp与endTime的大小进而确定是否达到的结束的条件。

  • 获取offset的超时时间建议设置为1s,consumer的poll方法的超时时间建议设置为50ms。

6、设置topic数据过期时间

设置单个topic

./kafka-configs.sh --zookeeper localhost:2181 --alter --entity-name WHPointCloudSimulation --entity-type topics --add-config retention.ms=86400000

./kafka-configs.sh --zookeeper localhost:2181 --describe --entity-name WHPointCloudSimulation --entity-type topics

注:WHPointCloudSimulation为Topic名称

全局设置

修改配置文件 server.properties

log.retention.hours=24

三、问题

1、Kafka 中重要的组件

Producer消息生产者,发布消息到Kafka集群的终端或服务

Broker一个 Kafka 节点就是一个 Broker,多个Broker可组成一个Kafka 集群。

Topic消息主题,每条发布到Kafka集群的消息都会归集于此,Kafka是面向Topic 的

PartitionPartition 是Topic在物理上的分区,一个Topic可以分为多个Partition,每个Partition是一个有序的不可变的记录序列。单一主题中的分区有序,无法保证Topic中所有分区的消息有序。

Consumer从Kafka集群中消费消息的终端或服务(主动去 Pull)

Consumer Group每个Consumer都属于一个Consumer Group,每条消息只能被Consumer Group中的一个Consumer消费,但可以被多个Consumer Group消费。

ReplicaPartition 的副本,用来保障Partition的高可用性。

Controller: Kafka 集群中的其中一个服务器,用来进行Leader election以及各种 Failover 操作

ZookeeperKafka 通过Zookeeper来存储集群中的 meta 消息

 2、Kafka 性能高原因

  1. 利用了 PageCache 缓存
  2. 磁盘顺序写
  3. 零拷贝技术
  4. pull 拉模式

3、Kafka文件高效储存的设计原理

  1. Kafka把Topic中一个Partition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完成的文件,减少磁盘占用
  2. 通过索引信息可以快速定位Message和确定response的最大大小
  3. 通过将索引元数据全部映射到 memory,可以避免 Segment 文件的磁盘I/O操作
  4. 通过索引文件稀疏存储,可以大幅降低索引文件元数据占用空间大小

4、Kafka 的优缺点

优点

  • 高性能、高吞吐量、低延迟:Kafka 生产和消费消息的速度都达到每秒10万级
  • 高可用:所有消息持久化存储到磁盘,并支持数据备份防止数据丢失
  • 高并发:支持数千个客户端同时读写
  • 容错性:允许集群中节点失败(若副本数量为n,则允许 n-1 个节点失败)
  • 高扩展性:Kafka 集群支持热伸缩,无须停机

缺点

  • 没有完整的监控工具集
  • 不支持通配符主题选择

5、Kafka 的应用场景

  1. 日志聚合:可收集各种服务的日志写入kafka的消息队列进行存储
  2. 消息系统:广泛用于消息中间件
  3. 系统解耦:在重要操作完成后,发送消息,由别的服务系统来完成其他操作
  4. 流量削峰:一般用于秒杀或抢购活动中,来缓冲网站短时间内高流量带来的压力
  5. 异步处理:通过异步处理机制,可以把一个消息放入队列中,但不立即处理它,在需要的时候再进行处理

6、Kafka 中分区的概念

主题是一个逻辑上的概念,还可以细分为多个分区,一个分区只属于单个主题,很多时候也会把分区称为主题分区(Topic-Partition)。同一主题下的不同分区包含的消息是不同的,分区在存储层面可以看做一个可追加的日志文件 ,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)。offset 是消息在分区中的唯一标识,kafka 通过它来保证消息在分区内的顺序性,不过 offset 并不跨越分区,也就是说,kafka保证的是分区有序而不是主题有序。

在分区中又引入了多副本(replica)的概念,通过增加副本数量可以提高容灾能力。同一分区的不同副本中保存的是相同的消息。副本之间是一主多从的关系,其中主副本负责读写,从副本只负责消息同步。副本处于不同的 broker 中,当主副本出现异常,便会在从副本中提升一个为主副本。

7、Kafka 中分区的原则

  1. 指明Partition的情况下,直接将指明的值作为Partition值
  2. 没有指明Partition值但有 key 的情况下,将 key 的 Hash 值与 topic 的Partition值进行取余得到Partition值
  3. 既没有Partition值又没有 key 值的情况下,第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与Topic可用的Partition总数取余得到Parittion值,也就是常说的 round-robin 算法

8、Kafka 中生产者运行流程

  1. 一条消息发过来首先会被封装成一个 ProducerRecord 对象
  2. 对该对象进行序列化处理(可以使用默认,也可以自定义序列化)
  3. 对消息进行分区处理,分区的时候需要获取集群的元数据,决定这个消息会被发送到哪个主题的哪个分区
  4. 分好区的消息不会直接发送到服务端,而是放入生产者的缓存区,多条消息会被封装成一个批次(Batch),默认一个批次的大小是 16KB
  5. Sender 线程启动以后会从缓存里面去获取可以发送的批次
  6. Sender 线程把一个一个批次发送到服务端

9、Kafka 的 consumer 如何消费数据

Kafka采用大部分消息系统遵循的传统模式:Producer将消息推送到BrokerConsumer从Broker拉取消息。

如果Consumer采用 Push 模式,则Consumer难以处理不同速率的上游推送消息。

采用 Pull 模式的好处是Consumer可以自主决定是否批量的从Broker拉取数据。

Pull模式有个缺点是,如果Broker没有可供消费的消息,将导致Consumer不断在循环中轮询,直到新消息到达。为了避免这点,Kafka有个参数可以让Consumer阻塞直到新消息到达。

10、Kafka 中 Zookeeper 的作用

Broker 注册(broker的上下线

Topic 注册(topic的分区副本分配和leader的选举

负载均衡等

11、kafka中Producer的ack机制

  • 0: 相当于异步操作,Producer 不需要Leader给予回复,发送完就认为成功,继续发送下一条(批)Message。此机制具有最低延迟,但是持久性可靠性也最差,当服务器发生故障时,很可能发生数据丢失。
  • 1: Kafka 默认的设置。表示 Producer 要 Leader 确认已成功接收数据才发送下一条(批)Message。不过 Leader 宕机,Follower 尚未复制的情况下,数据就会丢失。此机制提供了较好的持久性和较低的延迟性。
  • -1: Leader 接收到消息之后,还必须要求ISR列表里跟Leader保持同步的那些Follower都确认消息已同步,Producer 才发送下一条(批)Message。此机制持久性可靠性最好,但延时性最差。

12、kafka判断节点是否活着的条件

1,节点必须维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接
2,如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久

13、Kafka 的Topic中 Partition 数据是怎么存储到磁盘的

Topic 中的多个 Partition 以文件夹的形式保存到 Broker,每个分区序号从0递增,且消息有序。

Partition 文件下有多个Segment(xxx.index,xxx.log),Segment文件里的大小和配置文件大小一致。默认为1GB,但可以根据实际需要修改。如果大小大于1GB时,会滚动一个新的Segment并且以上一个Segment最后一条消息的偏移量命名。

14、Kafka 的日志保留期与数据清理策略

超过保期的数据将被按清理策略进行清理;默认保留时间是7天,如果想修改时间,在server.properties里更改参数log.retention.hours/minutes/ms 的值便可。

清理策略

  • 删除: log.cleanup.policy=delete 表示启用删除策略,这也是默认策略。一开始只是标记为delete,文件无法被索引。只有过了log.Segment.delete.delay.ms这个参数设置的时间,才会真正被删除。
  • 压缩: log.cleanup.policy=compact 表示启用压缩策略,将数据压缩,只保留每个Key最后一个版本的数据。首先在Broker的配置中设置log.cleaner.enable=true 启用 cleaner,这个默认是关闭的。

15、Kafka 中什么情况下会出现消息丢失/不一致的问题

消息发送时

消息发送有两种方式:同步 - sync 和 异步 - async。默认是同步的方式,可以通过 producer.type 属性进行配置,kafka 也可以通过配置 acks 属性来确认消息的生产

  • 0:表示不进行消息接收是否成功的确认
  • 1:表示当 leader 接收成功时的确认
  • -1:表示 leader 和 follower 都接收成功的确认

当 acks = 0 时,不和 Kafka 进行消息接收确认,可能会因为网络异常,缓冲区满的问题,导致消息丢失

当 acks = 1 时,只有 leader 同步成功而 follower 尚未完成同步,如果 leader 挂了,就会造成数据丢失

消息消费时

Consumer有个”位移“(offset)的概念,表示Consumer当前消费到topic分区的哪个位置。

kafka通过先消费消息,后更新offset,来保证消息不丢失。但是这样可能会出现消息重复的情况

consumer不要开启自动提交offsetconsumer端程序自己来处理offset的提交更新。

16、Kafa 中如何保证顺序消费

Kafka 的消费单元是 Partition,同一个 Partition 使用 offset 作为唯一标识保证顺序性,但这只是保证了在 Partition 内部的顺序性而不是 Topic 中的顺序。

因此我们需要将所有消息发往统一 Partition 才能保证消息顺序消费,那么可以在发送的时候指定 MessageKey,同一个 key 的消息会发到同一个 Partition 中。

17、数据储存设计

 1、partition 的数据文件:

partition 中的每条 Message 包含了以下三个属性:offset,MessageSize,data
offset 表示 Message 在这个 partition 中的偏移量,offset 不是该 Message 在 partition 数据文件中的实际存储位置,而是逻辑上一个值, 它唯一确定了 partition 中的一条 Message
MessageSize 表示消息内容 data 的大小;
data 为 Message 的具体内容。
2、数据文件分段 segment:
partition 物理上由多个 segment 文件组成,每个 segment 大小相等,顺序读写。每个 segment
数据文件以该段中最小的 offset 命名,文件扩展名为.log。这样在查找指定 offset 的 Message 的
时候,用二分查找就可以定位到该 Message 在哪个 segment 数据文件中。
3、数据文件索引:
Kafka 为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩
展名为.index。index 文件中并没有为数据文件中的每条 Message 建立索引,而是采用了稀疏存
储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以
将索引文件保留在内存中。

18、常用术语

ISR:速率和leader相差低于10s的follower的集合

OSR:速率和leader相差大于10s的follwer

AR:所有分区的follower

HW:High Water高水位,根据同一分区中最低的LEO决定(Log End Offset)

LEO:每个分区最大的Offset

19、Kafka中分区器、序列化器、拦截器

分区器:Partitioner用来对分区进行处理的,即消息发送到哪一个分区的问题。

序列化器:这个是对数据进行序列化和反序列化的工具。

拦截器:即对于消息发送进行一个提前处理和收尾处理的类Interceptor

处理顺利:拦截器=>序列化器=>分区器

20、Kafka 生产者客户端的整体结构

使用两个线程来完成:mainsender 线程

main线程:会一次经过拦截器、序列化器、分区器将数据发送到RecoreAccumulator线程共享变量

sender:从共享变量中拉取数据发送到kafka broker

21、最新的消息时offset还是offset+1

生产者发送数据的offset是从0开始的,消费者消费的数据的offset是从1开始,故最新消息是offset+1

22、重复消费和消息遗漏

重复消费:先消费后提交offset,如果消费完,在提交offset时出现宕机,则会造成重复消费

消息遗漏:先提交offset,还没来得及消费就宕机了,则会造成漏消费

23、Kafka如何处理大量积压消息

1、如果是kafka消费能力不足,则可以考虑增加 topic 的 partition 的个数,
同时提升消费者组的消费者数量,消费数 = 分区数;(二者缺一不可)

2、若是下游数据处理不及时,则提高每批次拉取的数量。批次拉取数量过少
(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。

24、kafka消息丢失原因及解决方案

1、生产过程丢失消息 :

丢失原因:一般可能是网络故障,导致消息没有发送出去。

解决方案:重发就行了。

由于kafka为了提高性能,采用了异步发送消息。我们只有获取到发送结果,才能确保消息发送成功。 有两个方案可以获取发送结果。

  • kafka把发送结果封装在Future对象中,我可以使用Future的get方法同步阻塞获取结果。
 
Future<RecordMetadata> future = producer.send(new ProducerRecord<>(topic, message));
try {
    RecordMetadata recordMetadata = future.get();
    if (recordMetadata != null) {
        System.out.println("发送成功");
    }
} catch (Exception e) {
    e.printStackTrace();
}
  • 使用kafka的callback函数获取返回结果。
producer.send(new ProducerRecord<>(topic, message), new Callback() {
    @Override
    public void onCompletion(RecordMetadata metadata, Exception exception) {
        if (exception == null) {
            System.out.println("发送成功");
        } else {
            System.out.println("发送失败");
        }
    }
});

2、服务端持久化过程丢失消息:

为了保证性能,kafka采用的是异步刷盘,当我们发送消息成功后,Broker节点在刷盘之前宕机了,就会导致消息丢失。

当然我们也可以设置刷盘频率:

# 设置每1000条消息刷一次盘
flush.messages = 1000
# 设置每秒刷一次盘
flush.ms = 1000

3、消费过程丢失消息:

kafka中有个offset的概念,consumer从partition中拉取消息,consumer本地处理完成后需要commit一下offset,表示消费完成,下次就不会再拉取到这条消息。
所以我们需要关闭自动commit offset的配置,防止consumer拉到消息后,服务宕机,导致消息丢失。

enable.auto.commit = false

总结: 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值