本章将介绍Kafka的基础应用,并提供相关使用示例,包括Kafka脚本、客户端、配额等内容,以帮助读者掌握Kafka的使用方式。
3.1 脚本
前面我们使用zookeeper-server-start.sh脚本启动ZooKeeper服务,除了该脚本外,Kafka在安装包bin目录中还提供了很多脚本,帮忙我们更好地使用Kafka。
3.1.1 主题管理
(1)使用kafka-topics.sh创建主题。
$ ./bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 2 --partitions 3 --topic hello-topic
Created topic hello-topic.
该命令创建了一个新主题,新主题存在3个分区,每个分区存在2个副本。kafka-topics.sh提供了以下参数:
-
--bootstrap-server:Kafka Broker列表,这里只需要列举Kafka集群的部分节点信息,Kafka脚本(或者客户端)会自动查找Kafka集群所有节点信息。
-
--create:创建主题。
-
--partitions:分区数量,该主题会划分为多少个分区。
-
--replication-factor:副本数量,每个分区总共有多少个副本。该数量需要小于或等于Broker的数量。
-
--replica-assignment:指定副本分配方案,不能与—partitions和--replication- factor参数同时使用(第7章详细介绍该参数)。
-
--alter:变更主题,如修改分区数量、AR副本列表等。
-
--config :设置主题的配置项,Kafka中可以针对主题设置配置,如cleanup.policy、compression.type、delete.retention.ms等,这些配置项会在后面内容中介绍。
-
--delete:删除主题。
-
--list:列举有效的主题。
-
--describe:查询主题详细信息。
提示:从Kafka 3.0开始,很多Kafka脚本都已经移除了zookeeper参数。
(2)使用以下命令查看主题详细信息。
$./bin/kafka-topics.sh --describe --bootstrap-server localhost:9092 --topic hello-topic
Topic: hello-topic TopicId: PXL26BtdQsWxFJSi3glVag PartitionCount: 3 ReplicationFactor: 2 Configs: segment.bytes=1073741824
Topic: hello-topic Partition: 0 Leader: 2 Replicas: 2,0 Isr: 2,0
Topic: hello-topic Partition: 1 Leader: 1 Replicas: 1,2 Isr: 1,2
Topic: hello-topic Partition: 2 Leader: 0 Replicas: 0,1 Isr: 0,1
该命令会输出主题每个分区的leader副本、AR副本列表、ISR等信息。
3.1.2 生产者与消费者
(1)使用kafka-console-producer.sh脚本可以给指定主题发送消息。
$ ./bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic hello-topic
> hello kafka(输入消息内容,回车发送)
(2)使用kafka-console-consumer.sh脚本可以消费指定主题的消息。
$ ./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic hello-topic --group hello-group --from-beginning
hello-kafka(收到消息)
- --group:指定消费者所在消费组,如果不指定,则脚本自动为我们指定一个消费组:console-consumer-<随机数>,随机数小于100000。
kafka-console-producer.sh、kafka-console-consumer.sh同样提供了很多参数,这些参数在3.2小节会进行详细说明。
(3)使用kafka-consumer-groups.sh脚本可以管理消费组,如查询消费组列表。
$ ./bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list
hello-group
使用以下命令可以查看指定消费组的详细信息:
$ ./bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG ...
my-group my-topic 0 917 924 7 ...
my-group my-topic 1 0 0 0 ...
my-group my-topic 2 0 0 0 ...
该命令会输出指定消费组订阅的所有主题、分区、最新消费偏移量(CURRENT-OFFSET)、分区最新偏移量(LOG-END-OFFSET)、待消费数量(LAG)等信息。我们可以通过待消费数量(LAG)判断消费者是否消费滞后:消费者消费速度跟不上生产者生产速度。从分区最新偏移量(LOG-END-OFFSET)可以看到,hello-topic可能存在数据不均衡的问题,大量的数据存储在Partition0中。kafka-consumer-groups.sh脚本还支持以下关键参数:
-
--state:查看消费组状态。
-
--members:查看消费组成员。
-
--offsets:查询ACK偏移量。
-
--reset-offsets:重置消费组的ACK偏移量。
3.1.3 动态配置
前面我们使用Kafka安装目录下的config/server.properties配置文件启动Kafka服务,该配置文件可以设置Broker配置项。如果我们要修改Kafka相关配置,那么必须修改该配置文件并重启Kafka服务。在生产环境中,并不能随便重启Kafka服务,所以Kafka提供了动态配置机制,允许我们在Kafka运行时修改配置项,而Broker服务会在运行时定时拉取这些动态配置项的最新值。
使用kafka-configs.sh脚本可以管理动态配置。该脚本可以针对Kafka的主题、Broker、用户、客户端等对象设置动态配置。
提示:可以执行./bin/kafka-configs.sh --help命令查看kafka-configs.sh的使用方式及支持的动态配置。
(1)对指定主题添加动态配置。
$ ./bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter --topic hello-topic --add-config 'max.message.bytes=1048576'
对指定Broker节点添加动态配置:
$ ./bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter --broker 0 --add-config 'log.segment.bytes=788888888'
(2)查看指定主题的动态配置。
$ ./bin/kafka-configs.sh --bootstrap-server localhost:9092 --describe --topic hello-topic
查看指定Broker节点的动态配置:
$ ./bin/kafka-configs.sh --bootstrap-server localhost:9092 --describe --broker 0
(3)kafka-configs.sh脚本也可以设置默认的动态配置。例如,为所有的Broker节点设置一个默认的动态配置。
$ ./bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter --broker-defaults --add-config 'log.segment.bytes=788888888'
使用以下命令可以查看默认动态配置:
$ ./bin/kafka-configs.sh --bootstrap-server localhost:9092 --describe --broker-defaults
那么如果在Broker配置文件、默认动态配置、指定BrokerId的动态配置中,都存在同一个配置项,哪个配置项的优先级更高呢?配置优先级为:Broker配置文件<默认动态配置<指定BrokerId的动态配置
(4)使用以下命令删除动态配置。
$ ./bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter --broker 0 --delete-config 'log.segment.bytes'
Kafka还提供了其他脚本,如kafka-producer-perf-test.sh与kafka-consumer-perf-test.sh脚本可以进行性能测试,kafka-get-offsets.sh脚本可以获取ACK偏移量。本书就不一一介绍了,读者可以自行了解。
3.2 客户端
Kafka官方提供了Java客户端,用于发送、接收消息。下面介绍如何使用Kafka的客户端。
3.2.1 生产者
1.生产者示例(1)添加Kafka客户端的Maven引用。
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.0.0</version>
</dependency>
(2)编写一个简单的Kafka生产者。
public class BasicProducer {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties props = new Properties();
props.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
props.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 【1】
KafkaProducer producer = new KafkaProducer<String, String>(props);
// 【2】
List<Future<RecordMetadata>> futureList = new ArrayList<>();
for(int i = 0; i < 10; i++) {
Future<RecordMetadata> future = producer.send(
new ProducerRecord<String, String>("hello-topic", String.valueOf(i), "message-" + i));
futureList.add(future);
}
// 【3】
for (Future<RecordMetadata> future : futureList) {
System.out.println("send:" + future);
}
}
}
【1】构建生产者KafkaProducer,并使用Properties指定生产者配置项,如bootstrap.servers等。
【2】调用KafkaProducer#send方法发送消息。该方法异步发送消息,返回一个Future结果。
【3】打印Future结果,这里异步发送了所有消息后再一起打印结果,这样才能使用生产者批量发送机制。2.消息属性Kafka为消息定义了ProducerRecord类型:
public class ProducerRecord<K, V> {
private final String topic;
private final Integer partition;
private final Headers headers;
private final K key;
private final V value;
private final Long timestamp;
...
}
可以看到,Kafka消息中包含了主题(topic)、分区(partition)、消息头(header,可以携带额外的键值对,默认为空)、消息键(key)、消息值(value)、时间戳(timestamp)等信息。
提示:Kafka中也会将消息称为记录(record)。
3.生产者配置上面的例子中仅使用了基础的生产者配置项,Kafka提供了很多配置项,这些配置项可以帮助我们更好地使用客户端。常用的配置项如下:
-
bootstrap.servers:Broker集群地址,只需要配置集群中的部分节点即可。
-
key.serializer、value.serializer:消息键、值的序列化器。
-
client.id:作为客户端标志,发送给Broker,用于跟踪请求源、在配额机制中标识客户端等场景。
-
ack:生产者和Broker(客户端会将该参数发送给Broker)判断消息是否写入成功的策略,有如下取值。
0:生产者正常发送消息后不需要等待任何Broker响应,就可以判定消息写入成功。这样可以以最大速度发送消息,但可能丢失消息(由于网络阻塞、Broker故障等原因)。
1:只要leader副本收到并保存消息,就会返回成功响应给生产者,生产者收到响应后就会认为该消息写入成功。如果在follow副本还没有同步这些消息前,leader副本就因故障下线了,则这些消息将丢失。
-1:也可以配置为all,leader副本收到消息后,需要等待ISR(已同步副本)中所有副本都同步消息后,才返回成功响应给生产者,这种模式最安全。
-
batch.size:消息批次大小上限,默认值为16384(16KB)。生产者会将消息缓存到消息批次(batch)中,直到批次大小达到该配置值,再将消息批次发送给Broker。
-
linger.ms:消息延迟时间上限。如果消息批次中消息缓存时间达到该配置值,生产者就会发送该消息批次(即使消息批次大小未达到上限),默认值为0。批量发送消息减少了网络通信次数,并且有助于Broker利用磁盘顺序读写机制提高I/O效率,从而提高Kafka性能,所以可以根据实际情况(生产者内存大小、消息及时性要求)适当增大batch.size、linger.ms的配置值。
-
buffer.memory:生产者整个缓冲区(即所有消息批次)的大小上限,默认值为33554432(32MB)。
-
max.request.size:单个消息的大小上限,默认值为1048576(1MB)。
-
compression.type:生产者生成的所有数据的压缩类型。默认值为none(即无压缩),有效值为none、gzip、snappy、lz4 或 zstd。
-
max.block.ms:指定生产者最长阻塞等待时间,默认值为60000(1分钟)。生产者发送消息或获取集群元数据时,如果当前缓冲区已满或没有可用的元数据,那么生产者将阻塞等待,该配置项指定了这两个操作最长的阻塞时间。
-
request.timeout.ms:指定客户端等待请求响应的最长等待时间,默认值为30000(30秒)。如果等待超时且没有收到响应,那么客户端将在必要时重新发送消息,如果重试次数用尽,则请求失败。
-
retries、retry.backoff.ms:retires指定消息发送失败后自动重发的次数,默认值为2147483647;retry.backoff.ms指定重发的时间间隔,默认值为100(ms)。
-
delivery.timeout.ms:发送消息的最长时间,即多次重试发送所允许的最长时间。超过该配置值指定时间且未成功发送的消息将发送失败,不允许再重试(即使重试次数没用完)。该配置值应该大于request.timeout.ms+linger.ms,默认值为120000(2分钟)。
-
max.in.flight.requests.per.connection:每个连接上等待响应的最大消息数量,默认值为5,即如果存在5个已发送但Broker未返回响应的消息,则不允许再发送新的消息。
-
metadata.max.idle.ms:集群元数据有效时间,默认值为300000(5分钟)。生产者会缓存集群元数据,当元数据缓存时间超过该配置值时,缓存将失效,生产者会获取新的元数据,以发现集群变化,如主题、Broker的变化。
-
enable.idempotence:是否启动生产者幂等机制,默认值为true,后面会详细分析。
-
receive.buffer.bytes、send.buffer.bytes:这两个配置项分别指定了TCP Socket接收缓冲区和发送缓冲区的大小,如果设置成-1,则使用操作系统的默认值。如果客户端与Broker 网络通信延迟比较高,则可以适当增大这两个配置项的值。
-
reconnect.backoff.ms:当连接失败时,尝试重新连接Broker之前的等待时间(也称退避时间),避免频繁地连接主机,默认值为50(ms)。
-
partitioner.class:指定生产者分区器实现类,该分区器负责将消息划分给不同的分区,Kafka提供了如下消息分区器:
RoundRobinPartitioner:轮询分区器,该分区器使用round-robin轮询的方式将消息指定给存活的分区。
UniformStickyPartitioner:黏性分区器,该分区器会尽可能地先向一个分区发送消息,将这个分区的缓冲区快速填满后再切换到下一个分区,这样可以降低消息的发送延迟。 DefaultPartitioner:默认分区器,使用消息键的Hash值对主题分区数量取模,得到该消息的分区。如果消息键为空,则使用同UniformStickyPartitioner一样的策略,集中向一个分区发送键为空的消息,让这个分区的缓冲区快速填满。
用户也可以实现org.apache.kafka.clients.producer.Partitioner接口来自定义消息分区器,并通过生产者配置项partitioner.class指定自定义的消息分区器。
4.生产者拦截器Kafka提供了ProducerInterceptor接口,用于实现生产者拦截器,在生产者执行某些操作时添加额外的处理逻辑。ProducerInterceptor接口提供了以下方法:
-
onSend:发送消息前触发该方法,通过该方法可以修改发送消息的内容。
-
onAcknowledgement:生产者收到Broker返回响应(成功或者异常)时触发该方法。
-
close:拦截器被关闭时触发该方法(关闭生产者时会关闭拦截器),可以执行一些清理资源的工作。
下面展示一个生产者拦截器的使用示例。
实现ProducerInterceptor接口,统计消息发送数量:
public class CounterInterceptor implements ProducerInterceptor<String, String> {
private AtomicInteger sendCount = new AtomicInteger(0);
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
System.out.println("send count:" + sendCount.incrementAndGet());
return record;
}
...
}
(2)通过生产者配置interceptor.classes,将拦截器注册到生产者中。
Properties props = new Properties();
...
List<String> interceptors = new ArrayList<>();
interceptors.add(CounterInterceptor.class.getName());
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
KafkaProducer producer = new KafkaProducer<String, String>(props);
这样就可以使用拦截器了。生产者配置interceptor.classes指定了一个拦截器列表,生产者会按照该列表中的顺序执行其中的拦截器。
3.2.2 消费者
1.消费者示例
(1)添加Kafka客户端的Maven引用,与生产者相同。
(2)编写一个简单的Kafka消费者。
public class BasicConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.GROUP_ID_CONFIG, "hello-group");
// 【1】
KafkaConsumer consumer = new KafkaConsumer<String, String>(props);
// 【2】
consumer.subscribe(Arrays.asList("hello-topic"));
// 【3】
for(;;) {
ConsumerRecords<String, String> records = consumer.poll (Duration.ofSeconds(5));
// 【4】
for (ConsumerRecord<String, String> record : records) {
System.out.println("receive:" + record);
}
}
}
}
【1】创建消费者KafkaConsumer,并使用Properties指定配置项,如group.id等。
【2】调用KafkaConsumer#subscribe方法订阅指定主题。
【3】调用KafkaConsumer#poll方法拉取数据,该方法的参数指定请求的最长阻塞时间。Kafka消费者使用“拉模式”拉取消息。
【4】处理拉取到的消息。
2.消费者配置下面介绍Kafka消费者常用的配置项。
-
group.id:消费组名称。
-
enable.auto.commit:消费者是否自动提交ACK偏移量,默认值为true。
-
auto.commit.interval.ms:指定提交ACK偏移量的时间间隔,默认值为5000(5秒)。
-
auto.offset.reset:如果该消费组是新的消费组或者消费组ACK偏移量已经被清除,指定消费者从哪里开始消费,存在以下选项: > earliest:从最早的偏移量开始消费,即消费所有的历史消息。 > latest:从最近的偏移量开始消费,只消费新的消息,默认值。 > none:抛出异常。
-
fetch.max.bytes:每次拉取数据的最大数据量,默认值为52428800(50MB)。
-
max.partition.fetch.bytes:一个分区中每次拉取数据的最大数据量,默认值为1048576(1MB)。
-
fetch.min.bytes:每次拉取数据的最小数据量,消费者会要求Broker聚集消息,直到消息数据量达到该配置值后再将数据返回给消费者,默认值为1(字节)。
-
fetch.max.wait.ms:拉取数据的最大阻塞时间。如果拉取数据时阻塞时间达到该配置值,Broker立刻返回数据(即使数据量达不到fetch.min.bytes),默认值为500(ms)。
-
max.poll.records:每次拉取消息的最大数量,默认值为500。
-
session.timeout.ms:消费者超时时间,如果Broker超过该时间没有收到消费者心跳,则认为该消费者因故障而失效,默认值为45000(45秒)。
-
max.poll.interval.ms:指定两次拉取数据的最大时间间隔,默认值为300000(5分钟),注意消费者每次通过调用Consumer#poll方法拉取到的消息需要在该配置项指定的时间内处理完成,并及时调用下一次Consumer#poll方法,否则Kafka认为该消费者出现故障,并执行消费者重平衡操作,这样会影响消费者性能。
-
metadata.max.age.ms:自动刷新集群元数据的时间间隔,默认值为300000(5分钟)。
-
heartbeat.interval.ms:心跳发送时间间隔,默认值为3000(3秒)。消费者需要定时发送心跳给Broker,用于保持会话处于活动状态。注意该配置值必须小于session.timeout.ms,通常需要小于session.timeout.ms配置项的1/3。
-
partition.assignment.strategy:消费者分区分配策略,指定将分区分配给消费者的具体策略,默认值为RangeAssignor。这是一个重要的配置项,在第9章将详细介绍。
消费者同样支持receive.buffer.bytes、send.buffer.bytes、request.timeout.ms、connections.max. idle.ms等配置项,作用与生产者一样,不再赘述。
3. ACK提交方式在上面的例子中,我们使用了自动提交ACK的方式,由消费者定时将最新已处理成功的ACK偏移量发送给Broker。我们也可以使用手动提交ACK的方式,自行将ACK偏移量提交给Broker。
将上面的消费者例子调整为如下代码:
// 【1】
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
KafkaConsumer consumer = new KafkaConsumer<String, String>(props);
consumer.subscribe(Arrays.asList("hello-topic"));
for(;;) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(5));
for (ConsumerRecord<String, String> record : records) {
...
}
// 【2】
consumer.commitAsync();
}
【1】关闭消费者自动提交ACK偏移量的机制。
【2】处理消息后,调用Consumer#commitAsync方法提交ACK偏移量。
4.正则表达式订阅在上面的例子中,我们使用主题名集合订阅主题。我们也可以使用正则表达式订阅主题:所有主题名匹配该正则表达式模式的主题都可以被订阅。
Pattern pattern = Pattern.compile(".+-topic");
consumer.subscribe(pattern);
以上示例就可以同时订阅my-topic、user-topic两个主题。消费者会定时更新Kafka元数据(由配置项metadata.max.age.ms指定时间间隔),如果Kafka中创建了新的匹配正则表达式的主题,则消费者更新元数据后也可以订阅。相关配置:
- exclude.internal.topics:使用正则表达式订阅时是否过滤Kafka内部主题。Kafka定义了两个内部主题:_consumer_offsets和_transaction_state,分别用于存储ACK偏移量和Kafka事务信息,该配置项的默认值为true,即不能使用subscribe(Pattern)的方式来订阅内部主题(可以使用subscribe(Collection)的方式)。
5.消费定位Kafka提供了KafkaConsumer#seek方法,可以让消费者从指定位置开始消费:
consumer.seek(new TopicPartition("hello-topic", 0), 10);
上述代码会将该消费者在"hello-topic"主题0分区的消费位置定位到偏移量10,该消费者将重新消费偏移量为10的消息及其后续的消息。
由于Kafka并没有提供定位到指定时间的方法,因此如果我们需要定位到某个时间点,那么可以使用如下方式:
// 【1】
consumer.subscribe(Arrays.asList("hello-topic"), new ConsumerRebalanceListener() {
public void onPartitionsAssigned(Collection<TopicPartition> collection) {
// 【2】
Map<TopicPartition, Long> timestampsToSearch = new HashMap<>();
timestampsToSearch.put(new TopicPartition("hello-topic", 0),
System.currentTimeMillis() - 1000 * 60);
Map<TopicPartition, OffsetAndTimestamp> offsetMap =
consumer.offsetsForTimes (timestampsToSearch);
// 【3】
offsetMap.forEach((tp, OffsetAndTime) -> {
if(OffsetAndTime != null) {
consumer.seek(tp, OffsetAndTime.offset());
}
});
}
...
});
【1】订阅hello-topic主题并注册ConsumerRebalanceListener回调类,当订阅成功后再执行【2】、【3】步骤,重定位消费位置。注意:消费者每次重平衡都会触发ConsumerRebalanceListener中的回调方法。
【2】调用Consumer#offsetsForTimes获取“hello-topic”主题0分区1分钟前的偏移量。
【3】调用Consumer#seek将消费者消费位置定位到1分钟前。
6.消费者拦截器Kafka消费者同样支持拦截器,只要实现ConsumerInterceptor就可以。ConsumerInterceptor提供了3个方法:
-
onConsume:在Broker返回消息后,并且消息返回到KafkaConsumer#poll前触发该方法。
-
onCommit:当提交ACK偏移量时触发该方法。
-
close:关闭拦截器时触发该方法。
使用消费者配置interceptor.classes可以将拦截器注册到消费者中。与生产者类似,不再赘述。
3.3 消息序列化
我们可以指定消息键、值的序列化器,Kafka还提供了String、Long、Integer、Float、Double、Byte的序列化器。另外,读者也可以实现org.apache.kafka.common.serialization.Serializer、org.apache.kafka.common.serialization.Deserializer两个接口,自定义序列化器、反序列化器。
(1)下面使用Jackson实现一个简单的JSON序列化器,并使用该JSON序列化器序列化消息。
public class JsonSerializer<T> implements Serializer<T>{
private static ObjectMapper objectMapper = new ObjectMapper();
public byte[] serialize(String topic, T data) {
try {
return objectMapper.writeValueAsBytes(data);
} ...
return null;
}
}
该JSON序列化器的实现很简单,要使用该JSON序列化器也很简单,使用生产者配置项value.serializer指定该序列化器即可。
props.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class.getName());
...
KafkaProducer producer = new KafkaProducer<String, User>(props);
这样就可以直接使用Bean实例作为消息值,生产者会使用JSON序列化器序列化Bean实例:
User u = new User();
Future<RecordMetadata> future = producer.send(new ProducerRecord<Long, User>("user-topic", null , u));
(2)我们也可以使用Jackson实现反序列化器。
public class JsonDeserializer<T> implements Deserializer<T> {
private static ObjectMapper objectMapper = new ObjectMapper();
private Class targetClass;
// 【1】
public void configure(Map<String, ?> configs, boolean isKey) {
Object clazz;
if(isKey) {
clazz = configs.get("key.deserializer.class");
} else {
clazz = configs.get("value.deserializer.class");
}
targetClass = (Class) clazz;
}
public T deserialize(String topic, byte[] data) {
try {
// 【2】
return (T) objectMapper.readValue(data, targetClass);
} ...
return null;
}
}
【1】configs参数就是构建KafkaConsumer时传入的Properties参数,这里从该参数中读取反序列化的目标类。笔者定义了两个配置项:key.deserializer.class、value.deserializer.class,用于在创建消费者时指定反序列化的目标类。
【2】使用Jackson反序列化数据。下面指定反序列化器和反序列化目标类:
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class.getName());
props.put("value.deserializer.class", User.class);
...
KafkaConsumer consumer = new KafkaConsumer<String, User>(props);
这样消费者就可以直接接收JSON格式的数据,并将其转换为对应的Bean实例。
for(;;) {
ConsumerRecords<Long, User> records = consumer.poll(Duration.ofSeconds(5));
for (ConsumerRecord<Long, User> record : records) {
System.out.println("receive:" + record.value());
}
}
3.4 配额
某些生产者和消费者可能会快速生产/消费大量消息,从而垄断Kafka集群资源,导致其他生产者和消费者无法正常工作。这时可以使用配额机制对这些生产者和消费者限流,避免它们过度占用资源。
如果要对客户端限流,那么首先需要明确客户端的身份。客户端可以使用两种方式声明自己的身份:
(1)使用clientId属性声明身份(多个客户端可以使用同一个client-id)。
(2)通过认证机制声明用户身份。
下面介绍如何使用Kafka的配额机制。
(1)使用以下命令,给指定clientId的客户端添加配额配置。
$ bin/kafka-configs.sh --alter --bootstrap-server localhost:9092 --add-config 'producer_byte_rate=1024,consumer_byte_rate=1024' --entity-type clients --entity-name clientA
-
producer_byte_rate:生产者发送消息速率不能超过该阈值,单位为字节/秒。
-
consumer_byte_rate:消费者接收消息速率不能超过该阈值,单位为字节/秒。
客户端使用client-id声明身份:
props.setProperty(ProducerConfig.CLIENT_ID_CONFIG, "clientA");
这样该客户端发送和接收消息将受到配额的限制,每秒只能发送或者接收1KB的消息。
(2)使用以下命令可以给指定认证身份的客户端添加配额配置。
$ bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter --add-config 'producer_byte_rate=1024,consumer_byte_rate=2048,request_percentage=200' --entity-type users --entity-name alice
这时,使用alice用户认证的客户端将受到配额的限制。认证机制将在第22章介绍。
3.5 本章总结
本章介绍了Kafka的使用方式,包括管理脚本、生产者与消费者的使用方式和关键配置,消息序列化、配额机制的使用等内容。这部分内容可以帮助读者了解Kafka提供的功能及日常如何使用Kafka。
对了,五一开展的抽奖送书活动就剩下最后一周了,感兴趣的朋友可以关注公众号binecy,发送“抽奖Redis”或“抽奖Kafka/Pulsar”,即可抽奖笔者新书《Redis核心原理与实践》、《深入理解Kafka与Pulsar》,谢谢大家支持。