使用方式

KafkaProducer 发送消息主要有以下 3 种方式:

    Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
properties.setProperty("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.setProperty("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
ProducerRecord<String, String> record = new ProducerRecord<>("topic", "key", "value"); // 发送并忘记(fire-and-forget)
producer.send(record); // 同步发送
Future<RecordMetadata> future = producer.send(record);
RecordMetadata metadata = future.get(); // 异步发送
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) { }
}); producer.close();

具体的发送流程可以参考 KafkaProducer发送流程简析。

KafkaProducer 是线程安全的,多个线程可以共享同一个 KafkaProducer 对象。

配置解析

client.id

 该参数可以是任意的字符串,broker 会用它来识别消息的来源,会在日志和监控指标里展示。

bootstrap.servers

 该属性指定 broker 的地址列表。

 清单里不需要包含所有的 broker 地址,生产者会从给定的 broker 里查找到其他 broker 的信息。

 不过建议至少要提供两个 broker 的信息,一旦其中一个宕机,生产者仍然能够连接到集群上。

key.serializer & value.serializer

 这两个属性必须被设置为一个实现了org.apache.kafka.common.serialization.Serializer接口的类。

 生产者会使用这个类把键值对象序列化成字节数组。

receive.buffer.bytes & send.buffer.bytes

 设置 socket 读写数据时用到的 TCP 缓冲区大小。如果它们被设为 -1,就使用操作系统的默认值。

 当生产者或消费者与 broker 处于不同的机房时,可以适当增大这些值。

buffer.memory

 设置生产者内存缓冲区的大小,生产者用它缓冲要发送到服务器的消息。

 如果应用程序发送消息的速度超过发送到服务器的速度,会导致生产者空间不足。

 此时KafkaProducer.send()会阻塞等待内存释放,等待时间超过 max.block.ms 后会抛出超时异常。

compression.type

 该参数指定了消息被发送给 broker 之前,使用哪一种压缩算法(snappy,gzip或lz4)进行压缩。

 使用压缩可以降低网络传输开销和存储开销,而这往往是向 Kafka 发送消息的瓶颈所在。

batch.size

 该参数指定了一个批次可以使用的内存字节数(而不是消息个数)。

 消息批次ProducerBatch包含了一组将要发送至同个分区的消息,当批次被填满,批次里的所有消息会被立即发送出去。

 不过生产者并不一定都会等到批次被填满才发送,半满甚至只包含一个消息的批次也可能被发送。

 所以就算把批次大小设置得很大,也不会造成延迟,只是会占用更多的内存而已。

 但如果设置得太小,生产者会频繁地发送消息,会增加一些额外的网络开销。

linger.ms

 该参数指定了生产者在发送批次之前等待的时间。

 生产者会在批次填满或等待时间达到 linger.ms 时把批次发送出去。

 设置linger.ms>0会增加延迟,但也会提升吞吐量(一次性发送更多的消息,每个消息的开销就变小了)。

acks

 参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的。

 这个参数决定令消息丢失的可能性:

  • acks=0 生产者发出消息后不等待来自服务器的响应
    如果当中出现了问题,导致服务器没有收到消息,那么生产者就无从得知,消息也就丢失了。
    不过,因为生产者不需要等待服务器的响应,所以它可以以网络能够支持的最大速度发送消息,从而达到很高的吞吐量。

  • acks=1 只要集群的 leader 节点收到消息,生产者就会收到一个来自服务器的成功响应
    如果消息无法到达 leader 节点(比如:leader节点崩溃,新的 leader 还没有被选举出来),生产者会收到一个错误响应。
    为了避免数据丢失,生产者会重发消息。不过,如果一个没有收到消息的节点成为新 leader,消息还是会丢失。

    这个时候的吞吐量取决于使用的是同步发送还是异步发送:

    • 发送端阻塞等待服务器的响应(通过调用 Future.get() 方法),显然会增加延迟(在网络上传输一个来回的延迟)
    • 发送端使用回调可以缓解延迟问题,不过吞吐量仍受在途消息数量的限制(比如:生产者在收到服务器响应之前可以发送多少个消息)
  • acks=all 只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应
    这种模式是最安全的,就算有服务器发生崩溃,数据也不会丢失。
    不过,它的延迟比 acks=1 时更高,因为我们要等待不只一个服务器节点接收消息。

retries

 该参数决定了生产者可以重发消息的次数(每次重试之间等待 retry.backoff.ms)。

 服务器返回临时性的错误(比如:分区找不到 leader)时,生产者会自动重试,没必要在代码逻辑里处理可重试的错误。

 作为开发者,只需要处理那些不可重试的错误(比如:消息字节数超过单个发送批次上限)或重试次数超出上限的情况即可。

