Kafka

Kafka官网:http://kafka.apache.org/

一、Kafka 简介

1.1 简介

1.1.1 消息引擎

消息引擎(队列、中间件): 是在消息的传输过程中保存消息的容器。简单说就是A向消息引擎发送消息,B从消息引擎中获取数据。

作用:

  1. 系统解耦
  2. 系统之间异步处理
  3. 削峰填谷。将上游系统流量的"峰"填到消息引擎的“谷”中,缓解下游的压力。否则如果上游系统的流量过大,下游系统处理不过来,会造成下游系统的全链路雪崩。

消息引擎的基础:

  1. 消息的协议。保证了数据的传输格式。
  2. 消息引擎传输模型:点对点: A ---消息引擎--- B 表示A发送的消息只能是B接收(kafka中的ConsumerGroup消费就是点对点)。发布订阅:A ---消息引擎--- B C D 表示A发送的消息可用被多个用户接收(kafka中的Topic 就是发布订阅)。

1.1.2 为什么不适用Flume做消息引擎???

  1. Flume中的数据是存在Channel中,一旦数据被sink就没有了。数据不能指定保存周期。
  2. flume增加sink(消费),必须增加channle,修改配置文件,比较繁琐所以flume不适合用作消息引擎。flume在生产中的功能很单一,就是用作数据采集。

1.1.3 ApacheKafka 是一个分布式的流处理平台。它具有以下特点:

  • 支持消息的发布和订阅,类似于 RabbtMQ、ActiveMQ 等消息队列;
  • 支持数据实时处理(Kafka版本中添加了Stream);
  • 能保证消息的可靠性投递;
  • 支持消息的持久化存储,并通过多副本分布式的存储方案来保证消息的容错;
  • 高吞吐率,单 Broker 可以轻松处理数千个分区以及每秒百万级的消息量。

1.2 Kafka架构

整体架构图:

详细架构图:

1.3 Kafka相关概念

1)Producer :消息生产者,就是向kafka broker发消息的客户端;

2)Consumer :消息消费者,向kafka broker取消息的客户端;

3)Topic :可以理解为一个队列;

4) Consumer Group (消费者组):多个消费者组成的组。每一个消费者组都有自己唯一的groupID。kafka中消费数据是以consumer group为单位的。一个分区只能同时被一个消费者组中的一个消费者消费。一个消费者组中的消费者是可用消费多个分区的。

5)Broker :一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic;

6)Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。kafka只保证按一个partition中的顺序将消息发给consumer,不保证一个topic的整体(多个partition间)的顺序;每一个Topic中都是分区,分区中才存储数据。partition在物理上是使用目录表示的。格式:topic-分区号。分区号是从0开始的。

7)Offset(偏移量):kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka。

8)Consumer Offset(消费者偏移量):每次消费完成的最后一条数据的偏移量加1。消费者偏移量是一个动态的值。每一个消费者组对应的分区的消费者偏移量只有一个。

9)Repalica(副本):副本是针对分区的。kafka中的副本最小单位是分区。副本分为leader副本和follower副本。leader副本有且只有一个;follower副本可拥有n个。leader副本接收客户端的读写请求;follower副本只负责同步leader副本的数据。leader副本挂掉,follower副本就会成为leader副本。副本数是包含leader副本的,所以至少有一个副本。

10)segment log(分段日志):kafka中的数据是存储在磁盘上的;默认存储7天。数据是存储在磁盘上的,默认是7天,也就是说7天后需要删除到期的数据了。怎么删除数据呢?删除文件(分段日志)。00000000000000000000.log ---- 存储数据的分段日志。

11)controller:kafka的集群也是主从结构的。controller就是集群的master。只是controller不是配置的,而是选举的。

12)Messages和Batches:Kafka 的基本数据单元被称为 message(消息),为减少网络开销,提高效率,多个消息会被放入同一批次 (Batch) 中后再写入。

1.4 关联关系

1.4.1 Topics And Partitions

Kafka 的消息通过 Topics(主题) 进行分类,一个主题可以被分为若干个 Partitions(分区),一个分区就是一个提交日志 (commit log)。消息以追加的方式写入分区,然后以先入先出的顺序读取。Kafka 通过分区来实现数据的冗余和伸缩性,分区可以分布在不同的服务器上,这意味着一个 Topic 可以横跨多个服务器,以提供比单个服务器更强大的性能。

由于一个 Topic 包含多个分区,因此无法在整个 Topic 范围内保证消息的顺序性,但可以保证消息在单个分区内的顺序性。

1.4.2 Producers And Consumers

1. 生产者

