Kafka
一种高吞吐量的分布式发布-订阅消息系统,专为超高吞吐量的实时日志采集、实时数据同步、实时数据计算等场景来设计
-
Kafka架构
Topic:维护一个主题中的消息,可视为消息分类
主题是已发布消息的类别名称 发布和订阅数据必须指定主题 主题副本数量不大于Brokers个数
Producer:向Kafka主题发布(生产)消息
Consumer:订阅(消费)主题并处理消息
Broker:Kafka集群中的服务器
-
Partition
1、一个主题包含多个分区,默认按Key Hash分区
2、每个Partition对应一个文件夹<topic_name>-<partition_id>
3、每个Partition被视为一个有序的日志文件(LogSegment)
4、Replication策略是基于Partition,而不是Topic
5、每个Partition都有一个Leader,0或多个Followers -
Consumer
1、offset的管理是基于
消费组(group.id)
的级别
2、每个Partition
只能由同一消费组内的一个Consumer来消费
3、每个Consumer可以消费多个分区
4、消费过的数据仍会保留在Kafka中
5、消费者不能超过分区数量 -
Kafka数据流
副本同步(ISR)、容灾(Leader Partition)、高并发(读写性能、Consumer Group)、负载均衡
-
Kafka 是如何实现高吞吐的?
顺序读写:是不断追加到文件中的; 零拷贝:只用将磁盘文件的数据复制到页面缓存中一次,然后将数据从页面缓存直接发送到网络中; 分区: Kafka 的队列 topic 被分为了多个区 partition,每个partition又分为多个段segment, 每次文件操作都是对一个小文件的操作, 也增加了并行处理能力 批量发送: 允许进行批量发送消息,先将消息缓存在内存中,然后一次请求批量发送出去, 大大减少服务端的 I/O 次数 数据压缩: Producer 可以通过 GZIP 或 Snappy 格式对消息集合进行压缩压缩的好处就是减少传输的数据量,减轻对网络传输的压力 Consumer 的负载均衡: 当一个 group 中,有 consumer 加入或者离开时,会触发 partitions 均衡.均衡的最终目的,是提升topic 的并发消费能力
-
Kafka Producer API
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.3.0</version>
</dependency>
val pro = new Properties()
//设置kafka集群,kafka集群的broker-list
pro.put("bootstrap.servers","single:9092")
//0不需要leader partition确认接收成功
//1需要等待
//-1(all)需要leader和isr列表的follower都确认成功
pro.put("acks","all")
//Key和value序列化
pro.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
pro.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//创建kafka生产者
val producer = new KafkaProducer[String,String](pro)
for(i<-1 to 100) {
producer.send(new ProducerRecord[String,String("topic",Integer.toString(i), "dd:"+i))
}
producer.close()
val pro = new Properties()
//设置kafka集群,kafka集群的broker-list
pro.put("bootstrap.servers", "single:9092")
//设置消费者组id
pro.put("group.id", "testGroup1");
pro.put("enable.auto.commit", "true"); //默认值true
//每隔1000ms提交一次
pro.put("auto.commit.interval.ms", "1000"); //默认值5000
//Key和value反序列化
pro.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
pro.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
val consumer = new KafkaConsumer[String, String](pro)
val topic=new util.ArrayList[String]()
topic.add("test")
consumer.subscribe(topic)
//设置超时时间,单位为毫秒,返回一些条数据
while (true) {
val records: ConsumerRecords[String, String] = consumer.poll(1000)
val it = records.iterator()
while (it.hasNext) {
val next = it.next()
println(s"partition=${next.partition()},offset=${next.offset()},key=${next.key()},value=${next.value()}")
}
}
val props = new Properties()
// 配置Kafka集群的ip和端口号
props.put("bootstrap.servers", "single:9092")
// 设置消费者组的id, 如果有多个相同id的消费者程序, 那么他们将在一个组当中
props.put("group.id", "testGroup1")
// 关闭自动提交[默认就是开启]
props.put("enable.auto.commit", "false")
// key和value的反序列化
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
// 创建一个Consumer客户端
val consumer = new KafkaConsumer[String, String](props)
// consumer消费
val topics = new util.ArrayList[String]()
topics.add("test")
// 订阅topic
consumer.subscribe(topics)
val parts = new util.ArrayList[TopicPartition]()
parts.add(new TopicPartition("test",0))
consumer.assign(parts)
// 创建一个集合, 用来存放消息的个数
val buffer = new util.ArrayList[ConsumerRecord[String, String]]()
// 为了能够一直从Kafka中消费数据, 使用 while true 死循环
while (true) {
// 设置超时时间, 单位是毫秒, 返回一些条数据
val records: ConsumerRecords[String, String] = consumer.poll(1000)
// 对这些数据进行遍历输出
val iter: util.Iterator[ConsumerRecord[String, String]] = records.iterator()
while (iter.hasNext) {
val next: ConsumerRecord[String, String] = iter.next()
println(s"partition = ${next.partition()}, offset=${next.offset()}\nkey = ${next.key()}, value = ${next.value()}")
buffer.add(next)
}
if(buffer.size()>5){
// 手动提交offset有两种, 一种是同步阻塞方式, 一种是异步非阻塞方式
consumer.commitAsync()
// consumer.commitSync()
buffer.clear()
}
}