当我们使用kafka向指定Topic发送消息时,如果该Topic具有多个partition,无论消费者有多少,最终都会保证一个partition内的消息只会被一个Consumer group中的一个Consumer消费,也就是说同一Consumer group中的多个Consumer自动会起到负载均衡的效果。

1、消息构造

下面我们就针对调用kafka API发送消息到Topic时partition的分配策略,分析下其内部具体的源码码实现。

首先看下kafka API中消息体ProducerRecord类的构造函数,可以看到构造消息时可指定该消息要发送的Topic、partition、key、value等关键信息。

    /**
* Creates a record to be sent to a specified topic and partition
*
* @param topic The topic the record will be appended to
* @param partition The partition to which the record should be sent
* @param key The key that will be included in the record
* @param value The record contents
* @param headers The headers that will be included in the record
*/
public ProducerRecord(String topic, Integer partition, K key, V value, Iterable<Header> headers) {
this(topic, partition, null, key, value, headers);
} /**
* Creates a record to be sent to a specified topic and partition
*
* @param topic The topic the record will be appended to
* @param partition The partition to which the record should be sent
* @param key The key that will be included in the record
* @param value The record contents
*/
public ProducerRecord(String topic, Integer partition, K key, V value) {
this(topic, partition, null, key, value, null);
} /**
* Create a record to be sent to Kafka
*
* @param topic The topic the record will be appended to
* @param key The key that will be included in the record
* @param value The record contents
*/
public ProducerRecord(String topic, K key, V value) {
this(topic, null, null, key, value, null);
}

2、分发策略 

在实际使用中,我们一般不会指定消息发送的具体partition,最多只会传入key值,类似下面这种方式:

producer.send(new ProducerRecord<Object, Object>(topic, key, data));

而kafka也会根据你传入key的hash值,通过取余的方法,尽可能保证消息能够相对均匀的分摊到每个可用的partition上;

下面是kafka内部默认的分发策略:

public class DefaultPartitioner implements Partitioner {

    private final ConcurrentMap<String, AtomicInteger> topicCounterMap = new ConcurrentHashMap<>();

    public void configure(Map<String, ?> configs) {}

