/// <summary> /// Assign partition and replicas to kafka brokers. /// the basic idea here is to evenly assign the active brokers to a partition, and assign its replicas to other active brokers. /// </summary> /// <param name="numOfPartitions">the number of partitions.</param> /// <param name="numOfReplicas">the number of replications.</param> /// <param name="brokerInfos">the active kafka brokers.</param> /// <param name="startBrokerId">the perfered broker id to start for assignment.</param> /// <param name="startPartitionId">the perfered partition id to start for assignment.</param> /// <returns>The kafka partition assignment info.</returns> public IEnumerable <KafkaPartitionInfo> AssignPartitionsAndReplicasToBrokers(int numOfPartitions, int numOfReplicas, IEnumerable <KafkaBrokerInfo> brokerInfos, int startBrokerId = -1, int startPartitionId = -1) { if (numOfPartitions <= 0) { return(null); } if (numOfReplicas <= 0) { return(null); } if (brokerInfos == null) { return(null); } var brokersArray = brokerInfos.ToArray(); int numOfBrokers = brokersArray.Count(); if (numOfBrokers == 0) { return(null); } if (numOfReplicas > numOfBrokers) { return(null); } var result = new List <KafkaPartitionInfo>(); Random random = new Random(); // if caller did not specific a broker for the starting partition 0, then random choose a broker for the partition 0 var brokerIds = brokersArray.Select(e => e.Id); var minId = brokerIds.Min(); var maxId = brokerIds.Max(); if (startBrokerId > maxId) { startBrokerId = minId; } if (startPartitionId > numOfPartitions) { startPartitionId = 0; } var startIndex = startBrokerId >= 0 ? startBrokerId : random.Next(numOfBrokers); // partition always start from 0, unless caller specific a number to start with. var currentPartitionId = Math.Max(0, startPartitionId); var nextReplicaShift = startBrokerId >= 0 ? startBrokerId : random.Next(numOfBrokers); // for each partition , find a broker id for this partition, and also find broker ids for all replicas for (int i = 0; i < numOfPartitions; i++) { if (currentPartitionId > 0 && (currentPartitionId % numOfBrokers) == 0) { nextReplicaShift += 1; } var replicaIndex = (currentPartitionId + startIndex) % numOfBrokers; var assignmentInfo = new KafkaPartitionInfo(); assignmentInfo.PartitionId = currentPartitionId.ToString(CultureInfo.InvariantCulture); assignmentInfo.ReplicaOwners = new int[numOfReplicas]; // assign the broker id to first replica. assignmentInfo.ReplicaOwners[0] = brokersArray[replicaIndex].Id; // find borker ids for other replicas for (int j = 1; j < numOfReplicas; j++) { var factor = j % numOfBrokers; replicaIndex = (replicaIndex + factor) % numOfBrokers; assignmentInfo.ReplicaOwners[j] = brokersArray[replicaIndex].Id; } result.Add(assignmentInfo); currentPartitionId += 1; } return(result); }
/// <summary> /// Update partitions and others settings for a topic. /// </summary> /// <param name="topicConfiguration">The topic update information.</param> /// <returns>Task holds the topic info if update complete. null is return when topic does not exist or unable to update it.</returns> /// <remarks>this method is used to increase the number of partitions for a topic. the increasement of replica is not handled here.</remarks> public async Task <KafkaTopicInfo> UpdateKafkaTopicAsync(KafkaTopicConfiguration topicConfiguration) { if (topicConfiguration == null) { throw new ArgumentException("topicConfiguration is null or empty."); } // check arguments if (topicConfiguration == null || string.IsNullOrWhiteSpace(topicConfiguration.TopicName) || topicConfiguration.NumOfPartitions <= 0 || topicConfiguration.NumOfReplicas <= 0 || topicConfiguration.TopicName == "__consumer_offsets") { return(null); } // 1 get topic info from zookeeper var topicInfo = await this.GetKafkaTopicAsync(topicConfiguration.TopicName).ConfigureAwait(false); if (topicInfo == null) { return(null); } // 2 check partitions and replicas // only support add more partitions or replicas, not support reduce them int numOfExistingPartitions = 0; int numOfExistingReplicas = 0; var partitions = topicInfo.Partitions; KafkaPartitionInfo partitionInfo = null; if (partitions != null) { numOfExistingPartitions = partitions.Length; partitionInfo = partitions.FirstOrDefault(p => p.PartitionId == "0"); if (partitionInfo != null) { var replicas = partitionInfo.ReplicaOwners; if (replicas != null) { numOfExistingReplicas = replicas.Length; } } } if (numOfExistingPartitions == 0 || numOfExistingReplicas == 0) { return(null); } if (numOfExistingPartitions >= topicConfiguration.NumOfPartitions) { return(topicInfo); } // NOTE : if want to update replicas, need to use another call if (numOfExistingReplicas != topicConfiguration.NumOfReplicas) { return(topicInfo); } // 3 update zookeeper node content var partitionsToAdd = topicConfiguration.NumOfPartitions - numOfExistingPartitions; // assign the brokers to brokers var brokers = await this.ListKakfaBrokersAsync().ConfigureAwait(false); var newPartitionAssignmentInfo = this.AssignPartitionsAndReplicasToBrokers(partitionsToAdd, numOfExistingReplicas, brokers, Math.Max(0, partitionInfo.ReplicaOwners[0]), numOfExistingPartitions); // check the newPartitios to see whether some partitions get more replicats if (newPartitionAssignmentInfo.Any(p => p.ReplicaOwners.Length != partitionInfo.ReplicaOwners.Length)) { return(null); } // merge existing and new partition info var finalAssignmentInfo = new List <KafkaPartitionInfo>(); finalAssignmentInfo.AddRange(topicInfo.Partitions); finalAssignmentInfo.AddRange(newPartitionAssignmentInfo); topicInfo.Partitions = finalAssignmentInfo.ToArray(); // 4 update the node in zookeeper // update node at path 'brokers/topics/{name}', with new content var serializer = new ZookeeperDataSerializer(); var content = serializer.SerializeKafkaTopicInfo(topicInfo); await this.ZookeeperClient.SetDataAsync("/brokers/topics/" + topicConfiguration.TopicName, content, -1).ConfigureAwait(false); return(topicInfo); }