生产者负责创建消息。一般情况下,生产者在把消息均衡地分布到在主题的所有分区上,而并不关心消息会被写到哪个分区。如果我们想要把消息写到指定的分区,可以通过自定义分区器来实现。

2. 消费者

消费者是消费者群组的一部分,消费者负责消费消息。消费者可以订阅一个或者多个主题,并按照消息生成的顺序来读取它们。消费者通过检查消息的偏移量 (offset) 来区分读取过的消息。偏移量是一个不断递增的数值,在创建消息时,Kafka 会把它添加到其中,在给定的分区里,每个消息的偏移量都是唯一的。消费者把每个分区最后读取的偏移量保存在 Zookeeper 或 Kafka 上,如果消费者关闭或者重启,它还可以重新获取该偏移量,以保证读取状态不会丢失。

一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组中所组成的多个消费者共同读取。多个消费者群组中消费者共同读取同一个主题时,彼此之间互不影响。

1.4.3 Brokers And Clusters

一个独立的 Kafka 服务器被称为 Broker。Broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。Broker 为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘的消息。

Broker 是集群 (Cluster) 的组成部分。每一个集群都会选举出一个 Broker 作为集群控制器 (Controller),集群控制器负责管理工作,包括将分区分配给 Broker 和监控 Broker。

在集群中,一个分区 (Partition) 从属一个 Broker,该 Broker 被称为分区的首领 (Leader)。一个分区可以分配给多个 Brokers,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余,如果有一个 Broker 失效,其他 Broker 可以接管领导权。

1.5 kafka的消息协议

PLAINTEXT:纯二进制数据

二、基于Zookeeper搭建Kafka高可用集群

2.1 Zookeeper集群搭建

为保证集群高可用,Zookeeper 集群的节点数最好是奇数,最少有三个节点,所以这里搭建一个三个节点的集群。

2.1.1 下载 & 解压

下载对应版本 Zookeeper,这里我下载的版本 3.4.14。官方下载地址:https://archive.apache.org/dist/zookeeper/

# 下载
wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
# 解压
tar -zxvf zookeeper-3.4.14.tar.gz -C /usr/local

2.1.2 修改配置

单台服务器上搭建:拷贝三份 zookeeper 安装包。分别进入安装目录的 conf 目录,拷贝配置样本 zoo_sample.cfg 为 zoo.cfg 并进行修改,修改后三份配置文件内容分别如下:

zookeeper01 配置:

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/zookeeper-cluster/data/01
dataLogDir=/usr/local/zookeeper-cluster/log/01
clientPort=2181
# server.1 这个1是服务器的标识,可以是任意有效数字,标识这是第几个服务器节点,这个标识要写到dataDir目录下面myid文件里
# 指名集群间通讯端口和选举端口
server.1=127.0.0.1:2287:3387
server.2=127.0.0.1:2288:3388
server.3=127.0.0.1:2289:3389

#  如果是多台服务器,则集群中每个节点通讯端口和选举端口可相同,IP 地址修改为每个节点所在主机 IP 即可。

zookeeper02 配置,与 zookeeper01 相比,只有 dataLogDir 和 dataLogDir 不同:

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/zookeeper-cluster/data/02
dataLogDir=/usr/local/zookeeper-cluster/log/02
clientPort=2182
server.1=127.0.0.1:2287:3387
server.2=127.0.0.1:2288:3388
server.3=127.0.0.1:2289:3389

zookeeper03 配置,与 zookeeper01,02 相比,也只有 dataLogDir 和 dataLogDir 不同:

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/zookeeper-cluster/data/03
dataLogDir=/usr/local/zookeeper-cluster/log/03
clientPort=2183
server.1=127.0.0.1:2287:3387
server.2=127.0.0.1:2288:3388
server.3=127.0.0.1:2289:3389

配置参数说明:

  • tickTime:用于计算的基础时间单元。比如 session 超时:N*tickTime;
  • initLimit:用于集群,允许从节点连接并同步到 master 节点的初始化连接时间,以 tickTime 的倍数来表示;
  • syncLimit:用于集群, master 主节点与从节点之间发送消息,请求和应答时间长度(心跳机制);
  • dataDir:数据存储位置;
  • dataLogDir:日志目录;
  • clientPort:用于客户端连接的端口,默认 2181

多台服务器上搭建:将 zookeeper 安装包分发到其他服务器(Xsync)。分别进入安装目录的 conf 目录,拷贝配置样本 zoo_sample.cfg 为 zoo.cfg 并进行修改,修改三份配置文件内容如下(hadoop10、hadoop11、hadoop12):

多台服务器,则集群中每个节点通讯端口和选举端口可相同,IP 地址修改为每个节点所在主机 IP 即可。