    /**
* Compute the partition for the given record.
*
* @param topic The topic name
* @param key The key to partition on (or null if no key)
* @param keyBytes serialized key to partition on (or null if no key)
* @param value The value to partition on or null
* @param valueBytes serialized value to partition on or null
* @param cluster The current cluster metadata
*/
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
//获取该topic的分区列表
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
//如果key值为null
if (keyBytes == null) {
//维护一个key为topic的ConcurrentHashMap,并通过CAS操作的方式对value值执行递增+1操作
int nextValue = nextValue(topic);
//获取该topic的可用分区列表
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {//如果可用分区大于0
//执行求余操作,保证消息落在可用分区上
int part = Utils.toPositive(nextValue) % availablePartitions.size();
return availablePartitions.get(part).partition();
} else {
// 没有可用分区的话,就给出一个不可用分区
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
// 通过计算key的hash,确定消息分区
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
} private int nextValue(String topic) {
//获取一个AtomicInteger对象
AtomicInteger counter = topicCounterMap.get(topic);
if (null == counter) {//如果为空
//生成一个随机数
counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
//维护到topicCounterMap中
AtomicInteger currentCounter = topicCounterMap.putIfAbsent(topic, counter);
if (currentCounter != null) {
counter = currentCounter;
}
}
//返回值并执行递增
return counter.getAndIncrement();
} public void close() {} }

3、自定义负载策略

我们也可以通过实现Partitioner接口,自定义分发策略,看下具体实现

自定义实现Partitioner接口

/**
* 自定义实现Partitioner接口
*
*/
public class KeyPartitioner implements Partitioner { /**
* 实现具体分发策略
*/
@Override
public int partition(String topic, Object key, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);//拉取可用的partition
if (key == null||key.equals("")) {
int random = (int) (Math.random() * 10);
int part = random % availablePartitions.size();
return availablePartitions.get(part).partition();
}
return Math.abs(key.toString().hashCode() % 6);
} @Override
public void configure(Map<String, ?> configs) {
// TODO Auto-generated method stub } @Override
public void close() {
// TODO Auto-generated method stub } }

同时在初始化kafka生产者时,增加自定义配置

Properties properties = new Properties();
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,KeyPartitioner.class); //加入自定义的配置
producer = new KafkaProducer<Object, Object>(properties);

 4、总结

以上是对kafka消息分发的策略进行一定的分析与自定义扩展,希望对大家在使用kafka时有所帮助,其中如有不足与不正确的地方还望指出与海涵。

关注微信公众号,查看更多技术文章。

kafka消息分发策略分析的更多相关文章

  1. RabbitMQ,RocketMQ,Kafka 消息模型对比分析

    消息模型 消息队列的演进 消息队列模型 发布订阅模型 RabbitMQ的消息模型 交换器的类型 direct topic fanout headers Kafka的消息模型 RocketMQ的消息模型 ...

  2. apollo 消息分发源代码分析

    1.MessageDispatch消息分发信息 public static final byte DATA_STRUCTURE_TYPE = CommandTypes.MESSAGE_DISPATCH ...

  3. Kafka分区分配策略分析——重点:StickyAssignor

    “ 为什么Kafka在RangeAssigor.RoundRobinAssignor的基础上,又新增了PartitionAssignor,它解决了什么问题?” 背景 用过Kafka的同学应该都知道Ka ...

  4. Storm 消息分发策略

    1.Shuffle Grouping:随机分组,随机派发stream里面的tuple,保证每个bolt接收到的tuple数目相同.2.Fields Grouping:按字段分组,比如按userid来分 ...

  5. kafka消息的分发与消费

    关于 Topic 和 Partition: Topic: 在 kafka 中,topic 是一个存储消息的逻辑概念,可以认为是一个消息集合.每条消息发送到 kafka 集群的消息都有一个类别.物理上来 ...

  6. Kafka分片存储、消息分发和持久化机制

    Kafka 分片存储机制 Broker:消息中间件处理结点,一个 Kafka 节点就是一个 broker,多个 broker 可以组成一个 Kafka集群. Topic:一类消息,例如 page vi ...

  7. Kafka学习笔记(二):Partition分发策略

    kafka版本0.8.2.1 Java客户端版本0.9.0.0 为了更好的实现负载均衡和消息的顺序性,Kafka Producer可以通过分发策略发送给指定的Partition.Kafka保证在par ...

  8. 源码分析 Kafka 消息发送流程(文末附流程图)

    温馨提示:本文基于 Kafka 2.2.1 版本.本文主要是以源码的手段一步一步探究消息发送流程,如果对源码不感兴趣,可以直接跳到文末查看消息发送流程图与消息发送本地缓存存储结构. 从上文 初识 Ka ...

  9. 源码分析 Kafka 消息发送流程

    Futuresend(ProducerRecord<K, V> record) Futuresend(ProducerRecord<K, V> record, Callback ...

随机推荐

  1. Integrating Thymeleaf with Spring

    这个是基于注解的配置方式,基于配置文件的http://www.cnblogs.com/honger/p/6875148.html 一.整体结构图 二.web.xml文件,这里使用了注解的方式 < ...

  2. Initialization failed for 'https://start.spring.io' Please check URL

    错误描述:Initialization failed for 'https://start.spring.io' Please check URL, network and proxy setting ...

  3. 入门MySQL——架构篇

    前言:  上篇文章我们介绍了入门MySQL的基本概念,看完上篇文章,相信你应该了解MySQL的前世今生了吧.本篇文章将带你从架构体系来学习MySQL.我认为学习MySQL架构体系应该是入门阶段必须的, ...

  4. 用python绘制漂亮的图形

    先看效果,没有用任何绘图工具,只是运行了一段python代码. 代码如下: _ = ( 255, lambda V ,B,c :c and Y(V*V+B,B, c -1)if(abs(V)<6 ...

  5. 100天搞定机器学习|Day15 朴素贝叶斯

    Day15,开始学习朴素贝叶斯,先了解一下贝爷,以示敬意. 托马斯·贝叶斯 (Thomas Bayes),英国神学家.数学家.数理统计学家和哲学家,1702年出生于英国伦敦,做过神甫:1742年成为英 ...

  6. Tomcat 单(多)实例部署使用

    一.前言 (一).概述 Tomcat 是由 Apache 开发的一个 Servlet 容器,实现了对 Servlet 和 JSP 的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控 ...

  7. (通俗易懂小白入门)网络流最大流——EK算法

    网络流 网络流是模仿水流解决生活中类似问题的一种方法策略,来看这么一个问题,有一个自来水厂S,它要向目标T提供水量,从S出发有不确定数量和方向的水管,它可能直接到达T或者经过更多的节点的中转,目前确定 ...

  8. Keil5调试过程中遇到的一些警告和错误

    最近用keil5调试代码出了一些警告与错误,整理如下: 1.warning: #1295-D: Deprecated declaration run_c - give arg types void r ...

  9. SpringMVC的流程

    Springmvc的流程 1.用户发送请求至前端控制器DispatcherServlet 2.DispatcherServlet收到请求后,调用HandlerMapping处理映射器,请求获取Hand ...

  10. Web前端开发工程师课程大纲

    PHP程序员雷雪松整理出来的一套独一无二的Web前端开发课程.本套Web前端开发课程专门为想励志成为优秀web前端工程师的学习者而总结归纳的,本套Web前端课程舍弃了一些不常用的即将废弃的HTML标签 ...