private long ResetConsumerOffsets(string topic, int partitionId)
        {
            long offset;

            switch (_config.AutoOffsetReset)
            {
            case OffsetRequest.SmallestTime:
                offset = OffsetRequest.EarliestTime;
                break;

            case OffsetRequest.LargestTime:
                offset = OffsetRequest.LatestTime;
                break;

            default:
                return(0);
            }

            var requestInfo = new Dictionary <string, List <PartitionOffsetRequestInfo> >();

            requestInfo[topic] = new List <PartitionOffsetRequestInfo> {
                new PartitionOffsetRequestInfo(partitionId, offset, 1)
            };

            var            request     = new OffsetRequest(requestInfo);
            OffsetResponse offsets     = _simpleConsumer.GetOffsetsBefore(request);
            var            topicDirs   = new ZKGroupTopicDirs(_config.GroupId, topic);
            long           offsetFound = offsets.ResponseMap[topic].First().Offsets[0];

            Logger.InfoFormat("updating partition {0} with {1} offset {2}", partitionId, offset == OffsetRequest.EarliestTime ? "earliest" : "latest", offsetFound);
            ZkUtils.UpdatePersistentPath(_zkClient, topicDirs.ConsumerOffsetDir + "/" + partitionId, offsetFound.ToString(CultureInfo.InvariantCulture));

            return(offsetFound);
        }
Пример #2
0
            private void DeletePartitionOwnershipFromZK(string topic, int partition)
            {
                var topicDirs = new ZKGroupTopicDirs(group, topic);
                var znode     = topicDirs.ConsumerOwnerDir + "/" + partition;

                ZkUtils.DeletePath(this.parent.zkClient, znode);
                Logger.DebugFormat("Consumer {0} releasing {1}", consumerIdString, znode);
            }
