1. 订阅主题
(1)订阅主题的全部分区
package com.bonc.rdpe.kafka110.consumer;
import java.util.Arrays;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
/**
* @Title Subscribe.java
* @Description 订阅多个主题的全部分区
* @Author YangYunhe
* @Date 2018-06-28 09:53:41
*/
public class Subscribe {
public static void main(String[] args) throws Exception {
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.42.89:9092,192.168.42.89:9093,192.168.42.89:9094");
props.put("group.id", "dev3-yangyunhe-group001");
props.put("auto.offset.reset", "earliest");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer consumer = new KafkaConsumer<>(props);
String[] topics = new String[]{"dev3-yangyunhe-topic001", "dev3-yangyunhe-topic002"};
// 订阅指定主题的全部分区
consumer.subscribe(Arrays.asList(topics));
try {
while (true) {
/*
* poll() 方法返回一个记录列表。
* 每条记录都包含了记录所属主题的信息、记录所在分区的信息、记录在分区里的偏移量,以及记录的键值对。
* 我们一般会遍历这个列表,逐条处理这些记录。
* 传给poll() 方法的参数是一个超时时间,用于控制 poll() 方法的阻塞时间(在消费者的缓冲区里没有可用数据时会发生阻塞)。
* 如果该参数被设为 0,poll() 会立即返回,否则它会在指定的毫秒数内一直等待 broker 返回数据。
* 而在经过了指定的时间后,即使还是没有获取到数据,poll()也会返回结果。
*/
ConsumerRecords records = consumer.poll(1000);
for (ConsumerRecord record : records) {
System.out.println("topic = " + record.topic() + ", partition = " + record.partition());
}
}
} finally {
/*
* 在退出应用程序之前使用 close() 方法关闭消费者。
* 网络连接和 socket 也会随之关闭,并立即触发一次再均衡,而不是等待群组协调器发现它不再发送心跳并认定它已死亡,
* 因为那样需要更长的时间,导致整个群组在一段时间内无法读取消息。
*/
consumer.close();
}
}
}
(2) 用正则表达式来订阅主题的全部分区
KafkaConsumer consumer = new KafkaConsumer<>(props);
// 订阅所有以"dev3"开头的主题的全部分区
Pattern pattern = Pattern.compile("dev3.*");
consumer.subscribe(pattern, new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection arg0) {
// TODO nothing:再均衡监听器会在之后的文章中进行讨论
}
@Override
public void onPartitionsAssigned(Collection arg0) {
// TODO nothing:再均衡监听器会在之后的文章中进行讨论
}
});
...... 省略部分重复代码
try {
# 消费数据
}
(3) 订阅指定的分区
KafkaConsumer consumer = new KafkaConsumer<>(props);
TopicPartition[] topicPartitions = new TopicPartition[]{
new TopicPartition("dev3-yangyunhe-topic001", 0),
new TopicPartition("dev3-yangyunhe-topic002", 1)
};
// 订阅"dev3-yangyunhe-topic001"的分区0和"dev3-yangyunhe-topic002"的分区1
consumer.assign(Arrays.asList(topicPartitions));
...... 省略部分重复代码
try {
# 消费数据
}
2. 消费者常用配置
(1) fetch.min.bytes
- 类型:int
- 默认值:1
- 可设置值:[0,...]
- 重要性:高
- 说明:该属性指定了消费者从服务器获取记录的最小字节数。broker 在收到消费者的数据请求时,如果可用的数据量小于 fetch.min.bytes 指定的大小,那么它会等到有足够的可用数据时才把它返回给消费者。这样可以降低消费者和 broker 的工作负载,因为它们在主题不是很活跃的时候(或者一天里的低谷时段)就不需要来来回回地处理消息。如果没有很多可用数据,但消费者的 CPU 使用率却很高,那么就需要把该属性的值设得比默认值大。如果消费者的数量比较多,把该属性的值设置得大一点可以降低 broker 的工作负载。
(2) fetch.max.wait.ms
- 类型:int
- 默认值:500
- 可设置值:[0,...]
- 重要性:低
- 说明:我们通过 fetch.min.bytes 告诉 Kafka,等到有足够的数据时才把它返回给消费者。而 feth.max.wait.ms 则用于指定 broker 的等待时间,默认是如果没有足够的数据流入Kafka,消费者获取最小数据量的要求就得不到满足,最终导致 500ms 的延迟。如果 fetch.max.wait.ms 被设为 100ms,并且 fetch.min.bytes 被设为 1MB,那么 Kafka 在收到消费者的请求后,要么返回 1MB 数据,要么在 100ms 后返回所有可用的数据,就看哪个条件先得到满足。
(3) max.partition.fetch.bytes
- 类型:int
- 默认值:1048576
- 可设置值:[0,...]
- 重要性:高
- 说明:该属性指定了服务器从每个分区里返回给消费者的最大字节数。它的默认值是 1MB,也就是说,KafkaConsumer.poll() 方法从每个分区里返回的记录最多不超过 max.partition.fetch.bytes 指定的字节。如果一个主题有20个分区和5个消费者,那么每个消费者需要至少 4MB 的可用内存来接收记录。在为消费者分配内存时,可以给它们多分配一些,因为如果群组里有消费者发生崩溃,剩下的消费者需要处理更多的分区。max.partition.fetch.bytes 的值必须比 broker 能够接收的最大消息的字节数(通过 max.message.size 属性配置)大,否则消费者可能无法读取这些消息,导致消费者一直挂起重试。在设置该属性时,另一个需要考虑的因素是消费者处理数据的时间。消费者需要频繁调用 poll() 方法来避免会话过期和发生分区再均衡,如果单次调用 poll() 返回的数据太多,消费者需要更多的时间来处理,可能无法及时进行下一个轮询来避免会话过期。如果出现这种情况,可以把 max.partition.fetch.bytes 值改小,或者延长会话过期时间。
(4) session.timeout.ms
- 类型:int
- 默认值:10000
- 重要性:high
- 说明:该属性指定了消费者在被认为死亡之前可以与服务器断开连接的时间,默认是 1s。如果消费者没有在 session.timeout.ms 指定的时间内发送心跳给群组协调器,就被认为已经死亡,组协调器就会触发再均衡,把它的分区分配给群组里的其他消费者。该属性与heartbeat.interval.ms紧密相关。heartbeat.interval.ms 指定了 poll() 方法向协调器发送心跳的频率,session.timeout.ms 则指定了消费者可以多久不发送心跳。所以,一般需要同时修改这两个属性,heartbeat.interval.ms 必须比 session.timeout.ms 小,一般是 session.timeout.ms 的三分之一。如果 session.timeout.ms 是 3s,那么 heartbeat.interval.ms 应该是 1s。把 session.timeout.ms 值设得比默认值小,可以更快地检测和恢复崩溃的节点,不过长时间的轮询或垃圾收集可能导致非预期的再均衡。把该属性的值设置得大一些,可以减少意外的再均衡,不过检测节点崩溃需要更长的时间。
(5) auto.offset.reset
- 类型:latest
- 默认值:string
- 可设置值:[latest, earliest, none]
- 重要性:中等
- 说明:该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下(因消费者长时间失效,包含偏移量的记录已经过时并被删除)该作何处理。它的默认值是 latest,意思是说,在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)。另一个值是 earliest,意思是说,在偏移量无效的情况下,消费者将从起始位置读取分区的记录。none 则代表当偏移量失效后,直接抛出异常。
(6) enable.auto.commit
- 类型:boolean
- 默认值:true
- 重要性:中等
- 说明:该属性指定了消费者是否自动提交偏移量,默认值是 true。为了尽量避免出现重复数据和数据丢失,可以把它设为 false,由自己控制何时提交偏移量。如果把它设为 true,还可以通过配置 auto.commit.interval.ms 属性来控制提交的频率。
(7) partition.assignment.strategy
- 类型:list
- 默认值:org.apache.kafka.clients.consumer.RangeAssignor
- 可设置值:
org.apache.kafka.clients.consumer.RangeAssignor
org.apache.kafka.clients.consumer.RoundRobinAssignor
自定义的策略 - 重要性:中等
- 说明:PartitionAssignor 根据给定的消费者和主题,决定哪些分区应该被分配给哪个消费者。Kafka 有两个默认的分配策略。
- Range:该策略会把主题的若干个连续的分区分配给消费者。假设消费者 C1 和消费者 C2 同时订阅了主题 T1 和主题 T2,并且每个主题有 3 个分区。那么消费者 C1 有可能分配到这两个主题的分区 0 和分区 1,而消费者 C2 分配到这两个主题的分区2。因为每个主题拥有奇数个分区,而分配是在主题内独立完成的,第一个消费者最后分配到比第二个消费者更多的分区。只要使用了 Range 策略,而且分区数量无法被消费者数量整除,就会出现这种情况。
- RoundRobin:该策略把主题的所有分区逐个分配给消费者。如果使用 RoundRobin 策略来给消费者 C1 和消费者 C2 分配分区,那么消费者 C1 将分到主题 T1 的分区 0 和分区 2 以及主题 T2 的分区 1,消费者 C2 将分配到主题 T1 的分区 1 以及主题 T2 的分区 0 和分区 2。一般来说,如果所有消费者都订阅相同的主题(这种情况很常见),RoundRobin 策略会给所有消费者分配相同数量的分区(或最多就差一个分区)。
(8) client.id
- 类型:string
- 默认值:""
- 重要性:低
- 说明:该属性可以是任意字符串,代表消费的ID,broker 用它来标识从客户端发送过来的消息
(9) max.poll.records
- 类型:int
- 默认值:500
- 可设置值:[1,...]
- 重要性:中等
- 说明:该属性用于控制单次调用 poll() 方法最多能够返回的记录条数,可以帮你控制在轮询里需要处理的数据量。
(10) receive.buffer.bytes 和 send.buffer.bytes
receive.buffer.bytes
- 类型:int
- 默认值:65536(64K)
- 可设置值:[-1,...]
- 重要性:中等
send.buffer.bytes
- 类型:int
- 默认值:131072(128K)
- 可设置值:[-1,...]
- 重要性:中等
说明:这两个参数分别指定了 TCP socket 接收和发送数据包的缓冲区大小。如果它们被设为 -1,就使用操作系统的默认值。如果生产者或消费者与 broker 处于不同的数据中心,那么可以适当增大这些值,因为跨数据中心的网络一般都有比较高的延迟和比较低的带宽。