Kafka01-集群和基础原理
1.kafka概述
- Kafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域。
- Kaka的优势。解耦、可恢复性、缓冲、灵活性和峰值处理能力、异步通信。
- Kafka官网,https://kafka.apache.org/。
2.Kafka队列的两种模式
- 点对点模式。生产者和消费者一对一,消费者主动拉取数据,消息收到后消息清除,一条消息只能被一个消费者消费。
- 发布/订阅模式。一对多,消费者消费数据之后不会清除消息,消息会被多个消费者消费。
- Kafka的消息会被保存到磁盘上,保存7天后删除。
3.Kafka消息的消费方式
- 生成者推送。优点,生产者有消息了就会进行推送;缺点,生产者无法根据消费者的消费能力推送适当数量的消息。
- 消费者拉取。优点,消费者可以根据自己处理消息的能力来拉取消息。缺点,消费者的定时拉取,当消息队列(Topic 主题)中没有消息时,消费者依然需要拉取消息,即可能拉取到为空的消息。
4.Kafka架构组成
- Producer,生产者。向Kafka Broker中发消息的客户端。
- Consumer,消费者。从Kafka Broker中取消息的客户端。
- Consumer Group,消费者组。由多个Consumer组成,消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
- Broker,一台Kafka服务器就是一个broker;一个Kafka集群由多个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会成为新的Leader。
- 一个消费者组中的消费者可以消费Topic中不同Partition的消息;同时Topic中相同Partition的消息只能被消费者组的一个消费者消费。
- Kafka集群中有多个Topic,每个Topic中有多个分区(Partition),每个分区中有多个副本(Replication)。
- 一个Topic的多个分区可以存在到一个Broker中, 一个分区的多个副本只能在不同的Broker存在。
- Kafka集群依赖Zookeeper,每个Broker启动后需要向Zookeeper注册。同时Zookeeper负责Broker中Controller的选举(争抢策略)。
5.Kafka的下载和安装
- kafka安装包名称说明。kafka_2.11-2.4.1.tgz,Kafka使用Scale语言写的,2.11是Scale的版本;2.4.1是Kafka的版本。
- 安装Kafka。
# 解压
tar -zxvf kafka_2.11-2.4.1.tgz -C /opt/module/
# 配置环境变量
sudo vim /etc/profile.d/my_env.sh
# 配置Kafka环境变量
export KAFKA_HOME=/opt/module/kafka_2.11-2.4.1
export PATH=$PATH:$KAFKA_HOME/bin
source /etc/profile # 刷新配置文件
# 创建消费存储目录
mkdir /opt/module/kafka_2.11-2.4.1/datas
# 修改配置文件
vim /opt/module/kafka_2.11-2.4.1/config/server.properties
# 134=0,135=1,136=2,broker表示全局唯一编号,不能重复
broker.id=0
# 修改logs.dir为datas目录,logs.dir路径保存消息,保存168小时,七天。
logs.dir=/opt/module/kafka_2.11-2.4.1/datas
# 配置zookeeper集群
zookeeper.connect=192.168.253.134:2181,192.168.253.135:2181,192.168.253.136:2181
# 配置文件分发
mycopy.sh /opt/module/kafka_2.11-2.4.1/
mycopy.sh /etc/profile.d/my_env.sh
- 启动Kafka集群
# 删除zookeeper数据
rm -rf /opt/module/zookeeper-3.5.7/zkData/version-2/
# 脚本启动zookeeper集群
zookeeper-all.sh start
# 修改每个服务的 broker.id=0 # 134=0,135=1,136=2
vim kafka_2.11-2.4.1/config/server.properties
# 启动kafka集群
/opt/module/kafka_2.11-2.4.1/bin/kafka-server-start.sh -daemon /opt/module/kafka_2.11-2.4.1/config/server.properties
kafka-server-stop.sh # 停止kakfa集群。
# Zookeeper客户端连接,查看Kafka启动后再Zookeeper创建的节点
/opt/module/zookeeper-3.5.7/bin/zkCli.sh -server 192.168.253.134:2181
ls /broker/ids
get /broker/ids/0 # 获取broker0的信息
get /controller # 查看kafka集群的leader。
- 编写kafka集群启动脚本
vim /home/admin/bin/kafka-all.sh
#!/bin/bash
if [ $# -lt 1 ]
then
echo "=== 需要制定参数 ==="
exit
fi
for host in 192.168.253.134 192.168.253.135 192.168.253.136
do
case $1 in
start)
echo "=== start kafka in $host ==="
ssh $host /opt/module/kafka_2.11-2.4.1/bin/kafka-server-start.sh -daemon /opt/module/kafka_2.11-2.4.1/config/server.properties
;;
stop)
echo "=== stop kafka in $host ==="
ssh $host /opt/module/kafka_2.11-2.4.1/bin/kafka-server-stop.sh
;;
*)
echo "=== 参数错误 ==="
exit
;;
esac
done
6.Kafka客户端操作-Topic
# 查看topic列表
/opt/module/kafka_2.11-2.4.1/bin/kafka-topics.sh --list --bootstrap-server 192.168.253.136:9092
# 创建topic
./kafka-topics.sh --create --bootstrap-server 192.168.253.134:9092 --topic one
# 创建topic,指定分区,副本数。
# --topic two topic,主题名为two
# --partitions 2,分区数为 2
# --replication-factor 3,副本数为3
./kafka-topics.sh --create --bootstrap-server 192.168.253.134:9092 --topic two --partitions 2 --replication-factor 3
# 查看topic的描述信息
./kafka-topics.sh --describe --bootstrap-server 192.168.253.134:9092 --topic one
# Topic: one PartitionCount: 1 ReplicationFactor: 1 Configs: segment.bytes=1073741824
# Topic: one Partition: 0 Leader: 0 Replicas: 0 Isr: 0
# 修改topic
./kafka-topics.sh --alter --bootstrap-server 192.168.253.134:9092 --topic one --partitions 2
# 删除topic
./kafka-topics.sh --delete --bootstrap-server 192.168.253.134:9092 --topic one
# kafka创建的topic会存放在在datas目录,/opt/module/kafka_2.11-2.4.1/datas
7.Kafka客户端操作-消费者和生产者
- 连接到消费者端。
# 每一个消费端都默认是一个消费者组
kafka-console-consumer.sh --bootstrap-server 192.168.253.134:9092 --topic two
# 新加入的消费者不会消费历史消息,通知指定--from-beginning参数来指定新加入的消费者从头开始消费消息。
/opt/module/kafka_2.11-2.4.1/bin/kafka-console-consumer.sh --bootstrap-server 192.168.253.134:9092 --topic two --from-beginning
# 连接到kafka消费端,指定消费者组
# 通过配置文件指定组,配置文件中的组为 group.id=test-consumer-group
# 同一个topic中的消息只会被同一个消费者组的消费者消费一次。
kafka-console-consumer.sh --bootstrap-server 192.168.253.134:9092 --topic two --consumer.config /opt/module/kafka_2.11-2.4.1/config/consumer.properties
# 通过group参数指定消费者组。
kafka-console-consumer.sh --bootstrap-server 192.168.253.134:9092 --topic two --group aa
- 连接到生成者端。
kafka-console-producer.sh --broker-list 192.168.253.134:9092 --topic two
8.Kafka工作流程-文件存储机制
- Kafka的Topic是逻辑上的概念,而Partition是物理上的概念。每一个Partition都会保存在磁盘上。Partition被保存在/opt/module/kafka_2.11-2.4.1/datas目录下,命名为Topic名-0,即Partition文件名由Topic名+分区号组成。
- Producer生产的数据会被不断追加到.log文件末端,追加的速度快,且每条数据都有自己的offset或者每一个片段的数据都有自己的offset。
- 生产者生产的消息被不断追加到.log文件末尾,为防止log文件过大导致数据定位效率低下,Kafka采取了分片和索引机制。将每个Partition分为多个Segment片段,每个Segment对应两个文件,.index文件和.log文件。这些文件位于一个文件夹下,该文件夹的命名规则为:Topic名称+分区序号。
- 一个Topic由多个Partition组成,Partition中的消息会被保存到磁盘上,一个Partition分为多个Segment片段,并保存在磁盘上。
- 每个Segment对应一个.index和.log文件。.log文件保存消息和消息的offset(offer可以看做是消息的记录,如0号消息、1号消息);.index保存消息的offset和消息保存在.log文件中字节数的偏移量。
- 一个Partition有多个Segment,每个Segment的.index和.log文件名为消息的offset+后缀。同时如果有多个Segment,.index中保存的消息offset会被重置为从0开始计算,即实际消息的offset为Segment中的offset+文件名的offset。
9.Kafka生产者-分区Partition
- 分区的原因。适应较大的数据量,一个Topic由多个Partition组成,可以适应较大的数据量;可以提高并发,读写数据时以Partition为单位,多个Partition并行处理可以提高并发。
- 分区的原则,以Kafka客户端发送消息的API为例。
- 有Partition的情况下,直接使用传入的Partition值。
- 没有Partition,但是有key的情况下,将key的Hash值与Topic的Partition数进行取余得到Partition值。
- 既没有Partition又没有key值的情况下,Kafka采用Sticky Partition(黏性分区器)。会随机选择一个分区,并尽可能一直使用该分区,直到该分区的batch已满或者已完成,Kafka再随机一个分区使用。
10.Kafka生产者-数据可靠性保证
- 生产者投放消息时通过ACK保证消息的可靠性。为了保证Producer发送的数据能可靠的发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送Ack(Acknowledgement确认收到),如果Producer收到Ack,表示消费发送成功,否则重新发送数据。
- Topic的每个Partition在所有的副本将数据同步完成是发送ACK。全部副本完成数据同步和半数副本完成数据同步在发送ACK的区别:为了容忍n台节点的故障,半数同步完成需要2n+1个副本,而全部同步完成只需要n+1个副本,而Kafka的每个分区都有大量的数据据,第一种方案会造成大量数据的冗余。
- ISR,In-Sync Replica Set,解决Leader和所有的Follow同步完成消息发送Ack时,某个Follow不能同步消息,导致Leader不能发送ACK的情况。
- Leader维护了一个动态的ISR列表,意为和Leader保持同步的Follower集合。当ISR中的Follower完成数据的同步之后,leader就会给Producer发送Ack。
- 如果Follower在指定的时间没有向Leader同步数据,则该Follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。
- Leader发生故障之后,从ISR中选举新的Leader。
- replica.lag.time.max.ms默认为10000,即10秒中。
11.Kafka生产者-ACK应答级别
- 对于不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据少量丢失,所以没必要等ISR中的Follower全部接收成功,所以Kafka提供了三种可靠性级别。
- 0级别,Partition的Leader接收到消息但是还没有写入磁盘就已经返回Ack,当Leader故障时有可能丢失数据。
- 1级别,Partition的Leader落盘成功后返回Ack,如果在Follower同步成功之前Leader故障,那么将会丢失数据。
- -1,All级别。Partition的Leader和Follower全部落盘成功后才返回Ack。
- -1级别会产生消息重复的问题。如果在Follower同步完成后,Leader发送Ack之前,Leader发生故障,就不会发送ack。这时生产者没有收到Ack,会继续生产相同的消息,这个消息会被新选举出来的Leader处理,造成数据重复问题。
12.Kakfa的Leader和Follower故障处理
- LEO(Log End Offset),每个副本中最后一个offset,即每个副本最新一条消息的位置。
- HW(High Watermark),高水位。ISR队列中最新的LEO,即HW表示所有副本中最小的一个offset的位置,HW位置之前的数据都是响应了ACK的数据。
- Follower故障处理。
- Follower发生故障后会被临时踢出ISR,待该Follower恢复后,Follower会读取本地磁盘记录的上次的HW,即发生故障时的HW。
- 并将log文件高于HW的部分截取掉,HW位置之前的数据都是响应了ACK的数据,将高于HW的消息扔掉,主要是防止Follower故障时有Leader的切换动作,从而可能导致HW后的数据被删除。
- 从HW位置开始向Leader进行同步数据,等该Follower的LEO大于等于该Partition的HW,即Follower追上Leader。之后,将Follower重新加入ISR。
- Leader故障处理。
- Leader发生故障之后,会从ISR中选出一个新的Leader。
- 之后,为保证多个副本之间的数据一致性,其余的Follower会先将各自的log文件高于HW的部分截掉,然后从新的Leader同步数据。
- Leader的选举方法。如Replicas: 0,2,1,0当前是Leader,0宕机了。会去判断2是否在ISR列表中,如果2在,就将2选举为Leader。否则去判断0是否在ISR中。注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
./kafka-topics.sh --describe --bootstrap-server 192.168.253.134:9092 --topic one
Topic: one PartitionCount: 1 ReplicationFactor: 1 Configs: segment.bytes=1073741824
Topic: one Partition: 0 Leader: 0 Replicas: 0,2,1 Isr: 0
13.Kafka保证数据不重复而且不丢失
- 将服务器的ACK级别设置为-1,可以保证Producer到Server之间不会丢失数据,即At Least Once语义。相对的,将服务器ACK级别设置为0,可以保证生产者每条消息只会被发送一次,即At Most Once语义。
- At Least Once可以保证数据不丢失,但是不能保证数据不重复;相对的,At Least Once可以保证数据不重复,但是不能保证数据不丢失。
- Exactly Once语义,保证数据不会重复而别不丢失。在消息投放不跨分区、生产者不跨会话(消息由同一个生产者发送)的情况下消息不重复。
- 启用Exactly Once幂等性,只需要将Producer的参数中enable.idempotence设置为true即可。
- 开启幂等性的Producer在初始化的时候会被分配一个PID,发往同一Partition的消息会附带Sequence Number,而Broker端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时,Broker只会持久化一条。
- 但是PID重启就会变化,同时不同的Partition也具有不同主键,所以幂等性无法保证跨分区跨会话的Exactly Once。
14.Kafka消费者-分区策略
- 一个Consumer Group消费者组中有多个Consumer,一个Topic中有多个Partition,所以必然会涉及到Partition的分配问题,即确定那个Partition由哪个Consumer来消费。
- Kafka有三种分配策略:RoundRobin,Range , Sticky。
- RoundRobin,轮训。
- Range,范围分配,Kafka默认使用。
- Sticky,粘性分区。Sticky的第一次分区采用RoundRobin策略。但是如果有一个消费者宕机,在重新分配的时候,RoundRobin会重新进行轮训分配,而Sticky策略会保持原有的分区情况,只是将这个宕机的消费者所消费的Partition进行重新分配。
15.Kafka消费者-offset维护问题
- Kafka 0.9版本之前,Consumer默认将offset保存在Zookeeper中。
- 从0.9版本开始,Consumer默认将offset保存在Kafka一个内置的Topic中,该Topic为__consumer_offsets。
- /opt/module/kafka_2.11-2.4.1/bin/kafka-topics.sh --describe --bootstrap-server 192.168.253.134:9092 --topic __consumer_offsets,查看维护offset的信息,__consumer_offsets这个Topic有50个分区,和1个副本。
- 消费维护offset的消息队列,__consumer_offsets。
- 修改配置文件consumer.properties。
# 不排除内部的topic,添加到consumer.properties的最后。
exclude.internal.topics=false
# 修改group.id=text-offset
group.id=text-offset
- 观察__consumer_offsets中的消息。
# 创建测试的topic
/opt/module/kafka_2.11-2.4.1/bin/kafka-topics.sh --create --bootstrap-server 192.168.253.134:9092 --topic offsetdemo
# 启动两个消费者,连接到创建的测试队列offsetdemo中
/opt/module/kafka_2.11-2.4.1/bin/kafka-console-consumer.sh --bootstrap-server 192.168.253.134:9092 --topic offsetdemo01 --consumer.config /opt/module/kafka_2.11-2.4.1/config/consumer.properties
# 启动消费者连接__consumer_offsets,并且指定将消费进行格式化。
/opt/module/kafka_2.11-2.4.1/bin/kafka-console-consumer.sh --bootstrap-server 192.168.253.134:9092 --topic __consumer_offsets --consumer.config /opt/module/kafka_2.11-2.4.1/config/consumer.properties --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --from-beginning
# 启动生成者向offset中生产消息
/opt/module/kafka_2.11-2.4.1/bin/kafka-console-producer.sh --topic offsetdemo01 --broker-list 192.168.253.134:9092
# 观察__consumer_offsets的日志消费的数据
# [text-offset,offsetdemo01,1],消费者组 topic 分区 编号
# offset=3,当前消费者组,消费分区中消息的位置记录。
[text-offset,offsetdemo01,1]::OffsetAndMetadata(offset=3, leaderEpoch=Optional[0], metadata=, commitTimestamp=1644722496410, expireTimestamp=None)
[text-offset,offsetdemo01,0]::OffsetAndMetadata(offset=2, leaderEpoch=Optional[0], metadata=, commitTimestamp=1644722496420, expireTimestamp=None)
16.Kafka高效写数据的原因
- 顺序写数据。顺序写能到600M/s,而随机写只有100K/s。
- Kafka数据持久化是直接持久化到Pagecache中。Pagecache中缓存需要写入的数据,比如有5份数据需要写入磁盘,对应磁盘的5个位置。随机写可能需要磁头转动2-5圈;但是将数据缓存到Pagecache中,会根据文件的位置,按照磁头转动的方向进行排序,即磁头转动一圈就可以将这些数据写入磁盘。
- 在Linux中一般的文件复制需要将数据读入内存,然后经过内存的多次交换(用户态内存到内核的交换)在写入到磁盘。零复制技术直接写入内存,不需要交换,就可以在直接写入磁盘。
- 分区策略,多个分区可以并发写入数据。
- 分区中数据的分片。即分区中保存消息的文件,达到一定的大小就会进行分片,重新写入一个文件。同时.index文件保存消息的offset和消息在.log文件中所在的位置的偏移量。
17.Zookeeper在Kafka中的作用
- Kafka集群中有一个Broker会被选举为Controller,负责管理集群Broker的上下线、所有Topic的分区副本分配和Leader选举等工作。
- Controller根据基于Zookeeper的监听机制,对Broker进行监听,当有broker宕机,就会对存在于宕机Broker上的Leader对应的分区重新进行Leader的选举。
- Controller的管理工作都是依赖于Zookeeper的。
# 查看消息队列offsetdemo01的0号分区的状态
get /brokers/topics/offsetdemo01/partitions/0/state
{"controller_epoch":8,"leader":2,"version":1,"leader_epoch":0,"isr":[2]}
18.Kafka事务
- Producer事务,保证生成的消息不重复。通过引入Transaction Coordinator组件,将生产者的PID和Transaction Coordinator中的Transaction ID进行绑定,这样即使生产者的PID进行了更换,也会和之前的Transaction Coordinator中的Transaction ID进行绑定,从而解决跨会话的问题,来保证消息不重复消费。
- Consumer事务,精准一次性消费。利用Mysql将消费过程和提交offset过程做原子绑定,利用Mysql的事务来完成精准一次性消费,保证消费者事务。
19.Kafka客户端-发送消息流程
- Kafka的Producer发送消息采用的是异步发送的方式。
- 在消息发送的过程中,涉及到了两个线程:main线程和Sender线程,以及一个线程共享变量——RecordAccumulator。
- main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker。
- 客户端的两个参数。
- batch.size,只有数据积累到batch.size之后,Sender才会发送数据,默认值,16384。
- linger.ms,如果数据迟迟未达到batch.size,Sender等待linger.time之后就会发送数据,默认0。
20.Kafka监控-Kafka Eagle
- https://www.kafka-eagle.org/,Kafka Eagle官网。
- 搭建Kafka Eagle,修改Kafka启动脚本kafka-server-start.sh,修改完成后将启动脚本分发到其他机器。
# 修改前
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi
# 修改后
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-server -Xms2G -Xmx2G -XX:PermSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=5 -XX:InitiatingHeapOccupancyPercent=70"
export JMX_PORT="9999"
#export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi
- 搭建Kafka Eagle。
# 需要解压两次
tar -zxvf /opt/software/kafka-eagle-bin-1.4.5.tar.gz
tar -zxvf /opt/software/kafka-eagle-bin-1.4.5/kafka-eagle-web-1.4.5-bin.tar.gz -C /opt/module/
# 重新命名
mv kafka-eagle-web-1.4.5/ kafka-eagle
# 配置环境变量
sudo vim /etc/profile.d/my_env.sh
source /etc/profile
# 环境变量文件分发
mycopy.sh /etc/profile.d/my_env.sh
# 给kafka-eagle启动权限
chmod 777 /opt/module/kafka-eagle/bin/ke.sh
# 修改kafka-eagle配置文件,/opt/module/kafka-eagle/conf/system-config.properties
# 配置zookeeper
kafka.eagle.zk.cluster.alias=cluster1
cluster1.zk.list=192.168.253.134:2181,192.168.253.135:2181,192.168.253.136:2181
#cluster2.zk.list=xdn10:2181,xdn11:2181,xdn12:2181
# kafka的offset使用kafka进行存储
cluster1.kafka.eagle.offset.storage=kafka
#cluster2.kafka.eagle.offset.storage=zk
kafka.eagle.metrics.charts=true
kafka.eagle.metrics.retain=30
# 默认使用sqlite数据库,修改为mysql
# kafka-eagle会将存储到的数据保存到MySQL中。
######################################
# kafka mysql jdbc driver address
######################################
kafka.eagle.driver=com.mysql.jdbc.Driver
kafka.eagle.url=jdbc:mysql://192.168.2.104:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
kafka.eagle.username=root
kafka.eagle.password=123456
- kafka-eagle的启动和访问。
# 启动kafka-eagle,启动之前需要先启动Zookeeper以及Kafka
sh /opt/module/kafka-eagle/bin/ke.sh start
# 访问地址
http://192.168.253.134:8048/ke
# 用户名和密码
Account:admin ,Password:123456