package com.netflix.suro.sink.kafka; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.Singleton; import com.netflix.config.DynamicLongProperty; import org.apache.kafka.common.PartitionInfo; import java.util.List; import java.util.Random; import java.util.concurrent.*; @Singleton public class KafkaRetentionPartitioner { private final Random prng; // index cache for each topic private final ConcurrentMap<String, Integer> indexCache; private static DynamicLongProperty retention = new DynamicLongProperty( "kafka.producer.partition.retention", 1000); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setDaemon(false).setNameFormat("KafkaRetentionPartitioner-%d").build()); public KafkaRetentionPartitioner() { scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { indexCache.clear(); } }, retention.get(), retention.get(), TimeUnit.MILLISECONDS); this.prng = new Random(); // seed with a random integer this.indexCache = new ConcurrentHashMap<>(); // increment index every interval } public int getKey(String topic, List<PartitionInfo> partitions) { if(topic == null) { throw new IllegalArgumentException("topic is null"); } if(partitions.isEmpty()) { throw new IllegalArgumentException("no partitions for topic: " + topic); } final int numPartitions = partitions.size(); Integer index = indexCache.get(topic); if(index != null) { // stick to the same partition in cache return index; } else { // randomly pick a new partition from [0, numPartitions) range int partition = prng.nextInt(numPartitions); // try to find a partition with leader for (int i = 0; i < numPartitions; i++) { if (partitions.get(partition).leader() != null) { // found a partition with leader index = indexCache.putIfAbsent(topic, partition); return index != null ? index : partition; } else { // try next partition partition = (partition + 1) % numPartitions; } } // no partitions are available, give a non-available partition. // partition will loop back to its earlier value from prng.nextInt(numPartitions). // but don't update cache in this case. return partition; } } }