Kafka学习--进阶05

参考:深入理解Kafka核心设计和实践原理

9、 Kafka的多线程实现:

KafkaProducer是线程安全的,但是KafkaConsumer不是线程安全的。
多线程消费实例:

package com.paojiaojiang.consumer;
import kafka.consumer.ConsumerConfig;
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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
- @Author: jja
- @Description:
- @Date: 2019/3/19 23:52
  */
  public class MultiThreadConsumer implements Runnable {

    @Override
    public void run() {
        Map<String, Integer> topicCountMap = new HashMap<>();
        topicCountMap.put(TOPIC, new Integer(NUM_THREAD));
        ConsumerConfig consumerConfig = new ConsumerConfig(props);
        ConsumerConnector consumer = kafka.consumer.Consumer.createJavaConsumerConnector(consumerConfig);
        Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
        List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(TOPIC);
        ExecutorService executorService = Executors.newFixedThreadPool(NUM_THREAD);
        for (final KafkaStream stream : streams) {
            executorService.submit(new KafkaConsumerThread(stream));
        }
    }

    public static void main(String[] args) {
        System.out.println(TOPIC);
        Thread t = new Thread(new MultiThreadConsumer());
        t.start();
    }
}

多线程消费者的实现:

package com.paojiaojiang.consumer;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.message.MessageAndMetadata;

/**
- @Author: jja
- @Description:
- @Date: 2019/3/19 23:54
  */
  public class KafkaConsumerThread implements Runnable {
  private KafkaStream<byte[], byte[]> stream;
  public KafkaConsumerThread(KafkaStream<byte[], byte[]> stream) {
      this.stream = stream; 
  }
  @Override
  public void run() {
      ConsumerIterator<byte[], byte[]> it = stream.iterator();
      while (it.hasNext()) {
          MessageAndMetadata<byte[], byte[]> mam = it.next();
          System.out.println(Thread.currentThread().getName()+ ": partition[" + mam.partition() + "],"
                  + "offset[" + mam.offset() + "], " + new String(mam.message()));
      }
      
  }
}

消费者参数:
fetch.min.bytes: 用来配置Consumer在以此拉起请求(poll()方法)中能从Kafka中拉取的最小的数据量,默认为1B,如果拉取的值小宇宙这个值得话,需要进行等待,知道数据满足这个参数的配置的大小。

fetch.max.bytes : 与上面相对应,表示一次从Kafka中拉取的数据的最大量,默认为50MB。该参数设定的不是绝对的最大值,也就是说在第一个非空分区中拉取第一条数据的值大于该参数设定的值得话,该参数仍然是可以返回的,确保消费者能继续工作。

max.partition.fetch.bytes: 用来配置从每个分区返回给Consumer的最大数据量,默认为1MB。

max.poll.records: 表示Consumer在那一次请求中拉取的最大消息的条数,默认为500条。

receive.buffer.bytes: 表示Socket接收消息缓冲区的大小,默认值为64KB。

send.buffer.bytes: 设置Socket发送消息缓冲区的大小,默认值为128KB。

isolation.level: 配置消费者的事务隔离级别,字符串类型,有效值分别为:“read_nucommitted"和"read_committed”。表示消费者所消费到的位置。如果设置为"read_commmitted",那么消费者就会忽略事务未提交的消息,即只能消费到LSO(LastStableOffset)的位置,默认值为"read_uncommitted",即可以消费到HW的位置。‘’

topic 和 partition
只齐了一个broker节点是创建topic时发生的错误:

[root@spark kafka]# bin/kafka-topics.sh --zookeeper spark:2181/kafka --create --topic new-topic --partitions 4 --replication-factor 2

Error while executing topic command : replication factor: 2 larger than available brokers: 0

[2019-03-22 15:50:54,580] ERROR org.apache.kafka.common.errors.InvalidReplicationFactorException: replication factor: 2 larger than avai

lable brokers: 0

那就把三个节点全部启动起来创建topic:
在这里插入图片描述

[root@spark kafka]# bin/kafka-topics.sh --zookeeper spark:2181 --create --topic new-topic --partitions 4 --replication-factor 2

Created topic "new-topic"

三个broker节点一共创建了8个文件夹,即分区数4与副本因子的乘积。

可以看到,同一个分区中的多个副本因子必须分布在不同的broker中,这样才能提供有效的数据冗余,提高数据的可靠性。如果我们将broker1节点(名为spark1)的节点挂掉,可以看到其他两个节点上的分区数还能保持在分区0/1/2/3上。

分区副本的分配
生产者的分区分配是指每条消息指定其所要发往的分区,消费者中的分区分配是指为消费者指定其可以消费消息的分区。
在这里插入图片描述

