Kafka-分区
kafka的消息时一个个键值对,ProducerRecord对象可以包含目标主题和值,键可以设置为默认的null,不过大多数应用程序会用到键。
键有两个用途;可以作为消息的附加信息,也可以用来决定消息该被写到主题的哪个分区。拥有相同键的消息将被写到同一个分区。
如果键值为null,并且使用了默认的分区器,那么记录将被随机的发送到主题内各个可用的分区上。分区器使用轮询(Round Robin)算法将消息均衡地分不到各个分区上。
如果键不为空,并且使用了默认的分区器,那么kafka会对键进行散列(kafka自己的散列算法),然后根据散列值把消息映射到特定的分区上。同一个键总是被映射到同一个分区上,所以在进行映射时,我们会使用主题所有的分区,而不仅仅是可用的分区。所以,如果写入数据的分区是不可用的,那么就会发生错误。
如果要使用键来映射分区,那么最好在创建主题的时候就把分区规划好,而且永远不要增加新分区。
实现自定义分区策略
除了散列分区之外,有时候也需要对数据进行不一样的分区。
假设你是一个B2B供应商,有一个大客户,它是手持设备Apple的制造商。Apple占据了整体业务的10%的份额。如果使用默认的散列分区算法,Apple的账号记录将和其他账号记录一起被分配给相同的分区,导致这个分区比其它分区要大一些。服务器可能因此出现存储空间不足、处理缓慢等问题。我们需要给Apple分配单独的分区,然后使用散列分区算法处理其他账号。
代码如下
import org.apache.kafka.clients.producer.Partitioner; import org.apache.kafka.common.Cluster; import org.apache.kafka.common.InvalidRecordException; import org.apache.kafka.common.PartitionInfo; import org.apache.kafka.common.utils.Utils; import java.util.List; import java.util.Map; /** * @Author FengZhen * @Date 2020-03-31 22:38 * @Description 自定义分区 */ public class ApplePartitioner implements Partitioner { @Override 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 (null == keyBytes || !(key instanceof String)) { throw new InvalidRecordException("We expect all messages to have customer name as key"); } if (key.equals("Apple")){ //分配到最后一个分区 return numPartitions - 1; } return Math.abs(Utils.murmur2(keyBytes)) % (numPartitions - 1); } @Override public void close() { } @Override public void configure(Map<String, ?> map) { } }
测试
import org.apache.kafka.clients.producer.Callback; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; import java.util.Properties; /** * @Author FengZhen * @Date 2020-03-29 12:21 * @Description kafka生产者使用 */ public class KafkaProducerTest { private static Properties kafkaProps = new Properties(); static { kafkaProps.put("bootstrap.servers", "localhost:9092"); kafkaProps.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); kafkaProps.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); } public static void main(String[] args) { KafkaProducer<String, String> producer = new KafkaProducer(kafkaProps); ProducerRecord<String, String> record = new ProducerRecord<>("test","message_key","message_value"); // simpleSend(producer, record); // sync(producer, record); // aync(producer, record); udfPartition(); } /** * 使用自定义分区 * ./kafka-topics.sh --create --zookeeper localhost:2181/kafka_2_4_1 --replication-factor 1 --partitions 3 --topic test_partition */ public static void udfPartition(){ kafkaProps.put("partitioner.class", "com.chinaventure.kafka.partition.ApplePartitioner"); KafkaProducer<String, String> producer = new KafkaProducer(kafkaProps); for (int i = 0; i < 10; i++){ ProducerRecord<String, String> record = new ProducerRecord<>("test_partition",i % 3 == 0 ? "Apple": "Banana"+i,"我是" + i); producer.send(record, new DemonProducerCallback()); } while (true){ try { Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 最简单的方式发送,不管消息是否正常到达 * @param producer */ public static void simpleSend(KafkaProducer producer, ProducerRecord record){ try { producer.send(record); } catch(Exception e){ e.printStackTrace(); } } /** * 同步发送 * @param producer * @param record */ public static void sync(KafkaProducer producer, ProducerRecord record){ try { RecordMetadata recordMetadata = (RecordMetadata) producer.send(record).get(); System.out.println("topic:" + recordMetadata.topic()); System.out.println("partition:" + recordMetadata.partition()); System.out.println("offset:" + recordMetadata.offset()); System.out.println("metaData:" + recordMetadata.toString()); } catch(Exception e){ e.printStackTrace(); } } /** * 异步发送 * @param producer * @param record */ public static void aync(KafkaProducer producer, ProducerRecord record){ try { producer.send(record, new DemonProducerCallback()); while (true){ Thread.sleep(10 * 1000); } } catch(Exception e){ e.printStackTrace(); } } private static class DemonProducerCallback implements Callback { @Override public void onCompletion(RecordMetadata recordMetadata, Exception e) { if (null != e){ e.printStackTrace(); }else{ System.out.println("topic:" + recordMetadata.topic()); System.out.println("partition:" + recordMetadata.partition()); System.out.println("offset:" + recordMetadata.offset()); System.out.println("metaData:" + recordMetadata.toString()); } } } }
查看最后一个分区的日志
FengZhendeMacBook-Pro:bin FengZhen$ cat /tmp/kafka-logs/test_partition-2/00000000000000000000.log }??k?q1?q1???????????????$ Apple我是0$& Apple我是3$& Apple我是6$& Apple我是9