kafka入门(千峰)学习笔记
前言
视频链接
https://www.bilibili.com/video/BV1Xy4y1G7zA
一、kafka介绍
1、为什么使用消息队列
实现异步通信
2、消息队列的流派
消息队列解决了通信问题
(1)、有broker(类似消息中转站)
a.重topic:kafka、activemq、rocketmq
b.轻topic:rabbitmq
(2)、无broker:zeromq
3、kafka安装(linux)
下载地址
https://kafka.apache.org/downloads
// 使用2.4.0
解压目录
drwxr-xr-x 3 root root 4096 12月 10 2019 bin
drwxr-xr-x 2 root root 4096 12月 10 2019 config
drwxr-xr-x 2 root root 4096 8月 28 15:46 libs
-rw-r--r-- 1 root root 32216 12月 10 2019 LICENSE
-rw-r--r-- 1 root root 337 12月 10 2019 NOTICE
drwxr-xr-x 2 root root 4096 12月 10 2019 site-docs
配置文件
vi /config/server.properties
修改内容
listeners=PLAINTEXT://172.30.0.3:9092
// 当前主机地址
advertised.listeners=PLAINTEXT://公网ip:9092
// 云服务器公网ip,外部连接需要使用公网
log.dirs=../kafka-logs
// 日志地址 即消息地址 默认保存七天
zookeeper.connect=123.45.6.78:2181
// zk地址
启动服务
sh kafka-server-start.sh -daemon ../config/server.properties
// -daemon为后台启动
观察进程和日志
ps -ef|grep kafka
// tail -f logs/server.log
[2023-08-28 16:00:21,294] INFO [TransactionCoordinator id=0] Starting up. (kafka.coordinator.transaction.TransactionCoordinator)
[2023-08-28 16:00:21,295] INFO [Transaction Marker Channel Manager 0]: Starting (kafka.coordinator.transaction.TransactionMarkerChannelManager)
[2023-08-28 16:00:21,295] INFO [TransactionCoordinator id=0] Startup complete. (kafka.coordinator.transaction.TransactionCoordinator)
[2023-08-28 16:00:21,311] INFO [ExpirationReaper-0-AlterAcls]: Starting (kafka.server.DelayedOperationPurgatory$ExpiredOperationReaper)
[2023-08-28 16:00:21,324] INFO [/config/changes-event-process-thread]: Starting (kafka.common.ZkNodeChangeNotificationListener$ChangeEventProcessThread)
[2023-08-28 16:00:21,483] INFO [SocketServer brokerId=0] Started data-plane processors for 1 acceptors (kafka.network.SocketServer)
[2023-08-28 16:00:21,485] INFO Kafka version: 2.4.0 (org.apache.kafka.common.utils.AppInfoParser)
[2023-08-28 16:00:21,485] INFO Kafka commitId: 77a89fcf8d7fa018 (org.apache.kafka.common.utils.AppInfoParser)
[2023-08-28 16:00:21,485] INFO Kafka startTimeMs: 1693209621484 (org.apache.kafka.common.utils.AppInfoParser)
[2023-08-28 16:00:21,488] INFO [KafkaServer id=0] started (kafka.server.KafkaServer)
观察zk中有kafka节点信息
./zkCli.sh
ls /brokers/ids
[0]
二、kafka基本使用
1、kafka基本概念
名称 解释
broker 消息中间件处理节点,一个kafka节点就是一个broker,多个broker组成一个集群
topic kafka通过topic对消息进行归类,发布到kafka集群的每条消息都需要指定一个topic
producer 消息生产者,向broker发送消息的客户端
consumer 消息消费者,从broker读取消息的客户端
2、创建主题topic
使用脚本创建主题
sh kafka-topics.sh --create --zookeeper 123.45.6.78:2181 --replication-factor 1 --partitions 1 --topic test
Created topic test.
使用脚本查看创建是否成功
sh kafka-topics.sh --list --zookeeper 123.45.6.78:2181
test
在zk查看是否创建成功
./zkCli.sh
ls /brokers/topics
[test]
topic等信息存储在zk,保证kafka是无状态的
3、发送消息
sh kafka-console-producer.sh --broker-list 172.30.0.3:9092 --topic test
>123
>456
>qwe
4、接收消息
(1)、从最后一条消息的偏移量+1消费
sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092 --topic test
111
(2)、从开头开始消费
sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092 --from-beginning --topic test
123
456
qwe
111
几个需要注意的点
a.消息会被存储
b.消息是顺序存储
c.消息是有偏移量的
d.消费时可以指定偏移量进行消费
三、消息的顺序存储
1、查看消息存储的位置
[root@1669305868239 kafka-logs]# ls
cleaner-offset-checkpoint __consumer_offsets-33
__consumer_offsets-0 __consumer_offsets-34
__consumer_offsets-1 __consumer_offsets-35
__consumer_offsets-10 __consumer_offsets-36
__consumer_offsets-11 __consumer_offsets-37
__consumer_offsets-12 __consumer_offsets-38
__consumer_offsets-13 __consumer_offsets-39
__consumer_offsets-14 __consumer_offsets-4
__consumer_offsets-15 __consumer_offsets-40
__consumer_offsets-16 __consumer_offsets-41
__consumer_offsets-17 __consumer_offsets-42
__consumer_offsets-18 __consumer_offsets-43
__consumer_offsets-19 __consumer_offsets-44
__consumer_offsets-2 __consumer_offsets-45
__consumer_offsets-20 __consumer_offsets-46
__consumer_offsets-21 __consumer_offsets-47
__consumer_offsets-22 __consumer_offsets-48
__consumer_offsets-23 __consumer_offsets-49
__consumer_offsets-24 __consumer_offsets-5
__consumer_offsets-25 __consumer_offsets-6
__consumer_offsets-26 __consumer_offsets-7
__consumer_offsets-27 __consumer_offsets-8
__consumer_offsets-28 __consumer_offsets-9
__consumer_offsets-29 log-start-offset-checkpoint
__consumer_offsets-3 meta.properties
__consumer_offsets-30 recovery-point-offset-checkpoint
__consumer_offsets-31 replication-offset-checkpoint
__consumer_offsets-32 test-0
// __consumer_offsets-*为偏移量 test-0为存储消息的位置
进到test-0中
[root@1669305868239 test-0]# ls
00000000000000000000.index 00000000000000000000.log 00000000000000000000.timeindex leader-epoch-checkpoint
2、查看消费者组及信息息
通过命令创建两个消费者,他们在同一个消费者组中
sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092 --consumer-property group.id=testGroup --topic test
只有一个消费者消费到消息
通过命令创建两个消费者,他们不在同一个消费者组中
sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092 --consumer-property group.id=testGroup1 --topic test
sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092 --consumer-property group.id=testGroup --topic test
结果两个消费者都收到消息
前者为单播消息(消费者同消费者组)
后者为多播消息(消费者不同消费者组)
3、查看消费者组及信息
查看当前主题下有哪些消费者组
sh kafka-consumer-groups.sh --bootstrap-server 172.30.0.3:9092 --list
testGroup1
testGroup
查看某一消费者组的具体信息
sh kafka-consumer-groups.sh --bootstrap-server 172.30.0.3:9092 --describe --group testGroup
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID
testGroup test 0 7 7 0 consumer-testGroup-1-84dd47e0-1acb-4cd1-a7b0-95f063b90989 /172.30.0.3 consumer-testGroup-1
CURRENT-OFFSET:当前消费者组的已消费偏移量
LOG-END-OFFSET:主题对应分区消息的结束偏移量(HW)
LAG:当前未被消费的消息数量
四、主题、分区的概念
1、主题topic
主题topic可以理解为一个类别划分
2、partition分区
区分分区,可以解决统一存储文件过大问题
提高吞吐量,可以多个分区并行读写
3、创建多分区的主题
创建两个分区一个副本的主题
sh kafka-topics.sh --create --zookeeper 123.45.6.78:2181 --replication-factor 1 --partitions 2 --topic test1
Created topic test1.
查看主题存放目录信息
[root@1669305868239 kafka-logs]# ls
cleaner-offset-checkpoint __consumer_offsets-34
__consumer_offsets-0 __consumer_offsets-35
__consumer_offsets-1 __consumer_offsets-36
__consumer_offsets-10 __consumer_offsets-37
__consumer_offsets-11 __consumer_offsets-38
__consumer_offsets-12 __consumer_offsets-39
__consumer_offsets-13 __consumer_offsets-4
__consumer_offsets-14 __consumer_offsets-40
__consumer_offsets-15 __consumer_offsets-41
__consumer_offsets-16 __consumer_offsets-42
__consumer_offsets-17 __consumer_offsets-43
__consumer_offsets-18 __consumer_offsets-44
__consumer_offsets-19 __consumer_offsets-45
__consumer_offsets-2 __consumer_offsets-46
__consumer_offsets-20 __consumer_offsets-47
__consumer_offsets-21 __consumer_offsets-48
__consumer_offsets-22 __consumer_offsets-49
__consumer_offsets-23 __consumer_offsets-5
__consumer_offsets-24 __consumer_offsets-6
__consumer_offsets-25 __consumer_offsets-7
__consumer_offsets-26 __consumer_offsets-8
__consumer_offsets-27 __consumer_offsets-9
__consumer_offsets-28 log-start-offset-checkpoint
__consumer_offsets-29 meta.properties
__consumer_offsets-3 recovery-point-offset-checkpoint
__consumer_offsets-30 replication-offset-checkpoint
__consumer_offsets-31 test-0
__consumer_offsets-32 test1-0
__consumer_offsets-33 test1-1
发现有两个test1,test1-0和test1-1为test1的两个分区
即如某一个分区目录内
[root@1669305868239 test1-0]# ls
00000000000000000000.index 00000000000000000000.timeindex
00000000000000000000.log leader-epoch-checkpoint
其中index保存的为稀疏索引,timeindex为时间索引,log为消息
五、kafka集群和副本
1、搭建kafka三个broker的集群(伪集群)
复制两个server.properties,分别命名为server1.properties,server2.properties
并且修改以下内容
// server1.properties
broker.id=1
listeners=PLAINTEXT://172.30.0.3:9093
advertised.listeners=PLAINTEXT://公网ip:9093
log.dirs=../kafka-logs-1
和
// server2.properties
broker.id=2
listeners=PLAINTEXT://172.30.0.3:9094
advertised.listeners=PLAINTEXT://公网ip:9094
log.dirs=../kafka-logs-2
启动三个节点
[root@1669305868239 bin]# sh kafka-server-start.sh -daemon ../config/server.properties
[root@1669305868239 bin]# sh kafka-server-start.sh -daemon ../config/server1.properties
[root@1669305868239 bin]# sh kafka-server-start.sh -daemon ../config/server2.properties
在zk上查看是否都启动成功
[zk: localhost:2181(CONNECTED) 3] ls /brokers/ids
[0, 1, 2]
2、副本的概念
副本是对分区的备份。在集群中,不同的副本会被部署在不同的broker上。
下面例子:创建一个主题,两个分区,三个副本
sh kafka-topics.sh --create --zookeeper 123.45.67.89:2181 --replication-factor 3 --partitions 2 --topic my-replicated-topic
Created topic my-replicated-topic.
查看topic
sh kafka-topics.sh --describe --zookeeper 123.45.67.89:2181 --topic my-replicated-topic
Topic: my-replicated-topic PartitionCount: 2 ReplicationFactor: 3 Configs:
Topic: my-replicated-topic Partition: 0 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
Topic: my-replicated-topic Partition: 1 Leader: 2 Replicas: 2,0,1 Isr: 2,0,1
图解
图解
a.leader:kafka读和写的操作,都发生在leader身上。leader负责把数据同步给follower。当leader挂了,经过主从选举,从多个follower中选举产生一个新leader
b.follower:接收leader同步的数据
c.isr:可以同步和已同步的节点会被存入到isr集合中。这里有一个细节:如果isr中的节点性能较差会被isr踢出集合
3、向kafka集群发消息
sh kafka-console-producer.sh --broker-list 172.30.0.3:9092,172.30.0.3:9093,172.30.0.3:9094 --topic my-repliacated-topic
>111222333
接收消息
sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092,172.30.0.3:9093,172.30.0.3:9094 --from-beginning --topic my-replicated-topic
111222333
4、关于分区消费组和消费者的细节
按相同消费组消费
sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092,172.30.0.3:9093,172.30.0.3:9094 --from-beginning --consumer-property group.id=testGroup1 --topic my-replicated-topic
相同消费者组只有一个消费者收到消息,即单播消息
关于分区消费组和消费者的细节
a:一个partition只能被一个消费组中的一个消费者消费,目的是为了保证消费的顺序性,但是多个partition的多个消费者消费的总的顺序性是得不到保证的。
b:partition的数量决定了消费组中消费者的数量,建议同一个消费组中消费者的数量不超过partition的数量,否者多的消费者消费不到消息
c:如果消费者挂了,那么会触发Rebalance机制,会让其他消费者来消费该分区
六、kafka的Java客户端-生产者
1、引入依赖
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.4.1</version>
</dependency>
2、生产者发消息的基本实现
代码
public class KafkaProducerTest {
public static final String TOPIC_NAME = "my-replicated-topic";
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.设置参数
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "ip1:9092,ip2:9093,ip3:9094");
// key序列化方式为字节数组
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// value序列化方式为字节数组
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 2.创建生产消息的客户端,传入参数
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
Map<String, Object> map = new HashMap<>();
map.put("jjj11", "11111");
// 3.创建消息
ProducerRecord<String, String> record = new ProducerRecord<>(
TOPIC_NAME, UUID.randomUUID().toString(), JSON.toJSONString(map));
// 4.发送消息,得到消息发送的元数据并输出
RecordMetadata recordMetadata = kafkaProducer.send(record).get();
String topic = recordMetadata.topic();
System.err.println("topic = " + topic);
int partition = recordMetadata.partition();
System.err.println("partition = " + partition);
long offset = recordMetadata.offset();
System.err.println("offset = " + offset);
}
}
控制台输出
topic = my-replicated-topic
partition = 1
offset = 3
3、发送到指定分区
// 3.创建消息 指定0号分区
ProducerRecord<String, String> record = new ProducerRecord<>(
TOPIC_NAME, 0, UUID.randomUUID().toString(), JSON.toJSONString(map));
4、同步异步发送消息
同步消息图解
同步消息图解
生产者异步发送消息,不管是否发送成功即进行后面逻辑
// 异步发消息 没有ack容易丢消息
kafkaProducer.send(record, (recordMetadata1, e) -> {
if (e != null) {
//失败
}
if (recordMetadata1 != null) {
//成功
}
});
关于ack的配置
a:ack=0,kafka集群不需要任何broker收到消息,就立即返回ack给生产者,最容易丢失消息,效率是最高的。
b:ack=1(默认),多副本之间的leader已经收到消息,把消息写入到本地的log中,才会返回ack给生产者,性能和安全性是最均衡的。
c:ack=-1/all,里面有默认的配置min.insync.replicas=2(默认是1,效果同b,推荐配置大于等于2),此时需要leader和follower同步完后,才会返回ack给生产者(此时集群中有2个broker已经完成数据的接收),这种方式最安全,但性能最差。
代码
// ack
properties.put(ProducerConfig.ACKS_CONFIG, "1");
// 多少时间(ms)没有ack回复,就重试
properties.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 300);
// 重试次数
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
消费者的其他细节配置
// 应用发送消息缓冲区(占用应用服务内存)大小,默认32MB
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
// kafka本地线程会从缓冲区取数据,批量发送到broker,默认16kb,就是说一个batch满了16kb就发送出去
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// 默认值是0,即消息立即被发送出去,但这样会影响性能,一般设置10毫秒左右,即满足BATCH_SIZE_CONFIG,和超时两个满足一个即发送
properties.put(ProducerConfig.LINGER_MS_CONFIG, 10);
图解
其他参数细节图解
补充:CountDownLatch可以合并多线的执行结果
未指定partition时,发到哪个partition的源码
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
if (keyBytes == null) {
return this.stickyPartitionCache.partition(topic, cluster);
} else {
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}
// 通过key hash后取模partition个数,即相同的key在一个分区
七、Java消费者的实现细节
1、消费者的基本实现
public class MySimpleConsumer {
public static final String TOPIC_NAME = "my-replicated-topic";
public static final String GROUP_NAME = "testGroup111";
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "116.198.205.107:9092,116.198.205.107:9093,116.198.205.107:9094");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, GROUP_NAME);
// 1.创建消费者客户端
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);
// 2.订阅消费消息
kafkaConsumer.subscribe(Arrays.asList(TOPIC_NAME));
while (true) {
/**
* 3.拉取消息长轮训
*/
ConsumerRecords<String, String> poll = kafkaConsumer.poll(Duration.ofMillis(1000L));
for (ConsumerRecord<String, String> record : poll) {
// 4.解析消息
int partition = record.partition();
System.err.println("partition = " + partition);
long offset = record.offset();
System.err.println("offset = " + offset);
String key = record.key();
System.err.println("key = " + key);
String value = record.value();
System.err.println("value = " + value);
}
}
}
}
2、自动和手动提交offset
图解
自动和手动提交offset图解
开启自动提交
// 开启自动提交 (默认)
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
// 自动提交时间间隔 (默认)
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
消费者poll到消息后,默认情况下,会自动向broker的consumer_offsets主题提交当前主题-分区消费的偏移量
需要注意的是:自动提交不管是否消费成功(消息poll下来)就提交offset,消息poll下来后,此时消费者挂了,于是下一个消费者会从已经提交的offset的下一个位置开始消费消息,之前未被消费的消息就丢失了,所以存在丢失消息的风险。
开启手动提交
// 开启手动提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
手动提交分为两种:
(1)、手动同步提交
在消费完消息后,调用同步提交的方法,当集群返回ack前一直阻塞,返回ack后表示提交成功,执行之后的逻辑。
代码
while (true) {
/**
* 3.拉取消息长轮训
*/
ConsumerRecords<String, String> poll = kafkaConsumer.poll(Duration.ofMillis(1000L));
for (ConsumerRecord<String, String> record : poll) {
// 4.解析消息
int partition = record.partition();
System.err.println("partition = " + partition);
long offset = record.offset();
System.err.println("offset = " + offset);
String key = record.key();
System.err.println("key = " + key);
String value = record.value();
System.err.println("value = " + value);
}
// 所有消息已经消费完
if (poll.count() > 0) {
kafkaConsumer.commitAsync();
}
}
(2)、手动异步提交
在消息消费完后提交,不需要等到集群ack,直接执行之后的逻辑,可以设置一个回调方法,供集群调用。
代码
while (true) {
/**
* 3.拉取消息长轮训
*/
ConsumerRecords<String, String> poll = kafkaConsumer.poll(Duration.ofMillis(1000L));
for (ConsumerRecord<String, String> record : poll) {
// 4.解析消息
int partition = record.partition();
System.err.println("partition = " + partition);
long offset = record.offset();
System.err.println("offset = " + offset);
String key = record.key();
System.err.println("key = " + key);
String value = record.value();
System.err.println("value = " + value);
}
// 异步提交
kafkaConsumer.commitAsync(new OffsetCommitCallback() {
@Override
public void onComplete(Map<TopicPartition, OffsetAndMetadata> map, Exception e) {
// 回调
}
});
}
3、消费者poll消息的过程
消费者建立了与broker之前的长连接,开始poll消息。
默认一次poll500条消息
// 一次消费最大拉取消息数
properties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);
可以根据消费速度的快慢设置,因为如果两次poll的时间如果超过了30s的时间间隔,kafka会认为其消费能力过弱,将其踢出消费组,将分区分配给其他消费者消费。触发rebalance机制,要消耗一些性能。
可以通过这个值进行设置
// 30s内没消费掉poll消息会被kafka集群的消费者组踢掉 触发Rebalance
properties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 30 * 1000);
如果每隔1s没有poll到任何消息,则继续poll消息,循环往复,直到poll到消息。如果超出了1s,则此长轮训结束。
ConsumerRecords<String, String> poll = kafkaConsumer.poll(Duration.ofMillis(1000L));
4、消费者的健康状态检查
消费者每隔1s向kafka集群发送心跳,如果集群发现超过10s没有续约的消费者,将被踢出消费者组,触发该消费者组的Rebalance机制,将该分区交给消费组里的其他消费者进行消费。
// consumer给broker发送心跳的间隔时间
properties.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 1000);
5、消费者指定分区消费
// 指定分区消费
kafkaConsumer.assign(Arrays.asList(new TopicPartition(TOPIC_NAME, 0)));
6、消息回溯消费
// 消息回溯消费
kafkaConsumer.seekToBeginning(Arrays.asList(new TopicPartition(TOPIC_NAME, 0)));
7、指定offset消费
// 指定offset消费
kafkaConsumer.seek(new TopicPartition(TOPIC_NAME, 0), 10);
8、从指定时间点消费
// 从指定时间开始消费
List<PartitionInfo> partitionInfos = kafkaConsumer.partitionsFor(TOPIC_NAME);
long fetchDataTime = System.currentTimeMillis() - 1000 * 60 * 60;
Map<TopicPartition, Long> map = new HashMap<>();
for (PartitionInfo partitionInfo : partitionInfos) {
map.put(new TopicPartition(TOPIC_NAME, partitionInfo.partition()), fetchDataTime);
}
Map<TopicPartition, OffsetAndTimestamp> partitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(map);
for (Map.Entry<TopicPartition, OffsetAndTimestamp> entry : partitionOffsetAndTimestampMap.entrySet()) {
TopicPartition key = entry.getKey();
OffsetAndTimestamp value = entry.getValue();
if (key == null || value == null) {
continue;
}
long offset = value.offset();
int partition = key.partition();
System.err.println("partition = " + partition);
System.err.println("offset = " + offset);
kafkaConsumer.assign(Arrays.asList(key));
kafkaConsumer.seek(key, offset);
}
9、新消费者组的消费规则
// 当加入一个新消费者组的时候,可以指定offset开始消费,如果不指定,应该怎样消费的配置
// earliest:没消费过的最近一次消费的位置,并不完全是从头消费
// latest(默认):从结尾
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
八、springboot整合kafka
1、引入依赖
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
2、生产者配置及代码
yml配置
spring:
application:
name: kafka-product-demo
kafka:
bootstrap-servers: ip:9092,ip:9093,ip:9094
producer: # producer 生产者
retries: 3 # 没有收到ack的重试次数(默认3)
batch-size: 16384 # 批量大小(默认16KB)
buffer-memory: 33554432 # 生产端缓冲区大小(默认32MB)
acks: 1 # 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
代码
@Resource
private KafkaTemplate<String, Object> kafkaTemplate;
/**
* 同步发送
*
* @param msg
* @throws Exception
*/
@GetMapping("/kafka/sync/{msg}")
public void sync(@PathVariable("msg") String msg) throws Exception {
Message message = new Message();
message.setMessage(msg);
log.info("kafka send start....");
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send("test", JSON.toJSONString(message));
//注意,可以设置等待时间,超出后,不再等候结果
// SendResult<String, Object> result = future.get(2, TimeUnit.SECONDS);
SendResult<String, Object> result = future.get();
log.info("kafka send end....");
log.info("send result:{}", result.getProducerRecord().value());
}
3、消费者配置及代码
yml配置
spring:
application:
name: kafka-consumer-demo
kafka:
bootstrap-servers: 119.96.216.192:9092,119.96.216.192:9093,119.96.216.192:9094
consumer: # consumer消费者
group-id: javagroup # 默认的消费组ID
enable-auto-commit: false # 是否自动提交offset
# auto-commit-interval: 100 # 提交offset延时(接收到消息后多久提交offset)
# earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
# latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
# none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
max-poll-records: 500 #一次拉取多少条数据消费
listener:
# 当每一条记录被消费者监听器处理后提交
# record
# 当每一批poll()的数据被消费者监听器处理后提交
# batch
# 当每一批poll()的数据被消费者监听器处理后,距离上次提交时间大于time时提交
# time
# 当每一批poll()的数据被消费者监听器处理后,距离上次提交时间大于等于count时提交
# count
# time | count 有一个满足时提交
# count_time
# 当每一批poll()的数据被消费者监听器处理后,手动调用akc.acknowledge()后提交
# manual
# 手动调用akc.acknowledge()后立即提交,最常用
ack-mode: manual_immediate
代码
/**
* 不指定group,默认取yml里配置的
*
* @param consumerRecord
*/
@KafkaListener(topics = {"test"}, groupId = "javagroup")
public void onMessage1(ConsumerRecord<String, String> consumerRecord, Acknowledgment ack) {
// 单条消息消费逻辑
log.info("KafkaListener start.....");
String msg = consumerRecord.value();
log.info("message:{}", msg);
log.info("KafkaListener end.....");
// 手动提交不指定ack会有重复消费问题
ack.acknowledge();
}
消费者的详细配置
/**
* 指定消费分区,及多个主题等
*
* @param consumerRecord
* @param ack
*/
@KafkaListener(groupId = "javagroup", topicPartitions = {
@TopicPartition(topic = "topic1", partitions = {"0", "1"}),
@TopicPartition(topic = "topic2", partitions = {"0"},
partitionOffsets = @PartitionOffset(partition = "1", initialOffset = "100"))
}, concurrency = "3")
public void onMessage2(ConsumerRecord<String, String> consumerRecord, Acknowledgment ack) {
log.info("KafkaListener start.....");
String msg = consumerRecord.value();
log.info("message:{}", msg);
log.info("KafkaListener end.....");
ack.acknowledge();
}
九、kafka集群Controller、Rebalance和HW
1、Controller
集群中谁是controller
每个broker启动时会向zk创建一个临时序号节点,获得的序号最小的那个broker将会作为集群中的controller,负责管理集群中的所有分区和副本的状态:
a:当某个分区的leader副本出现故障时,由控制器负责为该分区选举新的leader副本。
b:当检测到某个分区的ISR集合发生变化时,由控制器负责所有broker更新其元数据信息。
c:当使用kafka.topic.sh脚本为某个topic增加分区数量时,同样还是由控制器负责让新分区被其他节点感知到。
2、Rebalance
前提是:消费者没有指明分区消费。当消费组里消费者和分区的关系发生变化,那么就会触发rebalance机制。
这个机制会重新调整消费者消费那个分区。
在触发rebalance机制之前,消费者消费分区有三种策略:
a:range:通过公式来计算某个消费者消费哪个分区。
图解
range图解
b:轮询:大家轮着消费。
图解
轮询图解
c:sticky:在触发了rebalance后,在消费者消费原分区不变的基础上进行调整。
如果不使用粘合策略,将重新打散消费关系,重新分配消费关系,消耗性能。
3、HW和LEO
HW俗称高水位,HighWatermarker的缩写,取一个partition对应的ISR中最小的LEO(log-end-offset)作为HW,consumer最多只能消费到HW所在的位置。另外每个replica(副本)都有HW,leader和follower各自负责更新自己的HW状态。对于leader新写入的消息,consumer不能立刻消费,leader会等待该消息被所有ISR中的replica同步后更新HW,此时消息才能被consumer消费。这样就保证了如果leader所在的broker失效,该消息仍然可以从新选举的leader中获取。
图解
高水位概念图解
十、kafka问题线上优化
1、如果防止消息丢失
a:发送方,ack是1或者-1/all可以防止消息丢失,如果要做成99.9999999%(N个9),ack设置成all,把min.insync.replicas配置成分区被分数
b:消费方,把自动提交改成手动提交
2、如何防止消息的重复消费
一条消息被消费者消费多次。如果为了消息的不重复消费,而把生产端的重试机制关闭、消费端的手动提交改成自动提交,这样反而会出现消息丢失,那么可以直接在防止消息的手段上再加上消费消息时的幂等性保证,就能解决消息重复消费的问题。
幂等性如何保证:
a:MySQL插入业务id作为主键,主键是唯一的,所以一次只能插入一条
b:使用redis或zk的分布式锁(主流方案)
图解
解决消息重复发送图解
3、如何做到顺序消费
发送方:在发送时将ack不能设置成0,关闭重试,使用同步发送,等消息发送成功再发送下一条。确保消息是顺序发送的。
接收方:消息是发送到一个分区中,只能有一个消费组的消费者接收消息。
因此,kafka的顺序消费会牺牲掉性能。
4、解决消息积压问题
消息积压会导致很多问题,比如磁盘被打满、生产端发消息导致kafka性能过慢,就容易发生服务雪崩,就需要有相应的手段:
方案一:在一个消费者中启动多个线程,让多个线程同时消费。--提升一个消费者的消费能力。
方案二:如果方案一还不够的话,这个时候可以启动多个消费者,多个消费者启动在不同的服务器上。其实多个消费者部署在同一服务器上也可以提高消能力。--充分利用服务器的CPU资源。
方案三:让一个消费者去把接收的消息往另一个topic上发,另一个topic设置多个分区和多个消费者,进行具体的业务消费。
图解
方案三图解
5、延迟队列
延迟队列的应用场景:在订单创建成功后如果超过30分钟没有付款,则需要取消订单,此时可以用延迟队列来实现。
a:创建多个topic,每个topic表示延时的间隔。
i:topic_5s,延时5秒执行的队列。
ii:topic_1m,延时1分钟执行的队列。
iii:topic_30m,延时30分钟执行的队列。
b:消息发送者发送消息到相应的topic,并带上消息的发送时间。
c:消费者订阅相应的topic,消费时轮询消费整个topic中的消息
i:如果消息的发送时间,和消费的当前时间超过预设的值,比如30分钟。
ii:如果消息的发送时间,和消费的当前时间没有超过预设的值,则不消费当前的offset及之后的offset的所有消息都消费。
iii:下次继续消费该offset处的消息,判断时间是否已满足预设值。
图解
延迟队列图解
十一、kafka-eagle监控平台
1、搭建
下载地址
https://github.com/smartloli/kafka-eagle-bin/archive/v3.0.1.tar.gz
需要jdk环境
配置kafka-eagle环境变量
vim /etc/profile
export KE_HOME=xxx/efak-web-3.0.1
export PATH=$PATH:$KE_HOME/bin
解压目录
drwxr-xr-x 2 root root 4096 8月 31 16:10 bin
drwxr-xr-x 2 root root 4096 8月 31 16:10 conf
drwxr-xr-x 2 root root 4096 9月 13 2021 db
drwxr-xr-x 2 root root 4096 8月 31 16:10 font
drwxr-xr-x 9 root root 4096 2月 23 2022 kms
drwxr-xr-x 2 root root 4096 4月 2 2022 logs
进入配置文件
cd conf
vi system-config.properties
修改配置
efak.zk.cluster.alias=cluster1
// kafka zk地址
cluster1.zk.list=123.45.67.89:2181
// 修改数据库信息
efak.driver=com.mysql.cj.jdbc.Driver //8的驱动 5.7用com.mysql.jdbc.Driver
efak.url=jdbc:mysql://127.0.0.1:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
efak.username=root
efak.password=123456
库和表都会自己创建
启动
cd bin
sh ke.sh start
日志
Welcome to
______ ______ ___ __ __
/ ____/ / ____/ / | / //_/
/ __/ / /_ / /| | / ,<
/ /___ / __/ / ___ | / /| |
/_____/ /_/ /_/ |_|/_/ |_|
( Eagle For Apache Kafka® )
Version v3.0.1 -- Copyright 2016-2022
*******************************************************************
* EFAK Service has started success.
* Welcome, Now you can visit 'http://172.30.0.3:8048'
* Account:admin ,Password:123456
*******************************************************************
* <Usage> ke.sh [start|status|stop|restart|stats] </Usage>
* <Usage> https://www.kafka-eagle.org/ </Usage>
*******************************************************************
2、访问
xxx:8048