生产集群(多台服务器搭建样例):

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/home/hadoop/apps/zookeeper/data
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

# server.1 这个1是服务器的标识,可以是任意有效数字,标识这是第几个服务器节点,这个标识要写到dataDir目录下面myid文件里
# 指名集群间通讯端口和选举端口
server.1=hadoop10:2888:3888
server.2=hadoop11:2888:3888
server.3=hadoop12:2888:3888

2.1.3 标识节点

分别在三个节点的数据存储目录下新建 myid 文件,并写入对应的节点标识。Zookeeper 集群通过 myid 文件识别集群节点,并通过上文配置的节点通信端口和选举端口来进行节点通信,选举出 leader 节点。

创建存储目录:

# dataDir
mkdir -vp  /usr/local/zookeeper-cluster/data/01
# dataDir
mkdir -vp  /usr/local/zookeeper-cluster/data/02
# dataDir
mkdir -vp  /usr/local/zookeeper-cluster/data/03

创建并写入节点标识到 myid 文件:

#server1
echo "1" > /usr/local/zookeeper-cluster/data/01/myid
#server2
echo "2" > /usr/local/zookeeper-cluster/data/02/myid
#server3
echo "3" > /usr/local/zookeeper-cluster/data/03/myid

2.1.4 启动集群

分别启动三个节点

# 启动节点1
/usr/app/zookeeper-cluster/zookeeper01/bin/zkServer.sh start
# 启动节点2
/usr/app/zookeeper-cluster/zookeeper02/bin/zkServer.sh start
# 启动节点3
/usr/app/zookeeper-cluster/zookeeper03/bin/zkServer.sh start

2.1.5 集群验证

使用 jps 查看进程,并且使用 zkServer.sh status 查看集群各个节点状态。如图三个节点进程均启动成功,并且两个节点为 follower 节点,一个节点为 leader 节点。

2.1.6 搭建多台服务器上的Zookeeper集群见我的博客Zookeeper 

2.2 Kafka多台服务器上集群部署

2.2.1 环境准备

2.2.1.1 集群规划

hadoop10

hadoop11hadoop12
zkzkzk
kafkakafkakafka

2.2.1.2 jar包下载

http://kafka.apache.org/downloads.html

这里解释一下 kafka 安装包的命名规则:以 kafka_2.12-2.8.0.tgz 为例,前面的 2.12 代表 Scala 的版本号(Kafka 采用 Scala 语言进行开发),后面的 2.8.0 则代表 Kafka 的版本号。

2.2.2 Kafka集群部署

1)解压安装包

[hadoop@hadoop10 software]$ tar -zxvf kafka_2.11-0.11.0.0.tgz -C /opt/module/

2)修改解压后的文件名称(创建软链接)

[hadoop@hadoop10 module]$ ln -s  kafka_2.11-0.11.0.0 kafka

3)在/opt/module/kafka目录下创建logs文件夹

[hadoop@hadoop10 kafka]$ mkdir logs

4)修改配置文件

[hadoop@hadoop10 kafka]$ cd config/

[hadoop@hadoop10 config]$ vi server.properties

输入以下内容:

#broker全局唯一编号,不能重复

broker.id=0

#删除topic功能使能

delete.topic.enable=true

#处理网络请求的线程数量

num.network.threads=3

#用来处理磁盘IO的现成数量

num.io.threads=8

#发送套接字的缓冲区大小

socket.send.buffer.bytes=102400

#接收套接字的缓冲区大小

socket.receive.buffer.bytes=102400

#请求套接字的缓冲区大小

socket.request.max.bytes=104857600

#kafka运行日志存放的路径

log.dirs=/opt/module/kafka/logs

#topic在当前broker上的分区个数

num.partitions=1

#用来恢复和清理data下数据的线程数量

num.recovery.threads.per.data.dir=1

#segment文件保留的最长时间,超时将被删除

log.retention.hours=168

#配置连接Zookeeper集群地址

zookeeper.connect=hadoop10:2181,hadoop11:2181,hadoop12:2181

实际生产中的配置:

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# see kafka.server.KafkaConfig for additional details and defaults

############################# Server Basics #############################

# The id of the broker. This must be set to a unique integer for each broker.
broker.id=10

# Switch to enable topic deletion or not, default value is false
delete.topic.enable=true

############################# Socket Server Settings #############################

# The address the socket server listens on. It will get the value returned from 
# java.net.InetAddress.getCanonicalHostName() if not configured.
#   FORMAT:
#     listeners = security_protocol://host_name:port
#   EXAMPLE:
#     listeners = PLAINTEXT://your.host.name:9092
#listeners=PLAINTEXT://:9092

