Kafka的生产者详解

消息发送

Kafka Java客户端数据生产流程

在这里插入图片描述

发送类型

  • 同步发送:
//通过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());
		}
	}
});

序列化器

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

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

自定义序列化器:

// Company是自定义的公司类
public class Company {
	private String name;
	private String address;
	...
}

public class MySerializer implements Serializer<Company> {
	@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(Exception e) {
		}
	}
}

分区器

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

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

org.apache.kafka.clients.producer.internals.DefaultPartitoner分析:

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) {
		int nextValue = this.nextValue(topic);
		List<PartitionInfo> 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;
	}
}

也可以自定义分区器:

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<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
		int numPartitions = partitions.size();
		if (keyBytes = null) {
			return counter.getAndIncrement() % numPartitions;
		} else {
			return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
		}
	}
	@Override
	public void close() {
		
	}

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

}

拦截器

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

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

使用场景:

  • 按照某个规则过滤掉不符合要求的消息。
  • 修改消息的内容。
  • 统计类的需求(统计发送量、发送成功率)。

拦截器直接在properties中配置使用。

自定义拦截器:

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 = "prefix-" + record.value();
		return new ProducerRecord<>(record.topic(),record.partition(),record.timestamp(),record.key(),modifiedValue,record.headers());
	}

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

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

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

发送原理剖析

在这里插入图片描述

消息发送过程中,涉及到两个线程协同工作,主线程首先将业务数据封装成ProducerRecord对象,之后调用send()方法将消息放入RecordAccumulator(消息收集器,也可以理解为主线程与sender线程直接的缓冲区)中暂存,Sender线程负责将消息信息构成请求,并最终执行网络I/O线程,它从RecordAccumulator中取出消息并批量发送出去,需要注意的是,KafkaProducer是线程安全的,多个线程可以共享使用同一个KafkaProducer对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值