使用Kafka-topics.sh脚本出给创建topic时的内部分批额逻辑是按照机架信息的两种策略:为指定机架信息和指定机架信息。如果几尊中的所有broker节点没有配置broker.rack参数,或者适应disable-rack-aware参数,那就使用未指定机架信息的分配策略,否则就是用指定机架信息的分配策略。

源码如下:

private def assignReplicasToBrokersRackUnaware(
   nPartitions: Int, // 分数数量
   replicationFactor: Int,  // 副本因子
   brokerList: Seq[Int],  // 集群中的brokerList
   fixedStartIndex: Int,  // 起始索引,第一个副本分配的位置
   startPartitionId: Int):  // 起始分区编号,默认值为-1
 Map[Int, Seq[Int]] = {
val ret = mutable.Map[Int, Seq[Int]]()   // 保存分配结果的集合
val brokerArray = brokerList.toArray  // list化
// 如果起始索引大于零,则使用默认的起始索引,否则,根据broker的length随机生成一个起始索引,保证持有有效的brokerID
val startIndex = if (fixedStartIndex >= 0) fixedStartIndex else rand.nextInt(brokerArray.length)
// 取最大值,确保持有有效的brokerID
var currentPartitionId = math.max(0, startPartitionId)
// 如果起始索引大于零,则将起始起始分区号即为起始索引,否则,随机产生一个,以保证更均匀的将副本分配到不同的broker上
var nextReplicaShift = if (fixedStartIndex >= 0) fixedStartIndex else rand.nextInt(brokerArray.length)
// 遍历所有的分区,将每个分区的副本分配到不同的broker上
for (_ <- 0 until nPartitions) {  // 遍历
  if (currentPartitionId > 0 && (currentPartitionId % brokerArray.length == 0))
    nextReplicaShift += 1
  val firstReplicaIndex = (currentPartitionId + startIndex) % brokerArray.length
  val replicaBuffer = mutable.ArrayBuffer(brokerArray(firstReplicaIndex))
  // 保存该分区所有副本到brokerList中
  for (j <- 0 until replicationFactor - 1)
    replicaBuffer += brokerArray(replicaIndex(firstReplicaIndex, nextReplicaShift, j, brokerArray.length))
    // 保存该分区所有的副本的分配信息
  ret.put(currentPartitionId, replicaBuffer)
  // 继续为下一个分区分配副本
  currentPartitionId += 1
}
ret
}

该方法的参数中的fixedStartIndex和startPartitionId是从上游的方法中调用传递下来的,默认值都是-1,分别表示第一个副本分配的位置和起始分区的位置。其方法的核心是遍历每个partition,然后从brokerArray中选取replicationFactor个brokerid分配给这个partition。

默认情况下创建主题总是从编码为0的的分配一次轮询进行分配。

当创建一个新的topic时,无论是通过Kafka-topics.sh还是通过KafkaAdminClient方式创建出题,其本质是在ZooKeeper中的/brokers/topics节点下创建于该主题对应的子节点并写入分区副本的分配方法中,且在/config/topics/节点下创建于该主题对应的子节点并写入topic相关的配置信息。

修改topic:
修改topic是由Kafka-topics中的alter指令提供的。
先创建名为paojiaojaing1的topic:
在这里插入图片描述
修改名称:
在这里插入图片描述
查看修改后的信息:
在这里插入图片描述
删除topic:

./bin/kafka-topics.sh  --zookeeper spark:2181 --delete --topic topic-del --if-exists

使用kafka-topics.sh 脚本删除topic的行为的本质只是在ZooKeeper中的/admin/delete_topics路径下创建一个与待删除topic同名的节点,以此标记该topic为待删除的状态,真正删除topic的动作是由Kafka的控制器执行的。
利用ZooKeeper的客户端来删除topic:

create /admin/dalete_topics/topic-del ""

--> Create /admin/delete_topics/topic-del

partition的管理:
partition使用多副本机制来提升可靠性,但是只有一个leader副本对外提供写服务,而follower副本只负责在内部进行消息的同步。如果一个partition的leader副本不可用,则意味着整个分区不可用,此时Kafka就从剩余的可用的follower副本中挑选出一个新的leader副本来继续对外提供写服务。

在创建topic的时候,该topic的partition及其副本尽可能的均匀分布在Kafka集群中得到各个broker节点上,对应的leader副本也尽可能的进行均匀的分配。

在这里插入图片描述
从图中我们可以看到同一个broker节点上不可能同时出现它的多个副本,即kafka集群中的一个节点上最多只能有一个它的副本。

如果一个partition的leader副本宕机后,会从其他的follower副本中选举出一个leader副本继续对外提供写服务,当原有的leader副本恢复后,只能充当follower副本而不能继续对外提供写服务。

在这里插入图片描述
手动指定消费分区:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值