我们在使用 Kafka 时,最简单、最常用的方式是只设置 topic(主题)和 value(消息体),如下所示:
这样的话获取消息的代码也很简单,如下所示:
@KafkaListener(topics = "mytopic", groupId = "my-group")
public void listen(String data) {
System.out.println("监听到消息:" + data);
}
但是,除了我们可以设置和传递 topic 和 value 之外,我们还可以传递 key,如下图所示:
那问题来了,发送消息时设置这个 key 有什么用呢?
key的作用
发送消息时,设置 key 的作用如下:
1.决定分区
当生产者发送消息时,如果指定了 key,Kafka 会根据 key 的 hash 值来决定这条消息应该发送到哪个分区。
如果没有指定 key,Kafka 会采用轮询(早期版本)或随机(最新版本)的方式将消息分配到其他分区中。
分区的具体实现源码在 DefaultPartitioner 中 partition 方法中体现,核心源码如下:
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster, int numPartitions) {
return keyBytes == null ? this.stickyPartitionCache.partition(topic, cluster) : BuiltInPartitioner.partitionForKey(keyBytes, numPartitions);
}
指定 key 之后的分区实现代码如下:
public static int partitionForKey(byte[] serializedKey, int numPartitions) {
return Utils.toPositive(Utils.murmur2(serializedKey)) % numPartitions;
}
以上源码的大概含义是:使用 MurmurHash2 算法对字节数组 serializedKey 进行哈希运算,并将其结果转换为正数,然后对 numPartitions 取模,以确定键在分区中的位置,返回值表示键所在的分区编号。
所以,从上述源码可以看出,发送消息如果设置了 key 之后,会将相同 key 放到同一个分区中。
2.保证消息顺序
在 Kafka 中,同一个分区中的消息是有序的。而相同的 key,根据上面的分区算法可知,它们会存放到同一个分区,这样就能保证消息的有序性了。
3.消息过滤
对于某些应用场景,消费者可以根据消息的键来进行过滤或聚合操作。例如,在实时数据分析场景中,可能需要对具有相同键的消息进行分组处理。
Kafka 设置了 key 之后,可以通过以下方式实现消息过滤,如下代码所示:
@KafkaListener(topics = "topicName", groupId = "groupId")
public void listen(String message, ConsumerRecord<?,?> record) {
Object key = record.key();
if (key instanceof String && ((String) key).matches("regexPattern")) {
// 处理满足正则表达式条件的消息
}
}
也就是,我们在接收到消息之后,通过对 key 的正则匹配实现消息的过滤和聚合等操作。
课后思考
保证 Kafka 消息的有序性,除了设置 key 之外,还有哪些实现手段?
本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。