Kafka 的安装和配置
0 安装
略,到官网下载即可。注意 Kafka 还需要 Zookeeper 支持。
- Kafka 版本 : kafka_2.13-2.4.0
- Zookeeper 版本 : Zookeeper-3.5.4-beta
- jdk 版本 : openjdk 8
1 Kafka 配置
Kafka 的主要配置文件是 /config/server.properties。
## 实例 id,同一集群中的所有实例的 id 不可相同
broker.id = 0
## kafka 服务监听的地址和端口
## 设置了 advertised.listeners 之后可以不设置客户端的 hosts 文件,原因未知
listener = PLAINTEXT://xxxxxxx:9092
advertised.listeners = PLAINTEXT://xxxxxxx:9092
##### 通用配置 #####
## 处理磁盘 io 的线程数
num.io.threads = 8
## 处理网络 io 的线程数
num.network.threads = 3
## 用来处理后台任务的线程数,例如过期消息文件的删除等
backgroud.threads = 4
## kafka 启动时回复数据和关闭时保存数据到磁盘时使用的线程数
num.recovery.threads.per.data.dir = 10
## 消息发送的缓存区大小
socket.send.buffer.bytes = 102400
## 消息接收的缓存区大小
socket.receive.buffer.bytes = 102400
## socket 请求的最大缓存值
socket.request.max.bytes = 10240000
## 每次 consumer 从 broker 中获取的消息的条数
max.poll.records = 100
##### 再平衡配置 #####
## consumer 的拉取间隔时间
## 如果在指定的间隔时间内 consumer 没有再次来拉取数据,
## broker 就会认为 consumer 挂掉了,并且引发再平衡
## 默认值 3000 ms
max.poll.interval.ms = 3000
## 再平衡延迟时间
## 再平衡的意义是当 consumer 发生上下线的时候,会重新分配 partition 的消费权
## 延迟的好处是如果 consumer 在一段时间内集中上下线,可以在延迟时间之后一次性处理
## 如果发生一次变动就处理一次,效率会较低
## 默认为 0 ms,即为不延迟
group.initial.rebalance.delay.ms = 10
## 每个 partition 的 leader 都会维护一份 isr 名单
## 即为和 leader 数据基本相差不多的 follower 的名单
## 如果某个 follower 的数据差距与 leader 相差太大,就会被剔除出名单
## 如果 leader 挂掉了,优先在 isr follower 中挑选成为新 leader
## 被剔除出 isr 的 follower 被存放在 osr 名单中
## 此处设置 follower 差距数据的时间
replica.lag.time.max.ms = 5000
## 是否允许 osr 名单中的 follower 参加 leader 选举
## 默认为 true
## 如果此处设置为 false,且 isr 名单为空,则 Kafka 集群会静止下来等待 leader 恢复
unclear.leader.election.enable = true
## 是否开启 partition leader 自动均衡机制
## 在开启此策略的情况下,如果一个 partition 中有 0 1 2 三个副本,其中 0 为 leader
## 此时 0 挂掉了,1 成为了新 leader,然后 0 重新启动,则 0 会恢复 leader 身份
## 这样的好处在于,不容易出现同一台机器上的很多 partition 都是 leader,导致系统不稳定
## 默认为 true
auto.leader.rebalance.enable = true
## leader 不平衡比例,若超过这个数值,就会对 partition 进行再平衡
leader.imbalance.per.broker.percentage = 10
## 检查 leader 是否不平衡的时间间隔
leader.imbalance.check.interval.seconds = 300
##### 数据日志配置 #####
## kafka 日志数据存储目录,用逗号分割可以指定多个
# log.dirs = /tmp/kafka-logs-1,/tmp/kafka-logs-2
log.dirs = /tmp/kafka-logs
## 刷新日志到磁盘的阈值
# 根据消息数量做刷新,默认使用该策略
# log.flush.interval.messages = 10000
# 根据时间做刷新,单位 毫秒,默认不开启
log.flush.interval.ms = 1000
## 检查刷新机制的时间间隔
## 该参数的意义是每隔一定时间检查一次是否到达 flush 的设置阈值
log.flush.scheduler.interval.ms = 3000
## 记录上次固化数据到硬盘的时间点,主要用于数据恢复
## 默认值 60000
log.flush.offset.checkpoint.interval.ms = 60000
## 日志的存储时间,可以以小时单位,也可以设置为分钟或者毫秒
## 当超过这个时间,就会执行日志清除
# log.retention.minutes = 120
# log.retention.ms = 120
# 默认使用小时,值为 168
log.retention.hours = 2
## 每个 partition 的存储大小,如果超过了就会执行日志清除
## -1 代表不限制,默认 1073741824
log.retention.bytes = 1073741824
## 日志大小的检查周期,如果已经到达了大小,就触发文件删除
log.retention.check.interval.ms = 300000
## 指定日志每隔多久检查一次是否可以被删除,默认为 1 min
log.cleanup.internal.mins = 1
## 日志清除的策略,默认为 delete
## 如果要使用日志压缩,就需要让策略包含 compact
## 需要注意的是,如果开启了 compact 策略,则客户端提交的消息的 key 不允许为 null,否则提交报错
# log.cleanup.policy = delete
log.cleanup.policy = delete
## 是否开启日志压缩
## 默认开启,但是只有在日志清除策略包含 compact 的时候日志压缩才会生效
## 日志压缩的逻辑是对 key 进行整合,对相同 key 的不同 value 值只保存最后一个版本
log.cleaner.enable = true
## 开启压缩的情况才生效,日志压缩运行的线程数
log.cleaner.threads = 8
## 日志压缩去重的缓存内存,内存越大效率越好
## 单位 byte,默认 524288 byte
log.cleaner.io.buffer.size = 524288
## 日志清理的频率,越大就越高效,但是内存消耗会更大
log.cleaner.min.cleanable.ratio = 0.7
## 单个日志文件的大小,默认 1073741824
log.segment.bytes = 1073741824
## 日志被真正清除的时间
## 日志过了保存时间之后,只是被逻辑性删除,无法被索引到,但是没有真的从磁盘中被删除
## 此参数用于设置在被标注为逻辑删除后的日志被真正删除的时间
log.segment.delete.delay.ms = 60000
##### Topic 配置 #####
## 是否允许自动创建 topic
## 若为 false,则只能通过命令创建 topic
## 默认 true
auto.create.topics.enable = true
## 每个 topic 的默认分区 partition 个数,在 topic 创建的时候可以指定,如果不指定就使用该参数
## partition 数量直接影响了能够容纳的 cosumer 数量
num.partitions = 1
## topic partition 的副本数,副本越多,越不容易因为个别 broker 的问题而丢失数据
## 副本越多,可用性越高,但是每次数据写入之后同步花费的时间更多
offsets.topic.replication.factor = 1
transaction.state.log.replication.factor = 1
transaction.state.log.min.isr = 1
##### Zookeeper 配置 #####
## Zookeeper 地址,用逗号分割可以指定多个
# zookeeper.connect = localhost:2181,localhost:2182
zookeeper.connect = localhost:2101
## Zookeeper 集群的超时时间
zookeeper.connection.timeout.ms = 6000
2 创建 topic
上述配置中设置了自动创建 topic,但是也可以手工创建 :
./bin/kafka-topics.sh --create \
--zookeeper localhost:2101 \
--replication-factor 1 \
--partitions 1 \
--topic test1
查看 topic :
./bin/kafka-topics.sh --zookeeper localhost:2101 --list
3 启动 Kafka
./bin/kafka-server-start.sh -daemon ./config/server.properties &
4 消息手动发送
进入到 kafka 的 bin 目录下,输入:
sh kafka-console-producer.sh --broker-list {ip1}:{port1},{ip2}:{port2},{ip3}:{port3} --topic {topic}
其中 {ip}:{port} 处填写 kafka 实例的 ip 和端口,{topic} 处填写要发送的 topic 的名称。
完毕之后进入到消息行中进行消息发送。消息输入以回车键为结尾,回车一次即为发送一条消息。
SpringBoot Kafka 配置代码
1 pom
spring boot 版本 :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
引入 jar 包 :
<!-- kafka 必须的包 -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.4.1.RELEASE</version>
<exclusions>
<!-- 如果已经在别处引入 spring-boot-starter,此处可以排除 spring 相关的包 -->
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-*</artifactId>
</exclusion>
</exclusions>
</dependency>
2 SpringBoot yaml 配置
spring:
kafka:
## 生产者配置,如果本实例只是消费者,可以不配置该部分
producer:
# client id,随意配置,不可重复
client-id: boot-producer
# kafka 服务地址,pi + 端口
bootstrap-servers: aliyun-ecs:9092
# 用于序列化的工具类
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
# 消息发送失败情况下的重试次数
retries: 1
# 批量上传的 buffer size,可以是消息数量,也可以是内存量
batch-size: 10000
buffer-memory: 300000
# 等待副本同步之后才确认消息发送成功,可选的值有 0,1,-1,all 等
# 设置为 0 的意思是不等待任何副本同步完成就直接返回
# 设置为 1 的意思是只等待 leader 同步完成
# all 的意思是全部同步完才确认,但是速度会比较慢
acks: 1
## 消费者配置,如果本实例只是生产者,可以不配置该部分
consumer:
# client id,随意配置,不可重复
client-id: boot-consumer
# 消费者分组 id,同一组别的不同消费者共同消费一份数据
group-id: consumer-group-1
# kafka 服务地址,pi + 端口
bootstrap-servers: aliyun-ecs:9092
# 用于反序列化的工具类
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 自动更新 offset
enable-auto-commit: true
# 如果 enable-auto-commit 设置为 true,则每隔一段时间提交一次 offset
# 时间单位为毫秒,默认值 5000 (5s)
auto-commit-interval: 1000
# offset 消费指针
# earliest 代表从头开始消费,lastest 代表从新产生的部分开始消费
auto-offset-reset: earliest
3 代码
生产者配套代码:
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* kafka mq 生产者包装类
*/
@Component
public class MQProductor {
@Resource
KafkaTemplate<String,String> kt;
/**
* 发送消息的方法
* @param topic 创建的 topic
* @param partition topic 分片编号,从 0 开始
* @param key 消息 key,主要用来分片和作为压缩凭据,可以重复,可以为空
* @param message 消息主体
*/
public void send(String topic,Integer partition,String key,String message) {
kt.send(topic,partition,key,message);
}
public void send(String message) {
send("test-topic",0,"",message);
}
}
消费者配套代码:
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
/**
* kafka mq 消费者包装类
*/
@Component
public class MQListener {
/**
* 监听方法,可以一次性监听多个 topic
* @param cr kafka 返回的消息包装类
*/
@KafkaListener(topics = {"test-topic" /*,"test-topic-2"*/ })
public void consume(ConsumerRecord<String,String> cr) {
// value
String value = cr.value();
System.out.println(value);
// key
String key = cr.key();
System.out.println(key);
// 读取指针
long offset = cr.offset();
System.out.println(offset);
// 读取的分区编号
int partition = cr.partition();
System.out.println(partition);
// topic
String topic = cr.topic();
System.out.println(topic);
}
}
4 日志屏蔽
Kafka 的监听者客户端会不断轮寻服务端,尝试获取最新的数据,同时打印很多的无用日志。
在 log4j2 的配置文件中可以配置 <Loggers></Loggers> 标签内的内容,屏蔽这部分的日志:
<!-- log4j.xml -->
<Loggers>
<!-- 其它配置省略 -->
<!-- 此处剔除 Kafka 监听类的日志打印 -->
<!-- 设置只有 ERROR 级别的日志才会打印出来,屏蔽掉 INFO / DEBUG 等级别的日志 -->
<Logger name="org.apache.kafka.clients.FetchSessionHandler" level="ERROR"/>
<Logger name="org.springframework.kafka.listener.KafkaMessageListenerContainer" level="ERROR"/>
</Loggers>
Kafka 相关原理思考
为什么使用 Kafka?
MQ 的核心功能是削峰填谷,由于业务本身有高峰期和低谷期的区别,所以可以把实时性要求不那么高的数据先临时放置在 MQ 里,等到服务器的算力有盈余的时候再取出来进行数据处理。
另外,异步的 MQ 对比实时的 RPC 连接,耦合性更低,就算是生产者和消费者中的一方出现了问题,也不影响另一方的正常使用。
在消费者的消费能力能够跟上的情况下,其实 MQ 的实时性也不会比 RPC 连接低太多;Kafka 实际上是一种极佳的用网力代替算力,用磁盘代替内存的解决方案。
当业务对吞吐量的要求比较高,但是对数据的实时性要求不那么高,且能够允许一定范围内的数据丢失等情况发生,那就可以选择 Kafka,反之可以选择其它业务可靠性更高的 MQ。
Kafka 是如何做到高效的?
Kafka 的高效体现在两个方面,第一方面是网络 io,第二方面是磁盘 io。
网络 io 方面,Kafka 底层使用了 Netty 来处理网络通讯,并且自己定义了紧凑的应用层协议,充分利用网络带宽和计算机的多线程性能,客户端一般会使用批量上传的方式,减少网络 io 的次数;
磁盘 io 方面,Kafka 采用了顺序写入的模式,从源码来看,Kafka 会使用一个 ByteBuffer 来包装消息,然后使用 RandomAccessFile 和 FileChannel 进行精确的写入操作。FileChannel 可以通过 force(...) 方法精确的把 PageCache 里的数据刷到磁盘里。
在读取数据的时候,Kafka 会通过调用 FileChannel.transferTo(...) 或者 FileChannel.map(...) 方法完成零拷贝。
Kafka 如何做到不丢数据?
在 Kafka 集群中,数据是多副本保存的,每个 partition 会有多份数据,每份都在不同的机器上,具体有几份副本是可以自行配置的;
Kafka 在客户端中,会有一个重试和 ack 的配置项。
重试机制的意义是如果客户端传输消息到服务端失败了,会重传的次数;
ack 机制的意义是只有当若干份副本都已经完成最新的数据同步工作的情况下,才会确认数据传输成功,否则则是失败的。
必须要注意的是,这些可以调节的机制,设置的越严谨,实际上 Kafka 集群的吞吐量就越小,但是数据就越可靠,丢失率低,但是同时可能会存在数据重复上传的风险;反之则吞吐量大,但是丢失率高。
Kafka 如何做到数据不重复消费?
从 Kafka 自身来说,一个 partition 只会被一个消费者消费,所以在同一个消费者组里,Kafka 服务和消费者近乎是一对一的,Kafka 通过内部维护一个 offset 指针的方式,来确保在正常情况下消费者不重复消费消息。当然如果需要重复消费消息,那么消费者可以选择在不同的组别中。
从生产者角度上来说,如果出现消息已经提交成功,但是 Kafka 由于网络原因没有返回成功的情况,可能会存在重复提交的情况。
从消费者角度上来说,如果消息已经被消费成功,但是由于网络原因没有成功提交 offset 的更新信息,就会出现重复提交的情况。
当出现这两种特殊情况的时候,可以使用类似于解决接口幂等性的思路来解决。比如对每一条数据都附加一个 token,当消息已经被消费的时候,就将这条 token 过期。
除了上述办法之外,通过手动控制 Kafka 的 offset 的提交也可以减少数据重复消费的情况。
在数据传输中,有三种事务级别:最多一次,最少一次,精确一次。
实际上精确一次这种事务级别很难达到,默认使用自动提交 offset 的 Kafka 的事务级别可以理解为最少一次,即为只有正确消费消息之后 Kafka 才会确定更新 offset,但是如果消费过程中出错,则 offset 不更新,数据会被重新消费;手动提交 offset 可以在消息被拉取但是还未被消费的时候就进行一次 offset 更新,就算后续逻辑出错,也不会重新消费这条数据了。
Kafka 如何保证数据一致性?
- 1 Kafka 中的副本机制只是用来备份数据,副本并不支持数据读取。换言之,consumer 只能向 leader partition 去拉取数据。
- 2 Kafka 中的数据备份机制是 follower partition 主动去向 leader partition 请求新数据。
- 3 leader partition 会维护一份 isr 副本名单,如果某个 follower partition 的数据进度落后 leader partition 太多,就会被剔除出名单。假如某一时刻 leader partition 崩溃,一般情况下会优先从 isr 名单中的 follower 中去挑选成为新的 leader,如果 isr 名单为空,才会考虑其它 follower。
什么是 Kafka 的再平衡?
当存在 consumer 突然上下线的时候,Kafka 需要重新分配 partition 资源给 consumer 消费,这个过程就是再平衡。
再平衡的默认策略是 range,即为先按照顺序进行分配,分配完成之后如果有多余的 partition,就再给编号靠前的几个 consumer。
最新的 sticky 策略会尽量保持现有的 consumer 和 partition 的关联关系。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。