package com.alibaba.rocketmq.namesrv.topic;
import static com.alibaba.rocketmq.common.MixAll.Localhost;
import static com.alibaba.rocketmq.common.namesrv.TopicRuntimeData.ORDER_PREFIX;
import static com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode.REGISTER_BROKER_FAIL_VALUE;
import static com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode.REGISTER_BROKER_TIMEOUT_VALUE;
import static com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode.REGISTER_ORDER_TOPIC_FAIL_VALUE;
import static com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode.REGISTER_ORDER_TOPIC_TIMEOUT_VALUE;
import static com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode.TOPIC_NOT_EXIST_VALUE;
import static com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode.UNREGISTER_BROKER_FAIL_VALUE;
import static com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode.UNREGISTER_BROKER_TIMEOUT_VALUE;
import static com.alibaba.rocketmq.namesrv.common.MergeResult.MERGE_INVALID;
import static com.alibaba.rocketmq.namesrv.common.MergeResult.NOT_MERGE;
import static com.alibaba.rocketmq.namesrv.common.MergeResult.SYS_ERROR;
import static com.alibaba.rocketmq.namesrv.sync.TaskType.REG_BROKER;
import static com.alibaba.rocketmq.namesrv.sync.TaskType.REG_TOPIC;
import static com.alibaba.rocketmq.namesrv.sync.TaskType.UNREG_BROKER;
import static com.alibaba.rocketmq.remoting.protocol.RemotingProtos.ResponseCode.SUCCESS_VALUE;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.rocketmq.common.MixAll;
import com.alibaba.rocketmq.common.protocol.MQProtos.MQResponseCode;
import com.alibaba.rocketmq.common.protocol.route.ObjectConverter;
import com.alibaba.rocketmq.common.namesrv.NamesrvConfig;
import com.alibaba.rocketmq.common.namesrv.TopicRuntimeData;
import com.alibaba.rocketmq.common.protocol.route.BrokerData;
import com.alibaba.rocketmq.common.protocol.route.QueueData;
import com.alibaba.rocketmq.common.protocol.route.TopicRouteData;
import com.alibaba.rocketmq.namesrv.common.MergeResultHolder;
import com.alibaba.rocketmq.namesrv.common.Result;
import com.alibaba.rocketmq.namesrv.processor.ChangeSpreadProcessor;
import com.alibaba.rocketmq.namesrv.sync.FutureGroup;
import com.alibaba.rocketmq.remoting.protocol.RemotingCommand;
/**
* @author lansheng.zj@taobao.com
*/
public class DefaultTopicRuntimeDataManager implements TopicRuntimeDataManager {
private static final Logger log = LoggerFactory.getLogger(MixAll.NamesrvLoggerName);
private NamesrvConfig namesrvConfig;
private TopicRuntimeData topicData;
private ChangeSpreadProcessor changeSpread;
private ReentrantReadWriteLock lock;
private ReadLock readLock;
private WriteLock writeLock;
// TODO version or md5 checksum
public DefaultTopicRuntimeDataManager(NamesrvConfig config) {
namesrvConfig = config;
topicData = new TopicRuntimeData();
changeSpread = new ChangeSpreadProcessor(namesrvConfig);
lock = new ReentrantReadWriteLock();
readLock = lock.readLock();
writeLock = lock.writeLock();
}
public boolean init() {
// ���س�ʼ������
if (!loadBrokerList() || !loadOrderConf())
return false;
changeSpread.init();
return true;
}
private boolean loadBrokerList() {
try {
String content = MixAll.file2String(namesrvConfig.getBrokerAddrConfPath());
if (null == content)
return true;
String[] addresses = content.split(";");
List<String> array = new ArrayList<String>();
for (String addr : addresses) {
array.add(addr);
}
topicData.setBrokerList(array);
topicData.firePropertyChange("brokerList", null, array);
if (log.isInfoEnabled())
log.info("load broker list:" + content);
return true;
}
catch (Exception e) {
// TODO log
}
return false;
}
private boolean loadOrderConf() {
try {
File file = new File(namesrvConfig.getOrderConfPath());
if (!file.exists())
return true;
FileInputStream fis = new FileInputStream(file);
Properties propers = new Properties();
propers.load(fis);
Map<String, String> topicOrderConfs = new HashMap<String, String>();
for (Entry<Object, Object> entry : propers.entrySet()) {
String key = (String) entry.getKey();
if (!topicOrderConfs.containsKey(key))
topicOrderConfs.put(key, (String) entry.getValue());
}
topicData.setTopicOrderConfs(topicOrderConfs);
fis.close();
if (log.isInfoEnabled())
log.info("load topic order conf:\n" + propers);
return true;
}
catch (Exception e) {
// TODO log
e.printStackTrace();
}
return false;
}
public int merge(TopicRuntimeData newTopicData) {
boolean equal = false;
readLock.lock();
try {
equal = topicData.equals(newTopicData);
}
catch (Exception e) {
// TODO
}
finally {
readLock.unlock();
}
if (!equal) {
writeLock.lock();
try {
return doMerge(newTopicData);
}
catch (Throwable e) {
// TODO log
e.printStackTrace();
return SYS_ERROR;
}
finally {
writeLock.unlock();
}
}
return NOT_MERGE;
}
private int doMerge(TopicRuntimeData newTopicData) {
if (log.isInfoEnabled())
log.info("namesrv data merge start ...");
MergeResultHolder ret = new MergeResultHolder();
// if(!ObjectConverter.equals(topicData.getTopicBrokers(),
// newTopicData.getTopicBrokers())) {
// topicData.setTopicBrokers(mergeMap(topicData.getTopicBrokers(),
// newTopicData.getTopicBrokers(), ret));
// }
if (!ObjectConverter.equals(topicData.getTopicOrderConfs(), newTopicData.getTopicOrderConfs())) {
topicData.setTopicOrderConfs(mergeMap(topicData.getTopicOrderConfs(),
newTopicData.getTopicOrderConfs(), ret));
storeOrderTopic();
}
// if(!ObjectConverter.equals(topicData.getBrokers(),
// newTopicData.getBrokers())) {
// topicData.setBrokers(mergeMap(topicData.getBrokers(),
// newTopicData.getBrokers(), ret));
// }
if (!ObjectConverter.equals(topicData.getBrokerList(), newTopicData.getBrokerList())) {
topicData.setBrokerList(mergeList(topicData.getBrokerList(), newTopicData.getBrokerList()));
storeBrokerList();
}
if (log.isInfoEnabled())
log.info("namesrv data merge end ...");
return ret.getResult();
}
private <K, V> Map<K, V> mergeMap(Map<K, V> oldMap, Map<K, V> newMap, MergeResultHolder holder) {
Map<K, V> r = new HashMap<K, V>();
for (Entry<K, V> entry : oldMap.entrySet()) {
K oldKey = entry.getKey();
V oldV = entry.getValue();
if (newMap.containsKey(oldKey)) {
if (null == oldV || oldV.equals(newMap.get(oldKey))) {
r.put(oldKey, null == oldV ? newMap.get(oldKey) : oldV);
}
else {
holder.setResult(MERGE_INVALID);
// key��ͬ��ֵ��ͬ,�������ز�һ�±���
log.error("fetal error merge inconsistent data, key:" + oldKey + ",local value:" + oldV
+ ",remote value:" + newMap.get(oldKey));
}
}
else {
r.put(oldKey, oldV);
}
}
for (Entry<K, V> entry : newMap.entrySet()) {
K newKey = entry.getKey();
if (!oldMap.containsKey(newKey)) {
r.put(newKey, entry.getValue());
}
}
return r;
}
private <E> List<E> mergeList(List<E> oldList, List<E> newList) {
List<E> r = new ArrayList<E>();
r.addAll(oldList);
for (E e : newList) {
if (!r.contains(e))
r.add(e);
}
return r;
}
private Result spreadSuccess(FutureGroup<Result> future) {
Result result = Result.SUCCESS;
try {
for (Entry<String, Future<Result>> entry : future.getResultMap().entrySet()) {
if (!entry.getValue().get().isSuccess()) {
result = new Result(false, future.detailInfo());
break;
}
}
}
catch (Exception e) {
// exception handle
log.error(Localhost + " spreadSuccess exception", e);
result = new Result(false, Localhost + " spreadSuccess exception" + e.getMessage());
}
return result;
}
@Override
public RemotingCommand getRouteInfoByTopic(String topic) {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
TopicRouteData snapshot = doGetRouteInfoByTopic(topic);
if (null != snapshot) {
response.setBody(snapshot.encode());
response.setCode(SUCCESS_VALUE);
}
else {
response.setCode(TOPIC_NOT_EXIST_VALUE);
response.setRemark("No topic config in this Name Server.");
}
return response;
}
private TopicRouteData doGetRouteInfoByTopic(String topic) {
readLock.lock();
try {
List<QueueData> queueDatas = topicData.getTopicBrokers().get(topic);
if (null != queueDatas) {
TopicRouteData topicRouteData = new TopicRouteData();
topicRouteData.setQueueDatas(queueDatas);
List<BrokerData> brokerDatas = new ArrayList<BrokerData>();
for (QueueData queueData : queueDatas) {
if (topicData.getBrokers().containsKey(queueData.getBrokerName())) {
brokerDatas.add(topicData.getBrokers().get(queueData.getBrokerName()));
}
}
topicRouteData.setBrokerDatas(brokerDatas);
topicRouteData.setOrderTopicConf(topicData.getOrderConfByTopic(topic));
return topicRouteData;
}
}
finally {
readLock.unlock();
}
return null;
}
@Override
public RemotingCommand getTopicRuntimeData() {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
byte[] data = serializeTopicRuntimeData();
response.setBody(data);
response.setCode(SUCCESS_VALUE);
return response;
}
public byte[] serializeTopicRuntimeData() {
readLock.lock();
try {
return topicData.encodeSpecific();
}
finally {
readLock.unlock();
}
}
@Override
public RemotingCommand registerBrokerSingle(String address) {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
doRegisterBroker(address);
response.setCode(SUCCESS_VALUE);
if (log.isInfoEnabled())
log.info("register broker single, address:" + address);
return response;
}
@Override
public RemotingCommand registerBroker(String address) {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
if (null == address || "".equals(address)) {
response.setCode(MQResponseCode.REGISTER_BROKER_FAIL_VALUE);
response.setRemark("empty address");
return response;
}
// register to local
doRegisterBroker(address);
// spread to other namesrv
FutureGroup<Result> future = changeSpread.spreadChange(REG_BROKER, new String[] { address });
// set return value
Result result = future.await(namesrvConfig.getGroupWaitTimeout());
if (result.isSuccess()) {
result = spreadSuccess(future);
if (result.isSuccess()) {
response.setCode(SUCCESS_VALUE);
}
else {
response.setCode(REGISTER_BROKER_FAIL_VALUE);
response.setRemark(result.getDetail());
}
}
else {
response.setCode(REGISTER_BROKER_TIMEOUT_VALUE);
response.setRemark(result.getDetail());
}
if (log.isInfoEnabled())
log.info("register broker and spread to others, address:" + address + ",result:" + result.isSuccess()
+ ",code:" + response.getCode());
return response;
}
public void doRegisterBroker(String address) {
readLock.lock();
try {
List<String> brokerList = topicData.getBrokerList();
if (!brokerList.contains(address)) {
brokerList.add(address);
storeBrokerList();
topicData.firePropertyChange("brokerList", null, null);
}
}
finally {
readLock.unlock();
}
}
private void storeOrderTopic() {
String content = topicData.encodeOrderTopicAsString();
String fileName = namesrvConfig.getOrderConfPath();
boolean success = MixAll.string2File(content, fileName);
log.info("store order topic in local " + success);
}
private void storeBrokerList() {
String content = topicData.encodeBrokerListAsString();
String fileName = namesrvConfig.getBrokerAddrConfPath();
boolean success = MixAll.string2File(content, fileName);
log.info("store broker list in local " + success);
}
/**
*
* @param topic
* @param orderConf
*/
public boolean doRegisterOrderTopic(String topic, String orderConf) {
boolean success = true;
readLock.lock();
try {
Map<String, String> orderTopicConf = topicData.getTopicOrderConfs();
String key = ORDER_PREFIX + topic;
if (orderTopicConf.containsKey(key)) {
String oldOrderConf = orderTopicConf.get(key);
if (oldOrderConf != null && !oldOrderConf.equals(orderConf)) {
success = false;
// key��ͬ��ֵ��ͬ,�������ز�һ�±���
log.error("fetal error register order topic, key:" + key + ",local value:" + oldOrderConf
+ ",remote value:" + orderConf);
}
}
else {
orderTopicConf.put(key, orderConf);
if (log.isInfoEnabled())
log.info("start register order topic, topic:" + key + ",order conf:" + orderConf);
// store in local
storeOrderTopic();
}
}
finally {
readLock.unlock();
}
return success;
}
@Override
public RemotingCommand registerOrderTopic(String topic, String orderConf) {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
if (!doRegisterOrderTopic(topic, orderConf)) {
response.setCode(REGISTER_ORDER_TOPIC_FAIL_VALUE);
return response;
}
FutureGroup<Result> future = changeSpread.spreadChange(REG_TOPIC, new String[] { topic, orderConf });
Result result = future.await(namesrvConfig.getGroupWaitTimeout());
if (result.isSuccess()) {
result = spreadSuccess(future);
if (result.isSuccess()) {
response.setCode(SUCCESS_VALUE);
}
else {
response.setCode(REGISTER_ORDER_TOPIC_FAIL_VALUE);
response.setRemark(result.getDetail());
}
}
else {
response.setCode(REGISTER_ORDER_TOPIC_TIMEOUT_VALUE);
response.setRemark(result.getDetail());
}
return response;
}
@Override
public RemotingCommand registerOrderTopicSingle(String topic, String orderConf) {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
response.setCode(REGISTER_ORDER_TOPIC_FAIL_VALUE);
if (doRegisterOrderTopic(topic, orderConf)) {
response.setCode(SUCCESS_VALUE);
}
return response;
}
@Override
public RemotingCommand unRegisterBroker(String brokerName) {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
if (null == brokerName || "".equals(brokerName)) {
response.setCode(MQResponseCode.UNREGISTER_BROKER_FAIL_VALUE);
response.setRemark("empty brokerName");
return response;
}
if (doUnRegisterBroker(brokerName)) {
response.setCode(MQResponseCode.UNREGISTER_BROKER_FAIL_VALUE);
// FIXME detail info
response.setRemark("");
}
// spread to other namesrv
FutureGroup<Result> future = changeSpread.spreadChange(UNREG_BROKER, new String[] { brokerName });
// set return value
Result result = future.await(namesrvConfig.getGroupWaitTimeout());
if (result.isSuccess()) {
result = spreadSuccess(future);
if (result.isSuccess()) {
response.setCode(SUCCESS_VALUE);
}
else {
response.setCode(UNREGISTER_BROKER_FAIL_VALUE);
response.setRemark(result.getDetail());
}
}
else {
response.setCode(UNREGISTER_BROKER_TIMEOUT_VALUE);
response.setRemark(result.getDetail());
}
if (log.isInfoEnabled())
log.info("register broker and spread to others, brokername:" + brokerName + ",result:"
+ result.isSuccess() + ",code:" + response.getCode());
return response;
}
@Override
public RemotingCommand unRegisterBrokerSingle(String brokerName) {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
response.setCode(UNREGISTER_BROKER_FAIL_VALUE);
if (doUnRegisterBroker(brokerName)) {
response.setCode(SUCCESS_VALUE);
}
return response;
}
@Override
public boolean mergeQueueData(Map<String, QueueData> queueDataMap) {
writeLock.lock();
try {
Map<String, List<QueueData>> queueMap = topicData.getTopicBrokers();
for (Entry<String, QueueData> entry : queueDataMap.entrySet()) {
String newKey = entry.getKey();
QueueData newQueueData = entry.getValue();
if (queueMap.containsKey(newKey)) {
boolean append = true;
List<QueueData> oldQueues = queueMap.get(newKey);
for (QueueData old : oldQueues) {
if (old.getBrokerName().equals(newQueueData.getBrokerName())) {
oldQueues.remove(old);
oldQueues.add(newQueueData);
append = false;
break;
}
}
if (append)
oldQueues.add(newQueueData);
}
else {
List<QueueData> queues = new ArrayList<QueueData>();
queues.add(newQueueData);
queueMap.put(newKey, queues);
}
}
}
finally {
writeLock.unlock();
}
return true;
}
@Override
public boolean mergeBrokerData(String brokerName, long brokerId, String address) {
writeLock.lock();
try {
Map<String, BrokerData> brokerMap = topicData.getBrokers();
if (brokerMap.containsKey(brokerName)) {
BrokerData brokerData = brokerMap.get(brokerName);
brokerData.getBrokerAddrs().put(brokerId, address);
}
else {
BrokerData brokerData = new BrokerData();
brokerData.setBrokerName(brokerName);
HashMap<Long, String> addrMap = new HashMap<Long, String>();
addrMap.put(brokerId, address);
brokerData.setBrokerAddrs(addrMap);
brokerMap.put(brokerName, brokerData);
}
}
finally {
writeLock.unlock();
}
return true;
}
public boolean doUnRegisterBroker(String brokerName) {
writeLock.lock();
try {
// remove broker
topicData.getBrokers().remove(brokerName);
// remove topic info
Iterator<Entry<String, List<QueueData>>> iterator = topicData.getTopicBrokers().entrySet().iterator();
while (iterator.hasNext()) {
Entry<String, List<QueueData>> entry = iterator.next();
List<QueueData> listQueue = entry.getValue();
Iterator<QueueData> iterQueueData = listQueue.iterator();
while (iterQueueData.hasNext()) {
QueueData queueData = iterQueueData.next();
if (queueData.getBrokerName().equals(brokerName)) {
iterQueueData.remove();
}
}
if (listQueue.size() <= 0)
iterator.remove();
}
return true;
}
finally {
writeLock.unlock();
}
}
@Override
public RemotingCommand unRegisterBrokerByAddr(String addr) {
if (null == addr || "".equals(addr))
return null;
String brokerName = findBrokerNameByAddr(addr);
return this.unRegisterBroker(brokerName);
}
@Override
public RemotingCommand unRegisterBrokerSingleByAddr(String addr) {
if (null == addr || "".equals(addr))
return null;
String brokerName = findBrokerNameByAddr(addr);
return this.unRegisterBrokerSingle(brokerName);
}
private String findBrokerNameByAddr(String addr) {
readLock.lock();
try {
for (Entry<String, BrokerData> entry : topicData.getBrokers().entrySet()) {
BrokerData brokerData = entry.getValue();
for (Entry<Long, String> brokerEntry : brokerData.getBrokerAddrs().entrySet()) {
if (brokerEntry.getValue().equals(addr))
return brokerData.getBrokerName();
}
}
return null;
}
finally {
readLock.unlock();
}
}
@Override
public ArrayList<String> getBrokerList() {
return (ArrayList<String>) ((ArrayList<String>) topicData.getBrokerList()).clone();
}
@Override
public void shutdown() {
changeSpread.shutdown();
}
public void addPropertyChangeListener(final String propertyName, final PropertyChangeListener listener) {
topicData.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(final PropertyChangeListener listener) {
topicData.removePropertyChangeListener(listener);
}
public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
topicData.firePropertyChange(propertyName, oldValue, newValue);
}
}