# Hostname and port the broker will advertise to producers and consumers. If not set, 
# it uses the value for "listeners" if configured.  Otherwise, it will use the value
# returned from java.net.InetAddress.getCanonicalHostName().
#advertised.listeners=PLAINTEXT://your.host.name:9092

# The number of threads handling network requests
num.network.threads=3

# The number of threads doing disk I/O
num.io.threads=8

# The send buffer (SO_SNDBUF) used by the socket server
socket.send.buffer.bytes=102400

# The receive buffer (SO_RCVBUF) used by the socket server
socket.receive.buffer.bytes=102400

# The maximum size of a request that the socket server will accept (protection against OOM)
socket.request.max.bytes=104857600


############################# Log Basics #############################

# A comma seperated list of directories under which to store log files
log.dirs=/home/hadoop/apps/kafka/logs

# The default number of log partitions per topic. More partitions allow greater
# parallelism for consumption, but this will also result in more files across
# the brokers.
num.partitions=1

# The number of threads per data directory to be used for log recovery at startup and flushing at shutdown.
# This value is recommended to be increased for installations with data dirs located in RAID array.
num.recovery.threads.per.data.dir=1

############################# Log Flush Policy #############################

# Messages are immediately written to the filesystem but by default we only fsync() to sync
# the OS cache lazily. The following configurations control the flush of data to disk.
# There are a few important trade-offs here:
#    1. Durability: Unflushed data may be lost if you are not using replication.
#    2. Latency: Very large flush intervals may lead to latency spikes when the flush does occur as there will be a lot of data to flush.
#    3. Throughput: The flush is generally the most expensive operation, and a small flush interval may lead to exceessive seeks.
# The settings below allow one to configure the flush policy to flush data after a period of time or
# every N messages (or both). This can be done globally and overridden on a per-topic basis.

# The number of messages to accept before forcing a flush of data to disk
#log.flush.interval.messages=10000

# The maximum amount of time a message can sit in a log before we force a flush
#log.flush.interval.ms=1000

############################# Log Retention Policy #############################

# The following configurations control the disposal of log segments. The policy can
# be set to delete segments after a period of time, or after a given size has accumulated.
# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens
# from the end of the log.

# The minimum age of a log file to be eligible for deletion
log.retention.hours=168

# A size-based retention policy for logs. Segments are pruned from the log as long as the remaining
# segments don't drop below log.retention.bytes.
#log.retention.bytes=1073741824

# The maximum size of a log segment file. When this size is reached a new log segment will be created.
log.segment.bytes=1073741824

# The interval at which log segments are checked to see if they can be deleted according
# to the retention policies
log.retention.check.interval.ms=300000

############################# Zookeeper #############################

# Zookeeper connection string (see zookeeper docs for details).
# This is a comma separated host:port pairs, each corresponding to a zk
# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002".
# You can also append an optional chroot string to the urls to specify the
# root directory for all kafka znodes.
zookeeper.connect=hadoop10:2181,hadoop11:2181,hadoop12:2181   #/kafka  这里可以后跟/kafka可以在Zookeeper的目录下将kafka的所有目录都放在kafka下,统一管理

# Timeout in ms for connecting to zookeeper
zookeeper.connection.timeout.ms=6000

5)配置环境变量

[hadoop@hadoop10 module]$ sudo vi /etc/profile
#KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka
export PATH=$PATH:$KAFKA_HOME/bin
[hadoop@hadoop10 module]$ source /etc/profile

6)分发安装包

[hadoop@hadoop10 module]$ xsync kafka/

注意:分发之后记得配置其他机器的环境变量

7)分别在hadoop11和hadoop12上修改配置文件/opt/module/kafka/config/server.properties中的broker.id=1broker.id=2

注:broker.id不得重复

8)启动集群

依次在hadoop10、hadoop11、hadoop12节点上启动kafka

[hadoop@hadoop10 kafka]$ bin/kafka-server-start.sh config/server.properties &

[hadoop@hadoop11 kafka]$ bin/kafka-server-start.sh config/server.properties &

[hadoop@hadoop12 kafka]$ bin/kafka-server-start.sh config/server.properties &

9)关闭集群

[hadoop@hadoop10 kafka]$ bin/kafka-server-stop.sh stop

[hadoop@hadoop11 kafka]$ bin/kafka-server-stop.sh stop

[hadoop@hadoop12 kafka]$ bin/kafka-server-stop.sh stop

2.3 Kafka单台服务器上集群部署

2.3.1 下载解压

Kafka 安装包官方下载地址:http://kafka.apache.org/downloads ,本用例下载的版本为 2.2.0,下载命令:

