Kafka生产者原理解析

文章目录

  • kafka消息发送流程
  • Kafka创建生产者
    • 初始化生产者发送消息示例
    • 分区策略
    • producer拦截器
    • 生产者配置
      • acks
      • buffer.memory
      • compression.type
      • retres
      • batch.size
      • linger.ms
      • max.request.size
      • max.in.flight.requests.per.connection
  • 参考

kafka消息发送流程

流程图如下所示:

  1. 先初始化一个ProducerRecord对象,其中包含Topic和Value。key和Partion可选,初始化完成后会进行序列化
  2. 数据被传到分区器,如果ProducerRecord对象指定了分区直接进入下一步,否则会根据ProducerRecord对象的key选择一个分区。在确定分区后,这条记录会被添加到一个记录批次。这个批次的消息会被发送到指定的主题和分区上
  3. 服务器收到消息后返回相应,如果消息写入成功,返回一个RecordMetaData对象,包含主题和分区信息,以及记录在分区里的偏移量;如果写入失败,会返回一个错误,生成着在收到错误后会尝试重新发送消息,重试多次失败后返回错误信息。

Kafka创建生产者

初始化生产者发送消息示例

初始化消息生产者有3个必选参数:

  1. boostrap.servers:指定broker的地址列表,格式为host:port。列表无需提供所有的broker地址,生产者会从给定的broker中查找到其他broker的消息,但建议至少提供两个,确保在其中一个宕机情况下,生产者仍然能够连接到集群
  2. key.serializer:指定发送消息的序列化类型,生产者在发送消息前,如果key为复杂Java对象,需要进行序列化,可以通过这个配置指定序列化类型,序列化类型必须实现org.apache.kafka.common.serialization.Serializer接口。
  3. value.serializer:指定值序列化类型,类似与key.serializer

创建和发送消息的流程示例如下所示:

Properties properties = new Properties();
// 指定broker连接
properties.put("bootstrap.servers", "127.0.0.1:9092");
// 指定key序列化器,这里为字符串序列化器
properties.put("key.serializer", StringSerializer.class.getName());
// 指定值序列化器,这里为字符串序列化器
properties.put("value.serializer", StringSerializer.class.getName());
// 初始化生产者
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

// 定义消息所属主题
String topic = "kafka_api";
// 异步发送消息,并获取发送结果
RecordMetadata recordMetadata = producer.send(new ProducerRecord<>(topic, "message")).get();
// 指定发送key=testKey
RecordMetadata recordMetadata2 = producer.send(new ProducerRecord<>(topic, "testKey", "message")).get();
// 指定发送partition=1,key=testKey
RecordMetadata recordMetadata3 = producer.send(new ProducerRecord<>(topic, 1, "testKey", "message")).get();

分区策略

在发送过程中,可以指定发送的key和partion,key一方面可以作为附加悉尼下,另一方面可以用于消息路由,这里涉及到Kafka的消息发送路由机制,有三种情况:

  1. 指定了patition,则直接发送到指定partition;
  2. 未指定patition但指定key,通过对key进行hash选出一个patition。在不改变主题分区数量情况下,每次同一个key都会被映射到相同的partition
  3. patition和key都未指定,使用轮询选出一个patition。

如果需要自定义分区策略,可以实现Partitioner接口,重写内部的partition()方法,在初始化生产者的时候放入配置属性中,其中key=“partitioner.class”,示例如:

// 指定自定义分区器,其中PARTITIONER_CLASS_CONFIG="partitioner.class"
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, new Partitioner() {
    @Override
    // 分区策略实现
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        return 0;
    }

    // 清理资源
    @Override
    public void close() {

    }

    // 初始化资源
    @Override
    public void configure(Map<String, ?> configs) {

    }
});

producer拦截器

producer拦截器(interceptor)使得用户在消息发送前后以及producer回调逻辑前有机会对喜爱做一些定制化需求,kafka允许用户指定多个interceptor按序作用于消息以形成一条拦截链。

定义一个拦截器需要实现ProducerInterceptor接口。
一个拦截器示例定义如下:

class MyInterceptor implements ProducerInterceptor{

    // 消息发送前回调
    @Override
    public ProducerRecord onSend(ProducerRecord record) {
        return null;
    }

    // 消息确认发送成功响应回调
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {

    }

    // 清理资源
    @Override
    public void close() {

    }

    // 初始化资源
    @Override
    public void configure(Map<String, ?> configs) {

    }
}

定义后,可以在创建生产者之前配置属性interceptor.classes,值为拦截器的权限定名字符串:

properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, Arrays.asList(MyInterceptor.class.getName()));

生产者配置

除了上面的必要参数外,还有一些比较核心的参数配置

acks

acks指定必须要有多少哥分区副本收到消息,生产者才认为消息写入成功。有以下几种情况

  1. acks=0:生产者不等待服务器的响应,这意味着生产者无法确定服务器消息写入成功,可能存在消息丢失,但因为不用等待服务器响应,可以以网络能够支持的最大速度发送消息,从而有很高的吞吐量
  2. acks=1:只要集群的首领节点收到消息,生产者就会收到一个来自服务器的成功响应,如果消息无法到达首领节点,则生产者会收到一个错误响应,为避免数据丢失,生产者会尝试重发消息。
  3. acks=all:只有所有参与复制的节点副本都收到消息,生产者才会收到成功响应,这种模式是最安全的,但吞吐量是最低的。

buffer.memory

设置生产者内存缓冲区大小,用户缓存要发送到服务器的消息,如果应用程序发送消息操作速度超过发送到服务器的处理响应速度,可能会导致生产者缓存不足,这时send方法调用会被堵塞或抛异常

compression.type

默认情况,消息发送不会被压缩,可以通过这个参数指定使用snappy,gzip,lz4等压缩算法来降低网络传输开销和内存开销

retres

当生产者向服务器发送消息失败时,会尝试根据此配置次数进行消息重复,如果超过配置次数仍失败,生产者会放弃重试并返回错误。默认情况,生产者会确保每次重试间隔大于100ms,可以通过retry.backoff.ms修改。

batch.size

当有多个消息需要被发送到同一个分区时,生产者会将他们放在同一个批次里,该参数指定了一个批次可以使用的内存大小,按照字节数计算,而非消息个数。当批次被填满时,批次里的消息会被发送出去。实际生产者不一定会在批次满后才发消息,因此设置得很大不会导致消息延迟,但会占用更多的内存。如果设置的过小,会导致生产者需要更频繁地发送消息。

linger.ms

指定生产者在发送批次之前,等待更多消息加入批次的时间,生产者会在批次被填满或达到linger.ms时把批次发送出去。可以通过调整linger.ms取到吞吐量和资源开销之间的平衡。

max.request.size

指定生产者在单个批次里能够发送消息的最大值,最好和broker对应的可接受的消息最大值匹配,避免生产者发送的消息被broker拒绝

max.in.flight.requests.per.connection

指定生产者在收到服务器响应前可以发送多少个消息,值越高,则吞吐量越高,但占用内存也越多,同时还会有在消息发送失败时乱序的风险。将值设为1,可以保证消息是按照发送的顺序写入服务器的,即使发生了重试。

参考

  1. Kafka权威指南
  2. Apache Kafka实战

你可能感兴趣的:(kafka)