com.alibaba.napoli.metamorphosis.client.producer.ProducerZooKeeper.java Source code

Java tutorial

Introduction

Here is the source code for com.alibaba.napoli.metamorphosis.client.producer.ProducerZooKeeper.java

Source

/*
 * (C) 2007-2012 Alibaba Group Holding Limited.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * Authors:
 *   wuhua <wq163@163.com> , boyan <killme2008@gmail.com>
 */
package com.alibaba.napoli.metamorphosis.client.producer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.alibaba.napoli.metamorphosis.client.MetaClientConfig;
import com.alibaba.napoli.metamorphosis.client.RemotingClientWrapper;
import com.alibaba.napoli.metamorphosis.client.ZkClientChangedListener;
import com.alibaba.napoli.gecko.service.exception.NotifyRemotingException;
import com.alibaba.napoli.metamorphosis.Message;
import com.alibaba.napoli.metamorphosis.cluster.Partition;
import com.alibaba.napoli.metamorphosis.exception.MetaClientException;
import com.alibaba.napoli.metamorphosis.utils.MetaZookeeper;
import com.alibaba.napoli.metamorphosis.utils.MetaZookeeperHelper;
import com.alibaba.napoli.metamorphosis.utils.ZkUtils;
import com.alibaba.napoli.metamorphosis.utils.MetaZookeeper.ZKTopicRightDirs;

/**
 * Producerzk
 * 
 * @author boyan
 * @Date 2011-4-26
 */
public class ProducerZooKeeper implements ZkClientChangedListener {

    private final RemotingClientWrapper remotingClient;

    private final ConcurrentHashMap<String, FutureTask<BrokerConnectionListener>> topicConnectionListeners = new ConcurrentHashMap<String, FutureTask<BrokerConnectionListener>>();

    private final MetaClientConfig metaClientConfig;

    private ZkClient zkClient;

    private final MetaZookeeper metaZookeeper;

    public MetaZookeeper getMetaZookeeper() {
        return metaZookeeper;
    }

    /**
     * topic???topicbroker
     */
    private String defaultTopic;

    static final Log log = LogFactory.getLog(ProducerZooKeeper.class);

    public static class BrokersInfo {
        final Map<Integer/* broker id */, String/* server url */> oldBrokerStringMap;
        final Map<String/* topic */, List<Partition>/* partition list */> oldTopicPartitionMap;

        public BrokersInfo(final Map<Integer, String> oldBrokerStringMap,
                final Map<String, List<Partition>> oldTopicPartitionMap) {
            super();
            this.oldBrokerStringMap = oldBrokerStringMap;
            this.oldTopicPartitionMap = oldTopicPartitionMap;
        }

    }

    final class BrokerConnectionListener implements IZkChildListener, IZkDataListener {

        final Lock lock = new ReentrantLock();
        volatile BrokersInfo brokersInfo = new BrokersInfo(new TreeMap<Integer, String>(),
                new HashMap<String, List<Partition>>());

        final String topic;

        public BrokerConnectionListener(final String topic) {
            super();
            this.topic = topic;
        }

        /**
         * ?broker?
         */
        @Override
        public void handleChildChange(final String parentPath, final List<String> currentChilds) throws Exception {
            this.syncedUpdateBrokersInfo();
        }

        private Map<String, List<Partition>> filterPartitionMap(
                Map<String, List<Partition>> oldPartitionsPerTopicMap) {
            Map<String, List<Partition>> partitionsPerTopicMap = new HashMap<String, List<Partition>>();
            if (oldPartitionsPerTopicMap == null || oldPartitionsPerTopicMap.size() == 0)
                return partitionsPerTopicMap;
            MetaZookeeperHelper helper = new MetaZookeeperHelper(metaZookeeper);
            List<String> brokerIds = helper.getAllWriteRightBrokers(topic);
            for (String topic : oldPartitionsPerTopicMap.keySet()) {

                partitionsPerTopicMap.put(topic, filterPartition(brokerIds, oldPartitionsPerTopicMap.get(topic)));
            }
            return partitionsPerTopicMap;
        }

