Kafka-Java客户端数据生产流程解析,从发送类型实现代码到序列化器实现代码!(1)

发送即忘记

producer.send(record)

同步发送

//通过send()发送完消息后返回一个Future对象,然后调用Future对象的get()方法等待kafka响应
//如果kafka正常响应,返回一个RecordMetadata对象,该对象存储消息的偏移量
// 如果kafka发生错误,无法正常响应,就会抛出异常,我们便可以进行异常处理
producer.send(record).get();

异步发送

producer.send(record, new Callback() {
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null) {
System.out.println(metadata.partition() + “:” + metadata.offset());
}
}
});

4.序列化器

消息要到网络上进行传输,必须进行序列化,而序列化器的作用就是如此。

Kafka 提供了默认的字符串序列化器(org.apache.kafka.common.serialization.StringSerializer),还有整型(IntegerSerializer)和字节数组(BytesSerializer)序列化器,这些序列化器都实现了接口(org.apache.kafka.common.serialization.Serializer)基本上能够满足大部分场景的需求。

5.自定义序列化器

见代码库:com.heima.kafka.chapter2.CompanySerializer

/**

  • 自定义序列化器
    */
    public class CompanySerializer implements Serializer {
    @Override
    public void configure(Map configs, boolean isKey) {
    }

@Override
public byte[] serialize(String topic, Company data) {
if (data == null) {
return null;
}
byte[] name, address;
try {
if (data.getName() != null) {
name = data.getName().getBytes(“UTF-8”);
} else {
name = new byte[0];
}
if (data.getAddress() != null) {
address = data.getAddress().getBytes(“UTF-8”);
} else {
address = new byte[0];
}
ByteBuffer buffer = ByteBuffer. allocate(4 + 4 + name.length + address.length);
buffer.putInt(name.length);
buffer.put(name);
buffer.putInt(address.length);
buffer.put(address);
return buffer.array();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return new byte[0];
}

@Override
public void close() {
}
}

  • 使用自定义的序列化器

见代码库:com.heima.kafka.chapter2.ProducerDefineSerializer

public class ProducerDefineSerializer {
public static final String brokerList = “localhost:9092”;
public static final String topic = “heima”;

public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties properties = new Properties();
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CompanySerializer.class.getName());
// properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
// ProtostuffSerializer.class.getName());
properties.put(“bootstrap.servers”, brokerList);

KafkaProducer<String, Company> producer = new KafkaProducer<>(properties);
Company company = Company.builder().name(“kafka”) .address(“北京”).build();
// Company company = Company.builder().name(“hiddenkafka”)
// .address(“China”).telphone(“13000000000”).build();
ProducerRecord<String, Company> record = new ProducerRecord<>(topic, company);
producer.send(record).get();
}
}

6.分区器

本身kafka有自己的分区策略的,如果未指定,就会使用默认的分区策略:

Kafka根据传递消息的key来进行分区的分配,即hash(key) % numPartitions。如果Key相同的话,那么就会分配到统一分区。

源代码org.apache.kafka.clients.producer.internals.DefaultPartitioner分析

public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
List partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
if (keyBytes == null) {
int nextValue = this.nextValue(topic);
List availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
int part = Utils.toPositive(nextValue) % availablePartitions.size();
return ((PartitionInfo)availablePartitions.get(part)).partition();
} else {
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}

  • 自定义分区器见代码库 com.heima.kafka.chapter2.DefinePartitioner

/**

  • 自定义分区器
    */
    public class DefinePartitioner implements Partitioner {
    private final AtomicInteger counter = new AtomicInteger(0);

@Override
public int partition(String topic, Object key, byte[] keyBytes,Object value, byte[] valueBytes, Cluster cluster) {
List partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
if (null == keyBytes) {
return counter.getAndIncrement() % numPartitions;
} else return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> configs) {
}
}

  • 实现自定义分区器需要通过配置参数ProducerConfig.PARTITIONER_CLASS_CONFIG来实现

// 自定义分区器的使用
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,DefinePartitioner.class.getNam e());

7.拦截器

Producer拦截器(interceptor)是个相当新的功能,它和consumer端interceptor是在Kafka 0.10版本被引入的,主要用于实现clients端的定制化控制逻辑

生产者拦截器可以用在消息发送前做一些准备工作。

使用场景

  1. 按照某个规则过滤掉不符合要求的消息
  2. 修改消息的内容
  3. 统计类需求

见代码库:自定义拦截器com.heima.kafka.chapter2.ProducerInterceptorPrefix

/**

  • 自定义拦截器
    */
    public class ProducerInterceptorPrefix implements ProducerInterceptor<String, String> {
    private volatile long sendSuccess = 0;
    private volatile long sendFailure = 0;

@Override
public ProducerRecord<String, String> onSend( ProducerRecord<String, String> record) {
String modifiedValue = “prefix1-” + record.value();
return new ProducerRecord<>(record.topic(), record.partition(), record.timestamp(), record.key(), modifiedValue, record.headers());
// if (record.value().length() < 5) {
// throw new RuntimeException();
// }
// return record;
}

@Override
public void onAcknowledgement( RecordMetadata recordMetadata, Exception e) {
if (e == null) {
sendSuccess++;
} else {
sendFailure++;
}
}

@Override
public void close() {
double successRatio = (double) sendSuccess / (sendFailure + sendSuccess);
System.out.println(“[INFO] 发送成功率=” + String.format(“%f”, successRatio * 100) + “%”);
}

@Override
public void configure(Map<String, ?> map) {
}
}

  • 实现自定义拦截器之后需要在配置参数中指定这个拦截器,此参数的默认值为空,如下:
1/kafka是一个分布式的消息缓存系统 2/kafka集群中的服务都叫做broker 3/kafka有两类客户端,一类叫producer(消息生产者),一类叫做consumer(消息消费者),客户端和broker服务之间采用tcp协议连接 4/kafka中不同业务系统的消息可以通过topic进行区分,而且每一个消息topic都会被分区,以分担消息读写的负载 5/每一个分区都可以有多个副本,以防止数据的丢失 6/某一个分区中的数据如果需要更新,都必须通过该分区所有副本中的leader来更新 7/消费者可以分组,比如有两个消费者组A和B,共同消费一个topic:order_info,A和B所消费的消息不会重复 比如 order_info 中有100个消息,每个消息有一个id,编号从0-99,那么,如果A组消费0-49号,B组就消费50-99号 8/消费者在具体消费某个topic中的消息时,可以指定起始偏移量 每个partition只能同一个group中的同一个consumer消费,但多个Consumer Group可同时消费同一个partition。 n个topic可以被n个Consumer Group消费,每个Consumer Group有多个Consumer消费同一个topic Topic在逻辑上可以被认为是一个queue,每条消费都必须指定它的Topic,可以简单理解为必须指明把这条消息放进哪个queue里。为了使得Kafka的吞吐率可以线性提高,物理上把Topic分成一个或多个Partition,每个Partition在物理上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件。若创建topic1和topic2两个topic,且分别有13个和19个分区 Kafka的设计理念之一就是同时提供离线处理和实时处理。根据这一特性,可以使用Storm这种实时流处理系统对消息进行实时在线处理,同时使用Hadoop这种批处理系统进行离线处理,还可以同时将数据实时备份到另一个数据中心,只需要保证这三个操作所使用的Consumer属于不同的Consumer Group即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值