Kafka(一)

第 1 章 Kafka 概 述

1.1定义

Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。

1.2消息队列

1.2.1传统消息队列的应用场景

在这里插入图片描述

使用消息队列的好处

  • 解耦

允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。

  • 可恢复性

系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。

  • 缓冲

有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。

  • 灵活性 & 峰值处理能力

在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。

  • 异步通信

很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。

1.2.2消息队列的两种模式

(1)点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除)
消息生产者生产消息发送到Queue 中,然后消息消费者从Queue 中取出并且消费消息。消息被消费以后,queue 中不再有存储,所以消息消费者不可能消费到已经被消费的消息。Queue 支持存在多个消费者, 但是对一个消息而言, 只会有一个消费者可以消费。
在这里插入图片描述

(2)发布/订阅模式(一对多,消费者消费数据之后不会清除消息)
消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到 topic 的消息会被所有订阅者消费。

在这里插入图片描述

1.3Kafka 基础架构

在这里插入图片描述

  • Producer :消息生产者,就是向 kafka broker 发消息的客户端;
  • Consumer :消息消费者,向 kafka broker 取消息的客户端;
  • Consumer Group (CG):消费者组,由多个 consumer 组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
  • Broker :一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker
    可以容纳多个topic。
  • Topic :可以理解为一个队列,生产者和消费者面向的都是一个 topic;
  • Partition:为了实现扩展性,一个非常大的 topic 可以分布到多个broker(即服务器)上,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队列;
  • Replica:副本,为保证集群中的某个节点发生故障时,该节点上的 partition 数据不丢失,

且kafka 仍然能够继续工作,kafka 提供了副本机制,一个 topic 的每个分区都有若干个副本,一个 leader 和若干个 follower。

  • leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对
    象都是 leader。
  • follower:每个分区多个副本中的“从”,实时从 leader 中同步数据,保持和 leader 数据的同步。leader 发生故障时,某个 follower 会成为新的 follower。

第 2 章 Kafka 快速入门

2.1安装部署

2.1.1集群规划

hadoop102 hadoop103 hadoop104
zk zk zk
kafka kafka kafka

2.1.2 jar 包下载

http://kafka.apache.org/downloads.html
在这里插入图片描述

2.1.3 集群部署

1)解压安装包

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

2)修改解压后的文件名称

[atguigu@hadoop102 module]$ mv kafka_2.11-0.11.0.0/ kafka	

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

[atguigu@hadoop102 kafka]$ mkdir logs	

4)修改配置文件
输入以下内容:

#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=hadoop102:2181,hadoop103:2181,hadoop104:2181

5)配置环境变量

[atguigu@hadoop102 module]$ sudo vi /etc/profile

#KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka 
export PATH=$PATH:$KAFKA_HOME/bin

[atguigu@hadoop102 module]$ source /etc/profile

6)分发安装包

[atguigu@hadoop102 module]$ xsync kafka/	

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

7)分别在 hadoop103 和 hadoop104 上修改配置文件/opt/module/kafka/config/server.properties 中的 broker.id=1、broker.id=2
注:broker.id 不得重复

8)启动集群

[atguigu@hadoop102 kafka]$ bin/kafka-server-start.sh -daemon
config/server.properties
[atguigu@hadoop103 kafka]$ bin/kafka-server-start.sh -daemon
config/server.properties
[atguigu@hadoop104 kafka]$ bin/kafka-server-start.sh -daemon
config/server.properties

依次在 hadoop102、hadoop103、hadoop104 节点上启动 kafka

9)关闭集群

[atguigu@hadoop102 kafka]$ bin/kafka-server-stop.sh stop
[atguigu@hadoop103 kafka]$ bin/kafka-server-stop.sh stop
[atguigu@hadoop104 kafka]$ bin/kafka-server-stop.sh stop

10)kafka 群起脚本

for i in hadoop102 hadoop103 hadoop104
do
echo "========== $i ==========" 
ssh $i '/opt/module/kafka/bin/kafka-server-start.sh -daemon 
/opt/module/kafka/config/server.properties'
done

2.2 Kafka 命令行操作

  • 查看当前服务器中的所有 topic
bin/kafka-topics.sh --zookeeper hadoop102:2181 --list
  • 创建 topic
bin/kafka-topics.sh --zookeeper hadoop102:2181 --create --replication-factor 2 --partitions 2 --topic first

选项说明:
– topic 定义 topic 名
– replication-factor 定义副本数,副本数不应超过当前broker数量,例如创建
– partitions 定义分区数
此时/opt/module/kafka/logs目录下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
first-0 ,first代表topic,0代表分区号,第一个分区
first-1, first代表topic,1代表分区号,第二个分区
由此可见kafka数据存于磁盘中,存于first-0和first-1中
kafka的log日志:server.log

bin/kafka-topics.sh --zookeeper hadoop102:2181 --create --replication-factor 3 --partitions 3 --topic second