        private List<Partition> filterPartition(List<String> brokerIds, List<Partition> oldPartitions) {

            List<Partition> partitions = new ArrayList<Partition>();
            if (brokerIds == null || brokerIds.size() == 0 || oldPartitions == null || oldPartitions.size() == 0)
                return partitions;
            for (Partition partition : oldPartitions) {
                for (String brokerId : brokerIds) {
                    if (StringUtils.equals(partition.getBrokerId() + "", brokerId)) {
                        partitions.add(partition);
                        break;
                    }
                }
            }
            return partitions;
        }

        private Map<Integer, String> filterBrokerStringMap(Map<Integer, String> brokerStringMap) {
            Map<Integer, String> newBrokerStringMap = new HashMap<Integer, String>();
            if (brokerStringMap == null || brokerStringMap.size() == 0) {
                return newBrokerStringMap;
            }
            MetaZookeeperHelper helper = new MetaZookeeperHelper(metaZookeeper);
            List<String> brokerIds = helper.getAllWriteRightBrokers(topic);
            if (brokerIds == null || brokerIds.size() == 0)
                return newBrokerStringMap;
            for (Integer brokerId : brokerStringMap.keySet()) {
                if (brokerIds.contains(brokerId + "")) {
                    newBrokerStringMap.put(brokerId, brokerStringMap.get(brokerId));
                }
            }
            return newBrokerStringMap;
        }

        void syncedUpdateBrokersInfo() throws NotifyRemotingException, InterruptedException {
            this.lock.lock();
            try {

                Map<Integer, String> newBrokerStringMap = ProducerZooKeeper.this.metaZookeeper
                        .getMasterBrokersByTopic(this.topic);

                newBrokerStringMap = filterBrokerStringMap(newBrokerStringMap);
                final List<String> topics = new ArrayList<String>(1);
                topics.add(this.topic);
                Map<String, List<Partition>> newTopicPartitionMap = ProducerZooKeeper.this.metaZookeeper
                        .getPartitionsForTopicsFromMaster(topics);
                newTopicPartitionMap = filterPartitionMap(newTopicPartitionMap);

                log.warn("Begin receiving broker changes for topic " + this.topic + ",broker ids:"
                        + newTopicPartitionMap);

                for (final Map.Entry<Integer, String> oldEntry : this.brokersInfo.oldBrokerStringMap.entrySet()) {
                    final Integer oldBrokerId = oldEntry.getKey();
                    final String oldBrokerString = oldEntry.getValue();
                    final String newBrokerString = newBrokerStringMap.get(oldBrokerId);
                    // 
                    if (newBrokerStringMap.containsKey(oldBrokerId)) {
                        // ??
                        if (!newBrokerString.equals(oldBrokerString)) {
                            ProducerZooKeeper.this.remotingClient.close(oldBrokerString, false);
                            ProducerZooKeeper.this.remotingClient.connect(newBrokerString);
                            ProducerZooKeeper.this.remotingClient.awaitReadyInterrupt(newBrokerString);
                            log.warn("Close " + oldBrokerString + ",connect to " + newBrokerString);
                        } else {
                            // ignore
                        }
                    } else {
                        // 
                        ProducerZooKeeper.this.remotingClient.close(oldBrokerString, false);
                        log.warn("Close " + oldBrokerString);
                    }
                }

                for (final Map.Entry<Integer, String> newEntry : newBrokerStringMap.entrySet()) {
                    final Integer newBrokerId = newEntry.getKey();
                    final String newBrokerString = newEntry.getValue();
                    // 
                    if (!this.brokersInfo.oldBrokerStringMap.containsKey(newBrokerId)) {
                        ProducerZooKeeper.this.remotingClient.connect(newBrokerString);
                        ProducerZooKeeper.this.remotingClient.awaitReadyInterrupt(newBrokerString);
                        log.warn("Connect to " + newBrokerString);
                    }
                }
                this.brokersInfo = new BrokersInfo(newBrokerStringMap, newTopicPartitionMap);
                log.warn("End receiving broker changes for topic " + this.topic);
            } finally {
                this.lock.unlock();
            }
        }

        @Override
        public void handleDataChange(String arg0, Object arg1) throws Exception {
            this.syncedUpdateBrokersInfo();

        }

        @Override
        public void handleDataDeleted(String arg0) throws Exception {
            //ignore,it should never happen

        }

    }

