Kafka整理
一,简介
Kafka是一个高吞吐量,低延迟分布式的消息队列系统。Kafka每秒可处理几十万条消息,它的低延迟最低只有几毫秒。
Kafka集群中有多个Broker服务器组成,每个类型的消息被定义为topic。
同一个topic内部的消息按照一定的key和算法被分区(partition)存储在不同的Broker上。
消息生产者producer和消费者consumer可以在多个Broker上生产/消费 topic。
概念理解;
Topic 和 Logs:
Topic就是每条发布到Kafka集群的消息都有一个类别,topic在Kafka中可以由多个消费者订阅,消费。
每个topic包含一个或多个partition(分区),分区数量可以在创建topic时指定,每个分区日志中记录了该分区的数据以及索引信息,如下图;
Kafka只保证分区内的消息有序,不能保证一个主题不同分区之间的消息有序。如果你想要保证所有的消息都绝对有序,可以只为一个主题分配一个分区。
分区会给每个消息记录分配一个顺序ID号(偏移量),能够唯一的标识该分区中的每个记录。Kafka集群保留所有发布的记录,不管这个记录是否被消费过,Kafka提供相应策略通过配置从而对旧数据处理。
实际上,每个消费者唯一保存的元数据信息就是消费者当前消费日志的位移位置。位移位置是由消费者控制,即消费者可以通过修改偏移量读取任何位置的数据。
Producers-- 生产者
指定topic来发送消息到Kafka Broker
Consumers-- 消费者
根据topic消费相应的消息
Topic-- 消息主题(类型)
一个topic可以有多个partition,分布在不同的broker server上
二,Kafka的使用场景
2.1 日志收集
一个公司可以用Kafka收集何种服务的log,通过Kafka以统一接口服务的方式开放给各种consumer。例如 hadoop,Hbase,Solr等…
2.2 消息系统
解耦生产者和消费者,缓存消息,对于一些常规的消息系统,Kafka是一个不错的选择Partition/replication和容错,可以使Kafka具有良好的扩展性和性能。但是我们应该清除的认识到,Kafka并没有提供JMS中的事务性消息传输担保(消息确认)等企业级特性。Kafaka作为“常规”的消息系统,在一定程度上,尚未确保消息的发送与接收绝对可靠(存在重复消息,丢失消息问题)。
2.3 用户活动跟踪
Kafka经常用来记录web用户或者app用户的各种活动,如浏览网页,搜索,点击等活动,这些活动信息被各个服务器发布到Kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop,数据仓库中做离线分析和挖掘。
2.4 运营指标
Kafka经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
2.5 流式处理
很好的应用于 spark streaming 和 storm
三,Kafka集群部署
zookeeper集群共三台,分别为:node01,node02,node03
Kafka集群共三台,分别为: node01,node02,node03
3.1 Zookeeper集群准备
Kafka是一个分布式消息队列,需要依赖Zookeeper,请先安装好zookeeper集群。
zookeeper集群安装参考—> https://blog.csdn.net/weixin_43270493/article/details/85870366
3.2 安装Kafka
下载安装包 地址: http://kafka.apache.org/downloads
解压:
tar zxvf kafka_2.10-0.9.0.1.tgz -C /opt/
mv kafka_2.10-0.9.0.1/ kafka
修改配置文件:config/server.properties
broker.id : 集群中唯一标识id: 1, 2, 3 依次增长(broker就是Kafka集群中的一台服务器)
注:当前我的Kafka集群共三台节点,分别为: node01,node02,node03,对应的Broker.id分别为1,2,3。
zookeeper.connect: zk集群地址列表
将当前node03服务器上的Kafka目录拷贝到node01,node02上。
3.3 启动Kafka集群
先启动zookeeper集群,bin目录下, zkServer.sh start
启动Kafka集群,bin目录下, kafka-server-start.sh …/config/server.properties
3.4 测试
创建话题(topic)
bin/kafka-topics.sh --zookeeper node01:2181,node02:2181,node03:2181
--create --replication-factor 2 --partitions 3 --topic test
参数说明:
– replication-factor : 指定每个分区的复制因子个数,默认是1个
– partitions : 指定当前创建的kafka分区数量,默认是1个
– topic : 指定新建的topic的名称
查看topic列表
bin/kafka-topics.sh --zookeeper node01:2181,node02:2181,node03:2181
--list
查看topic描述:
bin/kafka-topics.sh --zookeeper node01:2181,node02:2181,node03:2181 --describe --topic test
ISR机制
kafka不是完全同步,也不是完全异步,是一种ISR机制:
-
leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护
-
如果一个flower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其重ISR中移除
-
当ISR中所有Replica都向Leader发送ACK时,leader才commit
既然所有Replica都向Leader发送ACK时,leader才commit,那么flower怎么会leader落后太多?producer往kafka中发送数据,不仅可以一次发送一条数据,还可以发送message的数组;批量发送,同步的时候批量发送,异步的时候本身就是就是批量;底层会有队列缓存起来,批量发送,对应broker而言,就会收到很多数据(假设1000),这时候leader发现自己有1000条数据,flower只有500条数据,落后了500条数据,就把它从ISR中移除出去,这时候发现其他的flower与他的差距都很小,就等待;如果因为内存等原因,差距很大,就把它从ISR中移除出去。
leader挂掉了,从它的follower中选举一个作为leader,并把挂掉的leader从ISR中移除,继续处理数据。一段时间后该leader重新启动了,它知道它之前的数据到哪里了,尝试获取它挂掉后leader处理的数据,获取完成后它就加入了ISR。
创建生产者:
bin/kafka-console-producer.sh --broker-list node01:9092,node02:9092,node03:9092 --topic test
创建消费者:
bin/kafka-console-consumer.sh --zookeeper node01:2181,node02:2181,node03:2181 --from-beginning --topic testflume
生产者,发送消息到topic
消费者,消费topic中消息
四,Flume和Kafka整合
4.1 Flume安装
流程—> https://blog.csdn.net/weixin_43270493/article/details/86546471
flume安装目录conf目录下新建 fk.conf,内容如下
a1.sources = r1
a1.sinks = k1
a1.channels = c1
<!-- 数据源描述 -->
# Describe/configure the source
a1.sources.r1.type = avro
a1.sources.r1.bind = node03
a1.sources.r1.port = 41414
<!-- sink目的地配置 -->
# Describe the sink
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.topic = testflume
a1.sinks.k1.brokerList = node01:9092,node02:9092,node03:9092
a1.sinks.k1.requiredAcks = 1
a1.sinks.k1.batchSize = 20
a1.sinks.k1.channel = c1
# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000000
a1.channels.c1.transactionCapacity = 10000
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
4.2 启动
启动Kafka集群
bin/kafka-server-start.sh config/server.properties
启动Flume
bin/flume-ng agent -n a1 -c conf -f conf/fk.conf
-Dflume.root.logger=DEBUG,console
4.3 测试
Flume中source类型为AVRO类型,此时通过Java代码发送RPC请求,测试数据是否成功传入Kafka.
代码如下
package com.shsxt.flume.demo;
import org.apache.flume.Event;
import org.apache.flume.EventDeliveryException;
import org.apache.flume.api.RpcClient;
import org.apache.flume.api.RpcClientFactory;
import org.apache.flume.event.EventBuilder;
import java.nio.charset.Charset;
import java.util.Random;
public class ClientDemo {
public static void main(String args[]){
MyRpcClientFacade client = new MyRpcClientFacade();
client.init("node03",41414);
for (int i = 0; i < 100; i++) {
int number = new Random().nextInt(3);
String sampleData;
if (number==0){
sampleData = "hello Fulme Error "+i;
}else if (number==1){
sampleData = "hello Fulme INfo "+i;
}else {
sampleData = "hello Fulme WARning "+i;
}
client.sendDataToFlume(sampleData);
System.out.println("发送数据: "+sampleData);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyRpcClientFacade{
private RpcClient client;
private String hostname;
private int port;
public void init(String hostname,int port){
this.hostname = hostname;
this.port = port;
this.client = RpcClientFactory.getDefaultInstance(hostname,port);
}
public void sendDataToFlume(String data){
Event event = EventBuilder.withBody(data, Charset.forName("utf-8"));
try {
client.append(event);
} catch (EventDeliveryException e) {
e.printStackTrace();
client.close();
client = null;
client = RpcClientFactory.getDefaultInstance(hostname,port);
}
}
public void cleanUp(){
client.close();
}
}
启动消费者:
bin/kafka-console-consumer.sh --zookeeper node01:2181,node02:2181,node03:2181 --from-beginning --topic testflume
4.4 Kafka Api
KafkaProducer.java
package com.shsxt.kafka.demo;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
import kafka.serializer.StringEncoder;
import java.util.Properties;
public class KafkaProducer extends Thread {
private String topic; //发送给Kafka的数据,topic
private Producer<Integer,String> producerForKafka;
public KafkaProducer(String topic){
this.topic = topic;
Properties prop = new Properties();
prop.put("metadata.broker.list","node01:9092,node02:9092,node03:9092");
prop.put("serializer.class", StringEncoder.class.getName());
//下面详解
prop.put("acks",1);
producerForKafka = new Producer<Integer, String>(new ProducerConfig(prop));
}
@Override
public void run() {
int count = 0;
while (true){
count ++;
String value = "wcb_"+count;
KeyedMessage<Integer, String> message = new KeyedMessage<>(topic, value);
producerForKafka.send(message);
if (0==count%2){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String args[]){
new KafkaProducer("testflume").start();
}
}
KafkaConsumer.java
package com.shsxt.kafka.demo;
import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class KafkaConsumer extends Thread{
private ConsumerConnector consumer;
private String topic;
public KafkaConsumer(String topic){
this.topic = topic;
consumer = Consumer.createJavaConsumerConnector(createConsumerConfig());
}
private static ConsumerConfig createConsumerConfig(){
Properties props = new Properties();
// props.put("zookeeper.connect","node01:2181,node02:2181,node03:2181");
// props.put("group.id","xixi");
// props.put("zookeeper.session.timeout.ms","1000");
// props.put("auto.offset.reset","smallest");
// props.put("auto.commit.enable","false"); //关闭自动提交,手动提交
props.put("zookeeper.connect", "node01:2181,node02:2181,node03:2181");
props.put("group.id", "xixi");
props.put("zookeeper.session.timeout.ms", "400");
// props.put("auto.commit.interval.ms", "50000");
//自动读取偏移量设置为最小
props.put("auto.offset.reset","smallest");
props.put("auto.commit.enable","false"); // 关闭自动提交,开启手动提交
return new ConsumerConfig(props);
}
@Override
public void run() {
Map<String,Integer> topicCountMap = new HashMap<>();
topicCountMap.put(topic,1); //描述读取哪个topic 需要几个线程读
Map<String, List<KafkaStream<byte[], byte[]>>> consumerMessageMap= consumer.
createMessageStreams(topicCountMap);
List<KafkaStream<byte[], byte[]>> list = consumerMessageMap.get(topic);//每个线程对应一个KafkaStream
KafkaStream<byte[], byte[]> stream = list.get(0);
ConsumerIterator<byte[], byte[]> iterator = stream.iterator();
System.out.println("====================================");
while (iterator.hasNext()){
String data = new String(iterator.next().message());
System.out.println("开始处理数据................");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据完毕.......");
consumer.commitOffsets(); //手动提交offset
}
}
public static void main(String args[]){
KafkaConsumer kafkaConsumer = new KafkaConsumer("testflume");
kafkaConsumer.start();
}
}
4.5 acks
acks = -1 等同于 acks=all
不合理的设置acks会造成,数据丢失问题(Producer方造成)
五,Kafka中数据丢失和重复消费
针对Producer方数据丢失问题主要是acks的取值不合理(设置为0),现在,说下consumer端造成数据丢失和重复消费的问题。
重复消费:
自动提交的时间间隔设置太大,例如自动提交偏移量的时间设置为60秒,到达50秒时,服务器故障,意外宕机,这时候未进行偏移量的提交(consumer消费是根据偏移量去读取,存储在zookeeper集群中),这时候zk集群中的偏移量还没更新,服务器启动时,还会按照之前的偏移量消费消息,造成重复消费。
数据丢失:
自动提交的时间间隔设置太小,例如自动提交偏移量的时间设置为600毫秒,网络波动等原因,可能在消费消息时实际需要1秒钟,在提交偏移量完成后,服务器意外宕机,这时候zk集群中的偏移量已经提交(已经认为该消息已经消费,偏移量增加),服务器重启后,会从新的偏移量位置消费消息,造成数据丢失。
解决方案 :
把Kafka消费者的配置enable.auto.commit设置为false,禁止kafka自动提交offset,手动提交offset。即使中途出现异常情况,也能保证消息的正确性和安全性。
props.put("auto.commit.enable","false"); //关闭自动提交,手动提交
consumer.commitOffsets(); //手动提交offset