可以成功,3台机器每台机器上都有second0,如果partitions 4,那就是有一台机器上分配了两个second0,两个副本分配在一台机器上,没有意义。

  • 删除 topic
bin/kafka-topics.sh --zookeeper  hadoop102:2181 --delete --topic first

需要 server.properties 中设置 delete.topic.enable=true 否则只是标记删除。

  • 发送消息
bin/kafka-console-producer.sh --broker-list hadoop102:9092 --topic first
>hello world
>atguigu atguigu
  • 消费消息
bin/kafka-console-consumer.sh --zookeeper hadoop102:2181 --topic first
bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --from-beginning --topic first

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
消费者的消费位置存于kafka自动创建的topic中,默认50个分区,副本1,

第 3 章 Kafka 架构深入

3.1 Kafka 工作流程及文件存储机制

在这里插入图片描述

  • Kafka 中消息是以 topic 进行分类的,生产者生产消息,消费者消费消息,都是面向 topic的。
  • topic 是逻辑上的概念,而 partition 是物理上的概念,每个 partition 对应于一个 log 文件,该 log 文件中存储的就是 producer 生产的数据。Producer 生产的数据会被不断追加到该log 文件末端,且每条数据都有自己的 offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个 offset,以便出错恢复时,从上次的位置继续消费。
    在这里插入图片描述
  • 由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了分片和索引机制,将每个 partition 分为多个 segment。每个 segment对应两个文件——“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic 名称+分区序号。例如,first 这个 topic 有三个分区,则其对应的文件夹为 first-0,first-1,first-2。
00000000000000000000.index
00000000000000000000.log
00000000000000170410.index
00000000000000170410.log
00000000000000239430.index
00000000000000239430.log

index 和 log 文件以当前 segment 的第一条消息的 offset 命名。下图为 index 文件和 log文件的结构示意图。
在这里插入图片描述
“.index”文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元
数据指向对应数据文件中 message 的物理偏移地址。

3.2 Kafka 生产者

3.2.1 分区策略

  • 分区的原因
    (1)方便在集群中扩展,每个 Partition 可以通过调整以适应它所在的机器,而一个 topic
    又可以有多个 Partition 组成,因此整个集群就可以适应任意大小的数据了;
    (2)可以提高并发,因为可以以 Partition 为单位读写了。
  • 分区的原则
    我们需要将 producer 发送的数据封装成一个 ProducerRecord 对象。
    在这里插入图片描述

3.2.2 数据可靠性保证

为保证 producer 发送的数据,能可靠的发送到指定的 topic,topic 的每个 partition 收到
producer 发送的数据后,都需要向 producer 发送 ack(acknowledgement 确认收到),如果
producer 收到 ack,就会进行下一轮的发送,否则重新发送数据。
在这里插入图片描述

  1. 副本数据同步策略
    在这里插入图片描述
  • Kafka 选择了第二种方案,原因如下:
    1.同样为了容忍 n 台节点的故障,第一种方案需要 2n+1 个副本,而第二种方案只需要 n+1
    个副本,而 Kafka 的每个分区都有大量的数据,第一种方案会造成大量数据的冗余。
    2.虽然第二种方案的网络延迟会比较高,但网络延迟对 Kafka 的影响较小。
  • 思考:
    1.第一种方案为什么要半数以上?
    为了防止脑裂,半数以上可以确保只选出一个leader
    2.参与投票的follower至少有多少?
    半数以上
    3.如何保证参与投票的半数以上follower里边至少有一个同步完成的?
    半数以上的follower同步完成
  1. ISR
    采用第二种方案之后,设想以下情景:leader 收到数据,所有 follower 都开始同步数据, 但有一个 follower,因为某种故障,迟迟不能与 leader 进行同步,那 leader 就要一直等下去, 直到它完成同步,才能发送 ack。这个问题怎么解决呢?
    Leader 维护了一个动态的 in-sync replica set (ISR),意为和 leader 保持同步的 follower 集合。当 ISR 中的 follower 完成数据的同步之后,leader 就会给 follower 发送 ack。如果 follower长时间 未 向 leader 同 步 数 据 , 则 该 follower 将 被 踢 出 ISR , 该 时 间 阈 值 由replica.lag.time.max.ms 参数设定。Leader 发生故障之后,就会从 ISR 中选举新的 leader。

  2. ack 应答机制
    对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等 ISR 中的 follower 全部接收成功。所以 Kafka 为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择以下的配置。
    acks 参数配置:
    acks:
    0:producer 不等待 broker 的 ack,这一操作提供了一个最低的延迟,broker 一接收到还没有写入磁盘就已经返回,当 broker 故障时有可能丢失数据;
    1:producer 等待broker 的 ack,partition 的 leader 落盘成功后返回ack,如果在 follower
    同步成功之前leader 故障,那么将会丢失数据;
    在这里插入图片描述
    -1(all):producer 等待 broker 的 ack,partition 的 leader 和 follower 全部落盘成功后才
    返回 ack。但是如果在 follower 同步完成后,broker 发送 ack 之前,leader 发生故障,那么会
    造成数据重复。
    在这里插入图片描述
    4)故障处理细节
    在这里插入图片描述
    LEO:指的是每个副本最大的 offset;
    HW:指的是消费者能见到的最大的 offset,ISR 队列中最小的 LEO。
    (1)follower 故障
    follower 发生故障后会被临时踢出 ISR,待该 follower 恢复后,follower 会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从 HW 开始向 leader 进行同步。等该 follower 的 LEO 大于等于该 Partition 的 HW,即 follower 追上 leader 之后,就可以重新加入 ISR 了。
    (2)leader 故障
    leader 发生故障之后,会从 ISR 中选出一个新的 leader,之后,为保证多个副本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader同步数据。
    注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。

