- producer:消息生产者,发布消息到 kafka 集群的终端或服务
- broker:kafka 集群中包含的服务器。broker (经纪人,消费转发服务)
- topic:每条发布到 kafka 集群的消息属于的类别,即 kafka 是面向 topic 的
- partition:partition 是物理上的概念,每个 topic 包含一个或多个 partition。kafka 分配的单位是 partition。多个partition可以分配到不同的节点上
- consumer:从 kafka 集群中消费消息的终端或服务
- Consumer group:在较高的版本的consumer API 中,每个 consumer 都属于一个 consumer group,每条消息只能被 consumer group 中的一个 Consumer 消费,但可以被多个 consumer group 消费。即组间数据是共享的,组内数据是竞争的
- replica:partition 的副本,保障 partition 的高可用
- leader:replica 中的一个角色, producer 和 consumer 只跟 leader 交互
- follower:replica 中的一个角色,从 leader 中复制数据
- controller:kafka 集群中的其中一个服务器,用来进行 leader election 以及各种 failover
- zookeeper:kafka 通过 zookeeper 来存储集群的 meta 信息
TOP与分区
- Topic在逻辑上可以被认为是一个queue,每条消息都必须指定它的Topic,可以简单理解为必须指明把这条消息放进哪个queue里。
- 为了使得Kafka的吞吐率可以线性提高,物理上把Topic分成一个或多个Partition,每个Partition在物理(磁盘)上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件
- 因为每条消息都被append到该Partition中,属于顺序写磁盘,因此效率非常高(经验证,顺序写磁盘效率比随机写内存还要高,这是Kafka高吞吐率的一个很重要的保证)
- 对于传统的消息队列而言,一般会删除已经被消费的消息,而Kafka集群会保留所有的消息,无论其被消费与否。当然,因为磁盘限制,不可能永久保留所有数据(实际上也没必要),因此Kafka提供两种策略删除旧数据。一是基于时间,二是基于Partition文件大小。配置如下所示
# 日志删除间隔时间
log.retention.hours=168
# 日志文件大小,达到这个大小会产生一个新的日志文件
log.segment.bytes=1073741824
# 设置是否启用日志清理
log.cleaner.enable=false
Kafka消息流处理
- producer 先从 zookeeper 的 "/brokers/.../state" 节点找到该 partition 的 leader
- producer 将消息发送给该 leader
- leader 将消息写入本地 log
- followers 从 leader pull 消息,写入本地 log后,给leader 发送 ACK
- leader 收到所有 ISR中的 replica 的 ACK 后,增加 HW(high watermark,最后 commit 的 offset) 并向 producer 发送 ACK
ISR指的是:比如有三个副本 ,编号是① ② ③ ,其中② 是Leader ① ③是Follower。假设在数据同步过程中,①跟上Leader,但是③出现故障或没有及时同步,则① ②是一个ISR,而③不是ISR成员。后期在Leader选举时,会用到ISR机制。会优先从ISR中选择Leader
kafka HA
一、概述
- 同一个 partition 可能会有多个 replica(对应 server.properties 配置中的 default.replication.factor=N)
- 没有 replica 的情况下,一旦 broker 宕机,其上所有 patition 的数据都不可被消费,同时 producer 也不能再将数据存于其上的 patition
- 引入replication 之后,同一个 partition 可能会有多个 replica,而这时需要在这些 replica 之间选出一个 leader,producer 和 consumer 只与这个 leader 交互,其它 replica 作为 follower 从 leader 中复制数据
二、leader failover
- 当 partition 对应的 leader 宕机时,需要从 follower 中选举出新 leader
- 在选举新leader时,一个基本的原则是,新的 leader 必须拥有旧 leader commit 过的所有消息
- 由写入流程可知 ISR 里面的所有 replica 都跟上了 leader,只有 ISR 里面的成员才能选为 leader。对于n个 replica,一个 partition 可以在容忍 n-1个 replica 失效的情况下保证消息不丢失。例如 一个分区 有5个副本,挂了4个,剩一个副本,依然可以工作
- kafka的选举不同于zookeeper,用的不是过半选举
- 当所有 replica 都不工作时,有两种可行的方案:
- 等待 ISR 中的任一个 replica 活过来,并选它作为 leader。可保障数据不丢失,但时间可能相对较长
- 选择第一个活过来的 replica(不一定是 ISR 成员)作为 leader。无法保障数据不丢失,但相对不可用时间较短
kafka 0.8.* 使用第二种方式
- kafka 通过 Controller 来选举 leader
Kafka API使用
一、 生产者
@Test
public void producer() throws InterruptedException, ExecutionException {
// 设置属性
Properties props = new Properties();
// 设置键的类型,实际上是偏移量
props.put("key.serializer", "org.apache.kafka.common.serialization.IntegerSerializer");
// 设置值的类型,实际上是实际数据
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 设置Kafka的连接地址
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.234.11:9092");
// 添加数据
Producer<Integer, String> kafkaProducer = new KafkaProducer<Integer, String>(props);
for (int i = 0; i < 100; i++) {
ProducerRecord<Integer, String> message = new ProducerRecord<Integer, String>("enbook", "" + i);
kafkaProducer.send(message);
}
while (true) ;
}
二、创建Topic
@Test
public void create_topic() {
ZkUtils zkUtils = ZkUtils.apply("192.168.234.11:2181,192.168.234.210:2181,192.168.234.211:2181", 30000, 30000,
JaasUtils.isZkSecurityEnabled());
// 创建一个单分区单副本名为t1的topic
AdminUtils.createTopic(zkUtils, "t1", 1, 1, new Properties(), RackAwareMode.Enforced$.MODULE$);
zkUtils.close();
}
三、删除Topic
@Test
public void delete_topic() {
ZkUtils zkUtils = ZkUtils.apply("192.168.234.11:2181,192.168.234.210:2181,192.168.234.211:2181", 30000, 30000,
JaasUtils.isZkSecurityEnabled());
// 删除topic 't1'
AdminUtils.deleteTopic(zkUtils, "t1");
zkUtils.close();
}
四、消费者组
@Test
public void consumer_1() {
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.234.11:9092");
props.put("group.id", "consumer-tutorial");
props.put("key.deserializer", StringDeserializer.class.getName());
props.put("value.deserializer", StringDeserializer.class.getName());
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("enbook", "t2"));
try {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
for (ConsumerRecord<String, String> record : records)
System.out.println("c1消费:" + record.offset() + ":" + record.value());
}
} catch (Exception e) {
} finally {
consumer.close();
}
}
@Test
public void consumer_2() {
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.234.11:9092");
props.put("group.id", "consumer-tutorial");
props.put("key.deserializer", StringDeserializer.class.getName());
props.put("value.deserializer", StringDeserializer.class.getName());
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("enbook", "t2"));
try {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
for (ConsumerRecord<String, String> record : records)
System.out.println("c2消费:" + record.offset() + ":" + record.value());
}
} catch (Exception e) {
} finally {
consumer.close();
}
}
Kafka offset机制
- Consumer在从broker读取消息后,可以选择commit,该操作会在Kakfa中保存该Consumer在该Partition中读取的消息的offset
- 该Consumer下一次再读该Partition时会从下一条开始读取
- 通过这一特性可以保证同一消费者从Kafka中不会重复消费数据
- 执行过程:
- 在创建消费者的时候产生消费者组:执行:sh kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic enbook --from-beginning --new-consumer
- 获取消费者组的名字,执行:sh kafka-consumer-groups.sh --bootstrap-server hadoop01:9092 --list --new-consumer
- 进入kafka-logs目录查看,会发现多个很多目录,这是因为kafka默认会生成50个__consumer_offsets 的目录,用于存储消费者消费的offset位置
- Kafka在计算offset存储在哪一个目录中时,用:Math.abs(groupID.hashCode()) % 50计算S
Kafka的索引机制
- Kafka解决查询效率的手段之一是将数据文件分段,可以配置每个数据文件的最大值,每段放在一个单独的数据文件里面,数据文件以该段中最小的offset命名
- 每个log文件默认是1GB生成一个新的Log文件,比如新的log文件中第一条的消息的offset 16933,则此log文件的命名为:000000000000000016933.log,此外,每生成一个log文件,就会生成一个对应的索引(index)文件。(一个log+一个index文件合称为一个segment)这样在查找指定offset的Message的时候,用二分查找就可以定位到该Message在哪个段中
- 数据文件分段使得可以在一个较小的数据文件中查找对应offset的Message了,但是这依然需要顺序扫描才能找到对应offset的Message。为了进一步提高查找的效率,Kafka为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为.index。索引文件中包含若干个索引条目,每个条目表示数据文件中一条Message的索引——Offset与position(Message在数据文件中的绝对位置)的对应关系
- index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了(稀疏索引+二分查找,可以加快查找速度)
- 索引文件被映射到内存中,所以查找的速度还是很快的
简介
一、概述
- Kafka是发布订阅模式的消息队列
- Kafka是由LinkedIn(领英)公司开发后来贡献给了Apache的消息队列
- Kafka的特征:
- 发布和订阅消息流
- 在存储消息流的时候要提供容错机制
- 当数据流出现的时候能够及时处理
- Kafka的应用场景:
- 能够在系统或者应用之间构建可靠的数据传输的实时流管道
- 能够构建一个转化或者应对数据流的实时流应用
- Kafka是利用了Scala语言构建的,Scala天然支持并发和吞吐,保证Kafka的并发量和吞吐量是比较高的,实际过程中,Kafka的吞吐量是在60~80M/s - Kafka底层采用了零拷贝的技术
- Kafka在收到数据之后会把数据写入本地磁盘上保证数据不丢失。默认情况下,Kafka不会清理写入的数据
- Kafka中不存在单点故障
- Kafka集群中可以随时动态增删节点
- Kafka中存在副本策略
二、基本概念
- broker:
- 就表示Kafka中的节点
- 每一个broker都需要给一个编号,这个编号只要不相同的就可以
- topic:
- 作用是用于对数据进行分类的
- 在Kafka中,每一条数据都要发送到一个指定主题中
- 每一个主题对应一个到多个partition
- 当删除主题的时候,这个主题所对应的目录并不会被立即删除,而是被标记为删除状态,等待一分钟左右会将标记的目录删除
- 如果需要删除操作立即生效,需要将delete.topic.enable设置为true
- partition:
- 每一个partition对应一个目录
- 如果有多个Kafka节点,分区会平均分到每一个节点上,这样设计的目的是为了提高Kafka的吞吐量
- 如果出现了多个分区,则数据在向分区中写入的时候是轮训写入
- replicas:
- Kafka中,为了保证数据的可用,可以去设置多个副本
- 如果设置了多个副本,则副本是以分区为单位进行备份
- leader和follower
- 在Kafka中,如果设置了多个副本,则副本之间会自动通过Controller进行选举,选举出一个leader副本以及其他的follower副本
- 注意:leader和follower是指的副本之间的主从关系而不是Kafka节点之间的主从关系
- Producer和Consumer只和leader副本进行交互,不会和follower副本进行交互
- Controller:
- 用于进行leader副本和follower副本的选举
- Controller会在某一个Kafka节点上
- 如果Controller宕机,那么Zookeeper会在其他的Kafka节点上再来启动一个Controller进程
- Consumer Group:
- 默认情况下,每一个消费者对应一个消费者组
- 一个消费者组中可以包含1个到多个消费者
- 同一条消息可以被不同的消费者组订阅,但是同一个组内的消费者只能有一个去消费这个消息 - 组间共享,组内竞争
三、指令
指令 | 解释 |
sh kafka-server-start.sh ../config/server.properties | 开启Kafka |
sh kafka-topics.sh --create --zookeeper hadoop01:2181 --replication-factor 1 --partitions 1 --topic video | 创建主题 |
sh kafka-topics.sh --delete --zookeeper hadoop01:2181 --topic novel | 删除主题 |
sh kafka-topics.sh --list --zookeeper hadoop01:2181 | 查看所有的主题 |
sh kafka-topics.sh --describe --zookeeper hadoop01:2181 --topic txt | 描述主题 |
sh kafka-console-consumer.sh --zookeeper hadoop01:2181 --topic video | 启动消费者 |
sh kafka-console-producer.sh --broker-list hadoop01:9092 --topic video | 启动生产者 |
四、数据同步
- 生产者将数据写到leader副本上
- follower副本会给leader发送消息询问是否有需要更新的数据
- leader会把需要更新的数据发送给follower,并且等待follower的反馈
- 如果follower记录成功,则返回一个ack信号
- leader收到ack之后,会把follower副本所在的brokerid放入ISR队列中
- ISR是维系在Zookeeper上的,一旦leader副本lost,则Controller会优先从ISR中选择一个副本成为leader
五、语义
扩展视野
- Kafka和MQ的区别
- 了解Kafka sink
- 了解零拷贝