max.in.flight.requests.per.connection

 该参数指定生产者,最多可以发送未响应在途消息批次数量。

 在途消息批次越多,会占用更多的内存,不过也会提升吞吐量。

 当retries > 0且max.in.flight.requests.per.connection > 1时,可能出现消息乱序。

 如果第一个批次消息写入失败,而第二个批次写入成功,broker 会重试写入第一个批次。

 如果此时第一个批次也写入成功,那么两个批次的顺序就反过来了。

 一般不建议设置retries=0,而是令max.in.flight.requests.per.connection = 1来保证消息顺序。

 在生产者尝试发送第一批消息时,就不会有其他的消息发送给 broker,即使发生重试消息也不会乱序。

 不过这样会严重影响生产者的吞吐量,所以只有在对消息的顺序有严格要求的情况下才能这么做。

高级特性

幂等

当 broker 失效时生产者可能会自动重试,导致一条消息被重复写入多次。

为了避免这种情况,Kafka 在生产者端提供来幂等保证:同一条消息被生产者发送多次,但在 broker端这条消息只会被写入日志一次。

在发送端设置 enable.idempotence = true 可以开启幂等性,此时配置同时满足以下条件:

  • max.in.flight.requests.per.connection ≤ 5
  • retries > 0
  • acks = all

其工作机制如下:

  • producer 在初始化时必须分配一个 PIDproducer id该过程对用户来说是完全透明的)
  • 发送到 broker 端的每批消息都会被赋予一个单调递增的 SNsequence number用于消息去重(每个分区都有独立的序列号)
  • 接收到消息的 broker 会将批次的(PID, SN)信息一同持久化到对应的分区日志中(保证 leader 切换后去重仍然生效)

若重试导致 broker 接收到小于或等于已知最大序列号的消息,broker 会拒绝写入这些消息,从而保证每条消息也只会被保存在日志中一次。

由于每个 producer 实例都会被分配不同的 PID,该机制只能保证单个 producer 实例的幂等性,无法实现协同多个 producer 实现幂等。

事务

Kafka 事务可以实现 producer 对多个主题和分区的原子写入,并且保证 consumer 不会读取到未提交的数据。

Kafka 要求应用程序必须提供一个全局唯一的 TIDtransactional id:

初始化时,producer 首先要向 broker 集群注册其 TID,broker 会根据给定的 TID 检查是否存在未完成的事务。

如果某个 producer 实例失效,该机制能够保证下一个拥有相同 TID 的实例首先完成之前未完成的事务。

此外,broker 还会为自动每个 producer 分配一个epoch用于隔离fencing out失效但仍存活的 producer:

当 producer 参与事务时,broker 会检查是否存在相同的 TID 且 epoch 更大的活跃 producer。

如果存在,则认为当前 producer 是一个僵尸实例zombie instance并拒绝为其提供服务,防止其破坏事务的完整性。

下面是两个常见的应用场景:

实现跨主题原子写入

    Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
properties.setProperty("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.setProperty("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.setProperty("enable.idempotence", "true"); // 开启幂等
properties.setProperty("transactional.id", "my-transaction-id"); // 设置事务ID KafkaProducer<String, String> producer = new KafkaProducer<>(properties); ProducerRecord<String, String> record1 = new ProducerRecord<>("topic1", "key1", "value1");
ProducerRecord<String, String> record2 = new ProducerRecord<>("topic2", "key2", "value2");
ProducerRecord<String, String> record3 = new ProducerRecord<>("topic3", "key3", "value3"); producer.initTransactions(); // 初始化事务(只需执行一次)
try {
producer.beginTransaction(); // 开始事务 // 向多个不同的 topic 写入消息
producer.send(record1);
producer.send(record2);
producer.send(record3); producer.commitTransaction(); // 提交事务
} catch (ProducerFencedException e) {
producer.close(); // 事务ID 已被占用
} catch (KafkaException e) {
producer.abortTransaction();
}

实现 read-process-write 模式

    final String groupID = "my-group-id";

    Properties producerProps = new Properties();
producerProps.setProperty("bootstrap.servers", "localhost:9092");
producerProps.setProperty("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
producerProps.setProperty("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
producerProps.setProperty("enable.idempotence", "true"); // 开启幂等
producerProps.setProperty("transactional.id", "my-transaction-id"); // 设置事务ID Properties consumerProps = new Properties();
consumerProps.setProperty("bootstrap.servers", "localhost:9092");
consumerProps.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
consumerProps.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
consumerProps.setProperty("isolation.level","read_committed"); // 设置隔离级别
consumerProps.setProperty("group.id", groupID); // 设置消费者组群ID KafkaProducer<String, String> producer = new KafkaProducer<>(producerProps);
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps); producer.initTransactions();
consumer.subscribe(Collections.singletonList("ping")); while (true) { ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE); // 读取消息 try {
producer.beginTransaction(); // 开启事务 // 处理消息(可以是任意业务场景)
Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();
for(ConsumerRecord<String, String> record : records){
offsets.put(new TopicPartition(record.topic(), record.partition()), new OffsetAndMetadata(record.offset())); // 记录消费偏移量
producer.send(new ProducerRecord<>("pong", record.value())); // 发送消息
} producer.sendOffsetsToTransaction(offsets, groupID); // 提交消费偏移量
producer.commitTransaction(); // 事务提交
} catch (ProducerFencedException e) {
producer.close(); // 事务ID 已被占用
} catch (Exception e){
producer.abortTransaction(); // 回滚事务
}
}

