Kafka 基础梳理
角色
- Kafka Cluster(Kafka服务集群),每个服务节点称为:Broker。
- Broker之间基本上是对等关系,通常第一个启动的broker角色为:Controler
- Controler 通过zk集群获取kafka的元数据,同时负责把元数据信息同步到其他Broker上面
- 生成者(Producer)
- 消费者(Consumer)
- 内部是:主题(Topic)存储,是逻辑概念
- Topic可以有多个分区(Partition),主题默认一个分区,分区是数据存储的实体目录
- 分区采用主从架构,分为:leader 和 follower。对外服务统一都由 leader 负责
数据存储(Producer)
- 数据写入,采用磁盘 顺序写(追加写)
- 数据存储采用分段存储,存储参数如下:
// 数据存储参数
log.segment.bytes=1G // Partition默认切割文件大小 1G
log.roll.ms=60 * 60 * 1000 // 指定roll文件的时间,毫秒单位,优先级高于 log.roll.hours
log.roll.hours=168 // 指定roll的时间,小时单位,默认 168,即7天
- 文件类型
类型 | 说明 | 备注 |
.index | 偏移量索引文件 | 1、偏移量采用多级、稀松索引,每写4k数据创建一个索引 2、内容为 offset:position,即:偏移量 和 物理位置 |
.timestamp | 时间戳索引文件 | |
.log | 日志文件 | 存储消息数据,以当前文件中第一条数据的偏移量(offset)命名 |
数据读取(Consumer)
- 数据读取(Consumer),采用 零拷贝
zookeeper 参数配置
- zoo.cfg 文件配置
clientPort=2181 // 默认端口号
/**
* 默认将事务日志文件和快照日志文件都存储在dataDir对应的目录下。
* 建议将事务日志(dataLogDir)与快照日志(dataLog)单独配置,
* 因为当zk集群进行频繁的数据读写操作时,会产生大量的事务日志信息,
* 将两类日志分开存储会提高系统性能,
* 而且,可以允许将两类日志存在在不同的存储介质上,
* 利用磁盘顺序写的特性,提高日志写入速度。
*/
dataDir=/usr/local/zk/data // 数据存储目录(快照日志)
dataLogDir=/usr/local/zk/dataLog // 事物日志存储目录
/**
* zk集群服务器信息,IP(或主机名)、端口信息,
* 假如有3台服务器,配置参考如下:
* 其中 server后面的数字就是zk节点的别名,取值在 0-255,不可重复
* 端口 2888:选主的端口
* 端口 3888:集群内部通信端口
*/
server.1=hodoop1:2888:3888
server.2=hodoop2:2888:3888
server.3=hodoop3:2888:3888
- myid 文件
/**
* zoo.cfg 文件中配置项目:dataDir=/usr/local/zk/data 的目录下
* 创建 myid 文件(touch myid),里面输入zk节点的别名,
* 例如 server.1 的别名就是:1
*/
核心参数配置(server.properties)
broker.id=0 // 每个kafka节点都必须设置唯一的id,自然数即可
log.dirs=/d01/kafka/data,/d02/kafka/data // 设置kafka日志(即消息数据)存放的目录,通常一个磁盘对应一个目录,多个磁盘的目录使用逗号分隔
zookeeper.connect=Hadoop1:2181,hadoop2:2181,hadoop3:2181 // controller broker对kafka的管理,就是监听zk的元数据目录
listeners=PLAINTEXT://hadoop1:9092 // broker监听客户端发送请求的端口号,其中 hadoop1 是主机名,9092为端口号
delete.topic.enable=true // 默认为true,允许删除topic
log.retention.hours=168 // kafka的数据生命周期,默认是 168小时(7天),可以按需调整
log.retention.bytes=-1 // 如果分区的数据量超过这个限制,就会自动清理数据,此参数不常用,默认为 -1意味不按照此规则来处理
num.network.threads=3 // kafka接收数据请求的线程数,默认为3个,可以调整到 6 或 9个
num.io.threads=8 // kafka处理数据请求的线程数,默认为8个,可以根据cpu core数量调整,16、24、32都可,需要硬件支持
num.patitions=1 // topic默认的分区数,默认为 1,需用修改,一般在实际使用时具体指定
log.flush.intelval.ms=1000 // kafka将os cache里数据刷写到本地磁盘的等待时间,默认是 1s,太小,建议设置成1分钟,60 * 1000
log.flush.intelval.meassages=10000 // kafka将数据写入本地磁盘时,需要等待的消息条数,默认为 1w条,就够用了
message.max.bytes= // 单条消息的最大值,默认是 977k(约等于1m),太小,一般设置为 10m,10 * 1024 * 1024
/**
* 默认1,生产者发送数据成功最小需要ISR的副本数量。
* 这个参数会配合 生产者的 request.required.acks 参数使用,
* 尤其当 request.required.acks=-1 时,
* min.insync.replicas>=2 且 replication.factor>=2时,不会丢数据。
* request.required.acks 有3个值,默认为 1
* 1:服务端leader partition收到数据即认为发送成功;
* 0:不等待服务端响应就继续发送数据,此方式发送效率最高,但数据可靠性最低;
* -1:等待服务端的设定的最小副本都确认收到才认为发送成功,此方式数据可靠性最高,发送效率最低
*/
min.insync.replicas=1
replication.factor=1 // ISR(即:in sync replica),服务端设置的分区副本数,默认为1,考虑到数据可靠性,至少设置为 2
服务启动及测试
# 服务启动
./kafka-server-start.sh -deamon ../config/server.properties
# 服务停止
./kafka-server-stop.sh
# 创建主题,分区为3,每个分区副本为2
./kafka-topics.sh --create --zookeeper host1:2181,host2:2181,host3:2181 --replication-factor 2 --patitions 3 --topic test
# 查看topic
./kafka-topics.sh --list --zookeeper hodoop1:2181,hodoop2:2181,hodoop3:2181
# 客户端工具:创建生成者
./kafka-console-producer.sh --broker-list hadoop1:9092,hadoop2:9092,hadoop3:9092 --topic test
# 客户端工具:创建消费者
./kafka-console-consumer.sh --bootstrap-server hadoop1:9092,hadoop2:9092,hadoop3:9092 --topic test --from-beginning
# 压力测试
# 生产数据,往topic:test上写入 50w数据,每个数据大小是200字节
# 测试结果参考下图
./kafka-producer-perf-test.sh --topic test --num-records 500000 --record-size 200 --throughput 1 --producer-props bootstrap-servers=hadoop1:9092,hadoop2:9092,hadoop3:9092
# 消费数据,从topic:test上消费50w数据
./kafka-producer-perf-test.sh --broker-list hadoop1:9092,hadoop2:9092,hadoop3:9092 --fetch-size 2000 --messages 500000 --topic test
参考写入数据测试结果,50w条数据写入效率:大约 5w/s,效率很好!
参考消费数据测试结果,50w条数据消费效率:大约3.7w/s,效率很好!!
运维命令
# topic 数量大,需要调整分区
# 创建topic:test1,分区为1
./kafka-topics.sh --create --zookeeper host1:2181,host2:2181,host3:2181 --replication-factor 1 --patitions 1 --topic test1
# 调整分区为:3个
./kafka-topics.sh --alter --zookeeper host1:2181,host2:2181,host3:2181 --patitions 3 --topic test1
# 增加副本
# touch myjson.json
# 分区 patition0 副本3个,存储在 broker :0、1、2 三台服务器上
# 其他分区同理
{
"version":1,
"patitions": [
{"topic":"test1","partition":0,"replicas":[0,1,2]},
{"topic":"test1","partition":1,"replicas":[0,1,2]},
{"topic":"test1","partition":2,"replicas":[0,1,2]}
]
}
# 执行json脚本,增加副本
./kafka-reassign-partitions.sh --zookeeper host1:2181,host2:2181,host3:2181 --reassignment-json-file myjson --execute
# 负载不均衡topic,手动迁移
# touch topicMove.json <- 准备迁移的topic:test1 和 test2
{
"version":1,
"topics":[
{"topic":"test1"},
{"topic":"test2"}
]
}
# 迁移指令,“5,6” 就是要迁移的目标broker id,执行后会生成一个迁移方案
# 迁移方案会生成在文件:expand-cluster-reassignment.json 中
./kafka-reassign-partitions.sh --zookeeper host1:2181,host2:2181,host3:2181 --topics-to-move-json-file topicMove.json --broker-list "5,6" --generate
# 执行上述 迁移方案
./kafka-reassign-partitions.sh --zookeeper host1:2181,host2:2181,host3:2181 --reassignment-json-file expand-cluster-reassignment.json --execute
# 验证迁移方案
./kafka-reassign-partitions.sh --zookeeper host1:2181,host2:2181,host3:2181 --reassignment-json-file expand-cluster-reassignment.json --verify
# 默认为true,自动负载均衡
auto.leader.rebalance.enable=true
# 每个broker允许的不平衡leader的比例,
# 如果每个broker超过这个值,控制器会触发leader平衡
# 这个值表示百分比
leader.imbalance.per.broker.percentage=10
# 默认值 300s,每隔 300s检查leader是否平衡
leader.imbalance.check.intelval.seconds=300
生产者(Producer)吞吐量调优
buffer.memory=33554432 // 生成者缓存池大小,默认32m
compression.type=lz4 // 生产者发送数据是否启用压缩,默认none,不压缩;可以使用 lz4 算法压缩。开通压缩后,会增加cpu开销
batch.size=16384 // 生产者发送消息批次大小,默认 16k,较小,可以考虑适当调大,例如:32768(32m)
linger.ms=100 // 默认为0,意味着消息一来就要被发送出去,不合理。配合 batch.size 参数使用,当 batch.size 设定的大小在指定时间内无法达到的时候,批次也会正常发送出去,可以设置为 100ms
生产者其他参数
// 保证生产者(Producer)同时只能发送一条消息
// 防止消息乱序(消息重复发送的时候导致)
max.in.flight.requests.per.connection=1
// 重试,当消息发送失败的时候,系统会自动重试
retries=3
// 配合 retries 参数一起使用,重试等待时间,单位毫秒
retry.backoff.ms=100
消费者(Consumer)偏移量存储
- kafka内部集成 topic 用于存储消费者偏移量
- topic名称为:__comsumer_offsets,默认有 50个分区
- key:group_id + topic + partition_id
- value:当前offset
- 监控消费者的offset(工具:KafkaOffsetMonitor-assembly-0.3.0-SNAPSHOT.jar)
# touch monitor.sh & vi monitor.sh
# 工具支持kafka存储offset 和 zk存储offset
# 如果是zk存储,参数 --offsetStorage zookeeper 即可
# chmod +x monitor.sh 之后,后台运行脚本:nohup ./monitor.sh &
# 运行成功后,网页端输入:hadoop1:9004 即可
java -cp KafkaOffsetMonitor-assembly-0.3.0-SNAPSHOT.jar \
com.quantifind.kafka.offsetapp.OffsetGetterWeb \
--offsetStorage kafka \
--zk hadoop1:2181 \
--port 9004 \
--refresh 15.seconds \
--retain 2.days
消费者(Consumer)参数配置
/**
* 消费者(Consumer)与协调员(在某个broker上启动的Coordinator)
* 之间的心跳时间间隔,一旦故障了,会通过心跳下发rebalance到consumer
* 其他consumer会做rebalance操作
*/
heartbeat.intelval.ms=3000
// kafka长时间感知不到一个consumer就认为它故障了,默认10s
session.timeout.ms=10*1000
/**
* 如果两次poll操作时间超过设定值
* 就认为这个consumer处理能力太弱,会被提出消费组
*/
max.poll.interval.ms=5*1000
// 消费者获取单条消息的最大值,默认1m,建议设置大一些,比如10m
fetch.max.bytes=10*1024*1024
// 消费者一次最多获取多少条数据,默认500条
max.poll.records=500
// 消费者和broker的连接时间空闲一段时间,该连接资源会被回收
// 一般建议设置成-1,就是一直不回收
connection.max.idle.ms=-1
// 允许消费者消费后自动提交offset到__consumer_offsets主题中,默认为true
enable.auto.commit=true
// 每隔多久提交一次offset,参考参数:enable.auto.commit
auto.commit.intelval.ms=5*1000
/**
* 生产上配置为:latest
* ealiest:当各分区下有已提交的offset时,从提交offset开始消费,否则从头消费
* latest:当各分区下有已提交的offset时,从提交offset开始消费,否则消费新产生的该分区下的数据
* none:当各分区下有已提交的offset时,从提交offset开始消费,只要有一个分区不存在已提交offset记录,就报异常
*/
auto.offset.reset=latest
消费者(Consumer)与协调器(Coordinator)
- 使用groupId获取hash值,然后对__consumer_offsets的partition数量(默认为:50个)取模,取模得到的partition所在的broker就作为coordinator,该partition也是该group下所有分区提交offset的分区
- 每个consumer都发送JoinGroup请求到Coordinator
- Coordinator从consumer group中选择一个consumer作为leader
- 然后把consumer group的情况发送给leader
- leader接着会指定消费方案
- 通过SyncGroup发给Coordinator
- Coordinator把消费方案下发给所有consumer,他们会从指定的分区的leader broker开始进行socket连接以及消费信息