/**
* 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 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.MQClientInstance;
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.ResponseCode;
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.*;
import org.slf4j.Logger;
import java.io.UnsupportedEncodingException;
import java.util.*;
/**
* 所有运维接口都在这里实现
*
* @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 MQClientInstance 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.defaultMQAdminExt.changeInstanceNameToPID();
this.mQClientFactory =
MQClientManager.getInstance().getAndCreateMQClientInstance(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();
Map<MessageQueue, Long> allOffsetTable = new HashMap<MessageQueue, Long>();
if (brokerDatas != null) {
for (BrokerData brokerData : brokerDatas) {
String addr = brokerData.selectBrokerAddr();
if (addr != null) {
Map<MessageQueue, Long> offsetTable =
this.mQClientFactory.getMQClientAPIImpl().invokeBrokerToResetOffset(addr, topic,
group, timestamp, isForce, 5000);
if (offsetTable != null) {
allOffsetTable.putAll(offsetTable);
}
}
}
}
return allOffsetTable;
}
@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);
}
}
@Override
public GroupList queryTopicConsumeByWho(String topic) throws InterruptedException, MQBrokerException,
RemotingException, MQClientException {
TopicRouteData topicRouteData = this.examineTopicRouteInfo(topic);
for (BrokerData bd : topicRouteData.getBrokerDatas()) {
String addr = bd.selectBrokerAddr();
if (addr != null) {
return this.mQClientFactory.getMQClientAPIImpl().queryTopicConsumeByWho(addr, topic, 3000);
}
break;
}
return null;
}
@Override
public Set<QueueTimeSpan> queryConsumeTimeSpan(final String topic, final String group)
throws InterruptedException, MQBrokerException, RemotingException, MQClientException {
TopicRouteData topicRouteData = this.examineTopicRouteInfo(topic);
for (BrokerData bd : topicRouteData.getBrokerDatas()) {
String addr = bd.selectBrokerAddr();
if (addr != null) {
return this.mQClientFactory.getMQClientAPIImpl().queryConsumeTimeSpan(addr, topic, group,
3000);
}
break;
}
return null;
}
@Override
public void resetOffsetNew(String consumerGroup, String topic, long timestamp) throws RemotingException,
MQBrokerException, InterruptedException, MQClientException {
try {
this.resetOffsetByTimestamp(topic, consumerGroup, timestamp, true);
}
catch (MQClientException e) {
if (ResponseCode.CONSUMER_NOT_ONLINE == e.getResponseCode()) {
this.resetOffsetByTimestampOld(consumerGroup, topic, timestamp, true);
return;
}
throw e;
}
}
}