是一个分布式的流处理平台(以前是主打发布/订阅模式的消息队列)。
主要有一下三个功能:
1、发布和订阅消息流,这一点和消息队列或者企业消息系统类似
2、以容错的方式存储消息流
3、在消息流产生时处理它们
应用场景:
1.消息系统:解耦生产者和消费者,缓存消息
2.流式处理:1、构建实时的流管道,可靠的在系统和应用之间获取数据 2、构建实时流的应用程序,对数据流进行转换或响应
1.持久性,可靠性:基于磁盘的数据存储且支持数据备份
2.扩展性:kafka支持横向扩展
3.高吞吐量、低延迟:kafka每秒可以处理几十万条数据,最低延迟几毫秒,每个topic可以分多个partition,由多个consumer group 对partition进行consume操作。
4.容错性:允许集群中节点错误,n个副本允许n-1个节点失败
5.高并发:支持上千个客户端同时读写。
1.数据是批量发送,并非实时
2.对mqtt协议不支持
3.只支持统一分区内消息有序,无法实现全局有序
4.依赖zookeeper 进行元数据管理
broker : 一台服务器就是一个broker,一个集群由多个多个broker组成,一个broker可以容纳多个partition
topic:可以理解为队列,每条发布到kafka集群的消息都有一个类别,这个类别成为topic。(物理上:不同的topic分开存储,逻辑上:一个topic上的消息保存在一个或者多个broker)
parition: 为了实现可扩展性,一个topic可以分布存储到多个broker中,一个topic可以分为多个partition ,每个partition都是有序的队列,kafka只能partition 内有序,不能保证全局有序。
producer:消息生产者,发布消息到kafka
consumer:消息消费者,向kafka broker集群 读取消息的客户端。
consumer group : 消费组,一组消费组由多个不同consumer组成,消费组不同的consumer负责消费一个topic不同partition 数据,一个partition只能由一个消费组内一个consumer消费。消费组是逻辑上一个消息订阅者。
replica : 副本,为了保证集群的高可用。当集群某节点发生故障时,为了保证数据partition不丢失,kafka提供了副本机制,一个topic的不同partition都有若干个副本,分为一个leader和多个follower。
leader: replica中的一个角色,producer和consumer只跟leader交互。一个partition一个leader
follower:负责从leader复制数据
controller : kafka集群中的一个服务器,中来选择leader 和faillover
AR(Assigned Replicas): partition中所有的f副本。
ISR(In-Sync-Replicas): 所有与leader同步程度保持一致的副本
OSR(Out-Sync-Replicas):所有与leader停滞后过多的副本
HW(Hight Watermark): 高水位,标识一个特定的消息偏移量,消费者只能拉到这个offsets 之前的数据
LEO(Log End Offset):标识当前文件最大的ofset,写一条待写入消息的offset
一个group 有多个consumer,这样可以提高consumer消费消息的并发消费能力,同时增强容错性。如果group中的某个consumer失效了,消费的partition就会由其他的consumer自动接管。
一条消息只能被一个不同group 中的其中一个consumer消费,但是一个consumer 能同时消费多个partition的数据。
kafka是基于topic分类消息的。
topic是逻辑上的概念,partition是物理上的概念,每个partition都会对应一个log文件,该log文件存储的就是producer生产的数据,producer生产的数据会不断累积加入log末端。
但是为了防止log文件过大,遍历消耗的时间过长,效率低下,kafka采取了分片和索引机制,将partition分为多个segment(段)(分片机制),每一个segment以当前segment存储第一条消息的offset为命名。
索引机制即每个segment会对应有一个"xx.log"和"xx.index"文件,log文件存储大量的数据,index文件则存放大量的索引信息。
例如index其中一条索引文件是 3,497 ,则指的是在同segment的log文件上第三条log消息,该消息的物理偏移量是497
例如图:
原因:一个topic由多个partition组成,每个partition可以调整所在的服务器,方便集群横向扩展。(分布式))。2、可以提高并发读写数据,以partition为单位读写数据。
分区原则:分区可以有三种原则将数据分区为producerRecord对象。
1.分区的时候,用直接指明的值作为partition分区名。
2.没有指明partition分区名的时候,但是指明了key,将key的hash值和topic的partition进行取余得到partition值。
3.partition值和key都没有的情况下,kafka会采取sticky partition(黏性分区),随机选择一个分区,并且一直使用该分区,直至该分区的batch已经满了或者该topic已经完成了。
consumer 采用pull(拉)模式从broker拉取数据。
不使用push,推是因为推模式的发送数据 的 速率是由broker决定的,这样当消费者消费速率不一致的时候容易产生拒绝服务和网络拥塞。
但是pull也有缺点,如果kafka没有数据,则消费者可能会一直陷入循环中,返回空值。所以kafka消费数据时候会传入一个时长参数timeout。如果没有数据可供消费的时候,consumer会等待一段时间后再返回,这段时间为timeout。
默认策略,对于每个topic,设consumer总数为c,partition总数为p,则rangeAssignon会用p/c的结果得出区间值r,以及余数值p%c为m。
然后consumer会根据预设好的member id字典排序,从第一个consumer进行顺序分配,前m个consumer分配连续的(r+1)个partition,后(r-m)个consumer分配连续的r个partition。
即如果不能平分,前面的consumer会被分配多一个partition。
这种策略是尽量平分分配partition的。
roundRobin是轮询的意思,roundRobinAssignor策略还是先将consumer进行字典排序,然后同时也将partition按字典排序,然后进行轮询分配。
略
为了防止consumer在消费数据的时候出现断电宕机等意外,方便consumer继续从故障前的位置继续消费数据,所以consumer需要实时记录自己消费到了哪个offset。
kafka0.9前consumer默认将消费offset保存在zookeeper,后面版本后offset数据会保存在kafka内置的topic,_consumer_offsets。
每个partition都有一个replica机制进行数据冗余,每一个分区都有leader和follower,所有的读写操作都由leader负责,follower 定期进行复制数据。当leader挂了,follower会进行选举成新的leader。
副本同步策略有两种
1、半数follower完成同步,就发送ACK确认同步, 这样的优点是延迟低,缺点是选举新的leader的时候,容忍n台节点故障,需要2n+1个副本。
2、全部follower完成同步,就发送ACK确认同步,这样优点是选举新leader时,容忍n台节点故障,需要n+1个副本, 缺点是延迟高。
kafka默认是选择第二种,原因:
1、目的都是为了容忍n台节点故障,但是方案一需要2n+1个副本,方案二只要n+1个副本,kafka每个分区都有很大的数据量,方案一造成冗余过多。
2、方案二缺点是网络延迟高,但是网络延迟对kafka影响比较小。
producer发送数据给follower的时候,kakfa在producer制定了消息确认机制。可以通过配置来选择发送到分区partition的几个副本中才算成功。
1.ACK=0 最低延迟,partition的leader收到数据还没写入磁盘就发送ack,此时leader发送故障时候,会丢失数据
2.ACK=1 partitiond的全部leader罗盘成功后,才返回ACL,确认数据已发送成功。如果follower同步成功之前发送leader故障,数据可能会丢失。
3.ACK=-1(all):需求等到partition的leader和follower全部落盘后,leader才返回ack说消息发送成功。 当leader发送ack之前leader故障,数据可能回重复。
副本同步策略选择方案二后,leader 会维护一个动态的ISR(in-sync-replica set),意思是跟leader保持一致的集合。
ISR是为了防止全部follower同步数据时,防止有一个follower遭遇故障未及时跟leader同步数据,长时间不发生ACK给leader。
当部署了ISR后,如果follower长时间不同步数据,该follower会被提出ISR集合,leader发送故障时,新leader只会从ISR的节点中选举。
LEO:每个副本的最大的offset
HW高水位线:每个副本消费者consumer能读取到的最大的offer,也是ISR的最小的LEO。
这两个作用:
1:当follower发生故障时:follower发送故障,会被提出ISR,当follower恢复后,会读取记录的上次consumer读取的HW,然后将log文件里面高于HW的的数据截掉,然后向leader同步,当follower数据追上leader后,就能重新加入ISR.
2:当leader发生故障,会从ISR重新选出Leader,然后为了保持副本的一致性,其他follower会将各自log高于HW的数据截掉。然后从新的leader同步数据
上面第2条只能保持数据一致性,不能保整数据可靠性。
最多一次,将服务器的ack设置为0,每条生产者数据只会被发送一次,数据传输不会重复数据,但是可能会丢失
至少一次,服务器的ack设置为-1,可以保证数据传输不会丢失数据,但是可能存在重复数据。
0.11版本后,kafka引入幂等性,即无论paoducer发送多少条重复数据,server段只会持久化一条数据。所以是at least on + 幂等性 = exactly one
参数:enable.idempotene 设置为true
kafka 的producer生产数据,要写入log文件中,写入的时候是直接追加文件末端,顺序写入,顺序写入可以省去大量的磁头寻址时间。
linux系统依赖底层的senfile()方法实现了内核下数据的零拷贝,将数据直接从磁盘文件复制到网卡设备中,减少内核和用户模式下的上下文切换,大大提升了应用程序的性能。
kakfa发送数据不是实时的每条每条的发送,为了节约网络带来的性能开销,kafka会对消息进行批量发送。
通过batch.size调整批量提交的大小,默认16k。当同一分区积压的消息量达到这个值后就会统一发送。
producer段会压缩,broker保持,consumer进行解压。
index 文件中没有为数据文件中每条message建立索引,而是采取了稀疏存储的方式,每隔一定字节的数据建立一条索引,这也为了避免索引占用过多空间,从而可以将索引文件保留再内存中。
kafka集群会有一个broker被选举为controller,负责集群的broker上下线工作,以及所有topic的分区副本分配和leader选举工作。
以上的这些工作包括负载均衡,都需要zookeeper进行维护管理。
为了方便管理跨会话的事务,producer引入了新组件Transaction Coordinator ,producer与该组件交互可以获得唯一的Transaction ID,即producer pid 与Transaction id 进行绑定,同时Transaction Coordinator负责将事务写入kafka的一个内部topic,因为事务状态得到保存,所以即使是进行中的事务状态,当服务重启时也能重新恢复。
consumer事务无法像producer那么强大。consumer要想实现consumer端的精准一次性消费,就要保证实现消费过程和提交offset过程做原子绑定,即要保证消费不丢失数据(漏消费)和不重复消费数据,核心是解决偏移量offset的提交和消费后数组的输出(存数据)。
通常有两种策略:
把存数据操作和提交偏移量操作放到一个事务中,如果存失败了,就回滚,这样就达到了原子性。先保存还是先修改偏移量都没关系。
好处:事务能保证精准一次性消费
缺点:1、数据都要保存在关系型数据库中,无法使用nosql 2、事务本身性能不好 3、当数据量大的时候,还需要多节点(多数在小数据量上使用)
有两步操作:1、消费完数据后,首先手动提交offset,保证at least one消费数据。解决了漏消费问题 2、把数据的保存做成幂等性处理。例如一批数据重复被消费多次,然后第一次保存和第n次保存结果都说一样的(覆盖),用于保存数据的数据库能做到这个操作就达到了幂等性处理。
场景:使用在数据比较多或者数据存储实在不支持事务的数据库上。、
1、生产者发送消息是采取了异步发送的方式。涉及到两个进程-main进程和sender进程以及一个共享变量recordAccumulator。首先main进程生产producer对象,调用producer对象的send()方法发送消息。
2、send()后,消息会经过interceptors拦截器(过滤逻辑)、seriallzer序列化器(对消息进行key和value的序列化),和partitioner分区器(选择分区)后,进入分区其选择对应的分区。
3、消息紧接着进入到RecordAccumulator的缓冲队列中batch的末尾,当数据积累到batch.size(默认32M)之后或者等待时间达到linger.ms后(默认0毫秒,来一条发送一条),sender线程才会发送数据。
4、Sender线程通过sender()读取RecordAccumulator消息,同时创建发送请求。kafka集群每个broker都会有一个InFightRequest请求队列在NetWorkClient中,默认每个请求队列有5个请求。然后这些请求通过Selector发送到集群。
5、kafka集群收到请求后,返回相对应ack。集群根据不同情况选择ack信息:
0:生产者发送过来的数据,这一操作提供了一个最低的延迟,partition的leader接收到消息还没有写入磁盘就已经返回ack,当leader故障时有可能丢失数据。
1: partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据
-1:partition的leader和follower全部落盘成功后才返回ack。但是如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复
用到三个类:
KafkaProducer :创建生产者对象
ProducerConfig:获取一系列配置参数
ProducerRecord:每个消息封装成ProducerRecord对象
public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties props = new Properties();
//kafka集群,broker-list
props.put("bootstrap.servers", "hadoop102:9092");
props.put("acks", "all");
//重试次数
props.put("retries", 1);
//批次大小
props.put("batch.size", 16384);
//等待时间
props.put("linger.ms", 1);
//RecordAccumulator缓冲区大小
props.put("buffer.memory", 33554432);
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i)));
}
producer.close();
}
}
producer收到ack时会调用一个回调函数,该方法参数有两个,recordMeteData和Exception,如果Exception为null则成功发送,否则发送失败(发送失败会自动重试)。可以通过overwrite实现。
public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties props = new Properties();
props.put("bootstrap.servers", "hadoop102:9092");//kafka集群,broker-list
props.put("acks", "all");
props.put("retries", 1);//重试次数
props.put("batch.size", 16384);//批次大小
props.put("linger.ms", 1);//等待时间
props.put("buffer.memory", 33554432);//RecordAccumulator缓冲区大小
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i)), new Callback() {
//回调函数,该方法会在Producer收到ack时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null) {
System.out.println("success->" + metadata.offset());
} else {
exception.printStackTrace();
}
}
});
}
producer.close();
}
}
Consumer消费数据时的可靠性是很容易保证的,因为数据在Kafka中是持久化的,故不用担心数据丢失问题。
由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
所以offset的维护是Consumer消费数据是必须考虑的问题。
KafkaConsumer:需要创建一个消费者对象,用来消费数据
ConsumerConfig:获取所需的一系列配置参数
ConsuemrRecord:每条数据都要封装成一个ConsumerRecord对象
enable.auto.commit:是否开启自动提交offset功能
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "hadoop102:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "1000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("first"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records)
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}
}
public class CustomConsumer {
public static void main(String[] args) {
Properties props = new Properties();
//Kafka集群
props.put("bootstrap.servers", "hadoop102:9092");
//消费者组,只要group.id相同,就属于同一个消费者组
props.put("group.id", "test");
//关闭自动提交offset
props.put("enable.auto.commit", "false");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("first"));//消费者订阅主题
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);//消费者拉取数据
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
//异步提交
consumer.commitAsync(new OffsetCommitCallback() {
@Override
public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {
if (exception != null) {
System.err.println("Commit failed for" + offsets);
}
}
});
}
}
}
public class CustomComsumer {
public static void main(String[] args) {
Properties props = new Properties();
//Kafka集群
props.put("bootstrap.servers", "hadoop102:9092");
//消费者组,只要group.id相同,就属于同一个消费者组
props.put("group.id", "test");
props.put("enable.auto.commit", "false");//关闭自动提交offset
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("first"));//消费者订阅主题
while (true) {
//消费者拉取数据
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
//同步提交,当前线程会阻塞直到offset提交成功
consumer.commitSync();
}
}
}