3.2.3 Exactly Once 语义

  1. 将服务器的 ACK 级别设置为-1,可以保证 Producer 到 Server 之间不会丢失数据,即 AtLeast Once 语义。相对的,将服务器 ACK 级别设置为 0,可以保证生产者每条消息只会被发送一次,即 At Most Once 语义。
  2. At Least Once 可以保证数据不丢失,但是不能保证数据不重复;相对的,At Least Once可以保证数据不重复,但是不能保证数据不丢失。但是,对于一些非常重要的信息,比如说交易数据,下游数据消费者要求数据既不重复也不丢失,即 Exactly Once 语义。在 0.11 版本以前的 Kafka,对此是无能为力的,只能保证数据不丢失,再在下游消费者对数据做全局去重。对于多个下游应用的情况,每个都需要单独做全局去重,这就对性能造成了很大影响。
  3. 0.11 版本的 Kafka,引入了一项重大特性:幂等性。所谓的幂等性就是指 Producer 不论向 Server 发送多少次重复数据,Server 端都只会持久化一条。幂等性结合 At Least Once 语义,就构成了 Kafka 的 Exactly Once 语义。即:At Least Once + 幂等性 = Exactly Once
  4. 要启用幂等性,只需要将 Producer 的参数中 enable.idompotence 设置为 true 即可。Kafka的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的 Producer 在初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而Broker 端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条。
    == 但是 PID 重启就会变化,同时不同的 Partition 也具有不同主键,所以幂等性无法保证跨分区跨会话的 Exactly Once ==

3.2.4 kafka常用配置

名词解释:
broker: 一个kafka启动后,就是一个broker,生产者和消费者都和broker通信;
topic: 主题,生产者发消息需要指定某个主题,消费者拉取消息的时候也需要指定某个主题;
partition: 分区,一个主题分为多个分区,producer可以指定往特定主题的特定分区发消息,同一个topic分区同时只能被一个consumer group的某个consumer消费;
producer: 消息发送者
consumer: 消息消费者
consumer group: 消费者组,比如有个消费者组g1,有一个消费者c1 ,一个topic:test ,它有三个分区p1,p2,p3 ;test这个topic只会被g1这个消费者组中的消费者消费一次;c1会分别串行消费分区p1,p2,p3(同一个分区的消息会被顺序消费,因为一个分区同时只会有一个consumer在消费),为了提高消费速度,我们新建了两个消费者c2,c3,然后加入到;消费者组g1中,这是三个消费者c1,c2,c3就可以并行消费分区p1,p2,p3了;如果你想增加一个消费者,消费topic:test所有的消息,只需要新建消费者组g2,消费者c4,c4加入到g2中。

  1. broker 配置

broker.id=0 broker标识符,集群模式下这个值不能重复
num.network.threads=3 kafka处理网络连接的线程数

num.io.threads=8 kafka处理I/o操作的线程数

socket.send.buffer.bytes=102400 socker发送缓冲区大小

socket.receive.buffer.bytes=102400 socker接收缓冲区大小

socket.request.max.bytes=104857600 socker请求的最大大小
log.dirs= kafka把所有消息都持久化保存到磁盘上,配置日志的存放目录,多个用逗号分隔;同一个分区的日志保存在同一个目录下;在有新分区时,broker会往最少分区的目录下创建,而不是最多空余磁盘空间下新增。

auto.create.topics.enable=true 是否允许自动创建topic
比如设置为true 往一个不存在的topic发消息,consumer从不存在的topic拉取消息,都会创建topic。

num.partitions=2 新创建的topic包含多少个分区,该数量只能增加不能减少

num.recovery.threads.per.data.dir=1 在broker正常关闭和启动的时候,用于打开日志文件的线程数(如果设置为3,log.dirs配置了3个目录,就会启用9个线程)

log.retention.hours=168 设置log的保留时间,是通过日志文件的最后修改时间来实现的。

log.retention.bytes 作用在每个分区上,比如该参数设置为1G,也就是说一个分区的日志文件大小不能超过1G,超出的部分会被删除(假如一个topic有6个partiton,那么这个topic最多可以保留6G数据)

log.segment.bytes=1073741824 单个日志片段文件的大小(默认1G),当日志片段文件达到设定值,文件就会关闭,然后创建一个新的日志文件。

