maven依赖如下:
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.3.1</version>
</dependency>
生产者就是将消息发送到kafka上的应用程序。
一般来说正常的一个消费者需要准备以下几个步骤:
1)、配置生产者客户端参数及创建相应的生产者实例
2)、构建待发送的的消息
3)、发送消息
4)、关闭生产者实例
kafka生产者发送消息的示例:
public class ProducerDemo {
public static final String brokerList="192.168.195.134:9092";
public static final String topic = "topic-demo";
public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,brokerList);
properties.put(ProducerConfig.RETRIES_CONFIG,3);
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,MyProducerInterceptor.class.getName());
// objectJsonSerializer.setAddTypeInfo(false);
//实例化produce
KafkaProducer<String,String> producer = new KafkaProducer<>(properties, new StringSerializer(),new StringSerializer());
for(int i= 0;i<1000;i++){
//发送消息
//构建发送的记录
ProducerRecord<String,String> record = new ProducerRecord<>(topic,"hello","hello,kafka");
Future<RecordMetadata> send = producer.send(record,((metadata, exception) -> {
if(exception !=null){
exception.printStackTrace();
}else{
System.out.println(metadata.topic()+"-" + metadata.offset() + "-" + metadata.partition());
}
}));
//阻塞等待发送的结果
send.get();
// Thread.sleep(500);
}
//关闭客户端
producer.close();
}
}
上述代码按照 配置参数 -->实例化生产者–>构建发送记录 -->发送消息步骤进行。接下来我们将按照此步骤进行一一分析。
1、生产者参数配置
在java 客户端,生产者的参数配置我们可以参考org.apache.kafka.clients.producer.ProducerConfig
类。其中定义各参数的名称,源码如下:
public class ProducerConfig extends AbstractConfig {
/*
* NOTE: DO NOT CHANGE EITHER CONFIG STRINGS OR THEIR JAVA VARIABLE NAMES AS THESE ARE PART OF THE PUBLIC API AND
* CHANGE WILL BREAK USER CODE.
*/
private static final ConfigDef CONFIG;
/** <code>bootstrap.servers</code> */
public static final String BOOTSTRAP_SERVERS_CONFIG = CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG;
/** <code>client.dns.lookup</code> */
public static final String CLIENT_DNS_LOOKUP_CONFIG = CommonClientConfigs.CLIENT_DNS_LOOKUP_CONFIG;
/** <code>metadata.max.age.ms</code> */
public static final String METADATA_MAX_AGE_CONFIG = CommonClientConfigs.METADATA_MAX_AGE_CONFIG;
private static final String METADATA_MAX_AGE_DOC = CommonClientConfigs.METADATA_MAX_AGE_DOC;
/** <code>batch.size</code> */
public static final String BATCH_SIZE_CONFIG = "batch.size";
/** <code>acks</code> */
public static final String ACKS_CONFIG = "acks";
/** <code>linger.ms</code> */
public static final String LINGER_MS_CONFIG = "linger.ms";
/** <code>request.timeout.ms</code> */
public static final String REQUEST_TIMEOUT_MS_CONFIG = CommonClientConfigs.REQUEST_TIMEOUT_MS_CONFIG;
private static final String REQUEST_TIMEOUT_MS_DOC = CommonClientConfigs.REQUEST_TIMEOUT_MS_DOC
+ " This should be larger than <code>replica.lag.time.max.ms</code> (a broker configuration)"
+ " to reduce the possibility of message duplication due to unnecessary producer retries.";
/** <code>delivery.timeout.ms</code> */
public static final String DELIVERY_TIMEOUT_MS_CONFIG = "delivery.timeout.ms";
/** <code>client.id</code> */
public static final String CLIENT_ID_CONFIG = CommonClientConfigs.CLIENT_ID_CONFIG;
/** <code>send.buffer.bytes</code> */
public static final String SEND_BUFFER_CONFIG = CommonClientConfigs.SEND_BUFFER_CONFIG;
/** <code>receive.buffer.bytes</code> */
public static final String RECEIVE_BUFFER_CONFIG = CommonClientConfigs.RECEIVE_BUFFER_CONFIG;
/** <code>max.request.size</code> */
public static final String MAX_REQUEST_SIZE_CONFIG = "max.request.size";
/** <code>reconnect.backoff.ms</code> */
public static final String RECONNECT_BACKOFF_MS_CONFIG = CommonClientConfigs.RECONNECT_BACKOFF_MS_CONFIG;
/** <code>reconnect.backoff.max.ms</code> */
public static final String RECONNECT_BACKOFF_MAX_MS_CONFIG = CommonClientConfigs.RECONNECT_BACKOFF_MAX_MS_CONFIG;
/** <code>max.block.ms</code> */
public static final String MAX_BLOCK_MS_CONFIG = "max.block.ms";
/** <code>buffer.memory</code> */
public static final String BUFFER_MEMORY_CONFIG = "buffer.memory";
/** <code>retry.backoff.ms</code> */
public static final String RETRY_BACKOFF_MS_CONFIG = CommonClientConfigs.RETRY_BACKOFF_MS_CONFIG;
/** <code>compression.type</code> */
public static final String COMPRESSION_TYPE_CONFIG = "compression.type";
/** <code>metrics.sample.window.ms</code> */
public static final String METRICS_SAMPLE_WINDOW_MS_CONFIG = CommonClientConfigs.METRICS_SAMPLE_WINDOW_MS_CONFIG;
/** <code>metrics.num.samples</code> */
public static final String METRICS_NUM_SAMPLES_CONFIG = CommonClientConfigs.METRICS_NUM_SAMPLES_CONFIG;
/**
* <code>metrics.recording.level</code>
*/
public static final String METRICS_RECORDING_LEVEL_CONFIG = CommonClientConfigs.METRICS_RECORDING_LEVEL_CONFIG;
/** <code>metric.reporters</code> */
public static final String METRIC_REPORTER_CLASSES_CONFIG = CommonClientConfigs.METRIC_REPORTER_CLASSES_CONFIG;
/** <code>max.in.flight.requests.per.connection</code> */
public static final String MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION = "max.in.flight.requests.per.connection";
/** <code>retries</code> */
public static final String RETRIES_CONFIG = CommonClientConfigs.RETRIES_CONFIG;
/** <code>key.serializer</code> */
public static final String KEY_SERIALIZER_CLASS_CONFIG = "key.serializer";
/** <code>value.serializer</code> */
public static final String VALUE_SERIALIZER_CLASS_CONFIG = "value.serializer";
/** <code>connections.max.idle.ms</code> */
public static final String CONNECTIONS_MAX_IDLE_MS_CONFIG = CommonClientConfigs.CONNECTIONS_MAX_IDLE_MS_CONFIG;
/** <code>partitioner.class</code> */
public static final String PARTITIONER_CLASS_CONFIG = "partitioner.class";
/** <code>interceptor.classes</code> */
public static final String INTERCEPTOR_CLASSES_CONFIG = "interceptor.classes";
/** <code>enable.idempotence</code> */
public static final String ENABLE_IDEMPOTENCE_CONFIG = "enable.idempotence";
/** <code> transaction.timeout.ms </code> */
public static final String TRANSACTION_TIMEOUT_CONFIG = "transaction.timeout.ms";
/** <code> transactional.id </code> */
public static final String TRANSACTIONAL_ID_CONFIG = "transactional.id";
对于每一个参数具体含义及重要等级可以参考官网:http://kafka.apache.org/documentation/#producerconfigs
上面所有的参数,有三个是必须要自己指定的。
- **bootstrap.servers ** :kafka集群的地址列表。如:localhost:9092,localhost:9093 。多个之间采用","分隔。
- key.serializer :用来对消息的key值进行序列化
- value.serializer : 对消息的value值进行序列化
2、创建Producer对象
其主要的构建方法有如下的3个:
public KafkaProducer(final Map<String, Object> configs)
public KafkaProducer(Map<String, Object> configs, Serializer<K> keySerializer, Serializer<V> valueSerializer)
public KafkaProducer(Properties properties)
3、构建要发送的消息
在我们完成生产者实例后,接下来的工作就是构建消息,即创建org.apache.kafka.clients.producer.ProducerRecord
对象 ,topic和value值是必填的值。
public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value, Iterable<Header> headers)
public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value)
public ProducerRecord(String topic, Integer partition, K key, V value, Iterable<Header> headers)
public ProducerRecord(String topic, Integer partition, K key, V value)
public ProducerRecord(String topic, K key, V value)
public ProducerRecord(String topic, V value)
实例中是采用的是第四个构造函数来创建ProducerRecord对象。
生产者发送消息一般有两种方式:同步发送和异步发送。像示例中的发送方式就是同步的。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8m23bRLA-1585667238961)(assets/1585645796216.png)]
这里两个重载的send方法都会返回future对象,要想实现同步,需要用到Future对象实现。
如:
future.send(record).get();
kakfa的生产者发送消息如果发生可重试的异常且配置了重置参数,那么只要在规定重试次数内自行恢复,就不会抛出异常。retries参数的默认值为0;配置方式参考如下:
properties.put(ProducerConfig.RETRIES_CONFIG,3);
示例中配置的重试次数为5。如果重试了5次后还没有恢复,那么依旧会抛出异常。
采用同步发送的方式最为可靠,但是性能低。异步的发送就在send()方法中指定一个Callback回调方法。kafka在返回响应的时候来调用该函数来实现异步的发送确认。如通过以下示例进行异步发送:
producer.send(record,((metadata, exception) -> {
if(exception !=null){
exception.printStackTrace();
}else{
System.out.println(metadata.topic()+"-" + metadata.offset() + "-" + metadata.partition());
}
}));
4、序列化器
生产需要使用序列化器(Serializer)把对象转化为字节数组才能通过网络发送个kafka。在对应的消费端,消费者则需要使用反序列化器(Deserializer)把从kafka中收到的字节数组转换成相应的java对象。示例中的key和value都使用了字符串。
kafka默认是实现的序列化器的实现如下:
序列化接口定义的几个方法如下
default void configure(Map<String, ?> configs, boolean isKey) {
// intentionally left blank
}
byte[] serialize(String topic, T data);
default byte[] serialize(String topic, Headers headers, T data) {
return serialize(topic, data);
}
@Override
default void close() {
// intentionally left blank
}
configure方法是用来配置当前类的。serialize方法是用来执行序列操作的。而close方法是用来关闭当前的序列化器的,一般情况下close()是一个空方法。
5、分区器
消息在通过send()方法发往broker的过程中,有可能需要经过拦截器(interceptor)、序列器(serializer)及分区器(Partitioner)的一系列作用后才能被真正的发往broker.拦截器一般不是必需的,而序列化器是必需的 。消息经过序列化器后就要确定它发往的分区,如果消息(ProducerRecord)对象中指定了patition字段,那么不需要分区器,因为partition字段代表的就是其将要发送的分区号
可以通过org.apache.kafka.clients.producer.KafkaProducer#partitionsFor方法来获取一个主题目前所拥有的所有分区
如果没有指定,就使用配置的分区器来处理。以下代码是配置分区器的源码,这里配置是如果创建生产者实例的时候有配置分区器就使用配置的分区器,如果没有那么使用DefaultPartitioner进行分区。
this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);
org.apache.kafka.clients.producer.Partitioner
接口定义如下:
/**
* Compute the partition for the given record.
*
* @param topic The topic name
* @param key The key to partition on (or null if no key)
* @param keyBytes The serialized key to partition on( or null if no key)
* @param value The value to partition on or null
* @param valueBytes The serialized value to partition on or null
* @param cluster The current cluster metadata
*/
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);
/**
* This is called when partitioner is closed.
*/
public void close();
默认分区器选择分区的逻辑是:如果发送的消息(ProducerRecord)key值为空,那么就轮询所有分区。否则对key进行hash运算(采用murmur2算法,具备高性能,低碰撞性),根据获取到的hash值来选择对应的分区。源码如下所示:
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 = nextValue(topic);
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
int part = Utils.toPositive(nextValue) % availablePartitions.size();
return availablePartitions.get(part).partition();
} else {
// no partitions are available, give a non-available partition
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
// hash the keyBytes to choose a partition
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}
如果需要自定义一个分区,可以实现partitioner接口。实现后可以通过如下代码进行配置
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,MyPartition.class.getName())
6、拦截器
拦截器是早在kafka 0.10.0.0中就已经引入的一个功能,kafka一共有两种拦截器:生产者拦截器和消费者拦截器。这里我们将着重的介绍生产者拦截器,后期介绍《kafka消费者详解》的时候将会介绍消费者拦截器。
生产者拦截器的即可以在消息发送前做一些准备工作,比如按照某个规则过滤不符合要求的消息、修改消息的内容等。也可以用来在发送回调逻辑前做一些定制化的需求。比如统计类的工作。
以下是一个拦截器实现的示例:
public class MyProducerInterceptor implements ProducerInterceptor<String,String> {
private AtomicLong success = new AtomicLong();
private AtomicLong fail = new AtomicLong();
/**
* 能够让我们可以在发送前修改消息的内容,但是没有办法做到取消该条消息的发送
* @param record
* @return
*/
@Override
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
return record;
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
if (exception == null) {
success.incrementAndGet();
return;
}
fail.incrementAndGet();
}
@Override
public void close() {
double successRatio = (double)success.get()/(success.get() + fail.get());
System.out.println("成功率:" + successRatio *100 + "%");
}
@Override
public void configure(Map<String, ?> configs) {
}
}
给是生产者配置拦截器:
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,MyProducerInterceptor.class.getName());