# 下载
wget https://www-eu.apache.org/dist/kafka/2.2.0/kafka_2.12-2.2.0.tgz
# 解压
tar -xzf kafka_2.12-2.2.0.tgz

2.3.2 拷贝配置文件

进入解压目录的 config 目录下 ,拷贝三份配置文件:

# cp server.properties server-1.properties
# cp server.properties server-2.properties
# cp server.properties server-3.properties

2.3.3 修改配置

分别修改三份配置文件中的部分配置,如下:

server-1.properties:

# The id of the broker. 集群中每个节点的唯一标识
broker.id=0
# 监听地址
listeners=PLAINTEXT://hadoop001:9092
# 数据的存储位置
log.dirs=/usr/local/kafka-logs/00
# Zookeeper连接地址
zookeeper.connect=hadoop001:2181,hadoop001:2182,hadoop001:2183

server-2.properties:

broker.id=1
listeners=PLAINTEXT://hadoop001:9093
log.dirs=/usr/local/kafka-logs/01
zookeeper.connect=hadoop001:2181,hadoop001:2182,hadoop001:2183

server-3.properties:

broker.id=2
listeners=PLAINTEXT://hadoop001:9094
log.dirs=/usr/local/kafka-logs/02
zookeeper.connect=hadoop001:2181,hadoop001:2182,hadoop001:2183

这里需要说明的是 log.dirs 指的是数据日志的存储位置,确切的说,就是分区数据的存储位置,而不是程序运行日志的位置。程序运行日志的位置是通过同一目录下的 log4j.properties 进行配置的。

2.3.4 启动集群

分别指定不同配置文件,启动三个 Kafka 节点。启动后可以使用 jps 查看进程,此时应该有三个 zookeeper 进程和三个 kafka 进程。

bin/kafka-server-start.sh config/server-1.properties
bin/kafka-server-start.sh config/server-2.properties
bin/kafka-server-start.sh config/server-3.properties

2.3.5 创建测试主题

创建测试主题:

bin/kafka-topics.sh --create --bootstrap-server hadoop001:9092 \
                    --replication-factor 3 \
                    --partitions 1 --topic my-replicated-topic

创建后可以使用以下命令查看创建的主题信息:

bin/kafka-topics.sh --describe --bootstrap-server hadoop001:9092 --topic my-replicated-topic

可以看到分区 0 的有 0,1,2 三个副本,且三个副本都是可用副本,都在 ISR(in-sync Replica 同步副本) 列表中,其中 1 为首领副本,此时代表集群已经搭建成功。

2.4 Kafka命令行操作

2.4.1 查看当前服务器中的所有topic

[hadoop@hadoop10 kafka]$ bin/kafka-topics.sh --zookeeper hadoop10:2181 --list

2.4.2 创建topic

[hadoop@hadoop10 kafka]$ bin/kafka-topics.sh --zookeeper hadoop10:2181 \

--create --replication-factor 3 --partitions 1 --topic first

选项说明:

--topic 定义topic名

--replication-factor  定义副本数

--partitions  定义分区数

2.4.3 删除topic

[hadoop@hadoop10 kafka]$ bin/kafka-topics.sh --zookeeper hadoop10:2181 \

--delete --topic first

需要server.properties中设置delete.topic.enable=true否则只是标记删除或者直接重启。

2.4.4 发送消息

[hadoop@hadoop10 kafka]$ bin/kafka-console-producer.sh \

--broker-list hadoop10:9092 --topic first

>hello world

>hadoop  hadoop

2.4.5消费消息

[hadoop@hadoop10 kafka]$ bin/kafka-console-consumer.sh \

--zookeeper hadoop10:2181 --from-beginning --topic first

--from-beginning会把first主题中以往所有的数据都读取出来。根据业务场景选择是否增加该配置。

2.4.6 查看某个Topic的详情

[hadoop@hadoop10 kafka]$ bin/kafka-topics.sh --zookeeper hadoop10:2181 \

--describe --topic first

三、Kafka工作流程分析

3.1 Kafka生产过程分析

3.1.1 写入方式

producer采用推(push)模式将消息发布到broker,每条消息都被追加(append)到分区(patition)中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障kafka吞吐率)。

3.1.2 分区(Partition)

消息发送时都被发送到一个topic,其本质就是一个目录,而topic是由一些Partition Logs(分区日志)组成,其组织结构如下图所示:

我们可以看到,每个Partition中的消息都是有序,生产的消息被不断追加到Partition log上,其中的每一个消息都被赋予了一个唯一的offset值

1)分区的原因

