Java tutorial
/* * (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); } } }