Kafka 是一款分布式的基于 发布/订阅 模式的消息中间件。
相较于其他框架,kafka与大数据生态的组件更兼容(比如直接有Flink-kafka数据源DataStream、甚至Kafka Table(Flink SQL) 的API,)。(大数据组件也可以使用其他MQ)
-
消息中间件主要作用:
- 异步通信
- 削峰
- 解耦:代替应用间接口调用,降低应用互相依赖
-
kafka用于数据实时采集流程举例
- log、csv文件 --> flume --> kafka -->ss / flink
- mysql / oracle --> maxwell / ogg
-
大数据场景数据来源:
- 数据库:(关系型、非关系型)
- 文件:log文件,csv文件(方便程序之间转移的excel表格数据)
kafka 概述
kafka 官网:kafka.apache.org
Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.
(distributed event streaming platform :分布式事件流平台 - -对标大数据流式组件 ss、flink、结构化流)
kafka 也不单纯做中间件,不甘心单单为ss \ flink 提供,也想有自己的流式平台streams,但很小众。
kafka 特点
- HIGH THROUGHPUT : 高吞吐
- SCALABLE : 可扩展
- PERMANENT STORAGE : 持久化
- HIGH AVAILABILITY :高可用
kafka 版本
- 0.8 、0.10 、 0.11 、2.0、2.8 、3.0+
- 从0.10 起是里程碑式版本。
- kafka 0.10.0 与spark 2.21 兼容,是经典组合。https://spark.apache.org/docs/2.2.1/streaming-kafka-integration.html
- 甚至spark3.2.1还可以用kafka 0.10。
- 从2.8.0起kafka弃用zookeper,改用自己实现HA,较少了组件部署维护依赖成本。
下载:https://archive.apache.org/dist/kafka/
kafka 模式
- 点对点模式
一个生产者、一个消费者。消费者主动从Message Queue中拉取消息并消费,再发送确认给Message Queue,Message Queue收到消费者确认消费后会删除消息。 - 发布/订阅模式
可以有多个topic,且一个topic可以被多个消费者订阅并消费。消费者消费后不会删除消息(供其他消费者消费)。多个消费者互相独立。
架构
-
topic 主题,可以有多个主题。
-
partition 是topic的物理分组,代表进入topic的文件可以被切分,一个topic至少一个paitition,在partition中有序 。
partition 的名称是以topic-编号组成。 -
replication 是备份的副本,防止每个partition的单点故障,每个partition(leader)个可以有多个follower。
TopicA leader follower TopicA-partition1 TopicA-repartition2 TopicA-partition2 TopicA-repartition3 TopicA-partition3 TopicA-repartition1
生产和消费消息都只针对leader,只有leader故障时才将特定follower转换为leader。(如何找到leader,如何将follower转换为leader由zookeeper完成,(2.8.0以后可选不用ZK,kafka自己处理))
-
zookeeper 会保存两类数据:
- kafka集群中节点的在线情况
leader是在ZK中以节点存在: /brokers/ids/[0,1,2]
- 所有leader与对应follower
同时对每个partition还保存了哪些是leader,哪些是follower: /brokers/topics/first/partitions/0/state/ "leader":0,"isr":[1,2]
-
acks
acks 有三个值,分别对应3种处理方式
acks = 0 : 生产者发送消息后不会等待kafka报告是否成功提交。
acks = 1 :当主副本提交成功数据(记录到日志)就返回生产者,不关心其他节点成功与否。
acks = all (等价 -1): 当主副本和所有备份副本都提交成功数据。最安全,但性能会有损耗。
部署使用
集群部署,三台机器
-
每台机器先配置hosts,如(hadoop001、hadoop002、hadoop003)
-
下载解压kafka后,进入 ./config 目录下,打开server.properties 做以下配置
-
每一台机器是一个broker,通过指定 broker.id区分集群中每台机器。broker.id=0 (其余两台分别是1,2)
-
log.dirs=/tmp/kafka-logs (存储的是消息内容的数据,而不是kafka运行的日志。修改到非/tmp的目录)
-
log.retention.hours=168 [默认保留数据7天,按需修改]
-
zookeeper.connect=hadoop001:port,hadoop002:port,hadoop003:port/kafka (部署zookeeper集群的hostname:port) (/kafka 表示在三台ZK下创建一个/kafka目录统一存放)
-
进kafka的数据,会经过压缩,默认保留7天
-
-
修改文件后,在 ./bin 目录找到启动命令
kafka-server-start.sh [-daemon] 可选指定以后台进程的方法启动 kafka-server-start.sh -daemon [../config/server.properties] 可选带配置文件启动
启动后会有一个进程Kafka(zookeeper的进程是QuorumpeerMain),stop时先关闭kafka再关闭zk.
乱序
由于多个partition并行提交消息,不同partition之前的顺序不能保证全局有序的。(一个partition内是有序的)
解决乱序:
- 牺牲吞吐量,只设置一个partition,缺失了使用kafka的意义。
- 消费后先做全局排序,性能开销很大。
- 将部分有关联的,有顺序要求的数据指定到同一个Key,保证进入同一个partition。
那么如何关联让partition认这个key呢:
代码层面:send消息的时候指定key, 源码算法就是取这个key做hash运算以确定partition,key为空才取其他的内容做hash
配置层面:如果使用maxwell,可以有参数 producer_partition_by 指定如按数据库主键为key等,保证对同一个主键数据的操作是按顺序的
047 kafka-04 J哥解决乱序的过程和配置,这套配置仍在使用
kafka.acks = all
producer_partition_by = primary_key
kafka.retries = 100
max.in.flight.requests.per.connection = 1
补充,如果仍出现数据差异,在业务低谷时,对比大数据和RDBMS,对有差异的数据多退少补。做补偿机制,
常用命令
topic
- 创建topic
./kafka-topic.sh \ --create \ --zookeeper hostname:port,hostname:port,... \ --topic test_topic \ --partitions 3 \ --replication-factor 3
- 注意:如果kafka配置zookeeper.connect保持时指定了根目录/kafka,则这里–zookeeper 的内容也要指定到/kafka。也就是要和配置文件server.properties中zookeeper.connect保持一致。
kafka-topics.sh \ --create \ --zookeeper localhost:2181/kafka \ --replication-factor 1 \ --partitions 1 \ --topic test
-
查看所有topic
./kafka-topic.sh \ --list \ --zookeeper hostname:port,hostname:port,...
-
描述(也可验证topic是否正常创建)
./kafka-topic.sh \ --describe \ --zookeeper hostname:port,hostname:port,...
描述中的内容代表的意义 Partition: 0 代表分区的number Leader: 0 和 Repicas:0,1,2 代表当前分区所在的机器的broker.id
-
修改topic (尽量一次创建好,不得已才修改)
./kafka-topic.sh \ --alter \ --zookeeper hostname:port,hostname:port,... \ --topic test_topic \ --partitions 3 // 不能往小了调
建议值 partitions 8,replication-factor 3 。
为什么要调整 partitions ,kafka partitions : spark partitions == 1:1; 临时加了机器。 -
删除topic
./kafka-topics.sh \ --delete \ --zookeeper ruozedata001:2181,ruozedata002:2181,ruozedata003:2181/kafka \ --topic test_topic
删除时出现:
Topic jj is marked for deletion. --仅仅时打了’删除’的标签
Note: This will have no impact if delete.topic.enable is not set to true.delete.topic.enable=true 是受这个配置决定,(不要删除是最保险的,不打算用的话等着7天回收就行)
-
迁移(机器、磁盘)
反复验证后使用
http://kafka.apache.org/documentation/#basic_ops_automigrate// 创建一个json文件 cat topics-to-move.json {"topics": [{"topic": "foo1"}], "version":1 }
bin/kafka-reassign-partitions.sh \ --zookeeper localhost:2181 \ --topics-to-move-json-file topics-to-move.json \ --broker-list "5,6" \ --generate bin/kafka-reassign-partitions.sh \ --zookeeper localhost:2181 \ --reassignment-json-file expand-cluster-reassignment.json \ --execute
关于topic创建:
- kafka topic名称规范: 英文字母小写
- 在建topic之前,名字想好了 真的想好了再建
- 不要轻易删除topic 就算生产上这个topic不用了 且 数据量较大, 也没关系 7天后数据会自己删除
- 不要有强迫症,不要随意修改、删除
生产/消费者
- 创建生产者和消费者 (用于验证是否生产、消费)
./kafka-console-producer.sh \ --zookeeper hostname:port,hostname:port,... \ --topic test_topic ./kafka-console-consumer.sh \ --zookeeper hostname:port,hostname:port,... \ --topic test_topic
Kafka容错机制的理解
kafka为保证高可用做了很多处理,以副本机制在多个服务端节点上对每个主题分区的日志进行复制,副本的单位是按主题下的分区,即对每个主题的每个分区都有副本。
其中一个为主副本Leader,其余多个备份副本,主副本负责响应客户端的读写请求,备份副本则从主副本拉取数据,保持和主副本的同步。
基于多副本冗余设计,kafka就有了容错的保证。即当部分节点宕机时,仍然保证系统正常对外提供服务,且外界无感知。
当主副本宕机时,kafka会在备份副本中选择一个作为主副本,对客户端提供响应。
(为尽量保证主副本和备份副本不同时宕机,主副本应该均匀的分布在各个服务器上,且互相做备份副本,即一台机器做其中一个分区的主副本,同时还做另一分区的备份副本)
当一个备份副本宕机(没有和ZK节点保持会话)、或备份进度落后太多时,主副本就会将其从同步副本集合中移除,反之,如果备份副本重新赶上主副本,它就会加入到主副本的同步集合中。
(一组主副本和其备份副本,成为一组 In Sync Replicas 即ISR)
当然这样的设计会对数据的同步和可靠性增加管理难度。
当acks = all,一条消息只有被所有副本都运用到本地的日志文件,才会认为消息被成功提交到kafka。只有已经提交的消息才能被消费者消费。
当acks = all,对于生产者所发送的一条消息已经写入到主副本中,但是此时备份副本还没来及进行数据copy时,主副本就挂掉的情况,那么此时消息提交不成功,生产者需要重新发送该消息。
acks 有三个值,分别对应3种处理方式
acks = 0 : 生产者发送消息后不会等待kafka报告是否成功提交。
acks = 1 :当主副本提交成功数据(记录到日志)就返回生产者,不关心其他节点成功与否。
acks = all (等价 -1): 当主副本和所有备份副本都提交成功数据。最安全,但性能会有损耗。
生产环境会在 1 和 all 之间根据业务类型选择。
当 acks = all ,但Partition只有一个主副本Leader,没有任何Follower,则设置all与1并无区别,不能保证数据一定安全。
并且在有Follower情况下,如果副本放置不合理如一组ISR都在同一台机器导致全部宕机,也会丢失。
kafka集群部署
准备工作,zookeeper集群已提前搭建:
172.16.69.207|172.16.69.206|172.16.69.208
现搭建由4台主机组成的kafka集群:
172.16.69.205|172.16.69.207|172.16.69.206|172.16.69.208
下载 kafka_2.13-2.7.2.tgz,分别上传到集群每台主机,解压,配置环境变量等过程省略。
分别对4台主机修改配置文件 config/server.properties
broker.id=0
host.name=172.16.69.205
port=9092
listeners=PLAINTEXT://172.16.69.205:9092
log.dirs=/tmp/kafka-logs
zookeeper.connect=172.16.69.207:2181,172.16.69.206:2181,172.16.69.208:2181/kafka
- 每一台机器是一个broker,通过指定 broker.id区分集群中每台机器。broker.id=0 (其余3台分别是1,2,3)
- host.name=172.16.69.205。本节点的ip
- port=9092 配置文件中没有端口默认配置项,但默认端口就是9092,可改成其他端口
- listeners=PLAINTEXT://172.16.69.208:9092. 监听本节点的host.name和port,不然生产消息时就会报错:Error while fetching metadata with correlation id : {LEADER_NOT_AVAILABLE}
- log.dirs=/tmp/kafka-logs (存储的是消息内容的数据,而不是kafka运行的日志。修改到非/tmp的目录)
- log.retention.hours=168 [默认保留数据7天,按需修改]
- zookeeper.connect=node1:port,node2:port,node3:port/kafka (部署zookeeper集群的hostname:port) (/kafka 表示在三台ZK下创建一个/kafka目录统一存放)
- 其他配置项按需修改
其他3台主机:
broker.id=1
host.name=172.16.69.207
port=9092
listeners=PLAINTEXT://172.16.69.207:9092
log.dirs=/tmp/kafka-logs
zookeeper.connect=172.16.69.207:2181,172.16.69.206:2181,172.16.69.208:2181/kafka
broker.id=2
host.name=172.16.69.206
port=9092
listeners=PLAINTEXT://172.16.69.206:9092
log.dirs=/tmp/kafka-logs
zookeeper.connect=172.16.69.207:2181,172.16.69.206:2181,172.16.69.208:2181/kafka
broker.id=3
host.name=172.16.69.208
port=9092
listeners=PLAINTEXT://172.16.69.208:9092
log.dirs=/tmp/kafka-logs
zookeeper.connect=172.16.69.207:2181,172.16.69.206:2181,172.16.69.208:2181/kafka
分别修改好配置文件后,4台主机依次启动
kafka-server-start.sh -daemon ../config/server.properties
- 在任意一台主机下,创建topic
./kafka-topics.sh \
--create \
--zookeeper 172.16.69.207:2181,172.16.69.206:2181,172.16.69.208:2181/kafka \
--topic test_topic \
--partitions 4 \
--replication-factor 1
- 创建消费者
kafka-console-consumer.sh \
--bootstrap-server 172.16.69.207:9092,172.16.69.206:9092,172.16.69.208:9092,172.16.69.205:9092 \
--topic test_topic
创建后会保持会话,等待消息到来
Kafka常用命令之kafka-console-consumer.sh :https://blog.csdn.net/qq_29116427/article/details/80206125
- 创建生产者
执行命令后,就会在控制台等待键入消息体。
回车表示触发“发送”操作;
退出生产者控制台,直接使用“Ctrl + c”退出。
Kafka常用命令之kafka-console-producer.sh:https://blog.csdn.net/qq_29116427/article/details/105912397
- 创建生产者并发送消息(指定key)
kafka-console-producer.sh \
--bootstrap-server 172.16.69.207:9092,172.16.69.206:9092,172.16.69.208:9092,172.16.69.205:9092 \
--topic test_topic \
--property parse.key=true
默认消息key与消息value间使用“Tab键”进行分隔,切勿使用转义字符(\t),如下所示:
>Lei Li Hello Kafka!
>Meimei Han 你好 kafka!
- 创建生产者并发送消息(默认,无key)
kafka-console-producer.sh \
--bootstrap-server 172.16.69.207:9092,172.16.69.206:9092,172.16.69.208:9092,172.16.69.205:9092 \
--topic test_topic
控制台直接输入消息值(value)即可,每行表示一条消息,如下所示。
>Hello Kafka!
Spring Boot客户端整合kafka集群
上文测试了在集群中创建topic,创建消费者,创建生产者并发送消息都测试成功。(内网环境)
添加依赖
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
添加配置文件 application.yml
spring:
kafka:
bootstrap-servers: 外网ip:9092,外网ip:9092,外网ip:9092,外网ip:9092
consumer:
group-id: my-group
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
template:
default-topic: test_topic
创建消费者
@Service
public class KafkaConsumerService {
@KafkaListener(topics = "test_topic", groupId = "my-group")
public void consume(String message) {
System.out.println("Received message: " + message);
}
}
创建生产者
@Service
public class KafkaProducer {
private final KafkaTemplate<String, String> kafkaTemplate;
public KafkaProducer(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void sendMessage(String topic, String message) {
kafkaTemplate.send(topic, message);
}
}
创建启动类
@SpringBootApplication
public class KafkaSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(KafkaSpringBootApplication.class, args);
/**
* Kafka 提供两个API
* 操作topic的
* @Autowired
* private KafkaAdmin kafkaAdmin;
*
* 操作生产者的
* @Autowired
* private KafkaTemplate kafkaTemplate;
*
* 消费者直接使用注解
* @KafkaListener(topics = "test_topic", groupId = "my-group")
* public class KafkaConsumer{
* }
* */
}
}
启动便报错:
[Consumer clientId=consumer-my-group-1, groupId=my-group] Group coordinator 172.16.69.206:9092
(id: 2147483645 rack: null) is unavailable or invalid
其中这个172.16.69.206是内网ip,但java客户端现在是在外网环境,可是检查application.yml配置里,并没有使用内网ip,那么这个内网ip只可能是kafka配置里返回到java客户端的,而java客户端再使用内网ip去连接,于是连不上。
解决办法:再次回到kafka配置文件server.properties
vim ../config/server.properties
对集群中每一个主机的配置,都增加一项
advertised.listeners=PLAINTEXT://当前节点的外网ip:9092
重启kafka集群,再次启动KafkaSpringBootApplication,成功启动并消费了topic中消息。
上文部署集群时的配置文件不"完整"的,补充后配置文件:
broker.id=0
host.name=172.16.69.205
port=9092
listeners=PLAINTEXT://172.16.69.205:9092
advertised.listeners=PLAINTEXT://当前节点的外网ip:9092
log.dirs=/tmp/kafka-logs
zookeeper.connect=172.16.69.207:2181,172.16.69.206:2181,172.16.69.208:2181/kafka
查看kafka在zookeeper中的数据
启动zk客户端
[root@tidb1 data]# zkCli.sh
Connecting to localhost:2181
在zk客户端控制台,列出所有数据
[zk: localhost:2181(CONNECTED) 1] ls /
[kafka, zookeeper]
[zk: localhost:2181(CONNECTED) 3] ls /kafka
[admin, brokers, cluster, config, consumers, controller, controller_epoch, feature, isr_change_notification, latest_producer_id_block, log_dir_event_notification]
[zk: localhost:2181(CONNECTED) 4] ls /kafka/brokers
[ids, seqid, topics]
[zk: localhost:2181(CONNECTED) 5] ls /kafka/brokers/ids
[0, 1, 2, 3]
[zk: localhost:2181(CONNECTED) 6] ls /kafka/brokers/topics
[__consumer_offsets, multi_topic, test_topic]