message.max.bytes 限制单条消息的大小(默认1M),该参数指的是压缩后的大小
这个值需要和客户端设置的fetch.message.max.bytes(客户端允许拉取的最大消息)值对应

log.retention.check.interval.ms=300000 检查log是否保留的时间间隔

zookeeper.connect=localhost:3183 保存broker元数据的zk地址

zookeeper.connection.timeout.ms=6000 kafka连接zk的超时时间

总结:具体设置参数值时,需要根据自身的业务情况配置,比如设置分区数num.partitions,如果你topic的消息量很大,需要很高的吞吐量,那么你就可以将值设大一些,那是不是设置的越大越好呢?
当然不是,分区数量越多就越占内存,kafka需要打开的文件句柄就越多,同时也需要你有更多的消费者消费,但是消费者太多了又会引发什么问题呢?
多个消费者同时去poll消息,会提高broker的cpu消耗。所以具体配置需要更具自己业务情况和集群情况来确定。

3.2.5 kafka官网

kafka官网

3.2.6 kafka生产常用配置

[root@kafka116.yinzhengjie.org.cn ~]# cat /soft/kafka/config/server.properties 
# 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 #############################

#每一个broker在集群中的唯一表示,要求是正数。当该服务器的IP地址发生改变时,broker.id没有变化,则不会影响consumers的消息情况
broker.id=116

#这就是说,这条命令其实并不执行删除动作,仅仅是在zookeeper上标记该topic要被删除而已,同时也提醒用户一定要提前打开delete.topic.enable开关,否则删除动作是不会执行的。
delete.topic.enable=true

#是否允许自动创建topic,若是false,就需要通过命令创建topic
auto.create.topics.enable=false

############################# 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 = listener_name://host_name:port
#   EXAMPLE:
#     listeners = PLAINTEXT://your.host.name:9092

#Socket服务器侦听的地址。如果没有配置,它将获得从Java.NET.InAddio.GETCANONICALITHAMEMENE()返回的值
#listeners=PLAINTEXT://10.1.3.116:9092

#broker server服务端口
port=9092

#broker的主机地址,若是设置了,那么会绑定到这个地址上,若是没有,会绑定到所有的接口上,并将其中之一发送到ZK,一般不设置
host.name=10.1.3.116
# 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().

#kafka 0.9.x以后的版本新增了advertised.listeners配置,kafka 0.9.x以后的版本不要使用 advertised.host.name 和 advertised.host.port 已经deprecated.如果配置的话,它使用 "listeners" 的值。否则,它将使用从java.net.InetAddress.getCanonicalHostName()返回的值。
#advertised.listeners=PLAINTEXT://your.host.name:9092


#将侦听器(listener)名称映射到安全协议,默认情况下它们是相同的。有关详细信息,请参阅配置文档。
#listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL


#处理网络请求的最大线程数
num.network.threads=30

#处理磁盘I/O的线程数
num.io.threads=30


#套接字服务器使用的发送缓冲区(SOYSNDBUF)
socket.send.buffer.bytes=5242880

#套接字服务器使用的接收缓冲区(SOYRCVBUF)
socket.receive.buffer.bytes=5242880

#套接字服务器将接受的请求的最大大小(对OOM的保护)
socket.request.max.bytes=104857600

#I/O线程等待队列中的最大的请求数,超过这个数量,network线程就不会再接收一个新的请求。应该是一种自我保护机制。
queued.max.requests=1000

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

#日志存放目录,多个目录使用逗号分割,如果你有多块磁盘,建议配置成多个目录,从而达到I/O的效率的提升。
log.dirs=/home/yinzhengjie/kafka/logs

#每个topic的分区个数,若是在topic创建时候没有指定的话会被topic创建时的指定参数覆盖
num.partitions=20

#在启动时恢复日志和关闭时刷盘日志时每个数据目录的线程的数量,默认1
num.recovery.threads.per.data.dir=30


# 默认副本数
default.replication.factor=2

#服务器接受单个消息的最大大小,即消息体的最大大小,单位是字节
message.max.bytes=104857600

# 自动负载均衡,如果设为true,复制控制器会周期性的自动尝试,为所有的broker的每个partition平衡leadership,为更优先(preferred)的replica分配leadership。
# auto.leader.rebalance.enable=false


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

#在强制fsync一个partition的log文件之前暂存的消息数量。调低这个值会更频繁的sync数据到磁盘,影响性能。通常建议人家使用replication来确保持久性,而不是依靠单机上的fsync,但是这可以带来更多的可靠性,默认10000。
#log.flush.interval.messages=10000

#2次fsync调用之间最大的时间间隔,单位为ms。即使log.flush.interval.messages没有达到,只要这个时间到了也需要调用fsync。默认3000ms.
#log.flush.interval.ms=10000

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


# 日志保存时间 (hours|minutes),默认为7天(168小时)。超过这个时间会根据policy处理数据。bytes和minutes无论哪个先达到都会触发。
log.retention.hours=168

