Kafka生产者

开发者可以使用Kafka内置的客户端API开发应用程序

生产者

一个应用程序往kafka写入消息:记录用户的活动、记录度量指标、保存日志消息、记录智能家电的信息、与其他应用程序进行异步通信等

发送消息流程

  1. 创建一个ProducerRecord对象开始,ProducerRecord对象需要包含目标topic、partition、key、value。在发送Producer对象时,生产者要先把键和值对象序列化成字节数组。
  2. 数据被传送给分区器,分区器会根据分区或键哈希来选择一个分区,将记录添加到记录批次,这个批次的消息会被发送到同一主题和分区。
  3. 服务器收到消息,会返回响应。写入成功,返回一个RecordMetaData对象,包含主题和分区信息,以及记录偏移量,应该返回到zk。写入失败,返回错误,生产者收到错误后会重新发送消息。

创建Producer代码

private Properties kafkaProps = new Properties(); 
kafkaProps.put("bootstrap.servers", "broker1:9092,broker2:9092");
kafkaProps.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer"); 
kafkaProps.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
producer = new KafkaProducer<String, String>(kafkaProps);

发送消息由3种方式

  • 发送并忘记。消息发送给服务器后,并不关心是否正常到达
  • 同步发送。使用send()发送,返回一个Future对象,调用get()进行等待,可以直到消息是否发送成功
  • 异步发送。使用send()发送,并指定一个回调函数,服务器在返回响应式调用该函数

生产者配置

  1. acks,这个参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的。如果acks=1,只要集群的首领节点收到消息,生产者就会收到来自服务器的成功响应。
  2. buffer.memory,这个参数设置生产者内存缓冲区的大小。
  3. retries,这个参数决定了生产者可以重发消息的次数。
  4. batch.size,这个参数制定了一个批次可以使用的内存大小,多个消息放在同个批次里,同时发送。

序列化器

创建一个生产者对象必须指定序列化器。

如果发送到kafka的对象不是简单的字符串或整型,可以使用序列化框架来创建消息记录,如Avro、Thrift、Protobuf,或者使用自定义序列化器。

使用Avro序列化

数据被序列化成二进制文件或JSON文件,Avro在读写文件时需要用到schema,schema一般会被内嵌在数据文件里。特点,当写信息使用新的schema,负责读信息可以继续使用,无需改动。

分区

如果键位NULL,分区器使用Round Robin将消息均衡分布到各个分区,键不为空,则对键散列,映射到相应分区,同一个键总是在同一个分区。

只有在不改变分区数量的情况下,键与分区之间的映射才能保持不变。一旦主题增加了新的分区,旧数据仍然在各自的分区,但新数据可能散列写到其他分区上。所以最好在创建主题的时候就把分区规划好。

自定义分区策略
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.record.InvalidRecordException;
import org.apache.kafka.common.utils.Utils;
public class BananaPartitioner implements Partitioner {
    public void configure(Map<String, ?> configs) {} 
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
    int numPartitions = partitions.size();
    if ((keyBytes == null) || (!(key instanceOf String))) ➋
    throw new InvalidRecordException("We expect all messages to have customer name as key")

    if (((String) key).equals("Banana"))
    return numPartitions; // Banana总是被分配到最后一个分区
    // 其他记录被散列到其他分区
    return (Math.abs(Utils.murmur2(keyBytes)) % (numPartitions - 1))
    }
    public void close() {}
}
posted @ 2020-03-17 21:44  Tanglement  阅读(222)  评论(0)    收藏  举报