/** * Project: doris.config.server-1.0-SNAPSHOT * * File Created at 2011-4-27 * $Id$ * * Copyright 1999-2100 Alibaba.com Corporation Limited. * All rights reserved. * * This software is the confidential and proprietary information of * Alibaba Company. ("Confidential Information"). You shall not * disclose such Confidential Information and shall use it only in * accordance with the terms of the license agreement you entered into * with Alibaba.com. */ package com.alibaba.doris.admin.service.common.route; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.alibaba.doris.admin.core.AdminServiceLocator; import com.alibaba.doris.admin.dataobject.PhysicalNodeDO; import com.alibaba.doris.admin.dataobject.RouterConfigInstanceDO; import com.alibaba.doris.admin.monitor.MonitorEnum; import com.alibaba.doris.admin.monitor.SystemLogMonitor; import com.alibaba.doris.admin.service.AdminNodeService; import com.alibaba.doris.admin.service.PropertiesService; import com.alibaba.doris.admin.service.RouteConfigService; import com.alibaba.doris.admin.service.common.Managerable; import com.alibaba.doris.common.AdminServiceConstants; import com.alibaba.doris.common.MonitorWarnConstants; import com.alibaba.doris.common.NodeRouteStatus; import com.alibaba.doris.common.StoreNode; import com.alibaba.doris.common.StoreNodeSequenceEnum; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; /** * @author mianhe */ public class RouteConfigProcessor implements Managerable { private static final Log logger = LogFactory .getLog(RouteConfigProcessor.class); private volatile boolean initialized = false; private Thread configLoadThread; private volatile boolean stopped = false; private AdminNodeService nodeService = AdminServiceLocator .getAdminNodeService(); private volatile RouterConfigInstanceDO currentConfigInstanceDo = null; private volatile Map<Integer, PhysicalNodeDO> currentPhysicalNodeMap = new ConcurrentHashMap<Integer, PhysicalNodeDO>(); private static final RouteConfigProcessor instance = new RouteConfigProcessor(); private RouteConfigService routeConfigService = AdminServiceLocator .getRouteConfigService(); private static PropertiesService propertyService = AdminServiceLocator .getPropertiesService(); private static final long routeConfigScanInterval = propertyService .getProperty( "routeConfigScanInterval", Long.TYPE, AdminServiceConstants.ROUTER_SCAN_DEFAULT_RELOAD_INTERVAL); private RouteConfigProcessor() { try { init(); } catch (DorisConfigServiceException e) { SystemLogMonitor.error(MonitorEnum.ROUTER, MonitorWarnConstants.RE_GEN_ROUTE_FAILED, e); logger.error("failed to init " + RouteConfigProcessor.class, e); } } public static RouteConfigProcessor getInstance() { return instance; } private synchronized void init() throws DorisConfigServiceException { if (initialized == true) { if (logger.isInfoEnabled()) { logger.warn("RouteConfigCachableProcessor is already initialized."); } return; } //启动的时候,load配置数据库中的实时配置 load(); //启动定时刷新配置线程 configLoadThread = new Thread(new LoadTask(), "load-config-instance-thread"); configLoadThread.setDaemon(true); //configLoadThread.start(); this.initialized = true; } /** * @throws DorisConfigServiceException */ private void load() throws DorisConfigServiceException { //刚启动的时候,内存中没有缓存 Collection<PhysicalNodeDO> latestPhysicalNodeList = loadValidNodes(); // fill map boolean filled = fillPhysicalNodeMap(latestPhysicalNodeList, false); if (filled) { currentConfigInstanceDo = routeConfigService.loadLatestConfigInstance(); } if (currentConfigInstanceDo == null) { insertNewConfigInstance(false); } } private Collection<PhysicalNodeDO> loadValidNodes() throws DorisConfigServiceException { Collection<PhysicalNodeDO> latestPhysicalNodeList = nodeService.queryAllPhysicalNodes(); //filter this.filterNodes(latestPhysicalNodeList); //valid this.validateNodes(latestPhysicalNodeList); return latestPhysicalNodeList; } private boolean fillPhysicalNodeMap(Collection<PhysicalNodeDO> latestPhysicalNodeList, boolean clearmap) { if (clearmap) { currentPhysicalNodeMap.clear(); } if (currentPhysicalNodeMap.isEmpty()) { if (latestPhysicalNodeList != null && !latestPhysicalNodeList.isEmpty()) { for (PhysicalNodeDO latestNode : latestPhysicalNodeList) { if (isValidRoutableNode(latestNode)) { currentPhysicalNodeMap.put(latestNode.getId(), latestNode); } } } else { //正常情况下配置肯定不能是null/empty,警告,后续会定时刷新, 这里不需要抛异常 logger .warn("Cannot load doris config, DORIS config database may not have config data."); } return true; } return false; } private void insertNewConfigInstance(boolean needCheckDB) { if (needCheckDB) { RouterConfigInstanceDO newConfigInstance = buildNewConfigInstance(currentPhysicalNodeMap); // 重数据库中取最新的Config Instance, 如果路由一致则没有必要生成新的。 RouterConfigInstanceDO latestConfigFromDb = routeConfigService.loadLatestConfigInstance(); List<StoreNode> newStoreNodes = JSON.parseArray(newConfigInstance.getContent(), StoreNode.class); List<StoreNode> dbStoreNodes = JSON.parseArray(latestConfigFromDb.getContent(), StoreNode.class); long begin = System.currentTimeMillis(); boolean identical = checkIdentical(newStoreNodes, dbStoreNodes); if (logger.isWarnEnabled()) { logger.warn("indential compared cost:" + (System.currentTimeMillis() - begin)); } if (identical) { currentConfigInstanceDo = latestConfigFromDb; if (logger.isWarnEnabled()) { logger.warn("new config has already generated!!!"); } } else { int id = routeConfigService.insertConfigInstance(newConfigInstance); newConfigInstance.setId(id); currentConfigInstanceDo = routeConfigService.loadLatestConfigInstance(); if (logger.isWarnEnabled()) { int loadedid = currentConfigInstanceDo.getId(); if (id != loadedid) { logger.warn("Confilict route version occurs, use loaded id from db.[insert id:" + id + ", loaded id:" + loadedid + "]"); } logger.warn("current config id:" + id); } } } else { RouterConfigInstanceDO newConfigInstance = buildNewConfigInstance(currentPhysicalNodeMap); int id = routeConfigService.insertConfigInstance(newConfigInstance); newConfigInstance.setId(id); currentConfigInstanceDo = routeConfigService.loadLatestConfigInstance(); if (logger.isWarnEnabled()) { int loadedid = currentConfigInstanceDo.getId(); if (id != loadedid) { logger.warn("Confilict route version occurs, use loaded id from db.[insert id:" + id + ", loaded id:" + loadedid + "]"); } logger.warn("system init, new config id:" + loadedid); } } } private boolean checkIdentical(List<StoreNode> newStoreNodes, List<StoreNode> dbStoreNodes) { if ((newStoreNodes == null && dbStoreNodes != null) || (newStoreNodes != null && dbStoreNodes == null)) { return false; } if (newStoreNodes != null && dbStoreNodes != null) { if (newStoreNodes.size() != dbStoreNodes.size()) { return false; } Comparator<StoreNode> snComparator = new Comparator<StoreNode>() { public int compare(StoreNode o1, StoreNode o2) { return o1.getPhId().compareTo(o2.getPhId()); } }; Collections.sort(dbStoreNodes, snComparator); Collections.sort(newStoreNodes, snComparator); for (int i = 0; i < dbStoreNodes.size(); i++) { StoreNode dbNode = dbStoreNodes.get(i); StoreNode newNode = newStoreNodes.get(i); boolean isDifferent = isDifferent(dbNode, newNode); if (isDifferent) { return false; } } } return true; } public void close() { if (!initialized) { if (logger.isInfoEnabled()) { logger.warn("The RouteConfigCachableProcessor is not initialized yet."); } return; } if (logger.isInfoEnabled()) { logger.info("RouteConfigCachableProcessor is closing!!!"); } stopped = true; if (logger.isInfoEnabled()) { logger.info("Thread of config refresh is stopping!!!"); } if (configLoadThread != null) { configLoadThread.interrupt(); } } /** * @return the currentConfigInstanceDo * @throws DorisConfigServiceException */ public RouterConfigInstanceDO getCurrentConfigInstanceDo() throws DorisConfigServiceException { if (!initialized) { if (logger.isInfoEnabled()) { logger.warn("The RouteConfigCachableProcessor is not initialized yet."); } } if (currentConfigInstanceDo == null) { //refresh if (logger.isDebugEnabled()) { logger.debug("cannot find configinstance:refresh."); } refresh(); } return currentConfigInstanceDo; } public synchronized void refreshWithDbLatest() throws DorisConfigServiceException { if (logger.isWarnEnabled()) { logger.warn("forced refresh starts..."); } RouterConfigInstanceDO dbConfigInstanceDo = routeConfigService.loadLatestConfigInstance(); if (currentConfigInstanceDo == null || (dbConfigInstanceDo.getId() > currentConfigInstanceDo.getId())) { currentConfigInstanceDo = dbConfigInstanceDo; Collection<PhysicalNodeDO> latestPhysicalNodeList = loadValidNodes(); fillPhysicalNodeMap(latestPhysicalNodeList, true); if (logger.isWarnEnabled()) { logger.warn("forced refreshed..., db config id :" + dbConfigInstanceDo.getId()); } } } public synchronized void refresh() throws DorisConfigServiceException { if (!initialized) { if (logger.isWarnEnabled()) { logger.warn("The RouteConfigCachableProcessor is not initialized yet."); } return; } Collection<PhysicalNodeDO> latestPhysicalNodeList = loadValidNodes(); boolean hasNodeChanged = checkNodeChanges(latestPhysicalNodeList); if (hasNodeChanged) { insertNewConfigInstance(true); } else if (currentConfigInstanceDo == null) { //系统没有初始化,或者初始化失败的补偿机制 if (logger.isWarnEnabled()) { logger.warn("The RouteConfigCachableProcessor is not initialized, reload."); } currentConfigInstanceDo = routeConfigService.loadLatestConfigInstance(); } else { // 没有变更,无需更新currentConfigInstanceDo。 } if (currentConfigInstanceDo == null) { logger.warn("Cannot load doris config, DORIS config database may not have config data."); } } private boolean checkNodeChanges(Collection<PhysicalNodeDO> latestPhysicalNodeList) { int latestPhysicalNodesNum = (latestPhysicalNodeList == null) ? 0 : latestPhysicalNodeList.size(); // 数据库没有可用节点, if (latestPhysicalNodesNum == 0) { if (currentPhysicalNodeMap.isEmpty()) { return false; } else { //清空:currentPhysicalNodeMap currentPhysicalNodeMap.clear(); return true; } } // 数据库有可用节点, 检查node并且更新currentPhysicalNodeMap boolean hasNodeChanged = false; if (latestPhysicalNodeList != null && !latestPhysicalNodeList.isEmpty()) { List<Integer> allLatestPhysicalNodeIds = new ArrayList<Integer>(); for (PhysicalNodeDO latestNode : latestPhysicalNodeList) { allLatestPhysicalNodeIds.add(latestNode.getId()); PhysicalNodeDO currentNode = currentPhysicalNodeMap.get(latestNode.getId()); if (currentNode == null || isChanged(latestNode, currentNode)) { currentPhysicalNodeMap.put(latestNode.getId(), latestNode); hasNodeChanged = true; } } for (Integer nodeId : currentPhysicalNodeMap.keySet()) { if (!allLatestPhysicalNodeIds.contains(nodeId)) { currentPhysicalNodeMap.remove(nodeId); hasNodeChanged = true; } } } return hasNodeChanged; } private void validateNodes(Collection<PhysicalNodeDO> allPhyNodes) throws DorisConfigServiceException { //for validate logical id Map<StoreNodeSequenceEnum, List<PhysicalNodeDO>> nodesGroup = new HashMap<StoreNodeSequenceEnum, List<PhysicalNodeDO>>(); // for validate nodes. Map<String, Set<StoreNodeSequenceEnum>> ipGroups = new HashMap<String, Set<StoreNodeSequenceEnum>>(); for (PhysicalNodeDO node : allPhyNodes) { StoreNodeSequenceEnum seq = StoreNodeSequenceEnum.getTypeByValue(node.getSerialId()); if (seq == null) { throw new InvalidNodeException("invalid sequence number for node with id :" + node.getId()); } if (StringUtils.isBlank(node.getIp())) { throw new InvalidNodeException("ip for node with id :" + node.getId()); } if (StringUtils.isBlank(node.getPhysicalId())) { throw new InvalidNodeException("invalid sequence number for node with id :" + node.getId()); } if (node.getPort() <= 0 || node.getPort() > 65535) { throw new InvalidNodeException("port is not valid for node with id :" + node.getId()); } if (StringUtils.isBlank(node.getMachineId())) { throw new InvalidNodeException("machine id is empty for node with id :" + node.getId()); } List<PhysicalNodeDO> nodes = nodesGroup.get(seq); if (nodes == null) { nodes = new ArrayList<PhysicalNodeDO>(); nodesGroup.put(seq, nodes); } nodes.add(node); Set<StoreNodeSequenceEnum> ips = ipGroups.get(node.getIp()); if (ips == null) { ips = new HashSet<StoreNodeSequenceEnum>(); } if (StoreNodeSequenceEnum.isNormalSequence(seq)) { ips.add(seq); } ipGroups.put(node.getIp(), ips); } // validate logic id in one sequence: for (Map.Entry<StoreNodeSequenceEnum, List<PhysicalNodeDO>> oneSequenceNodes : nodesGroup .entrySet()) { //valid the logical id in one sequence. List<PhysicalNodeDO> nodes = oneSequenceNodes.getValue(); List<Integer> allLogicalIds = new ArrayList<Integer>(); for (PhysicalNodeDO node : nodes) { allLogicalIds.add(node.getLogicalId()); } Collections.sort(allLogicalIds); for (int i = 0; i < allLogicalIds.size(); i++) { Integer logicalId = allLogicalIds.get(i); if (!logicalId.equals(i)) { throw new InvalidNodeException("invalid logical id: " + logicalId + " in sequece :" + oneSequenceNodes.getKey()); } } } // the nodes in one machine cannot allocated to different normal sequence. for (Map.Entry<String, Set<StoreNodeSequenceEnum>> entry : ipGroups.entrySet()) { if (entry.getValue().size() > 1) { throw new InvalidNodeException("the nodes in machine (ip=" + entry.getKey() + ") is allocated in different sequence" + entry.getValue()); } } //Warning if there is no temp sequence. List<PhysicalNodeDO> tempNodes = nodesGroup.get(StoreNodeSequenceEnum.TEMP_SEQUENCE); if (tempNodes == null || tempNodes.isEmpty()) { SystemLogMonitor.error(MonitorEnum.ROUTER, MonitorWarnConstants.ROUTE_NO_TEMP_NODES); } } private void filterNodes(Collection<PhysicalNodeDO> latestPhysicalNodeList) { Iterator<PhysicalNodeDO> entryIter = latestPhysicalNodeList.iterator(); while (entryIter.hasNext()) { PhysicalNodeDO pNode = entryIter.next(); if (!isValidRoutableNode(pNode)) { entryIter.remove(); } } } private class LoadTask implements Runnable { public void run() { try { while (!stopped) { try { Thread.sleep(routeConfigScanInterval); } catch (Exception e) { logger.warn("Exception when the key fetch task sleep: " + e.toString()); } if (stopped) { return; } try { if (logger.isDebugEnabled()) { logger.debug("refresh route schedully."); } refresh(); } catch (Exception e) { logger.error("Exception when fetch doris config: ", e); SystemLogMonitor.error(MonitorEnum.ROUTER, MonitorWarnConstants.RE_GEN_ROUTE_FAILED, e); } } } finally { logger.warn("the key fetch thread exit!"); } } } private RouterConfigInstanceDO buildNewConfigInstance( Map<Integer, PhysicalNodeDO> currentPhysicalNodeMap) { RouterConfigInstanceDO configInstanceDO = new RouterConfigInstanceDO(); configInstanceDO.setGmtCreate(new Date()); String content = buildConfigInstanceContent(currentPhysicalNodeMap); configInstanceDO.setContent(content); return configInstanceDO; } private String buildConfigInstanceContent(Map<Integer, PhysicalNodeDO> currentPhysicalNodeMap) { List<StoreNode> storeNodeList = new ArrayList<StoreNode>(); if (currentPhysicalNodeMap == null || currentPhysicalNodeMap.isEmpty()) { logger.warn("Build config instance content, but the content might not be correct."); } else { for (PhysicalNodeDO node : currentPhysicalNodeMap.values()) { StoreNode storeNode = new StoreNode(); storeNode.setURL(node.getIp() + ":" + node.getPort()); storeNode.setIp(node.getIp()); storeNode.setPort(node.getPort()); storeNode.setLogicId(node.getLogicalId()); storeNode.setPhId(node.getPhysicalId()); storeNode.setSequence(StoreNodeSequenceEnum.getTypeByValue(node.getSerialId())); storeNode.setStatus(NodeRouteStatus.getTypeByValue(node.getStatus())); storeNodeList.add(storeNode); } } return JSON.toJSONString(storeNodeList, SerializerFeature.WriteEnumUsingToString); } private boolean isValidRoutableNode(PhysicalNodeDO latestNode) { if (latestNode == null) return false; NodeRouteStatus status = NodeRouteStatus.getTypeByValue(latestNode.getStatus()); StoreNodeSequenceEnum sequence = StoreNodeSequenceEnum.getTypeByValue(latestNode .getSerialId()); return isValidRoutableSequence(sequence) && isValidRoutableStatus(status); } private boolean isValidRoutableStatus(NodeRouteStatus status) { if (status == null) { return false; } return status == NodeRouteStatus.OK || status == NodeRouteStatus.TEMP_FAILED; } private boolean isValidRoutableSequence(StoreNodeSequenceEnum sequence) { if (sequence == null) return false; return (sequence != StoreNodeSequenceEnum.STANDBY_SEQUENCE) && (sequence != StoreNodeSequenceEnum.UNUSE_SEQUENCE); } private boolean isChanged(PhysicalNodeDO latestNode, PhysicalNodeDO currentNode) { // 任何一个属性不一样就认为有变更。 return !latestNode.getMachineId().equals(currentNode.getMachineId()) //|| !latestNode.getGmtCreate().equals(currentNode.getGmtCreate()) //|| !latestNode.getGmtModified().equals(currentNode.getGmtModified()) || latestNode.getStatus() != currentNode.getStatus() || latestNode.getLogicalId() != currentNode.getLogicalId() || !latestNode.getPhysicalId().equals(currentNode.getPhysicalId()) || latestNode.getSerialId() != currentNode.getSerialId() || !latestNode.getIp().equals(currentNode.getIp()) || latestNode.getPort() != currentNode.getPort(); } private boolean isDifferent(StoreNode sn1, StoreNode sn2) { // 任何一个属性不一样就认为有变更。 return (!sn1.getPhId().equals(sn2.getPhId())) || (sn1.getStatus() != sn2.getStatus()) || (sn1.getLogicId() != sn2.getLogicId()) || (sn1.getSequence() != sn2.getSequence()) || (!sn1.getIp().equals(sn2.getIp())) || (sn1.getPort() != sn2.getPort()); } public void start() { if(configLoadThread != null) { configLoadThread.start(); } } public void stop() { this.close(); } }