Kafka生产者
开发者可以使用Kafka内置的客户端API开发应用程序
生产者
一个应用程序往kafka写入消息:记录用户的活动、记录度量指标、保存日志消息、记录智能家电的信息、与其他应用程序进行异步通信等
发送消息流程
- 创建一个ProducerRecord对象开始,ProducerRecord对象需要包含目标topic、partition、key、value。在发送Producer对象时,生产者要先把键和值对象序列化成字节数组。
- 数据被传送给分区器,分区器会根据分区或键哈希来选择一个分区,将记录添加到记录批次,这个批次的消息会被发送到同一主题和分区。
- 服务器收到消息,会返回响应。写入成功,返回一个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()发送,并指定一个回调函数,服务器在返回响应式调用该函数
生产者配置
- acks,这个参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的。如果acks=1,只要集群的首领节点收到消息,生产者就会收到来自服务器的成功响应。
- buffer.memory,这个参数设置生产者内存缓冲区的大小。
- retries,这个参数决定了生产者可以重发消息的次数。
- 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() {}
}
浙公网安备 33010602011771号