(1)方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了;

      (2)可以提高并发,因为可以以Partition为单位读写了。

2)分区的原则

(1)指定了partition,则直接使用;

(2)未指定patition但指定key,通过对key的value进行hash出一个partition;

(3)partition和key都未指定,使用轮询选出一个partition。

DefaultPartitioner类
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int numPartitions = partitions.size();
        if (keyBytes == null) {
            int nextValue = nextValue(topic);
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) {
                int part = Utils.toPositive(nextValue) % availablePartitions.size();
                return availablePartitions.get(part).partition();
            } else {
                // no partitions are available, give a non-available partition
                return Utils.toPositive(nextValue) % numPartitions;
            }
        } else {
            // hash the keyBytes to choose a partition
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }

3.1.3 副本(Replication)

同一个partition可能会有多个replication(对应 server.properties 配置中的 default.replication.factor=N)。没有replication的情况下,一旦broker 宕机,其上所有 partition 的数据都不可被消费,同时producer也不能再将数据存于其上的partition。引入replication之后,同一个partition可能会有多个replication,而这时需要在这些replication之间选出一个leader,producer和consumer只与这个leader交互,其它replication作为follower从leader 中复制数据。

3.1.4 写入流程

 producer写入消息流程如下

  1. producer先从zookeeper的 "/brokers/.../state"节点找到该partition的leader
  2. producer将消息发送给该leader
  3. leader将消息写入本地log
  4. followers从leader pull消息,写入本地log后向leader发送ACK
  5. leader收到所有ISR中的replication的ACK后,增加HW(high watermark,最后commit 的offset)并向producer发送ACK                         

3.2 Broker 保存消息

3.2.1 存储方式

物理上把topic分成一个或多个partition(对应 server.properties 中的num.partitions=3配置),每个partition物理上对应一个文件夹(该文件夹存储该partition的所有消息和索引文件),如下:

[hadoop@hadoop10 logs]$ ll

drwxrwxr-x. 2 hadoop hadoop  4096 7月   4 14:37 first-0

drwxrwxr-x. 2 hadoop hadoop  4096 7月   4 14:35 first-1

drwxrwxr-x. 2 hadoop hadoop  4096 7月   4 14:37 first-2

[hadoop@hadoop10 logs]$ cd first-0

[hadoop@hadoop10 first-0]$ ll

-rw-rw-r--. 1 hadoop hadoop 10485760 7月   4 14:33 00000000000000000000.index

-rw-rw-r--. 1 hadoop hadoop      219 7月   4 15:07 00000000000000000000.log

-rw-rw-r--. 1 hadoop hadoop 10485756 7月   4 14:33 00000000000000000000.timeindex

-rw-rw-r--. 1 hadoop hadoop        8 7月   4 14:37 leader-epoch-checkpoint

3.2.2 存储策略

无论消息是否被消费,kafka都会保留所有消息。有两种策略可以删除旧数据:

1)基于时间:log.retention.hours=168

2)基于大小:log.retention.bytes=1073741824

需要注意的是,因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关。

3.2.3 Zookeeper存储结构

注意:producer不在zk中注册,消费者在zk中注册。

3.3 Kafka消费过程分析

3.3.1 消费者组

消费者是以consumer group消费者组的方式工作,由一个或者多个消费者组成一个组,共同消费一个topic。每个分区在同一时间只能由group中的一个消费者读取,但是多个group可以同时消费这个partition。在图中,有一个由三个消费者组成的group,有一个消费者读取主题中的两个分区,另外两个分别读取一个分区。某个消费者读取某个分区,也可以叫做某个消费者是某个分区的拥有者。

在这种情况下,消费者可以通过水平扩展的方式同时读取大量的消息。另外,如果一个消费者失败了,那么其他的group成员会自动负载均衡读取之前失败的消费者读取的分区。

3.3.2 消费方式

consumer采用pull(拉)模式从broker中读取数据。

push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。

对于Kafka而言,pull模式更合适,它可简化broker的设计,consumer可自主控制消费消息的速率,同时consumer可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的传输语义。

pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直等待数据到达。为了避免这种情况,我们在我们的拉请求中有参数,允许消费者请求在等待数据到达的“长轮询”中进行阻塞(并且可选地等待到给定的字节数,以确保大的传输大小)。

3.3.3 消费者组案例

1)需求:测试同一个消费者组中的消费者,同一时刻只能有一个消费者消费。

2)案例实操

(1)在hadoop10、hadoop11上修改/opt/module/kafka/config/consumer.properties配置文件中的group.id属性为任意组名。

[hadoop@hadoop10 config]$ vi consumer.properties

group.id=test01