#日志数据存储的最大字节数。超过这个时间会根据policy处理数据。
#log.retention.bytes=1073741824

#控制日志segment文件的大小,超出该大小则追加到一个新的日志segment文件中(-1表示没有限制)
log.segment.bytes=536870912

# 当达到下面时间,会强制新建一个segment
#log.roll.hours = 24*7

# 日志片段文件的检查周期,查看它们是否达到了删除策略的设置(log.retention.hours或log.retention.bytes)
log.retention.check.interval.ms=600000

#是否开启压缩
#log.cleaner.enable=false

#日志清理策略选择有:delete和compact主要针对过期数据的处理,或是日志文件达到限制的额度,会被 topic创建时的指定参数覆盖
#log.cleanup.policy=delete

# 日志压缩运行的线程数
#log.cleaner.threads=2


# 压缩的日志保留的最长时间
#log.cleaner.delete.retention.ms=3600000


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

#zookeeper集群的地址,可以是多个,多个之间用逗号分割.
zookeeper.connect=10.1.3.117:2181,10.1.3.118:2181,10.1.3.119:2181

#ZooKeeper的最大超时时间,就是心跳的间隔,若是没有反映,那么认为已经死了,不易过大
zookeeper.session.timeout.ms=180000

#指定多久消费者更新offset到zookeeper中。注意offset更新时基于time而不是每次获得的消息。一旦在更新zookeeper发生异常并重启,将可能拿到已拿到过的消息,连接zk的超时时间
zookeeper.connection.timeout.ms=6000

#请求的最大大小为字节,请求的最大字节数。这也是对最大记录尺寸的有效覆盖。注意:server具有自己对消息记录尺寸的覆盖,这些尺寸和这个设置不同。此项设置将会限制producer每次批量发送请求的数目,以防发出巨量的请求。
max.request.size=104857600

#每次fetch请求中,针对每次fetch消息的最大字节数。这些字节将会督导用于每个partition的内存中,因此,此设置将会控制consumer所使用的memory大小。这个fetch请求尺寸必须至少和server允许的最大消息尺寸相等,否则,producer可能发送的消息尺寸大于consumer所能消耗的尺寸
。fetch.message.max.bytes=104857600

#ZooKeeper集群中leader和follower之间的同步时间,换句话说:一个ZK follower能落后leader多久。
#zookeeper.sync.time.ms=2000


############################# Replica Basics #############################

# leader接收follower的"fetch请求"的超时时间,默认是10秒。
# replica.lag.time.max.ms=30000

# 如果relicas落后太多,将会认为此partition relicas已经失效。而一般情况下,因为网络延迟等原因,总会导致replicas中消息同步滞后。如果消息严重滞后,leader将认为此relicas网络延迟较大或者消息吞吐能力有限。在broker数量较少,或者网络不足的环境中,建议提高此值.follower落
后于leader的最大message数,这个参数是broker全局的。设置太大 了,影响真正“落后”follower的移除;设置的太小了,导致follower的频繁进出。无法给定一个合适的replica.lag.max.messages的值,因此不推荐使用,据说新版本的Kafka移除了这个参数。#replica.lag.max.messages=4000

# follower与leader之间的socket超时时间
#replica.socket.timeout.ms=30000

# follower每次fetch数据的最大尺寸
replica.fetch.max.bytes=104857600

# follower的fetch请求超时重发时间
replica.fetch.wait.max.ms=2000

# fetch的最小数据尺寸
#replica.fetch.min.bytes=1

#0.11.0.0版本开始unclean.leader.election.enable参数的默认值由原来的true改为false,可以关闭unclean leader election,也就是不在ISR(IN-Sync Replica)列表中的replica,不会被提升为新的leader partition。kafka集群的持久化力大于可用性,如果ISR中没有其它的replica,
会导致这个partition不能读写。unclean.leader.election.enable=false

# follower中开启的fetcher线程数, 同步速度与系统负载均衡
num.replica.fetchers=5

# partition leader与replicas之间通讯时,socket的超时时间
#controller.socket.timeout.ms=30000

# partition leader与replicas数据同步时,消息的队列尺寸.
#controller.message.queue.size=10

#指定将使用哪个版本的 inter-broker 协议。 在所有经纪人升级到新版本之后,这通常会受到冲击。升级时要设置
#inter.broker.protocol.version=0.10.1

#指定broker将用于将消息添加到日志文件的消息格式版本。 该值应该是有效的ApiVersion。 一些例子是:0.8.2,0.9.0.0,0.10.0。 通过设置特定的消息格式版本,用户保证磁盘上的所有现有消息都小于或等于指定的版本。 不正确地设置这个值将导致使用旧版本的用户出错,因为他们
将接收到他们不理解的格式的消息。#log.message.format.version=0.10.1

  1. producer配置