参考资料

KafkaProducer 简析的更多相关文章

  1. 简析.NET Core 以及与 .NET Framework的关系

    简析.NET Core 以及与 .NET Framework的关系 一 .NET 的 Framework 们 二 .NET Core的到来 1. Runtime 2. Unified BCL 3. W ...

  2. 简析 .NET Core 构成体系

    简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代 ...

  3. RecycleView + CardView 控件简析

    今天使用了V7包加入的RecycleView 和 CardView,写篇简析. 先上效果图: 原理图: 这是RecycleView的工作原理: 1.LayoutManager用来处理RecycleVi ...

  4. Java Android 注解(Annotation) 及几个常用开源项目注解原理简析

    不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...

  5. PHP的错误报错级别设置原理简析

    原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...

  6. Android 启动过程简析

    首先我们先来看android构架图: android系统是构建在linux系统上面的. 所以android设备启动经历3个过程. Boot Loader,Linux Kernel & Andr ...

  7. Android RecycleView + CardView 控件简析

    今天使用了V7包加入的RecycleView 和 CardView,写篇简析. 先上效果图: 原理图: 这是RecycleView的工作原理: 1.LayoutManager用来处理RecycleVi ...

  8. Java Annotation 及几个常用开源项目注解原理简析

    PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...

  9. 【ACM/ICPC2013】POJ基础图论题简析(一)

    前言:昨天contest4的惨败经历让我懂得要想在ACM领域拿到好成绩,必须要真正的下苦功夫,不能再浪了!暑假还有一半,还有时间!今天找了POJ的分类题库,做了简单题目类型中的图论专题,还剩下二分图和 ...

随机推荐

  1. spring-boot-starter

    Spring Boot Starter 是在 SpringBoot 组件中被提出来的一种概念,stackoverflow 上面已经有人概括了这个 starter 是什么东西,想看完整的回答戳 这里. ...

  2. AlanShan数据库课程设计报告

    目    录 1.绪论.... 2 1.1前言... 2 1.2社会背景... 2 1.3超市背景... 3 2.系统可行性研究.... 4 2.1 技术可行性研究... 4 2.2 经济可行性研究. ...

  3. 第7.21节 Python抽象类—register注册虚拟子类

    上两节介绍了Python抽象类的真实子类的定义和使用,本节介绍另一种抽象类的实现方法:虚拟子类方法. 一.    相关概念 虚拟子类是将其他的不是从抽象基类派生的类"注册"到抽象基 ...

  4. 第11.12节 Python元字符“|”支持的正则表达式多选一匹配模式

    re模块支持多个正则表达式使用"|"(逻辑或)模式来组合,扫描目标字符串时, '|' 分隔开的正则表达式组合从左到右进行匹配,只要其中一个匹配成功就认为该组合匹配成功,不再进行组合 ...

  5. PyQt(Python+Qt)学习随笔:QTreeView树形视图的uniformRowHeights属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 uniformRowHeights属性用于控制视图中所有数据项是否保持相同高度,所有高度都与视图中第 ...

  6. Hadoop 中HDFS、MapReduce体系结构

    在网络环境方面,作为分布式系统,Hadoop基于TCP/IP进行节点间的通信和传输. 在数据传输方面,广泛应用HTTP实现. 在监控.通知方面,Hadoop等分布式大数据软件则广泛使用异步消息队列等机 ...

  7. ASP.NET 漂亮美观的验证码

    效果预览: 1.随机码和图片流生成 public class ValidateCode { /// <summary> /// 產生圖形驗證碼. /// </summary> ...

  8. 在Linux中使用Dbeaver等GTK3界面的软件出现频繁闪烁的问题解决

    问题复现 复现环境LinuxMint 19 Dbeaver: 7.3 输入法: ibus + ibus-table-wubi 如图,当光标移动到Sql Editor中会不停的闪-- 解决过程 先百度. ...

  9. 【eJOI2020】考试(dp & 树状数组优化)

    Description \(n\) 个正整数排成一列,每个位置 \(i\) 有一个初始值 \(A_i\) 以及目标值 \(B_i\). 一次操作可以选定一个区间 \([l, r]\),并将区间内所有数 ...

  10. Power BI八年回望记

    本人从事BI,数据仓库领域相关工作15个年头,这15年目睹了这个方向从火爆到逐渐被大数据领域不断吞食.中间零散关注Power BI好长时间,也算目睹了它的成长. 那天在网络上搜索power bi,无意 ...