kafka

kafka

开始

简介

官方网站:https://kafka.apache.org/

官方网站中有软件下载地址和更详细的文档,值得参考!

是由领英(LinkedIn)设计实现,利用Scala语言编写,后贡献给了Apache,kafka是是一种分布式的消息队列,能构建实时流计算平台;可用于离线批处理(MR、Hive、Apark)和实时计算(Storm、SparkStreaming、Flink),一般用于flume和storm之间,另外kafka还实现了活动流和运营数据流;

  1. 活动流:客户访问量产生的数据,;
  2. 运营数据流:本地服务器产生的数据;

其只要功能包括:

  1. 发布订阅数据流,类似于消息队列(具有消息队列的相关功能);
  2. 屏蔽消费者和生产者之间的异构性,实现解耦;
  3. 是分布式架构,可吞吐海量数据,数据不丢,但是没有可靠事物;
  4. kafka的消费者采用的poll方式获取数据,故消费速率由消费者控制;
  5. kafka提供了数据的持久化机制和副本冗余机制,无论是否消费,数据都会存储,并且容错性高;

kafka为什么快

kafka的消息是保存或缓存在磁盘上的,一般认为磁盘上的读写性能都不会太高,但实际上kafka的特性之一就是高吞吐率,即使是普通的服务器,也可以轻松支持每秒百万级的写入,超过大部分其他MQ;

写入

kafka会把收到的消息写入到硬盘中,保证了数据不会丢失,同时为了优化写入速度采用了两种技术:顺序写入和MMFile;

顺序写入:kafka中,每一个分区对应一个文件,收到消息后会把数据插入到文件末尾;(该方式的缺陷就是没发删除数据,所以kafka不会直接删除数据,而是使用了两种策略来删除数据)

MMF(Memory Mapped Files):即kafka数据并不是实时写入硬盘的,而是使用了内存映射文件,其原理是利用操作系统的Page实现文件到物理内存的直接映射,实现对物理内存的操作会在适当的时候同步到硬盘上,这种方式省去了用户空间到内核空间复制的开销,极大的提升了I/O性能;但同时也存在不可靠的缺陷,因为写在MMF中的数据并没有立即写入硬盘,需要程序主动flush(可以通过参数producer.type设置);

读取

kafka使用了零拷贝技术,直接将上面的MMF作为文件句柄,将文件内容发送给消费者;利用偏移量(offset)记录从哪里开始读取。同时还使用了批量压缩的发送消息以节省带宽。

安装

  1. 解压
  2. 修改或添加如下配置server.properties
# 给每一个Kafka节点配置编号,集群中每个节点编号应该不同
broker.id=0
# 消息的存储目录
log.dirs=/data/kafka_2.11-1.0.0/kafka-logs
# Zookeeper的连接地址
zookeeper.connect=hadoop01:2181,hadoop02:2181,hadoop03:2181
# 允许删除主题,默认false
delete.topic.enable=true
# 端口,默认9092
# port=9092
# 下面是些常用的配置
# 默认分区数(创建topic没指定时才生效),数量大小决定了消费的并行度
num.partitions=3
# 分区日志文件的最大大小
log.segment.bytes=1073741824
# 分区日志文件达到此事件也使用新文件
log.retention.hours=168
# 生产者的确认机制,0-不需要确认,直接写入;1-至少要leader写入成功;all-保证所有备份写入成功
acks=all
# 生产端的批处理大小
batch.size=16384
# 生产端默认就有延时,这个是配置kafka的延时,这样可以聚合更多的消息,默认0,无延时
linger.ms=0

启动:./bin/kafka-server-start.sh ./config/server.properties

kafka集群中没有选举问题,没有leader;

常见命令

操作命令:

./bin/kafka-topics.sh --list --bootstrap-server hadoop01:9092

./bin/kafka-topics.sh --delete --bootstrap-server hadoop01:9092 --topic xxx

  1. 创建主题:./bin/kafka-topics.sh --create --zookeeper hadoop01:2181 --replication-factor 1 --partitions 1 --topic xxx
  2. 查看所有主题:./bin/kafka-topics.sh --list --zookeeper hadoop01:2181
  3. 启动生产者:./bin/kafka-console-producer.sh --broker-list hadoop01:9092 --topic xxx
  4. 启动消费者(用于测试,不会使用消费者组):./bin/kafka-console-consumer.sh --zookeeper hadoop01:2181 --topic xxx --from-begining
  5. 删除主题:./bin/kafka-topics.sh --delete --zookeeper hadoop01:2181 --topic xxx
  6. 创建分组:./bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic xxx --from-beginning --new-consumer
  7. 获取分组列表名称:./bin/kafka-consumer-groups.sh --bootstrap-server hadoop01:9092 --list --new-consumer
  8. 在一个分组内创建消费者:./bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic xxx --from-beginning --consumer.config config/consumer.properties