bootstrap.servers:broker地址,多个逗号分隔
acks :发送应答(0:收到后直接响应成功,1:leader保存则响应成功,-1/all:leader和isr列表保存后才响应成功)
batch.size:批量发送大小(默认:16384,16K)
buffer.memory:生产者最大可用缓存 (默认:33554432,32M)
compression.type:压缩类型(none,gzip,snappy等)
retries:失败重试次数
connections.max.idle.ms:关闭空闲连接时间(默认:540000)
key.serializer:key序列化器(默认无)
value.serializer:value序列化器(默认无)
linger.ms:发送延迟时间(默认:0)
max.request.size:最大请求字节大小(默认:1048576,1M)
request.timeout.ms:请求超时时间(默认:30000)
client.id:客户ID:用于跟踪调试

  1. consumer配置

enable.auto.commit:开启自动提交(默认:true)
auto.commit.interval.ms:上面为true时才有效,自动提交频率(默认:5000)
client.id:客户ID:用于跟踪调试
bootstrap.servers:broker配置
connections.max.idle.ms:关闭空间连接时间(默认:540000)
group.id:群组(默认:“”)唯一标识用户群组,同一个group每个partition只会分配到一个consumer。
max.poll.records:拉起最大记录(默认:500)
max.poll.interval.ms:拉取记录间隔(默认:300000,5分钟):如果在此超时过期之前没有调用poll(),则认为使用者失败,组将重新平衡,以便将分区重新分配给另一个成员。
auto.offset.reset:初始偏移量 (默认:latest):如果Kafka中没有初始偏移量,或者服务器上不再存在当前偏移量:earliest:自动重置偏移到最早的偏移; latest:自动将偏移量重置为最新偏移量

3.3 Kafka 消费者

3.3.1 消费方式

consumer 采用 pull(拉)模式从 broker 中读取数据。
push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息
典型的表现就是拒绝服务以及网。络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。
pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。

3.3.2 分区分配策略

一个 consumer group 中有多个 consumer,一个 topic 有多个 partition,所以必然会涉及到 partition 的分配问题,即确定那个 partition 由哪个 consumer 来消费。
Kafka 有两种分配策略,一是 RoundRobin,一是 Range。
1)RoundRobin
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
问题:
此方式问题是,会将消费者租A和B订阅的主题T1和T2,当做一个整体轮训,消费者A可能会受到来自T2的消息,但是消费者A并没有订阅T2,所以如果采用这种方式,消费者组必须订阅同一主题Topic,才会没有问题,所以kafka默认采用Range方式。

2)Range
在这里插入图片描述

3.3.3 offset 的维护

由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个 offset,以便故障恢复后继续消费。
在这里插入图片描述
问题:
假如消费者组中只有A,同时消费三个分区p0 p1 p2数据,并且offset都消费到了10,如果这时消费B加入消费者组中,假如P2分配给了B消费,那么B是从10以后开始消费,还是重新消费?为什么?
答:从10以后开始消费,因为offset是由 :消费者组+Topic+分区p来确定的,那么分配给B时,还是已经消费了10条,而不是重新消费,假如B又挂了,重新分配给A道理是一样
在这里插入图片描述
Kafka 0.9 版本之前,consumer 默认将 offset 保存在 Zookeeper 中,从 0.9 版本开始,consumer 默认将 offset 保存在 Kafka 一个内置的 topic 中,该 topic 为__consumer_offsets。
1)修改配置文件 consumer.properties

exclude.internal.topics=false

2)读取 offset
0.11.0.0 之前版本:

bin/kafka-console-consumer.sh --topic __consumer_offsets --
zookeeper hadoop102:2181 --formatter 
"kafka.coordinator.GroupMetadataManager\$OffsetsMessageFormatter" 
--consumer.config config/consumer.properties --from-beginning

0.11.0.0 之后版本(含):

bin/kafka-console-consumer.sh --topic __consumer_offsets --
zookeeper hadoop102:2181 --formatter 
"kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageForm
atter" --consumer.config config/consumer.properties --from-beginning

在这里插入图片描述

3.3.4 消费者组案例

1)需求:测试同一个消费者组中的消费者,同一时刻只能有一个消费者消费。
2)案例实操
(1)在 hadoop102、hadoop103 上修改/opt/module/kafka/config/consumer.properties 配置
文件中的 group.id 属性为任意组名。

[atguigu@hadoop103 config]$ vi consumer.properties group.id=atguigu

(2)在 hadoop102、hadoop103 上分别启动消费者

[atguigu@hadoop102 kafka]$ bin/kafka-console-consumer.sh --zookeeper hadoop102:2181 --topic first --consumer.config 
config/consumer.properties


[atguigu@hadoop103 kafka]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first --consumer.config 
config/consumer.properties

(3)在 hadoop104 上启动生产者

[atguigu@hadoop104 kafka]$ bin/kafka-console-producer.sh --broker-list hadoop102:9092 --topic first
>hello world

(4)查看 hadoop102 和 hadoop103 的接收者。
同一时刻只有一个消费者接收到消息。
再次启动一个hadoop104消费者,则该topic两个分区会重新分配,会分配给三个消费者中的两个,其中一个会接收不到数据