    public ProducerZooKeeper(final MetaZookeeper metaZookeeper, final RemotingClientWrapper remotingClient,
            final ZkClient zkClient, final MetaClientConfig metaClientConfig) {
        super();
        this.metaZookeeper = metaZookeeper;
        this.remotingClient = remotingClient;
        this.zkClient = zkClient;
        this.metaClientConfig = metaClientConfig;
    }

    public void publishTopic(final String topic) {
        if (this.topicConnectionListeners.get(topic) != null) {
            return;
        }
        final FutureTask<BrokerConnectionListener> task = new FutureTask<BrokerConnectionListener>(
                new Callable<BrokerConnectionListener>() {
                    @Override
                    public BrokerConnectionListener call() throws Exception {
                        final BrokerConnectionListener listener = new BrokerConnectionListener(topic);
                        if (ProducerZooKeeper.this.zkClient != null) {
                            ProducerZooKeeper.this.publishTopicInternal(topic, listener);
                        }
                        return listener;
                    }

                });

        final FutureTask<BrokerConnectionListener> existsTask = this.topicConnectionListeners.putIfAbsent(topic,
                task);
        if (existsTask == null) {
            task.run();
        }
    }

    private void publishTopicInternal(final String topic, final BrokerConnectionListener listener)
            throws Exception, NotifyRemotingException, InterruptedException {
        final String partitionPath = this.metaZookeeper.brokerTopicsPath + "/" + topic;
        ZkUtils.makeSurePersistentPathExists(ProducerZooKeeper.this.zkClient, partitionPath);
        ProducerZooKeeper.this.zkClient.subscribeChildChanges(partitionPath, listener);
        ProducerZooKeeper.this.zkClient.subscribeDataChanges(partitionPath, listener);
        ZKTopicRightDirs rightDirs = this.metaZookeeper.new ZKTopicRightDirs(topic);
        this.zkClient.subscribeChildChanges(rightDirs.infoTopicWriteRightDir, listener);

        listener.syncedUpdateBrokersInfo();
    }