(2)在hadoop10、hadoop11上分别启动消费者

[hadoop@hadoop10 kafka]$ bin/kafka-console-consumer.sh \

--zookeeper hadoop10:2181 --topic first --consumer.config config/consumer.properties

[hadoop@hadoop11 kafka]$ bin/kafka-console-consumer.sh --zookeeper hadoop10:2181 --topic first --consumer.config config/consumer.properties

(3)在hadoop12上启动生产者

[hadoop@hadoop12 kafka]$ bin/kafka-console-producer.sh \

--broker-list hadoop10:9092 --topic first

>hello world

(4)查看hadoop10和hadoop11的接收者。

同一时刻只有一个消费者接收到消息。

四、Kafka 生产者详解

4.1 Producer的生产流程:

 

五、Kafka 消费者详解

5.1 Kafka的三种消费语义

  1. at least once:至少消费一次的语义。保证数据不丢失,但是数据可能重复
  2. at most once:最多消费一次的语义。保证数据不重复,但是数据可能丢失
  3. exactly once:精确一次消费语义。不重复不丢失

六、深入理解 Kafka 副本机制

6.1 关于Kafka集群

Kafka 使用 Zookeeper 来维护集群成员 (brokers) 的信息。每个 broker 都有一个唯一标识 broker.id,用于标识自己在集群中的身份,可以在配置文件 server.properties 中进行配置,或者由程序自动生成。下面是 Kafka brokers 集群自动创建的过程:

  • 每一个 broker 启动的时候,它会在 Zookeeper 的 /brokers/ids 路径下创建一个 临时节点,并将自己的 broker.id 写入,从而将自身注册到集群;
  • 当有多个 broker 时,所有 broker 会竞争性地在 Zookeeper 上创建 /controller 节点,由于 Zookeeper 上的节点不会重复,所以必然只会有一个 broker 创建成功,此时该 broker 称为 controller broker。它除了具备其他 broker 的功能外,还负责管理主题分区及其副本的状态
  • 当 broker 出现宕机或者主动退出从而导致其持有的 Zookeeper 会话超时时,会触发注册在 Zookeeper 上的 watcher 事件,此时 Kafka 会进行相应的容错处理;如果宕机的是 controller broker 时,还会触发新的 controller 选举。

Controller的选举
哪一个Broker先在ZK的controller节点上创建节点,谁就是Contorller

6.2 副本机制

为了保证高可用,kafka 的分区是多副本的,如果一个副本丢失了,那么还可以从其他副本中获取分区数据。但是这要求对应副本的数据必须是完整的,这是 Kafka 数据一致性的基础,所以才需要使用 controller broker 来进行专门的管理。下面将详细介绍 Kafka 的副本机制。

6.2.1 分区和副本

Kafka 的主题被分为多个分区 ,分区是 Kafka 最基本的存储单位。每个分区可以有多个副本 (可以在创建主题时使用 replication-factor 参数进行指定)。其中一个副本是首领副本 (Leader replica),所有的事件都直接发送给首领副本;其他副本是跟随者副本 (Follower replica),需要通过复制来保持与首领副本数据一致,当首领副本不可用时,其中一个跟随者副本将成为新首领。

 6.2.2 ISR机制

每个分区都有一个 ISR(in-sync Replica) 列表,用于维护所有同步的、可用的副本。首领副本必然是同步副本,而对于跟随者副本来说,它需要满足以下条件才能被认为是同步副本:

  • 与 Zookeeper 之间有一个活跃的会话,即必须定时向 Zookeeper 发送心跳;
  • 在规定的时间内从首领副本那里低延迟地获取过消息。

如果副本不满足上面条件的话,就会被从 ISR 列表中移除,直到满足条件才会被再次加入。

这里给出一个主题创建的示例:使用 --replication-factor 指定副本系数为 3,创建成功后使用 --describe 命令可以看到分区 0 的有 0,1,2 三个副本,且三个副本都在 ISR 列表中,其中 1 为首领副本。

6.2.3 不完全的首领选举

对于副本机制,在 broker 级别有一个可选的配置参数 unclean.leader.election.enable,默认值为 fasle,代表禁止不完全的首领选举。这是针对当首领副本挂掉且 ISR 中没有其他可用副本时,是否允许某个不完全同步的副本成为首领副本,这可能会导致数据丢失或者数据不一致,在某些对数据一致性要求较高的场景 (如金融领域),这可能无法容忍的,所以其默认值为 false,如果你能够允许部分数据不一致的话,可以配置为 true。

6.2.4 最少同步副本