Пример #3
0
        /// <summary>
        /// Commit offset of specified topic/partition.
        /// Only used when customer has strong requirement for reprocess messages as few as possible.
        /// </summary>
        /// <param name="topic"></param>
        /// <param name="partition"></param>
        /// <param name="offset"></param>
        /// <param name="setPosition">Indicates whether to set the fetcher's offset to the value committed. Default = true.</param>
        public void CommitOffset(string topic, int partition, long offset, bool setPosition = true)
        {
            this.EnsuresNotDisposed();
            if (this.GetZkClient() == null)
            {
                return;
            }
            if (this.config.AutoCommit == true)
            {
                throw new ArgumentException(string.Format("When do commit offset with desired partition and offset, must set AutoCommit of ConsumerConfiguration as false!"));
            }
            try
            {
                IDictionary <int, PartitionTopicInfo> topicPartitionInfo = topicRegistry[topic];
                var topicDirs = new ZKGroupTopicDirs(this.config.GroupId, topic);
                PartitionTopicInfo partitionTopicInfo = topicPartitionInfo[partition];
                if (partitionTopicInfo.ConsumeOffsetValid)
                {
                    //Commit offset unconditionally. This would give consumes to decide which offset to read/skip
                    //if (offset > partitionTopicInfo.CommitedOffset)
                    try
                    {
                        ZkUtils.UpdatePersistentPath(GetZkClient(),
                                                     topicDirs.ConsumerOffsetDir + "/" +
                                                     partitionTopicInfo.PartitionId, offset.ToString());
                        partitionTopicInfo.CommitedOffset = offset;
                        if (setPosition)
                        {
                            partitionTopicInfo.ConsumeOffset = offset;
                            partitionTopicInfo.FetchOffset   = offset;
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("error in CommitOffsets UpdatePersistentPath : {0}", ex.FormatException());
                    }
                }
                else
                {
                    Logger.InfoFormat(
                        "Skip committing offset {0} for topic {1} because it is invalid (ZK session is disconnected)",
                        offset, partitionTopicInfo);
                }

                if (Logger.IsDebugEnabled)
                {
                    Logger.DebugFormat("Commited offset {0} for topic {1}", offset, partitionTopicInfo);
                }
            }
            catch (Exception ex)
            {
                Logger.ErrorFormat("exception during CommitOffsets: Topic:{0}  Partition:{1} offset:{2} Exception:{3} ", topic, partition, offset, ex.FormatException());
            }
        }
Пример #4
0
        /// <summary>
        /// Produce the given number of messages, create a consumer with the given offset policy,
        /// then reset the offset to the given value and consume until we get no new messages.
        /// </summary>
        /// <param name="numMessages"></param>
        /// <param name="resetTo"></param>
        /// <param name="offset"></param>
        /// <returns>The count of messages received.</returns>
        public int ResetAndConsume(int numMessages, string resetTo, long offset)
        {
            TestUtils.WaitUntilLeaderIsElectedOrChanged(this.ZkClient, Topic, 0, 1000);

            var producer = TestUtils.CreateProducer(
                TestUtils.GetBrokerListFromConfigs(Configs), new DefaultEncoder(), new StringEncoder());

            for (var i = 0; i < numMessages; i++)
            {
                producer.Send(new KeyedMessage <string, byte[]>(Topic, Topic, Encoding.UTF8.GetBytes("test")));
            }

            TestUtils.WaitUntilMetadataIsPropagated(this.Servers, Topic, 0, 1000);

            // update offset in zookeeper for consumer to jump "forward" in time
            var dirs           = new ZKGroupTopicDirs(Group, Topic);
            var consumerConfig = TestUtils.CreateConsumerProperties(ZkConnect, Group, TestConsumer);

            consumerConfig.AutoOffsetReset   = resetTo;
            consumerConfig.ConsumerTimeoutMs = 2000;
            consumerConfig.FetchWaitMaxMs    = 0;

            TestUtils.UpdateConsumerOffset(consumerConfig, dirs.ConsumerOffsetDir + "/" + "0", offset);
            Logger.InfoFormat("Update consumer offset to {0}", offset);

            var consumerConnector = Consumer.Create(consumerConfig);
            var messagesStream    = consumerConnector.CreateMessageStreams(new Dictionary <string, int> {
                { Topic, 1 }
            })[Topic].First();

            var received = 0;
            var iter     = messagesStream.GetEnumerator();

            try
            {
                for (var i = 0; i < numMessages; i++)
                {
                    iter.MoveNext(); // will throw a timeout exception if the message isn't there
                    received++;
                }
            }
            catch (ConsumerTimeoutException)
            {
                Logger.InfoFormat("consumer timeout out after receiving {0} messages", received);
            }
            finally
            {
                producer.Dispose();
                consumerConnector.Shutdown();
            }

            return(received);
        }
Пример #5
0
        private void AddPartitionTopicInfo(ZKGroupTopicDirs topicDirs,
                                           string partition,
                                           string topic,
                                           string consumerThreadId)
        {
            var partitionId      = int.Parse(partition);
            var partTopicInfoMap = topicRegistry[topic];

            //find the leader for this partition
            var leaderOpt = ZkUtils.GetLeaderForPartition(zkClient, topic, partitionId);

            if (!leaderOpt.HasValue)
            {
                throw new NoBrokersForPartitionException(
                          string.Format("No leader available for partitions {0} on topic {1}", partition, topic));
            }
            Logger.InfoFormat("Leader for partition {0} for topic {1} is {2}", partition, topic, leaderOpt.Value);
            var leader = leaderOpt.Value;
            var znode  = topicDirs.ConsumerOffsetDir + "/" + partition;

            var offsetCommitedString = zkClient.ReadData <string>(znode, true);

            //if first time starting a consumer, set the initial offset based on the config
            long offset         = -1;
            long offsetCommited = -1;

            if (offsetCommitedString != null)
            {
                offsetCommited = long.Parse(offsetCommitedString);
                offset         = offsetCommited + 1;
            }
            Logger.InfoFormat("Final offset {0} for topic {1} partition {2} OffsetCommited {3}"
                              , offset, topic, partition, offsetCommited);

            var queue         = queues[new Tuple <string, string>(topic, consumerThreadId)];
            var partTopicInfo = new PartitionTopicInfo(
                topic,
                leader,
                partitionId,
                queue,
                offsetCommited,
                offset,
                config.FetchSize,
                offsetCommited);

            partTopicInfoMap[partitionId] = partTopicInfo;
            Logger.InfoFormat("{0} selected new offset {1}", partTopicInfo, offset);
        }
Пример #6
0
 private void ReleasePartitionOwnership()
 {
     foreach (KeyValuePair <string, IDictionary <Partition, PartitionTopicInfo> > item in topicRegistry)
     {
         var topicDirs = new ZKGroupTopicDirs(this.config.GroupId, item.Key);
         foreach (var partition in item.Value.Keys)
         {
             string znode = topicDirs.ConsumerOwnerDir + "/" + partition.Name;
             ZkUtils.DeletePath(zkClient, znode);
             if (Logger.IsDebugEnabled)
             {
                 Logger.DebugFormat(CultureInfo.CurrentCulture, "Consumer {0} releasing {1}", this.consumerIdString, znode);
             }
         }
     }
 }
Пример #7
0
        private bool ReflectPartitionOwnershipDecision(string topic, string partition, string consumerThreadId)
        {
            var ownershipReflected = false;
            var topicDirs          = new ZKGroupTopicDirs(config.GroupId, topic);
            var partitionOwnerPath = topicDirs.ConsumerOwnerDir + "/" + partition;

            try
            {
                Logger.InfoFormat("Consumer {0} will own partition {1} for topic {2}, will create ZK path {3}",
                                  consumerThreadId, partition, topic, partitionOwnerPath);
                try
                {
                    zkClient.SlimLock.EnterWriteLock();
                    ZkUtils.CreateEphemeralPathExpectConflict(zkClient, partitionOwnerPath, consumerThreadId);
                    Logger.InfoFormat(
                        "Consumer {0} SUCC owned partition {1} for topic {2} . finish create ZK path {3}  .",
                        consumerThreadId, partition, topic, partitionOwnerPath);
                    ownershipReflected = true;
                }
                catch (Exception ex)
                {
                    Logger.InfoFormat(
                        "Consumer {0} FAILED owned partition {1} for topic {2} . finish create ZK path {3}  error:{4}.",
                        consumerThreadId, partition, topic, partitionOwnerPath, ex.FormatException());
                }
                finally
                {
                    zkClient.SlimLock.ExitWriteLock();
                }
            }
            catch (KeeperException e)
            {
                if (e.ErrorCode == KeeperException.Code.NODEEXISTS)
                {
                    Logger.InfoFormat(
                        "{0} failed to own partition {1} for topic {2}. Waiting for the partition owner to be deleted",
                        consumerThreadId, partition, topic);
                    ownershipReflected = false;
                }
                else
                {
                    throw;
                }
            }

            return(ownershipReflected);
        }
Пример #8
0
        private bool ProcessPartition(ZKGroupTopicDirs topicDirs, string partition, string topic, string consumerThreadId)
        {
            var partitionOwnerPath = topicDirs.ConsumerOwnerDir + "/" + partition;

            try
            {
                ZkUtils.CreateEphemeralPathExpectConflict(zkClient, partitionOwnerPath, consumerThreadId);
            }
            catch (KeeperException.NodeExistsException)
            {
                //// The node hasn't been deleted by the original owner. So wait a bit and retry.
                Logger.InfoFormat(CultureInfo.CurrentCulture, "waiting for the partition ownership to be deleted: {0}", partition);
                return(false);
            }

            AddPartitionTopicInfo(topicDirs, partition, topic, consumerThreadId);
            return(true);
        }
Пример #9
0
        private bool ProcessPartition(ZKGroupTopicDirs topicDirs,
                                      string partition,
                                      string topic,
                                      string consumerThreadId,
                                      List <string> curConsumers,
                                      List <string> curPartitions,
                                      CancellationTokenSource cancellationTokenSource)
        {
            var partitionOwned = false;

            // Loop until we can successfully acquire partition, or are canceled by a
            // future rebalance event
            var partitionOwnerPath = topicDirs.ConsumerOwnerDir + "/" + partition;

            while (true)
            {
                var currentPartitionOwner = zkClient.ReadData <string>(partitionOwnerPath, true);
                if (currentPartitionOwner == null || currentPartitionOwner.Equals(consumerThreadId))
                {
                    Logger.InfoFormat("{0} is null or equals our consumer ID. Reflect partition ownership in ZK",
                                      partitionOwnerPath);
                    AddPartitionTopicInfo(topicDirs, partition, topic, consumerThreadId);
                    if (ReflectPartitionOwnershipDecision(topic, partition, consumerThreadId))
                    {
                        partitionOwned = true;
                        break;
                    }
                }

                if (cancellationTokenSource.IsCancellationRequested)
                {
                    Logger.Info(
                        "Rebalance operation has been canceled externally by a future rebalance event. Exiting immediately");
                    break;
                }
                Logger.InfoFormat("{0} exists with value {1}. Sleep and retry ownership attempt", partitionOwnerPath,
                                  currentPartitionOwner);
                Thread.Sleep(1000);
            }

            return(partitionOwned);
        }
Пример #10
0
        private bool ReflectPartitionOwnership(Dictionary <Tuple <string, string>, string> partitionOwnershipDecision)
        {
            var successfullyOwnedPartitions  = new List <Tuple <string, string> >();
            var partitionOwnershipSuccessful = new List <bool>();

            foreach (var partitionOwner in partitionOwnershipDecision)
            {
                var topic              = partitionOwner.Key.Item1;
                var partition          = partitionOwner.Key.Item2;
                var consumerThreadId   = partitionOwner.Value;
                var topicDirs          = new ZKGroupTopicDirs(config.GroupId, topic);
                var partitionOwnerPath = topicDirs.ConsumerOwnerDir + "/" + partition;
                try
                {
                    ZkUtils.CreateEphemeralPathExpectConflict(zkClient, partitionOwnerPath, consumerThreadId);
                    Logger.InfoFormat("{0} successfully owned partition {1} for topic {2}", consumerThreadId, partition, topic);
                    successfullyOwnedPartitions.Add(new Tuple <string, string>(topic, partition));
                    partitionOwnershipSuccessful.Add(true);
                }
                catch (KeeperException.NodeExistsException)
                {
                    Logger.InfoFormat("waiting for the partition owner to be deleted: {0}", partition);
                    partitionOwnershipSuccessful.Add(false);
                }
            }
            var hasPartitionOwnershipFailed = partitionOwnershipSuccessful.Contains(false);

            if (hasPartitionOwnershipFailed)
            {
                foreach (var topicAndPartition in successfullyOwnedPartitions)
                {
                    var topicDirs = new ZKGroupTopicDirs(config.GroupId, topicAndPartition.Item1);
                    var znode     = topicDirs.ConsumerOwnerDir + "/" + topicAndPartition.Item2;
                    ZkUtils.DeletePath(zkClient, znode);
                    Logger.DebugFormat("Consumer {0} releasing {1}", consumerIdString, znode);
                }
                return(false);
            }
            return(true);
        }
Пример #11
0
        /// <summary>
        /// Commits the offsets of all messages consumed so far.
        /// </summary>
        public void CommitOffsets()
        {
            this.EnsuresNotDisposed();
            if (this.zkClient == null)
            {
                return;
            }
            this.zkClient.SlimLock.EnterReadLock();
            try
            {
                foreach (KeyValuePair <string, IDictionary <Partition, PartitionTopicInfo> > topic in topicRegistry)
                {
                    var topicDirs = new ZKGroupTopicDirs(this.config.GroupId, topic.Key);
                    foreach (KeyValuePair <Partition, PartitionTopicInfo> partition in topic.Value)
                    {
                        var newOffset = partition.Value.GetConsumeOffset();
                        try
                        {
                            ZkUtils.UpdatePersistentPath(zkClient,
                                                         topicDirs.ConsumerOffsetDir + "/" +
                                                         partition.Value.Partition.Name, newOffset.ToString());
                        }
                        catch (Exception ex)
                        {
                            Logger.WarnFormat(CultureInfo.CurrentCulture, "exception during CommitOffsets: {0}", ex);
                        }

                        if (Logger.IsDebugEnabled)
                        {
                            Logger.DebugFormat(CultureInfo.CurrentCulture, "Commited offset {0} for topic {1}",
                                               newOffset, partition);
                        }
                    }
                }
            }
            finally
            {
                this.zkClient.SlimLock.ExitReadLock();
            }
        }
Пример #12
0
        private void AddPartitionTopicInfo(ZKGroupTopicDirs topicDirs, string partitionString, string topic, string consumerThreadId)
        {
            var  partition        = Partition.ParseFrom(partitionString);
            var  partTopicInfoMap = this.topicRegistry[topic];
            var  znode            = topicDirs.ConsumerOffsetDir + "/" + partition.Name;
            var  offsetString     = this.zkClient.ReadData <string>(znode, true);
            long offset           = string.IsNullOrEmpty(offsetString) ? 0 : long.Parse(offsetString, CultureInfo.InvariantCulture);
            var  queue            = this.queues[new Tuple <string, string>(topic, consumerThreadId)];
            var  partTopicInfo    = new PartitionTopicInfo(
                topic,
                partition.BrokerId,
                partition,
                queue,
                offset,
                offset,
                this.config.FetchSize);

            partTopicInfoMap.Add(partition, partTopicInfo);
            if (Logger.IsDebugEnabled)
            {
                Logger.DebugFormat(CultureInfo.CurrentCulture, "{0} selected new offset {1}", partTopicInfo, offset);
            }
        }
        private long ResetConsumerOffsets(string topic, int partitionId)
        {
            long offset;
            switch (_config.AutoOffsetReset)
            {
                case OffsetRequest.SmallestTime:
                    offset = OffsetRequest.EarliestTime;
                    break;
                case OffsetRequest.LargestTime:
                    offset = OffsetRequest.LatestTime;
                    break;
                default:
                    return 0;
            }

            var requestInfo = new Dictionary<string, List<PartitionOffsetRequestInfo>>();
            requestInfo[topic] = new List<PartitionOffsetRequestInfo> { new PartitionOffsetRequestInfo(partitionId, offset, 1) };

            var request = new OffsetRequest(requestInfo);
            OffsetResponse offsets = _simpleConsumer.GetOffsetsBefore(request);
            var topicDirs = new ZKGroupTopicDirs(_config.GroupId, topic);
            long offsetFound = offsets.ResponseMap[topic].First().Offsets[0];
            Logger.InfoFormat("updating partition {0} with {1} offset {2}", partitionId, offset == OffsetRequest.EarliestTime ? "earliest" : "latest", offsetFound);
            ZkUtils.UpdatePersistentPath(_zkClient, topicDirs.ConsumerOffsetDir + "/" + partitionId, offsetFound.ToString(CultureInfo.InvariantCulture));

            return offsetFound;
        }
Пример #14
0
        private bool Rebalance()
        {
            var myTopicThresdIdsMap       = this.GetTopicCount(this.consumerIdString).GetConsumerThreadIdsPerTopic();
            var cluster                   = new Cluster(zkClient);
            var consumersPerTopicMap      = this.GetConsumersPerTopic(this.config.GroupId);
            var partitionsPerTopicMap     = ZkUtils.GetPartitionsForTopics(this.zkClient, myTopicThresdIdsMap.Keys);
            var relevantTopicThreadIdsMap = GetRelevantTopicMap(
                myTopicThresdIdsMap,
                partitionsPerTopicMap,
                this.oldPartitionsPerTopicMap,
                consumersPerTopicMap,
                this.oldConsumersPerTopicMap);

            if (relevantTopicThreadIdsMap.Count <= 0)
            {
                Logger.InfoFormat(CultureInfo.CurrentCulture, "Consumer {0} with {1} doesn't need to rebalance.", this.consumerIdString, consumersPerTopicMap);
                return(true);
            }

            this.CloseFetchers(cluster, myTopicThresdIdsMap, this.zkConsumerConnector);

            Logger.Info("Releasing parittion ownership");
            this.ReleasePartitionOwnership();
            var currentTopicRegistry = new ConcurrentDictionary <string, IDictionary <Partition, PartitionTopicInfo> >();

            var partitionOwnershipDecision = new Dictionary <Tuple <string, string>, string>();

            foreach (var item in myTopicThresdIdsMap)
            {
                currentTopicRegistry.GetOrAdd(item.Key, new ConcurrentDictionary <Partition, PartitionTopicInfo>());

                var topicDirs     = new ZKGroupTopicDirs(config.GroupId, item.Key);
                var curConsumers  = consumersPerTopicMap[item.Key];
                var curPartitions = new List <string>(partitionsPerTopicMap[item.Key]);

                var numberOfPartsPerConsumer       = curPartitions.Count / curConsumers.Count;
                var numberOfConsumersWithExtraPart = curPartitions.Count % curConsumers.Count;

                Logger.InfoFormat(
                    CultureInfo.CurrentCulture,
                    "Consumer {0} rebalancing the following partitions: {1} for topic {2} with consumers: {3}",
                    this.consumerIdString,
                    string.Join(",", curPartitions),
                    item.Key,
                    string.Join(",", curConsumers));

                foreach (string consumerThreadId in item.Value)
                {
                    var myConsumerPosition = curConsumers.IndexOf(consumerThreadId);
                    if (myConsumerPosition < 0)
                    {
                        continue;
                    }

                    var startPart = (numberOfPartsPerConsumer * myConsumerPosition) +
                                    Math.Min(myConsumerPosition, numberOfConsumersWithExtraPart);
                    var numberOfParts = numberOfPartsPerConsumer + (myConsumerPosition + 1 > numberOfConsumersWithExtraPart ? 0 : 1);

                    if (numberOfParts <= 0)
                    {
                        Logger.WarnFormat(CultureInfo.CurrentCulture, "No broker partitions consumed by consumer thread {0} for topic {1}", consumerThreadId, item.Key);
                    }
                    else
                    {
                        for (int i = startPart; i < startPart + numberOfParts; i++)
                        {
                            var partition = curPartitions[i];
                            Logger.InfoFormat(CultureInfo.CurrentCulture, "{0} attempting to claim partition {1}", consumerThreadId, partition);
                            AddPartitionTopicInfo(currentTopicRegistry, topicDirs, partition, item.Key, consumerThreadId);
                            partitionOwnershipDecision.Add(new Tuple <string, string>(item.Key, partition), consumerThreadId);
                        }
                    }
                }
            }

            if (ReflectPartitionOwnership(partitionOwnershipDecision))
            {
                zkClient.SlimLock.EnterWriteLock();
                try
                {
                    this.topicRegistry.Clear();
                    foreach (var item in currentTopicRegistry)
                    {
                        this.topicRegistry.Add(item);
                    }

                    this.UpdateFetcher(cluster);
                }
                finally
                {
                    zkClient.SlimLock.ExitWriteLock();
                }
                this.oldPartitionsPerTopicMap = partitionsPerTopicMap;
                this.oldConsumersPerTopicMap  = consumersPerTopicMap;
                return(true);
            }
            else
            {
                return(false);
            }
        }
Пример #15
0
            private bool Rebalance(Cluster cluster)
            {
                var myTopicThreadIdsMap =
                    TopicCount.ConstructTopicCount(group, consumerIdString, parent.zkClient)
                    .GetConsumerThreadIdsPerTopic();
                var consumersPerTopicMap = ZkUtils.GetConsumersPerTopic(parent.zkClient, group);
                var brokers = ZkUtils.GetAllBrokersInCluster(parent.zkClient);

                if (brokers.Count == 0)
                {
                    // This can happen in a rare case when there are no brokers available in the cluster when the consumer is started.
                    // We log an warning and register for child changes on brokers/id so that rebalance can be triggered when the brokers
                    // are up.
                    Logger.Warn("no brokers found when trying to rebalance.");
                    parent.zkClient.SubscribeChildChanges(ZkUtils.BrokerIdsPath, parent.loadBalancerListener);
                    return(true);
                }
                else
                {
                    var partitionsAssignmentPerTopicMap = ZkUtils.GetPartitionAssignmentForTopics(
                        parent.zkClient, myTopicThreadIdsMap.Keys.ToList());
                    var partitionsPerTopicMap = partitionsAssignmentPerTopicMap.ToDictionary(
                        p => p.Key, p => p.Value.Keys.OrderBy(x => x).ToList());

                    /**
                     * fetchers must be stopped to avoid Data duplication, since if the current
                     * rebalancing attempt fails, the partitions that are released could be owned by another consumer.
                     * But if we don't stop the fetchers first, this consumer would continue returning Data for released
                     * partitions in parallel. So, not stopping the fetchers leads to duplicate Data.
                     */

                    this.CloseFetchers(cluster, (IDictionary <string, IList <KafkaStream <TKey, TValue> > >)KafkaMessageAndMetadataStreams, myTopicThreadIdsMap);

                    this.ReleasePartitionOwnership(parent.topicRegistry);

                    var partitionOwnershipDecision = new Dictionary <Tuple <string, int>, string>();
                    var currentTopicRegistry       = new Pool <string, Pool <int, PartitionTopicInfo> >();

                    foreach (var topicAndConsumerThreadIsSet in myTopicThreadIdsMap)
                    {
                        var topic = topicAndConsumerThreadIsSet.Key;
                        var consumerThreadIdSet = topicAndConsumerThreadIsSet.Value;

                        currentTopicRegistry[topic] = new Pool <int, PartitionTopicInfo>();

                        var topicDirs     = new ZKGroupTopicDirs(group, topic);
                        var curConsumers  = consumersPerTopicMap.Get(topic);
                        var curPartitions = partitionsPerTopicMap.Get(topic);

                        var nPartsPerConsumer       = curPartitions.Count / curConsumers.Count;
                        var nConsumersWithExtraPart = curPartitions.Count % curConsumers.Count;

                        Logger.InfoFormat("Consumer {0} rebalancing the following partitions: {1} for topic {2} with consumers: {3}", consumerIdString, string.Join(",", curPartitions), topic, string.Join(",", curConsumers));

                        foreach (var consumerThreadId in consumerThreadIdSet)
                        {
                            var myConsumerPosition = curConsumers.IndexOf(consumerThreadId);
                            Contract.Assert(myConsumerPosition >= 0);
                            var startPart = (nPartsPerConsumer * myConsumerPosition)
                                            + Math.Min(nConsumersWithExtraPart, myConsumerPosition);
                            var nParts = nPartsPerConsumer + (myConsumerPosition + 1 > nConsumersWithExtraPart ? 0 : 1);

                            /**
                             *   Range-partition the sorted partitions to consumers for better locality.
                             *  The first few consumers pick up an extra partition, if any.
                             */

                            if (nParts <= 0)
                            {
                                Logger.WarnFormat(
                                    "No broker partitions consumed by consumer thread {0} for topic {1}",
                                    consumerThreadId,
                                    topic);
                            }
                            else
                            {
                                for (var i = startPart; i < startPart + nParts; i++)
                                {
                                    var partition = curPartitions[i];
                                    Logger.InfoFormat("{0} attempting to claim partition {1}", consumerThreadId, partition);
                                    this.AddPartitionTopicInfo(currentTopicRegistry, topicDirs, partition, topic, consumerThreadId);

                                    // record the partition ownership decision
                                    partitionOwnershipDecision[Tuple.Create(topic, partition)] = consumerThreadId;
                                }
                            }
                        }
                    }

                    /**
                     * move the partition ownership here, since that can be used to indicate a truly successful rebalancing attempt
                     * A rebalancing attempt is completed successfully only after the fetchers have been started correctly
                     */
                    if (this.ReflectPartitionOwnershipDecision(partitionOwnershipDecision))
                    {
                        Logger.Info("Updating the cache");
                        Logger.Debug("Partitions per topic cache " + JObject.FromObject(partitionsPerTopicMap).ToString(Formatting.None));
                        Logger.Debug("Consumers per topic cache " + JObject.FromObject(consumersPerTopicMap).ToString(Formatting.None));
                        parent.topicRegistry = currentTopicRegistry;
                        this.UpdateFetcher(cluster);
                        return(true);
                    }
                    else
                    {
                        return(false);
                    }
                }
            }
Пример #16
0
        /// <summary>
        ///     Commits the offsets of all messages consumed so far.
        /// </summary>
        public void CommitOffsets()
        {
            EnsuresNotDisposed();
            if (GetZkClient() == null)
            {
                return;
            }
            try
            {
                foreach (var topic in topicRegistry)
                {
                    var topicDirs = new ZKGroupTopicDirs(config.GroupId, topic.Key);
                    foreach (var partition in topic.Value)
                    {
                        var newOffset = partition.Value.ConsumeOffset;
                        try
                        {
                            if (partition.Value.ConsumeOffsetValid)
                            {
                                // Save offsets unconditionally. Kafka's latestOffset for a particular topic-partition can go backward
                                // if a follwer which is not fully caught up becomes a leader. We still need to save the conumed offsets even then.
                                //skip only if we are trying to commit the same offset

                                if (newOffset != partition.Value.CommitedOffset)
                                {
                                    try
                                    {
                                        ZkUtils.UpdatePersistentPath(GetZkClient(),
                                                                     topicDirs.ConsumerOffsetDir + "/" +
                                                                     partition.Value.PartitionId, newOffset.ToString());
                                        partition.Value.CommitedOffset = newOffset;
                                    }
                                    catch (Exception ex)
                                    {
                                        Logger.ErrorFormat("error in CommitOffsets UpdatePersistentPath : {0}",
                                                           ex.FormatException());
                                    }
                                }
                            }
                            else
                            {
                                Logger.InfoFormat(
                                    "Skip committing offset {0} for topic {1} because it is invalid (ZK session is disconnected)",
                                    newOffset, partition);
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.WarnFormat("exception during CommitOffsets: {0}", ex.FormatException());
                        }

                        //if (Logger.IsDebugEnabled)
                        {
                            Logger.DebugFormat("Commited offset {0} for topic {1}", newOffset, partition);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.ErrorFormat("error in CommitOffsets : {0}", ex.FormatException());
            }
        }
 public ZookeeperConsumerConnectorTest()
 {
     this.dirs = new ZKGroupTopicDirs(Group, Topic);
 }
Пример #18
0
        private void AddPartitionTopicInfo(ZKGroupTopicDirs topicDirs, string partition, string topic, string consumerThreadId)
        {
            var partitionId      = int.Parse(partition);
            var partTopicInfoMap = this.topicRegistry[topic];

            //find the leader for this partition
            var leaderOpt = ZkUtils.GetLeaderForPartition(this.zkClient, topic, partitionId);

            if (!leaderOpt.HasValue)
            {
                throw new NoBrokersForPartitionException(string.Format("No leader available for partitions {0} on topic {1}", partition, topic));
            }
            else
            {
                Logger.InfoFormat("Leader for partition {0} for topic {1} is {2}", partition, topic, leaderOpt.Value);
            }
            var leader       = leaderOpt.Value;
            var znode        = topicDirs.ConsumerOffsetDir + "/" + partition;
            var offsetString = this.zkClient.ReadData <string>(znode, true);

            //if first time starting a consumer, set the initial offset based on the config
            long offset         = 0;
            long offsetCommited = 0;

            if (offsetString == null)
            {
                switch (config.AutoOffsetReset)
                {
                case OffsetRequest.SmallestTime:
                    offset = this.EarliestOrLatestOffset(topic, leader, partitionId, OffsetRequest.EarliestTime);
                    break;

                case OffsetRequest.LargestTime:
                    offset = this.EarliestOrLatestOffset(topic, leader, partitionId, OffsetRequest.LatestTime);
                    break;

                default:
                    throw new ConfigurationErrorsException("Wrong value in autoOffsetReset in ConsumerConfig");
                }
            }
            else
            {
                offsetCommited = long.Parse(offsetString);
                long latestOffset = this.EarliestOrLatestOffset(topic, leader, partitionId, OffsetRequest.LatestTime);
                offset = Math.Min(offsetCommited + 1, latestOffset);
                Logger.InfoFormat("Final offset {0} for topic {1} partition {2} OffsetCommited {3} latestOffset {4}"
                                  , offset, topic, partition, offsetCommited, latestOffset);
            }

            var queue         = this.queues[new Tuple <string, string>(topic, consumerThreadId)];
            var partTopicInfo = new PartitionTopicInfo(
                topic,
                leader,
                partitionId,
                queue,
                offsetCommited,
                offset,
                offset,
                this.config.FetchSize,
                offsetCommited);

            partTopicInfoMap[partitionId] = partTopicInfo;
            Logger.InfoFormat("{0} selected new offset {1}", partTopicInfo, offset);
        }
Пример #19
0
        private bool Rebalance()
        {
            var myTopicThresdIdsMap       = this.GetTopicCount(this.consumerIdString).GetConsumerThreadIdsPerTopic();
            var cluster                   = new Cluster(zkClient);
            var consumersPerTopicMap      = this.GetConsumersPerTopic(this.config.GroupId);
            var partitionsPerTopicMap     = ZkUtils.GetPartitionsForTopics(this.zkClient, myTopicThresdIdsMap.Keys);
            var relevantTopicThreadIdsMap = GetRelevantTopicMap(
                myTopicThresdIdsMap,
                partitionsPerTopicMap,
                this.oldPartitionsPerTopicMap,
                consumersPerTopicMap,
                this.oldConsumersPerTopicMap);

            if (relevantTopicThreadIdsMap.Count <= 0)
            {
                Logger.InfoFormat(CultureInfo.CurrentCulture, "Consumer {0} with {1} doesn't need to rebalance.", this.consumerIdString, consumersPerTopicMap);
                return(true);
            }

            Logger.Info("Committing all offsets");
            this.zkConsumerConnector.CommitOffsets();

            Logger.Info("Releasing parittion ownership");
            this.ReleasePartitionOwnership();

            var queuesToBeCleared = new List <BlockingCollection <FetchedDataChunk> >();

            foreach (var item in relevantTopicThreadIdsMap)
            {
                this.topicRegistry.Remove(item.Key);
                this.topicRegistry.Add(item.Key, new Dictionary <Partition, PartitionTopicInfo>());

                var topicDirs     = new ZKGroupTopicDirs(config.GroupId, item.Key);
                var curConsumers  = consumersPerTopicMap[item.Key];
                var curPartitions = new List <string>(partitionsPerTopicMap[item.Key]);

                var numberOfPartsPerConsumer       = curPartitions.Count / curConsumers.Count;
                var numberOfConsumersWithExtraPart = curPartitions.Count % curConsumers.Count;

                Logger.InfoFormat(
                    CultureInfo.CurrentCulture,
                    "Consumer {0} rebalancing the following partitions: {1} for topic {2} with consumers: {3}",
                    this.consumerIdString,
                    string.Join(",", curPartitions),
                    item.Key,
                    string.Join(",", curConsumers));

                foreach (string consumerThreadId in item.Value)
                {
                    var myConsumerPosition = curConsumers.IndexOf(consumerThreadId);
                    if (myConsumerPosition < 0)
                    {
                        continue;
                    }

                    var startPart = (numberOfPartsPerConsumer * myConsumerPosition) +
                                    Math.Min(myConsumerPosition, numberOfConsumersWithExtraPart);
                    var numberOfParts = numberOfPartsPerConsumer + (myConsumerPosition + 1 > numberOfConsumersWithExtraPart ? 0 : 1);

                    if (numberOfParts <= 0)
                    {
                        Logger.WarnFormat(CultureInfo.CurrentCulture, "No broker partitions consumed by consumer thread {0} for topic {1}", consumerThreadId, item.Key);
                    }
                    else
                    {
                        for (int i = startPart; i < startPart + numberOfParts; i++)
                        {
                            var partition = curPartitions[i];
                            Logger.InfoFormat(CultureInfo.CurrentCulture, "{0} attempting to claim partition {1}", consumerThreadId, partition);
                            if (!this.ProcessPartition(topicDirs, partition, item.Key, consumerThreadId))
                            {
                                return(false);
                            }
                        }

                        queuesToBeCleared.Add(queues[new Tuple <string, string>(item.Key, consumerThreadId)]);
                    }
                }
            }

            this.UpdateFetcher(cluster, queuesToBeCleared);
            this.oldPartitionsPerTopicMap = partitionsPerTopicMap;
            this.oldConsumersPerTopicMap  = consumersPerTopicMap;
            return(true);
        }
Пример #20
0
        private bool Rebalance(Cluster.Cluster cluster, CancellationTokenSource cancellationTokenSource)
        {
            var topicCount        = GetTopicCount(consumerIdString);
            var topicThreadIdsMap = topicCount.GetConsumerThreadIdsPerTopic();

            if (!topicThreadIdsMap.Any())
            {
                Logger.ErrorFormat("Consumer ID is not registered to any topics in ZK. Exiting rebalance");
                return(false);
            }
            var consumersPerTopicMap = GetConsumersPerTopic(config.GroupId);

            var brokers = ZkUtils.GetAllBrokersInCluster(zkClient);

            if (!brokers.Any())
            {
                Logger.Warn("No brokers found when trying to rebalance.");
                zkClient.Subscribe(ZooKeeperClient.DefaultBrokerIdsPath, this);
                zkConsumerConnector.subscribedChildCollection.Add(
                    new Tuple <string, IZooKeeperChildListener>(ZooKeeperClient.DefaultBrokerIdsPath, this));
                Logger.ErrorFormat(
                    "Subscribe count: subscribedChildCollection:{0} , subscribedZookeeperStateCollection:{1} subscribedZookeeperDataCollection:{2} "
                    , zkConsumerConnector.subscribedChildCollection.Count,
                    zkConsumerConnector.subscribedZookeeperStateCollection.Count,
                    zkConsumerConnector.subscribedZookeeperDataCollection.Count);
                return(false);
            }

            var partitionsPerTopicMap = ZkUtils.GetPartitionsForTopics(zkClient, topicThreadIdsMap.Keys);

            // Check if we've been canceled externally before we dive into the rebalance
            if (cancellationTokenSource.IsCancellationRequested)
            {
                Logger.ErrorFormat(
                    "Rebalance operation has been canceled externally by a future rebalance event. Exiting immediately");
                return(false);
            }

            CloseFetchers(cluster, topicThreadIdsMap, zkConsumerConnector);
            ReleasePartitionOwnership(topicThreadIdsMap);

            try
            {
                foreach (var item in topicThreadIdsMap)
                {
                    var topic = item.Key;
                    var consumerThreadIdSet = item.Value;

                    topicRegistry.Add(topic, new ConcurrentDictionary <int, PartitionTopicInfo>());

                    var topicDirs    = new ZKGroupTopicDirs(config.GroupId, topic);
                    var curConsumers = new List <string>(consumersPerTopicMap[topic]);
                    curConsumers.Sort();

                    var curPartitions = partitionsPerTopicMap[topic].OrderBy(p => int.Parse(p)).ToList();

                    Logger.InfoFormat(
                        "{4} Partitions. {5} ConsumerClients.  Consumer {0} rebalancing the following partitions: {1} for topic {2} with consumers: {3}",
                        consumerIdString,
                        string.Join(",", curPartitions),
                        topic,
                        string.Join(",", curConsumers),
                        curPartitions.Count,
                        curConsumers.Count);

                    var numberOfPartsPerConsumer = curPartitions.Count / curConsumers.Count;
                    Logger.Info("Number of partitions per consumer is: " + numberOfPartsPerConsumer);

                    var numberOfConsumersWithExtraPart = curPartitions.Count % curConsumers.Count;
                    Logger.Info("Number of consumers with an extra partition are: " + numberOfConsumersWithExtraPart);

                    foreach (var consumerThreadId in consumerThreadIdSet)
                    {
                        var myConsumerPosition = curConsumers.IndexOf(consumerThreadId);
                        Logger.Info("Consumer position for consumer " + consumerThreadId + " is: " +
                                    myConsumerPosition);

                        if (myConsumerPosition < 0)
                        {
                            continue;
                        }

                        var startPart = numberOfPartsPerConsumer * myConsumerPosition +
                                        Math.Min(myConsumerPosition, numberOfConsumersWithExtraPart);
                        Logger.Info("Starting partition is: " + startPart);

                        var numberOfParts = numberOfPartsPerConsumer +
                                            (myConsumerPosition + 1 > numberOfConsumersWithExtraPart ? 0 : 1);
                        Logger.Info("Number of partitions to work on is: " + numberOfParts);

                        if (numberOfParts <= 0)
                        {
                            Logger.InfoFormat("No broker partitions consumed by consumer thread {0} for topic {1}",
                                              consumerThreadId, item.Key);
                        }
                        else
                        {
                            for (var i = startPart; i < startPart + numberOfParts; i++)
                            {
                                var partition = curPartitions[i];

                                Logger.InfoFormat("{0} attempting to claim partition {1}", consumerThreadId, partition);
                                var ownPartition = ProcessPartition(topicDirs, partition, topic, consumerThreadId,
                                                                    curConsumers, curPartitions, cancellationTokenSource);
                                if (!ownPartition)
                                {
                                    Logger.InfoFormat(
                                        "{0} failed to claim partition {1} for topic {2}. Exiting rebalance",
                                        consumerThreadId, partition, topic);
                                    return(false);
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.ErrorFormat("error when rebalance: {0}", ex.FormatException());
                return(false);
            }

            // If we get here, we know that we have owned all partitions successfully,
            // therefore it is safe to update fetcher threads and begin dequeuing
            Logger.Info("All partitions were successfully owned. Updating fetchers");

            UpdateFetcher(cluster);

            return(true);
        }