核心概念

topic

主题是生产者和消费者传递消息的基础,具有以下特点:

  1. 主题(topic):每个生产者消费者都要对应一个主题,每个topic在磁盘中都对应一个目录;
  2. topic逻辑上可认为是一个queue,每条消息都需要指定topic;
  3. topic会分为一个或者多个partition

partition

一个主题可以指定多个分区,分区在物理上就是一个文件目录,底层是队列,满足FIFO,具有以下特点:

  1. 分区(partitions):分区数等于目录数,且会分布在不同的节点;
  2. 生产者发送的消息会轮询的append到partition中,属于顺序写磁盘,效率高,相当于随机写内存;
  3. 消费后的数据不会立即删除,而遵循消息删除策略
  4. 按时间间隔删除:log.retention.hours=168(1周);
  5. 按日志大小清理:log.segment.bytes=1073741824(1GB);
  6. 是否开启清理:log.cleaner.enable=false;

replication

副本是为了保证数据的容错性,可以在创建主题时指定副本数量,但不能操作broker数量,副本具有以下特点:

  1. 副本因子(replication-factor):创建主题时指定,决定副本数量(不能超过节点数量),以分区作为单元备份,
  2. kafka中删除不会立即进行;
  3. 副本之间会选举一个leader,leader和follower之间会同步备份,生产者和消费者只和leader交互;
  4. 副本的选举是通过controller完成的,controller只有某一台kafka会运行;

producer

生产者用于向kafka发送数据,生产者可以是flume、web等源;

消费者(组)

用于从kafka消费数据,一般消费者可以有storm、spark等计算框架,还可以为一些消费者指定消费者组,消费者组,即一个或者多个consumer放在同一个组中,消费者组具有以下特点:

  1. 具有组间共享、组内竞争的特点;
  2. 组间共享:每个组都可以获取相同的数据;
  3. 组内竞争:同一个组内的成员会竞争同一个数据,最终只有一个能获取;
  4. 一个分区对应组内一个消费者,一个消费者可以对应多个分区(即当组内消费者数大于分区数时,多余的消费者会空闲,当分区数大于组内消费者数时一个消费者可能会对应多个分区);
  5. 在一个分组内创建消费者:./bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic xxx --from-beginning --consumer.config config/consumer.properties
  6. 消费者组对应的offset文件为:Math.abs(groupID.hashCode()) % 50

三种语义

kafka生产者产生数据的流程如下:

  1. 生产者产生数据后,会访问zookeeper,找到副本leader的brokerid;
  2. 生产者根据brokerid发送数据给leader;
  3. leader将操作记录写到log中;
  4. follower通过RPC访问leader,保证数据一致性,完成后follower向leader返回ack;
  5. leader收到所有ISR的ack后,提交offset,并向生产者返回ack;

ISR机制:若同步数据过程中,返回了ack的副本节点会放到ISR队列,这个队列是用于选举副本leader的;

kafka的三种语义:

  1. 至少一次:数据一定不会丢,但可能有重复数据(0.11版本之前的默认值);
  2. 至多一次:数据一定不会重复,但可能会丢失;
  3. 精确一次:数据一定不会丢且不会重复,方法是在每条数据中添加全局递增id,收到数据后会和已有的最新的id比较,若id小于等于最新id则舍弃该数据,且无论如何都返回ack(设置enable.idempotence=true即可开启);

同样,消费者端也有类似的三种语义;

产生原因:

  • 重复消费:当数据已经被处理,然后自动提交offset时,消费者出现故障或者有新消费者加入组导致再均衡,这时候offset提交失败,使得这一批已经处理的数据信息没有记录,导致后续会重复消费一次;
  • 丢失数据:如果业务处理时间较长,这时业务还未处理完成,offset已经提交了,但是在后续的消息处理中发生错误,导致数据未正常消费,使得后续消费者将不在消费这批数据,导致这批数据丢失;

至少一次(重复消费)实现:关闭自动提交(enable.auto.commit=false),在业务处理完成后手动提交offset;

至多一次(丢失数据)实现:关闭自动提交,在处理业务前手动提交offset;