ISR是一个动态集合列表。该列表中表示正在同步的副本。Leader天生就在这个列表中。如果follower同步leader副本的数据超过时间(默认10S),就会被剔除ISR列表。如果在这个时间范围内又同步成功了,会再次进入ISR列表。

replica.lag.time.max.ms=10000 #同步leader副本的数据超过时间

ISR 机制的另外一个相关参数是 min.insync.replicas , 可以在 broker 或者主题级别进行配置,代表 ISR 列表中至少要有几个可用副本。这里假设设置为 2,那么当可用副本数量小于该值时,就认为整个分区处于不可用状态。此时客户端再向分区写入数据时候就会抛出异常 org.apache.kafka.common.errors.NotEnoughReplicasExceptoin: Messages are rejected since there are fewer in-sync replicas than required。

6.2.5 发送确认

Kafka 在生产者上有一个可选的参数 ack,该参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入成功:

  • acks=0 :消息发送出去就认为已经成功了,不会等待任何来自服务器的响应;
  • acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应;
  • acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。

说明:

acks=0 #表示producer生产数据不需要包括leader在内的任何响应,效率很高,但是数据不安全 (at most once)
acks=1 #表示producer生产数据只要leader保存到本地就会给出响应,不需要follower的响应。效率相对高,数据相对安全。但是leader如果早folower同步之前挂掉,数据丢失 (at most once)
acks=-1(all) #表示producer生产数据需要leader和所有follower副本的响应。效率不到,安全性最高。只要有一个副本存活,数据就不会丢失。(at least once)

6.2.6 Exactly Once 消息的保证

at least once保证数据至少消费一次,数据不丢失,但是会重复。
幂等性:无论操作多少次方法,结果不变。kafka 0.11版本才被引入。
在kafka中表示就是无论发送多少次,消息不重复。
at least once + 幂等性(idempotence) = Exactly Once
从0.11版本开始可用开启幂等性:

enable.idempotence=true

at least once + 幂等性(idempotence) = Exactly
Once只能保证单分区、单会话内的精确一次。

6.3 物理存储

6.3.1 分区分配

在创建主题时,Kafka 会首先决定如何在 broker 间分配分区副本,它遵循以下原则:

  • 在所有 broker 上均匀地分配分区副本;
  • 确保分区的每个副本分布在不同的 broker 上;
  • 如果使用了 broker.rack 参数为 broker 指定了机架信息,那么会尽可能的把每个分区的副本分配到不同机架的 broker 上,以避免一个机架不可用而导致整个分区不可用。

基于以上原因,如果你在一个单节点上创建一个 3 副本的主题,通常会抛出下面的异常:

    Error while executing topic command : org.apache.kafka.common.errors.InvalidReplicationFactor   
    Exception: Replication factor: 3 larger than available brokers: 1.

6.3.2 分区数据保留规则

保留数据是 Kafka 的一个基本特性, 但是 Kafka 不会一直保留数据,也不会等到所有消费者都读取了消息之后才删除消息。相反, Kafka 为每个主题配置了数据保留期限,规定数据被删除之前可以保留多长时间,或者清理数据之前可以保留的数据量大小。分别对应以下四个参数:

  • log.retention.bytes :删除数据前允许的最大数据量;默认值-1,代表没有限制;
  • log.retention.ms:保存数据文件的毫秒数,如果未设置,则使用 log.retention.minutes 中的值,默认为 null;
  • log.retention.minutes:保留数据文件的分钟数,如果未设置,则使用 log.retention.hours 中的值,默认为 null;
  • log.retention.hours:保留数据文件的小时数,默认值为 168,也就是一周。

因为在一个大文件里查找和删除消息是很费时的,也很容易出错,所以 Kafka 把分区分成若干个片段,当前正在写入数据的片段叫作活跃片段。活动片段永远不会被删除。如果按照默认值保留数据一周,而且每天使用一个新片段,那么你就会看到,在每天使用一个新片段的同时会删除一个最老的片段,所以大部分时间该分区会有 7 个片段存在。

6.3.3 文件格式

通常保存在磁盘上的数据格式与生产者发送过来消息格式是一样的。 如果生产者发送的是压缩过的消息,那么同一个批次的消息会被压缩在一起,被当作“包装消息”进行发送 (格式如下所示) ,然后保存到磁盘上。之后消费者读取后再自己解压这个包装消息,获取每条消息的具体信息。

 6.4.4 Kafka为什么快?(为什么是高吞吐??)

  1. 顺写磁盘的,log数据是采用追加的方式
  2. 写数据的时候使用了操作系统的页缓存(pagecache)
  3. kafka使用操作系统提供的sendFile函数提供的零复制(Zero Copy)技术

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大数据翻身

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值