3.4 Kafka 高效读写数据

  • 顺序写磁盘
    Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。
    官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。

  • 零复制技术

  1. 什么是零拷贝,WIKI中对其有如下定义:

    “Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.

从WIKI的定义中,我们看到“零拷贝”是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。

零拷贝给我们带来的好处

  1. 减少甚至完全避免不必要的CPU拷贝,从而让CPU解脱出来去执行其他的任务
  2. 减少内存带宽的占用
  3. 通常零拷贝技术还能够减少用户空间和操作系统内核空间之间的上下文切换

零拷贝的实现
零拷贝实际的实现并没有真正的标准,取决于操作系统如何实现这一点。零拷贝完全依赖于操作系统。操作系统支持,就有;不支持,就没有。不依赖Java本身。

传统I/O
在Java中,我们可以通过InputStream从源数据中读取数据流到一个缓冲区里,然后再将它们输入到OutputStream里。我们知道,这种IO方式传输效率是比较低的。那么,当使用上面的代码时操作系统会发生什么情况:
在这里插入图片描述
这是一个从磁盘文件读取并且通过socket写出的过程,对应的系统调用如下:

read(file,tmp_buf,len)
write(socket,tmp_buf,len)
  1. 程序使用read()系统调用。系统由用户态转换为内核态(第一次上线文切换),磁盘中的数据有DMA(Direct Memory Access)的方式读取到内核缓冲区(kernel buffer)。DMA过程中CPU不需要参与数据的读写,而是DMA处理器直接将硬盘数据通过总线传输到内存中。
  2. 系统由内核态转换为用户态(第二次上下文切换),当程序要读取的数据已经完成写入内核缓冲区以后,程序会将数据由内核缓存区,写入用户缓存区),这个过程需要CPU参与数据的读写。
  3. 程序使用write()系统调用。系统由用户态切换到内核态(第三次上下文切换),数据从用户态缓冲区写入到网络缓冲区(Socket Buffer),这个过程需要CPU参与数据的读写。
  4. 系统由内核态切换到用户态(第四次上下文切换),网络缓冲区的数据通过DMA的方式传输到网卡的驱动(存储缓冲区)中(protocol engine)
    可以看到,传统的I/O方式会经过4次用户态和内核态的切换(上下文切换),两次CPU中内存中进行数据读写的过程。这种拷贝过程相对来说比较消耗资源

内存映射方式I/O
mmap.jpg

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

这是使用的系统调用方法,这种方式的I/O原理就是将用户缓冲区(user buffer)的内存地址和内核缓冲区(kernel buffer)的内存地址做一个映射,也就是说系统在用户态可以直接读取并操作内核空间的数据。

  1. mmap()系统调用首先会使用DMA的方式将磁盘数据读取到内核缓冲区,然后通过内存映射的方式,使用户缓冲区和内核读缓冲区的内存地址为同一内存地址,也就是说不需要CPU再讲数据从内核读缓冲区复制到用户缓冲区。
  2. 当使用write()系统调用的时候,cpu将内核缓冲区(等同于用户缓冲区)的数据直接写入到网络发送缓冲区(socket buffer),然后通过DMA的方式将数据传入到网卡驱动程序中准备发送。
    可以看到这种内存映射的方式减少了CPU的读写次数,但是用户态到内核态的切换(上下文切换)依旧有四次,同时需要注意在进行这种内存映射的时候,有可能会出现并发线程操作同一块内存区域而导致的严重的数据不一致问题,所以需要进行合理的并发编程来解决这些问题。

通过sendfile实现的零拷贝I/O
在这里插入图片描述

sendfile.jpg
sendfile(socket, file, len);

通过sendfile()系统调用,可以做到内核空间内部直接进行I/O传输。

  1. sendfile()系统调用也会引起用户态到内核态的切换,与内存映射方式不同的是,用户空间此时是无法看到或修改数据内容,也就是说这是一次完全意义上的数据传输过程。
  2. 从磁盘读取到内存是DMA的方式,从内核读缓冲区读取到网络发送缓冲区,依旧需要CPU参与拷贝,而从网络发送缓冲区到网卡中的缓冲区依旧是DMA方式。
  3. 依旧有一次CPU进行数据拷贝,两次用户态和内核态的切换操作,相比较于内存映射的方式有了很大的进步,但问题是程序不能对数据进行修改,而只是单纯地进行了一次数据的传输过程。

理想状态下的零拷贝I/O
在这里插入图片描述
依旧是系统调用sendfile()

sendfile(socket, file, len);

可以看到,这是真正意义上的零拷贝,因为其间CPU已经不参与数据的拷贝过程,也就是说完全通过其他硬件和中断的方式来实现数据的读写过程吗,但是这样的过程需要硬件的支持才能实现。