精确一次实现:

  • 数据有状态或数据容器具备幂等性:可以根据数据信息判断是否重复消费,这时可以设置为至少一次,在消费逻辑中判断数据重复,以达到幂等效果;
  • 数据无状态:利用关系型数据库存储offset,主要思路为:利用seek方法指定offset消费,在启动消费者时,去数据库中查询offset信息,如果第一次启动则从0开始,利用关系型数据库的事务特性,将业务处理与offset记录操作放在同一个事务中进行;

offset与消费机制

Consumer从broker读取消息后,可以选择commit,该操作会保存在partition的offset中,下次再读会从下一条开始读取,从而保证不被重复消费,offset的主要特点有:

  1. 查看offset:到zookeeper的kafka相关目录可以找到(/consumers/g1/offsets/t1/0);
  2. 在sparkStreaming中加入的消费者组offset默认最大(即不消费加入以前的数据),通过kafka命令方式加入的消费者组offset默认为0;

kafka消息的默认保留策略是:要么保留一定时间要么保留消息达到一定大小,任意满足一个条件,该消息就会被删除;

创建消费者组相关命令有:

  1. 创建分组:./bin/kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic xxx --from-beginning --new-consumer
  2. 获取分组列表名称:./bin/kafka-consumer-groups.sh --bootstrap-server hadoop01:9092 --list --new-consumer

索引机制

  1. kafka每次接收数据都会持久化到本地磁盘上,在partition下的log文件中;
  2. log文件大小默认1G,数据超过log文件大小后会产生新的log文件;
  3. log文件会以偏移量命名;
  4. 每个log文件在同一目录下都有对应的index文件,也就是log的索引文件;
  5. 索引文件是一个稀疏索引,即从log随机选一些偏移量记录索引,类似于跳表;
  6. 查询时会先在索引中查找到数据文件的范围,然后在log中的对应范围进行二分查找;
  7. 索引文件会被映射到内存中;

kafka HA

  1. 同一个partition可能会有多个replica,这些replica之间会选出一个leader与producer和consumer交互;
  2. 当partition对应的leader宕机后会从follower中的ISR队列中选举出新的leader;
  3. 一般只有ISR中的成员才能成为leader,但是当所有replica都不工作时,则会选举第一个复活的replica作为leader;
  4. kafka通过Controller选举leader;

Java连接kafka

依赖

方式一:

  • 添加依赖:
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>2.4.1</version>
</dependency>

方式二:

  • 将kafka安装目录的libs目录下的所有jar包引入即可;

创建topic

@Test
public void create_topic() {
    Properties props = new Properties();
    // 多个用逗号隔开,下面的示例代码也是一样
    props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.48.101:9092");

    AdminClient client = KafkaAdminClient.create(props);
    client.createTopics(Arrays.asList(new NewTopic("test01", 1, (short) 1),
                                      new NewTopic("test02", 1, (short) 1)));
    client.close();
}

删除topic

@Test
public void delete_topic() {
    Properties props = new Properties();
    props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.48.101:9092");
    AdminClient client = KafkaAdminClient.create(props);
    client.deleteTopics(Arrays.asList("test01", "test02"));
    client.close();
}

生产者

@Test
public void producer() {
    Properties props = new Properties();
    props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.IntegerSerializer");
    props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
    props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.48.101:9092");
    // 添加数据
    Producer<String, String> producer = new KafkaProducer<>(props);
    for (int i = 0; i < 50; i++) {
        ProducerRecord<String, String> message = new ProducerRecord<>("test01", "hello-" + i);
        producer.send(message);
    }
    producer.close();
}

消费者(组)

@Test
public void consumer(){
    Properties props = new Properties();
    props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.48.101:9092");
    props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "test");
    props.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
    props.setProperty(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
    props.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
    props.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
    KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
    String topic = "test01";
    // 下面这段消费者利用了seek方法实现了从头开始消费
    List<PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
    List<TopicPartition> topicPartitions = new ArrayList<>(partitionInfos.size());
    for (PartitionInfo partitionInfo : partitionInfos) {
        topicPartitions.add(new TopicPartition(topic, partitionInfo.partition()));
    }
    consumer.assign(topicPartitions);
    Map<TopicPartition, Long> offsets = consumer.beginningOffsets(topicPartitions);
    for (TopicPartition partition : topicPartitions) {
        Long offset = offsets.get(partition);
        System.out.println(partition + "===>" + offset);
        consumer.seek(partition, offset);
    }
    // 这行就是普通的消费者,不能和上面的一起用
    // consumer.subscribe(Arrays.asList(topic));
    while (true) {
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
        for (ConsumerRecord<String, String> record : records)
            System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
    }
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值