    BrokerConnectionListener getBrokerConnectionListener(final String topic) {
        final FutureTask<BrokerConnectionListener> task = this.topicConnectionListeners.get(topic);
        if (task != null) {
            try {
                return task.get();
            } catch (final Exception e) {
                log.error("?BrokerConnectionListener", e);
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * ?topic?url
     * 
     * @param topic
     * @return
     */
    Set<String> getServerUrlSetByTopic(final String topic) {
        final BrokerConnectionListener brokerConnectionListener = this.getBrokerConnectionListener(topic);
        if (brokerConnectionListener != null) {
            final BrokersInfo info = brokerConnectionListener.brokersInfo;

            final Map<Integer/* broker id */, String/* server url */> brokerStringMap = info.oldBrokerStringMap;
            final Map<String/* topic */, List<Partition>/* partition list */> topicPartitionMap = info.oldTopicPartitionMap;
            final List<Partition> plist = topicPartitionMap.get(topic);
            if (plist != null) {
                final Set<String> result = new HashSet<String>();
                for (final Partition partition : plist) {
                    final int brokerId = partition.getBrokerId();
                    final String url = brokerStringMap.get(brokerId);
                    if (url != null) {
                        result.add(url);
                    }
                }
                return result;
            }
        }
        return Collections.emptySet();
    }

    /**
     * topic?
     * 
     * @param topic
     */
    public synchronized void setDefaultTopic(final String topic) {
        if (this.defaultTopic != null && !this.defaultTopic.equals(topic)) {
            throw new IllegalStateException("Default topic has been setup already:" + this.defaultTopic);
        }
        this.defaultTopic = topic;
        this.publishTopic(topic);
    }

    /**
     * 
     * broker????local transaction
     * 
     * @param topic
     * @return
     */
    Partition selectPartition(final String topic, final Message msg, final PartitionSelector selector,
            final String serverUrl) throws MetaClientException {
        final BrokerConnectionListener brokerConnectionListener = this.getBrokerConnectionListener(topic);
        if (brokerConnectionListener != null) {
            final BrokersInfo brokersInfo = brokerConnectionListener.brokersInfo;
            final List<Partition> partitions = brokersInfo.oldTopicPartitionMap.get(topic);
            final Map<Integer/* broker id */, String/* server url */> brokerStringMap = brokersInfo.oldBrokerStringMap;
            // broker
            final List<Partition> partitionsForSelect = new ArrayList<Partition>();
            for (final Partition partition : partitions) {
                if (serverUrl.equals(brokerStringMap.get(partition.getBrokerId()))) {
                    partitionsForSelect.add(partition);
                }
            }
            return selector.getPartition(topic, partitionsForSelect, msg);
        } else {
            return this.selectDefaultPartition(topic, msg, selector, serverUrl);
        }
    }

    /**
     * ?partitionbroker url
     * 
     * @param topic
     * @param message
     * @return brokerurl
     */
    public String selectBroker(final String topic, final Partition partition) {
        if (this.metaClientConfig.getServerUrl() != null) {
            return this.metaClientConfig.getServerUrl();
        }
        if (partition != null) {
            final BrokerConnectionListener brokerConnectionListener = this.getBrokerConnectionListener(topic);
            if (brokerConnectionListener != null) {
                final BrokersInfo brokersInfo = brokerConnectionListener.brokersInfo;
                return brokersInfo.oldBrokerStringMap.get(partition.getBrokerId());
            } else {
                return this.selectDefaultBroker(topic, partition);
            }
        }
        return null;
    }

    /**
     * defaultTopicbroker
     * 
     * @param topic
     * @param partition
     * @return
     */
    private String selectDefaultBroker(final String topic, final Partition partition) {
        if (this.defaultTopic == null) {
            return null;
        }
        final BrokerConnectionListener brokerConnectionListener = this
                .getBrokerConnectionListener(this.defaultTopic);
        if (brokerConnectionListener != null) {
            final BrokersInfo brokersInfo = brokerConnectionListener.brokersInfo;
            return brokersInfo.oldBrokerStringMap.get(partition.getBrokerId());
        } else {
            return null;
        }
    }

    /**
     * ?topicmessage
     * 
     * @param topic
     * @param message
     * @return 
     */
    public Partition selectPartition(final String topic, final Message message,
            final PartitionSelector partitionSelector) throws MetaClientException {
        if (this.metaClientConfig.getServerUrl() != null) {
            return Partition.RandomPartiton;
        }
        final BrokerConnectionListener brokerConnectionListener = this.getBrokerConnectionListener(topic);
        if (brokerConnectionListener != null) {
            final BrokersInfo brokersInfo = brokerConnectionListener.brokersInfo;
            return partitionSelector.getPartition(topic, brokersInfo.oldTopicPartitionMap.get(topic), message);
        } else {
            return this.selectDefaultPartition(topic, message, partitionSelector, null);
        }
    }

    private Partition selectDefaultPartition(final String topic, final Message message,
            final PartitionSelector partitionSelector, final String serverUrl) throws MetaClientException {
        if (this.defaultTopic == null) {
            return null;
        }
        final BrokerConnectionListener brokerConnectionListener = this
                .getBrokerConnectionListener(this.defaultTopic);
        if (brokerConnectionListener != null) {
            final BrokersInfo brokersInfo = brokerConnectionListener.brokersInfo;
            if (serverUrl == null) {
                return partitionSelector.getPartition(this.defaultTopic,
                        brokersInfo.oldTopicPartitionMap.get(this.defaultTopic), message);
            } else {
                final List<Partition> partitions = brokersInfo.oldTopicPartitionMap.get(this.defaultTopic);
                final Map<Integer/* broker id */, String/* server url */> brokerStringMap = brokersInfo.oldBrokerStringMap;
                // broker
                final List<Partition> partitionsForSelect = new ArrayList<Partition>();
                for (final Partition partition : partitions) {
                    if (serverUrl.equals(brokerStringMap.get(partition.getBrokerId()))) {
                        partitionsForSelect.add(partition);
                    }
                }
                return partitionSelector.getPartition(this.defaultTopic, partitionsForSelect, message);
            }
        } else {
            return null;
        }
    }

    @Override
    public void onZkClientChanged(final ZkClient newClient) {
        this.zkClient = newClient;
        try {
            for (final String topic : this.topicConnectionListeners.keySet()) {
                log.info("re-publish topic to zk,topic=" + topic);
                this.publishTopicInternal(topic, this.getBrokerConnectionListener(topic));
            }
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (final Exception e) {
            log.error("?zKClient", e);
        }
    }

}