借助于硬件上的帮助,我们是可以办到的。之前我们是把页缓存的数据拷贝到socket缓存中,实际上,我们仅仅需要把缓冲区描述符传到socket缓冲区,再把数据长度传过去,这样DMA控制器直接将页缓存中的数据打包发送到网络中就可以了。

系统调用sendfile()发起后,磁盘数据通过DMA方式读取到内核缓冲区,内核缓冲区中的数据通过DMA聚合网络缓冲区,然后一齐发送到网卡中。
可以看到在这种模式下,是没有一次CPU进行数据拷贝的,所以就做到了真正意义上的零拷贝,虽然和前一种是同一个系统调用,但是这种模式实现起来需要硬件的支持,但对于基于操作系统的用户来讲,操作系统已经屏蔽了这种差异,它会根据不同的硬件平台来实现这个系统调用

Java的实现
NIO的零拷贝

  File file = new File("test.zip");
  RandomAccessFile raf = new RandomAccessFile(file, "rw");
  FileChannel fileChannel = raf.getChannel();
  SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("", 1234));
  // 直接使用了transferTo()进行通道间的数据传输
  fileChannel.transferTo(0, fileChannel.size(), socketChannel);

NIO的零拷贝由transferTo()方法实现。transferTo()方法将数据从FileChannel对象传送到可写的字节通道(如Socket Channel等)。在内部实现中,由native方法transferTo0()来实现,它依赖底层操作系统的支持。在UNIX和Linux系统中,调用这个方法将会引起sendfile()系统调用。

使用场景一般是:

  1. 较大,读写较慢,追求速度
  2. M内存不足,不能加载太大数据
  3. 带宽不够,即存在其他程序或线程存在大量的IO操作,导致带宽本来就小

以上都建立在不需要进行数据文件操作的情况下,如果既需要这样的速度,也需要进行数据操作怎么办?
那么使用NIO的直接内存!

NIO的直接内存

  File file = new File("test.zip");
  RandomAccessFile raf = new RandomAccessFile(file, "rw");
  FileChannel fileChannel = raf.getChannel();
  MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());

首先,它的作用位置处于传统IO(BIO)与零拷贝之间,为何这么说?

  1. IO,可以把磁盘的文件经过内核空间,读到JVM空间,然后进行各种操作,最后再写到磁盘或是发送到网络,效率较慢但支持数据文件操作。
    零拷贝则是直接在内核空间完成文件读取并转到磁盘(或发送到网络)。由于它没有读取文件数据到JVM这一环,因此程序无法操作该文件数据,尽管效率很高!
  2. 而直接内存则介于两者之间,效率一般且可操作文件数据。直接内存(mmap技术)将文件直接映射到内核空间的内存,返回一个操作地址(address),它解决了文件数据需要拷贝到JVM才能进行操作的窘境。而是直接在内核空间直接进行操作,省去了内核空间拷贝到用户空间这一步操作。

NIO的直接内存是由MappedByteBuffer实现的。核心即是map()方法,该方法把文件映射到内存中,获得内存地址addr,然后通过这个addr构造MappedByteBuffer类,以暴露各种文件操作API。

由于MappedByteBuffer申请的是堆外内存,因此不受Minor GC控制,只能在发生Full GC时才能被回收。而DirectByteBuffer改善了这一情况,它是MappedByteBuffer类的子类,同时它实现了DirectBuffer接口,维护一个Cleaner对象来完成内存回收。因此它既可以通过Full GC来回收内存,也可以调用clean()方法来进行回收。

另外,直接内存的大小可通过jvm参数来设置:-XX:MaxDirectMemorySize。

NIO的MappedByteBuffer还有一个兄弟叫做HeapByteBuffer。顾名思义,它用来在堆中申请内存,本质是一个数组。由于它位于堆中,因此可受GC管控,易于回收。
参考
https://blog.csdn.net/localhost01/article/details/83422888
https://blog.csdn.net/cringkong/article/details/80274148

3.5 Zookeeper 在 Kafka 中的作用

在这里插入图片描述

3.6 Kafka 事务

Kafka 从 0.11 版本开始引入了事务支持。事务可以保证 Kafka 在 Exactly Once 语义的基
础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败。

3.6.1 Producer 事务

为了实现跨分区跨会话的事务,需要引入一个全局唯一的 Transaction ID,并将 Producer获得的PID 和Transaction ID 绑定。这样当Producer 重启后就可以通过正在进行的 TransactionID 获得原来的 PID。为了管理 Transaction,Kafka 引入了一个新的组件 Transaction Coordinator。Producer 就是通过和 Transaction Coordinator 交互获得 Transaction ID 对应的任务状态。Transaction Coordinator 还负责将事务所有写入 Kafka 的一个内部 Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。

3.6.2 Consumer 事务

上述事务机制主要是从 Producer 方面考虑,对于 Consumer 而言,事务的保证就会相对
较弱,尤其时无法保证 Commit 的信息被精确消费。这是由于 Consumer 可以通过 offset 访问任意信息,而且不同的 Segment File 生命周期不同,同一事务的消息可能会出现重启后被删除的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值