/** * Copyright (C) 2010-2013 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. */ package com.alibaba.rocketmq.tools.admin; import java.io.UnsupportedEncodingException; import java.util.*; import org.slf4j.Logger; import com.alibaba.rocketmq.client.QueryResult; import com.alibaba.rocketmq.client.admin.MQAdminExtInner; import com.alibaba.rocketmq.client.exception.MQBrokerException; import com.alibaba.rocketmq.client.exception.MQClientException; import com.alibaba.rocketmq.client.impl.MQClientManager; import com.alibaba.rocketmq.client.impl.factory.MQClientFactory; import com.alibaba.rocketmq.client.log.ClientLogger; import com.alibaba.rocketmq.common.MixAll; import com.alibaba.rocketmq.common.ServiceState; import com.alibaba.rocketmq.common.TopicConfig; import com.alibaba.rocketmq.common.UtilAll; import com.alibaba.rocketmq.common.admin.ConsumeStats; import com.alibaba.rocketmq.common.admin.OffsetWrapper; import com.alibaba.rocketmq.common.admin.RollbackStats; import com.alibaba.rocketmq.common.admin.TopicStatsTable; import com.alibaba.rocketmq.common.help.FAQUrl; import com.alibaba.rocketmq.common.message.MessageExt; import com.alibaba.rocketmq.common.message.MessageQueue; import com.alibaba.rocketmq.common.namesrv.NamesrvUtil; import com.alibaba.rocketmq.common.protocol.body.*; import com.alibaba.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader; import com.alibaba.rocketmq.common.protocol.route.BrokerData; import com.alibaba.rocketmq.common.protocol.route.TopicRouteData; import com.alibaba.rocketmq.common.subscription.SubscriptionGroupConfig; import com.alibaba.rocketmq.remoting.exception.*; /** * 所有运维接口都在这里实现 * * @author shijia.wxr<vintage.wang@gmail.com> * @since 2013-7-21 */ public class DefaultMQAdminExtImpl implements MQAdminExt, MQAdminExtInner { private final Logger log = ClientLogger.getLog(); private final DefaultMQAdminExt defaultMQAdminExt; private ServiceState serviceState = ServiceState.CREATE_JUST; private MQClientFactory mQClientFactory; public DefaultMQAdminExtImpl(DefaultMQAdminExt defaultMQAdminExt) { this.defaultMQAdminExt = defaultMQAdminExt; } @Override public void start() throws MQClientException { switch (this.serviceState) { case CREATE_JUST: this.serviceState = ServiceState.START_FAILED; this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientFactory(this.defaultMQAdminExt); boolean registerOK = mQClientFactory.registerAdminExt(this.defaultMQAdminExt.getAdminExtGroup(), this); if (!registerOK) { this.serviceState = ServiceState.CREATE_JUST; throw new MQClientException("The adminExt group[" + this.defaultMQAdminExt.getAdminExtGroup() + "] has created already, specifed another name please."// + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null); } mQClientFactory.start(); log.info("the adminExt [{}] start OK", this.defaultMQAdminExt.getAdminExtGroup()); this.serviceState = ServiceState.RUNNING; break; case RUNNING: case START_FAILED: case SHUTDOWN_ALREADY: throw new MQClientException("The AdminExt service state not OK, maybe started once, "// + this.serviceState// + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null); default: break; } } @Override public void shutdown() { switch (this.serviceState) { case CREATE_JUST: break; case RUNNING: this.mQClientFactory.unregisterAdminExt(this.defaultMQAdminExt.getAdminExtGroup()); this.mQClientFactory.shutdown(); log.info("the adminExt [{}] shutdown OK", this.defaultMQAdminExt.getAdminExtGroup()); this.serviceState = ServiceState.SHUTDOWN_ALREADY; break; case SHUTDOWN_ALREADY: break; default: break; } } @Override public void createAndUpdateTopicConfig(String addr, TopicConfig config) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.mQClientFactory.getMQClientAPIImpl().createTopic(addr, this.defaultMQAdminExt.getCreateTopicKey(), config, 3000); } @Override public void createAndUpdateSubscriptionGroupConfig(String addr, SubscriptionGroupConfig config) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.mQClientFactory.getMQClientAPIImpl().createSubscriptionGroup(addr, config, 3000); } @Override public SubscriptionGroupConfig examineSubscriptionGroupConfig(String addr, String group) { // TODO Auto-generated method stub return null; } @Override public TopicConfig examineTopicConfig(String addr, String topic) { // TODO Auto-generated method stub return null; } @Override public TopicStatsTable examineTopicStats(String topic) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { TopicRouteData topicRouteData = this.examineTopicRouteInfo(topic); TopicStatsTable topicStatsTable = new TopicStatsTable(); for (BrokerData bd : topicRouteData.getBrokerDatas()) { String addr = bd.selectBrokerAddr(); if (addr != null) { TopicStatsTable tst = this.mQClientFactory.getMQClientAPIImpl().getTopicStatsInfo(addr, topic, 3000); topicStatsTable.getOffsetTable().putAll(tst.getOffsetTable()); } } if (topicStatsTable.getOffsetTable().isEmpty()) { throw new MQClientException("Not found the topic stats info", null); } return topicStatsTable; } @Override public ConsumeStats examineConsumeStats(String consumerGroup) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { String retryTopic = MixAll.getRetryTopic(consumerGroup); TopicRouteData topicRouteData = this.examineTopicRouteInfo(retryTopic); ConsumeStats result = new ConsumeStats(); for (BrokerData bd : topicRouteData.getBrokerDatas()) { String addr = bd.selectBrokerAddr(); if (addr != null) { // 由于查询时间戳会产生IO操作,可能会耗时较长,所以超时时间设置为15s ConsumeStats consumeStats = this.mQClientFactory.getMQClientAPIImpl().getConsumeStats(addr, consumerGroup, 15000); result.getOffsetTable().putAll(consumeStats.getOffsetTable()); long value = result.getConsumeTps() + consumeStats.getConsumeTps(); result.setConsumeTps(value); } } if (result.getOffsetTable().isEmpty()) { throw new MQClientException( "Not found the consumer group consume stats, because return offset table is empty, maybe the consumer not consume any message", null); } return result; } @Override public ClusterInfo examineBrokerClusterInfo() throws InterruptedException, MQBrokerException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { return this.mQClientFactory.getMQClientAPIImpl().getBrokerClusterInfo(3000); } @Override public TopicRouteData examineTopicRouteInfo(String topic) throws RemotingException, MQClientException, InterruptedException { return this.mQClientFactory.getMQClientAPIImpl().getTopicRouteInfoFromNameServer(topic, 3000); } @Override public void putKVConfig(String namespace, String key, String value) { // TODO Auto-generated method stub } @Override public String getKVConfig(String namespace, String key) throws RemotingException, MQClientException, InterruptedException { return this.mQClientFactory.getMQClientAPIImpl().getKVConfigValue(namespace, key, 3000); } @Override public void createTopic(String key, String newTopic, int queueNum) throws MQClientException { this.mQClientFactory.getMQAdminImpl().createTopic(key, newTopic, queueNum); } @Override public long searchOffset(MessageQueue mq, long timestamp) throws MQClientException { return this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } @Override public long maxOffset(MessageQueue mq) throws MQClientException { return this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } @Override public long minOffset(MessageQueue mq) throws MQClientException { return this.mQClientFactory.getMQAdminImpl().minOffset(mq); } @Override public long earliestMsgStoreTime(MessageQueue mq) throws MQClientException { return this.mQClientFactory.getMQAdminImpl().earliestMsgStoreTime(mq); } @Override public MessageExt viewMessage(String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { return this.mQClientFactory.getMQAdminImpl().viewMessage(msgId); } @Override public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return this.mQClientFactory.getMQAdminImpl().queryMessage(topic, key, maxNum, begin, end); } @Override public ConsumerConnection examineConsumerConnectionInfo(String consumerGroup) throws InterruptedException, MQBrokerException, RemotingException, MQClientException { String topic = MixAll.getRetryTopic(consumerGroup); TopicRouteData topicRouteData = this.examineTopicRouteInfo(topic); ConsumerConnection result = new ConsumerConnection(); for (BrokerData bd : topicRouteData.getBrokerDatas()) { String addr = bd.selectBrokerAddr(); if (addr != null) { return this.mQClientFactory.getMQClientAPIImpl().getConsumerConnectionList(addr, consumerGroup, 3000); } } if (result.getConnectionSet().isEmpty()) { throw new MQClientException("Not found the consumer group connection", null); } return result; } @Override public ProducerConnection examineProducerConnectionInfo(String producerGroup, final String topic) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { TopicRouteData topicRouteData = this.examineTopicRouteInfo(topic); ProducerConnection result = new ProducerConnection(); for (BrokerData bd : topicRouteData.getBrokerDatas()) { String addr = bd.selectBrokerAddr(); if (addr != null) { return this.mQClientFactory.getMQClientAPIImpl().getProducerConnectionList(addr, producerGroup, 300); } } if (result.getConnectionSet().isEmpty()) { throw new MQClientException("Not found the consumer group connection", null); } return result; } @Override public int wipeWritePermOfBroker(final String namesrvAddr, String brokerName) throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQClientException { return this.mQClientFactory.getMQClientAPIImpl().wipeWritePermOfBroker(namesrvAddr, brokerName, 3000); } @Override public List<String> getNameServerAddressList() { return this.mQClientFactory.getMQClientAPIImpl().getNameServerAddressList(); } @Override public ConsumeByWho whoConsumeTheMessage(String msgId) { // TODO Auto-generated method stub return null; } @Override public TopicList fetchAllTopicList() throws RemotingException, MQClientException, InterruptedException { return this.mQClientFactory.getMQClientAPIImpl().getTopicListFromNameServer(3000); } @Override public KVTable fetchBrokerRuntimeStats(final String brokerAddr) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException { return this.mQClientFactory.getMQClientAPIImpl().getBrokerRuntimeInfo(brokerAddr, 3000); } @Override public void deleteTopicInBroker(Set<String> addrs, String topic) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { for (String addr : addrs) { this.mQClientFactory.getMQClientAPIImpl().deleteTopicInBroker(addr, topic, 3000); } } @Override public void deleteTopicInNameServer(Set<String> addrs, String topic) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { if (addrs == null) { String ns = this.mQClientFactory.getMQClientAPIImpl().fetchNameServerAddr(); addrs = new HashSet(Arrays.asList(ns.split(";"))); } for (String addr : addrs) { this.mQClientFactory.getMQClientAPIImpl().deleteTopicInNameServer(addr, topic, 3000); } } @Override public void deleteSubscriptionGroup(String addr, String groupName) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.mQClientFactory.getMQClientAPIImpl().deleteSubscriptionGroup(addr, groupName, 3000); } @Override public void createAndUpdateKvConfig(String namespace, String key, String value) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.mQClientFactory.getMQClientAPIImpl().putKVConfigValue(namespace, key, value, 3000); } @Override public void deleteKvConfig(String namespace, String key) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { this.mQClientFactory.getMQClientAPIImpl().deleteKVConfigValue(namespace, key, 3000); } @Override public String getProjectGroupByIp(String ip) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { return this.mQClientFactory.getMQClientAPIImpl().getProjectGroupByIp(ip, 3000); } @Override public String getIpsByProjectGroup(String projectGroup) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { String namespace = NamesrvUtil.NAMESPACE_PROJECT_CONFIG; return this.mQClientFactory.getMQClientAPIImpl().getKVConfigByValue(namespace, projectGroup, 3000); } @Override public void deleteIpsByProjectGroup(String projectGroup) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { String namespace = NamesrvUtil.NAMESPACE_PROJECT_CONFIG; this.mQClientFactory.getMQClientAPIImpl().deleteKVConfigByValue(namespace, projectGroup, 3000); } @Override public List<RollbackStats> resetOffsetByTimestampOld(String consumerGroup, String topic, long timestamp, boolean force) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { TopicRouteData topicRouteData = this.examineTopicRouteInfo(topic); List<RollbackStats> rollbackStatsList = new ArrayList<RollbackStats>(); for (BrokerData bd : topicRouteData.getBrokerDatas()) { String addr = bd.selectBrokerAddr(); if (addr != null) { // 根据 consumerGroup 查找对应的 mq ConsumeStats consumeStats = this.mQClientFactory.getMQClientAPIImpl().getConsumeStats(addr, consumerGroup, 3000); // 根据 topic 过滤不需要的 mq for (Map.Entry<MessageQueue, OffsetWrapper> entry : consumeStats.getOffsetTable().entrySet()) { MessageQueue queue = entry.getKey(); OffsetWrapper offsetWrapper = entry.getValue(); if (topic.equals(queue.getTopic())) { // 根据 timestamp 查找对应的offset long offset = this.mQClientFactory.getMQClientAPIImpl().searchOffset(addr, topic, queue.getQueueId(), timestamp, 3000); // 构建按时间回溯消费进度 RollbackStats rollbackStats = new RollbackStats(); rollbackStats.setBrokerName(bd.getBrokerName()); rollbackStats.setQueueId(queue.getQueueId()); rollbackStats.setBrokerOffset(offsetWrapper.getBrokerOffset()); rollbackStats.setConsumerOffset(offsetWrapper.getConsumerOffset()); rollbackStats.setTimestampOffset(offset); rollbackStats.setRollbackOffset(offsetWrapper.getConsumerOffset()); // 更新 offset if (force || offset <= offsetWrapper.getConsumerOffset()) { rollbackStats.setRollbackOffset(offset); UpdateConsumerOffsetRequestHeader requestHeader = new UpdateConsumerOffsetRequestHeader(); requestHeader.setConsumerGroup(consumerGroup); requestHeader.setTopic(topic); requestHeader.setQueueId(queue.getQueueId()); requestHeader.setCommitOffset(offset); this.mQClientFactory.getMQClientAPIImpl().updateConsumerOffset(addr, requestHeader, 3000); } rollbackStatsList.add(rollbackStats); } } } } return rollbackStatsList; } @Override public KVTable getKVListByNamespace(String namespace) throws RemotingException, MQClientException, InterruptedException { return this.mQClientFactory.getMQClientAPIImpl().getKVListByNamespace(namespace, 5000); } @Override public void updateBrokerConfig(String brokerAddr, Properties properties) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, UnsupportedEncodingException, InterruptedException, MQBrokerException { this.mQClientFactory.getMQClientAPIImpl().updateBrokerConfig(brokerAddr, properties, 5000); } @Override public Map<MessageQueue, Long> resetOffsetByTimestamp(String topic, String group, long timestamp, boolean isForce) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { TopicRouteData topicRouteData = this.examineTopicRouteInfo(topic); List<BrokerData> brokerDatas = topicRouteData.getBrokerDatas(); // 每个 broker 上有所有的 consumer 连接,故只需要在一个 broker 执行即可。 if (brokerDatas != null && brokerDatas.size() > 0) { String addr = brokerDatas.get(0).selectBrokerAddr(); if (addr != null) { return this.mQClientFactory.getMQClientAPIImpl().invokeBrokerToResetOffset(addr, topic, group, timestamp, isForce, 5000); } } return Collections.EMPTY_MAP; } @Override public Map<String, Map<MessageQueue, Long>> getConsumeStatus(String topic, String group, String clientAddr) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { TopicRouteData topicRouteData = this.examineTopicRouteInfo(topic); List<BrokerData> brokerDatas = topicRouteData.getBrokerDatas(); // 每个 broker 上有所有的 consumer 连接,故只需要在一个 broker 执行即可。 if (brokerDatas != null && brokerDatas.size() > 0) { String addr = brokerDatas.get(0).selectBrokerAddr(); if (addr != null) { return this.mQClientFactory.getMQClientAPIImpl().invokeBrokerToGetConsumerStatus(addr, topic, group, clientAddr, 5000); } } return Collections.EMPTY_MAP; } public void createOrUpdateOrderConf(String key, String value, boolean isCluster) throws RemotingException, MQBrokerException, InterruptedException, MQClientException { if (isCluster) { this.mQClientFactory.getMQClientAPIImpl().putKVConfigValue( NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, key, value, 3000); } else { String oldOrderConfs = null; try { oldOrderConfs = this.mQClientFactory.getMQClientAPIImpl().getKVConfigValue( NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, key, 3000); } catch (Exception e) { e.printStackTrace(); } // 添加或替换需要更新的 broker Map<String, String> orderConfMap = new HashMap<String, String>(); if (!UtilAll.isBlank(oldOrderConfs)) { String[] oldOrderConfArr = oldOrderConfs.split(";"); for (String oldOrderConf : oldOrderConfArr) { String[] items = oldOrderConf.split(":"); orderConfMap.put(items[0], oldOrderConf); } } String[] items = value.split(":"); orderConfMap.put(items[0], value); StringBuilder newOrderConf = new StringBuilder(); String splitor = ""; for (String tmp : orderConfMap.keySet()) { newOrderConf.append(splitor).append(orderConfMap.get(tmp)); splitor = ";"; } this.mQClientFactory.getMQClientAPIImpl().putKVConfigValue( NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG, key, newOrderConf.toString(), 3000); } } }