Kafka快速⼊⻔
1.Kafka的特点
1.解耦:
允许你独⽴的扩展或修改两边的处理过程,只要确保它们遵守同样的接⼝约束。
2.冗余:
消息队列把数据进⾏持久化直到它们已经被完全处理,通过这⼀⽅式规避了数据丢失⻛险。许多消息队
列所采⽤的"插⼊-获取-删除"范式中,在把⼀个消息从队列中删除之前,需要你的处理系统明确的指出该消
息已经被处理完毕,从⽽确保你的数据被安全的保存直到你使⽤完毕。
3.扩展性:
因为消息队列解耦了你的处理过程,所以增⼤消息⼊队和处理的频率是很容易的,只要另外增加处理过
程即可。
4.灵活性 & 峰值处理能⼒:
在访问量剧增的情况下,应⽤仍然需要继续发挥作⽤,但是这样的突发流量并不常⻅。如果为以能处理
这类峰值访问为标准来投⼊资源随时待命⽆疑是巨⼤的浪费。使⽤消息队列能够使关键组件顶住突发的访问
压⼒,⽽不会因为突发的超负荷的请求⽽完全崩溃。
5.可恢复性:
系统的⼀部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使⼀个处理
消息的进程挂掉,加⼊队列中的消息仍然可以在系统恢复后被处理。
6.顺序保证:
在⼤多使⽤场景下,数据处理的顺序都很重要。⼤部分消息队列本来就是排序的,并且能保证数据会按
照特定的顺序来处理。(Kafka 保证⼀个 Partition 内的消息的有序性)
7.缓冲:
有助于控制和优化数据流经过系统的速度,解决⽣产消息和消费消息的处理速度不⼀致的情况。
8.异步通信:
很多时候,⽤户不想也不需要⽴即处理消息。消息队列提供了异步处理机制,允许⽤户把⼀个消息放⼊
队列,但并不⽴即处理它。想向队列中放⼊多少消息就放多少,然后在需要的时候再去处理它们。
2.Kafka架构图
3.Kafka相关概念
1.producer:
消息⽣产者,发布消息到 kafka 集群的Borker(下⾯的Leader分区)。
2.broker:
kafka 集群中安装Kafka的服务器(容器),broker要有唯⼀的ID。
3.topic:
每条发布到 kafka 集群的消息属于的类别,即 kafka 是⾯向 topic 的(相当于数据库中的表)
4.partition:
partition 是物理上的概念,每个 topic 包含⼀个或多个 partition。kafka 分配的单位是
partition。
5.consumer:
从 kafka 集群中消费消息的终端或服务。
6.Consumer group:
high-level consumer API 中,每个 consumer 都属于⼀个 consumer group,每条消息只能被
consumer group 中的⼀个 Consumer 消费,但可以被多个 consumer group 消费。
7.replica:
partition 的副本,保障 partition 的⾼可⽤。
8.leader:
replica 中的⼀个⻆⾊, producer 和 consumer 只跟 leader 交互。
9.follower:
replica 中的⼀个⻆⾊,从 leader 中复制数据。
10.zookeeper:
kafka 通过 zookeeper 来存储集群的 meta 信息,并对分区leader做负载均衡
4安装Kafka集群
4.1 安装zookeeper
4.2.安装Kafka集群
上传Kafka安装包
解压
修改配置⽂件
vi servier.properties
#指定broker的id
broker.id=1 #数据存储的⽬录
log.dirs=/data/kafka
#指定zk地址
zookeeper.connect=node-1.51doit.cn:2181,node-2.51doit.cn:2181,node-3.51doit.cn:2181
#可以删除topic的数据(⽣成环境不⽤配置)
#delete.topic.enable=true
将配置好的kafka拷⻉的其他节点
修改其他节点Kafka的broker.id
4.3 启动zk
/bigdata/zookeeper-3.4.13/bin/zkServer.sh start
/bigdata/zookeeper-3.4.13/bin/zkServer.sh status
4.4在所有节点启动Kafka
/bigdata/kafka_2.12-2.4.1/bin/kafka-server-start.sh -daemon /bigdata/kafka_2.12-2.4.1/config/server.properties
4.5查看Kafka进程信息
jps
4.6查看Kafka的topic
/bigdata/kafka_2.12-2.4.1/bin/kafka-topics.sh --list --zookeeper localhost:2181
4.7 创建topic
/bigdata/kafka_2.12-2.4.1/bin/kafka-topics.sh --zookeeper localhost:2181 --create --topic wordcount --replication-factor 3 --partitions 3
4.8启动命令⾏⼀个⽣产者
/bigdata/kafka_2.12-2.4.1/bin/kafka-console-producer.sh --broker-list node-1.51doit.com:9092,node-2.51doit.com:9092,node-3.51doit.com:9092 --topic wordcount
4.8 启动⼀个命令⾏消费者
/bigdata/kafka_2.12-2.4.1/bin/kafka-console-consumer.sh --bootstrap-server node-1.51doit.com:9092,node-2.51doit.com:9092,node-3.51doit.com:9092 --topic wordcount --from-beginning
–from-beginning 消费以前产⽣的所有数据,如果不加,就是消费消费者启动后产⽣的数据
4.9 删除topic
/bigdata/kafka_2.12-2.4.1/bin/kafka-topics.sh --delete --topic wordcount --zookeeper localhost:2181
4.10 查看topic详细信息
/bigdata/kafka_2.12-2.4.1/bin/kafka-topics.sh --zookeeper localhost:2181 --describe --topic wordcount
4.11查看某个topic的偏移量
/bigdata/kafka_2.12-2.4.1/bin/kafka-console-consumer.sh --topic __consumer_offsets --bootstrap-server node-1.51doit.com:9092,node-2.51doit.com:9092,node-3.51doit.com:9092 --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --consumer.config /bigdata/kafka_2.12-2.4.1/config/consumer.properties --from-beginning
使用scala代码操作kafka
生产者:
package cn._51doit.kafkaclient
import java.util.{Properties, UUID}
import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}
import org.apache.kafka.common.serialization.StringSerializer
object ProducerDemo {
def main(args: Array[String]): Unit = {
// 1 配置参数
val props = new Properties()
// 连接kafka节点
props.setProperty("bootstrap.servers", "node-1.51doit.cn:9092,node-2.51doit.cn:9092,node-3.51doit.cn:9092")
//指定key序列化方式(ProtoBuf)
props.setProperty("key.serializer", "org.apache.kafka.common.serialization.StringSerializer")
//指定value序列化方式
props.setProperty("value.serializer", classOf[StringSerializer].getName) // 两种写法都行
//props.setProperty("acks", "1") //ack应答
//props.setProperty("compression.type", "gzip") //压缩方式
//props.setProperty("retries", "100") //重试次数
//props.setProperty("request.timeout.ms", "60000") //超时时间
val topic = "test"
// 2 kafka的生产者
val producer: KafkaProducer[String, String] = new KafkaProducer[String, String](props)
for (i <- 2021 to 2030) {
// 3 封装的对象
//将数据发送到指定的分区编号
//val record = new ProducerRecord[String, String](topic, 1 , "keyabc","myvalue:"+i)
//val partitionNum = i % 3 // 指定数据均匀写入3个分区中
//val record = new ProducerRecord[String, String](topic, partitionNum, null,"myvalue:"+i)
//不指定分区编号,指定key, 分区编号 = key.hasacode % 3
//相同key的数据一定会到kafka的同一个分区,但是同一个分区中可以有多个key的数据
//val record = new ProducerRecord[String, String](topic , "laozhao","myvalue:"+i)
//根据key的hashcode值模除以topic分区的数量,返回一个分区编号
val record = new ProducerRecord[String, String](topic , UUID.randomUUID().toString ,"myvalue:"+i)
//没有指定Key和分区,默认的策略就是轮询,将数据均匀写入多个分区中
//val record = new ProducerRecord[String, String](topic,"value-" + i)
// 4 发送消息
producer.send(record)
}
println("message send success")
// 释放资源
producer.close()
}
}
消费者:
package cn._51doit.kafkaclient
import java.time.Duration
import java.util
import java.util.Properties
import org.apache.kafka.clients.consumer.{ConsumerRecords, KafkaConsumer}
import org.apache.kafka.common.serialization.StringDeserializer
//topic、分区、groupId -> 偏移量
object ConsumerDemo {
def main(args: Array[String]): Unit = {
// 1 配置参数
val props = new Properties()
//从哪些broker消费数据
props.setProperty("bootstrap.servers", "node-1.51doit.cn:9092,node-2.51doit.cn:9092,node-3.51doit.cn:9092")
// 反序列化的参数
props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
props.setProperty("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer")
// 指定group.id
props.setProperty("group.id","doit02")
// 指定消费的offset从哪里开始
//earliest:从头开始 --from-beginning
//latest:从消费者启动之后
props.setProperty("auto.offset.reset","earliest") //[latest, earliest, none]
// 是否自动提交偏移量 offset
// enable.auto.commit 默认值就是true【5秒钟更新一次】,消费者定期会更新偏移量 groupid,topic,parition -> offset
props.setProperty("enable.auto.commit", "false") // 不让kafka自动维护偏移量 手动维护偏移量
//enable.auto.commit 5000
// 2 消费者的实例对象
val consumer: KafkaConsumer[String, String] = new KafkaConsumer[String, String](props)
// 订阅 参数类型 java的集合
val topic: util.List[String] = java.util.Arrays.asList("test")
// 3 订阅主题
consumer.subscribe(topic)
while (true){
// 4 拉取数据
val msgs: ConsumerRecords[String, String] = consumer.poll(Duration.ofMillis(5000))
//导入隐式转换
import scala.collection.JavaConverters._
//将Java的集合或迭代器转成Scala的集合或迭代器
for(cr <- msgs.asScala){
//ConsumerRecord[String, String]
println(cr)
//msgs迭代器
if(msgs.isEmpty) {
//获取该分区的最大的偏移量
}
}
}
//consumer.close()
}
}
总结:
1.Kafka的⽣产者直接向Broker的Leader分区写⼊数据,不需要连接ZK
2.Kafka的消费者(⽼的API需要先连接ZK,获取Broker信息和topic、分区偏移量信息),新的API(0.8以后)不需要
连接ZK(直连⽅式,新的API,底层的API,效率更⾼)
3.kafka叫消息中间件又叫消息队列,具有高可用,高并发,高吞吐,多副本的特点
4.kafka的主从节点体现在borker内的分区,是分区分为主从leader和follower,不是borker,只有leader负责读写数据,leader接收到数据后follower从leader中拉取数据生成副本,当leader挂了之后,follower会挑选出一个新的leader
5.构建topic时分区数量时可以超过节点数量,但副本数量不能超过节点数量
6.一个生成者会默认以轮询的方式通过多个分区leader将数据写入kafka中,但一个消费者只能对接一个leader,避免重复消费,如果消费者数量大于leader数量,那么有的消费者会处于空闲状态,如果小于leader数量,可能某个消费者需要消费多个leader,避免数据丢失,所以最佳方式为消费者数量与节点数量保持一致
7.生成者写入数据时有事务机制,当写入不成功时会回滚,且可以设置写入响应等级ack,ack设置为0是,写入leader后就不管了,不等leader回应是否确定写入成功就直接开始写入下一条,设置为1,等leader回应写入成功后再开始写入下一条,设置为-1为all时,等leader和follower都回应写入成功才开始写入下一条,写入速度越来愈慢,但安全等级越来愈高
8.kafka中有一个自带的topic(_consumer_ofsets),它里面存储着其他topic的分区信息和消费者组的偏移量信息(kafka的偏移量信息是按消费者组为单位进行存储的),它的默认分区数为50个,副本个数一个
9.创建topic时不要使用"_“和”."否则可能会报错
10.当kafka出现负载不均衡时,可能是分区数量设置太少导致的,分区数量建议:节点数量*菜谱核数/预计的topic数量,分区数量创建之后还能进行后续的增添,分区数量太少也可能会导致消费者不能及时消费,造成数据积压
11.生产者按默认的方式进行轮询写入时,不是每来一条数据就写入一次的,是达到一定大小或到达一定时间之后就flush刷入broker
12.解决数据积压和负载不均衡的策略:合理的增加分区的数量,合理的设置flush大小和flush时间,设置合理的retries写入失败重试的次数(默认是int的最大值,不是特别重要的数据可以减少为100次左右),设置压缩格式:lzo/snappy
13.影响偏移量的参数:topic,组id,分区数量,记录偏移量默认是5秒记录一次,那么可能会出现还未记录就消费者挂掉的情况,会导致重复消费数据,所以我们可以自定义记录偏移量的方式,例如将他写入到支持事务的数据库中
14.存数据时,可以支持一条数据的key,当数据都有了key之后,会按key的hashcode值%分区数,将key值相同的数据放入同一个分区当中,但这样可能存在数据倾斜
15.kafka最优的序列化方式:protoBuf