package com.alibaba.doris.common.router.service;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.alibaba.doris.algorithm.RouteAlgorithm;
import com.alibaba.doris.algorithm.util.RandomNumUtil;
import com.alibaba.doris.algorithm.vpm.VpmRouterAlgorithm;
import com.alibaba.doris.common.NodeRouteStatus;
import com.alibaba.doris.common.StoreNode;
import com.alibaba.doris.common.StoreNodeSequenceEnum;
import com.alibaba.doris.common.adminservice.AdminServiceFactory;
import com.alibaba.doris.common.config.ConfigManager;
import com.alibaba.doris.common.event.RouteConfigChangeEvent;
import com.alibaba.doris.common.operation.OperationEnum;
import com.alibaba.doris.common.route.DorisRouterException;
import com.alibaba.doris.common.route.RouteStrategy;
import com.alibaba.doris.common.route.RouteTable;
/**
* doris路由算法实现
*
* @author frank
*/
public class RouteStrategyImpl implements RouteStrategy {
private static final Log log = LogFactory.getLog(RouteStrategyImpl.class);
RouterListContainer rlc = new RouterListContainer(); // 路由相关对象的容器,这些对象需要同时更新,保证一致
private RouteTable routeTable;
private ConfigManager configManager;
private int virtualNum = AdminServiceFactory.getVirtualNumberService().getVirtualNumber();
public void setRouteTable(RouteTable routeTable) {
this.routeTable = routeTable;
}
public RouteTable getRouteTable() {
return routeTable;
}
public List<StoreNode> findNodes(OperationEnum type, int copyCount, String key) throws DorisRouterException {
if (rlc.getVpmrList() == null || rlc.getVpmrList().isEmpty()) {
log.error("Current RouterListContainer is:" + rlc);
throw new DorisRouterException("There is no store node.");
}
if (copyCount > rlc.getVpmrList().size()) {
log.error("Current RouterListContainer is:" + rlc);
throw new DorisRouterException("There is only " + rlc.getVpmrList().size() + " sequence, Can't support "
+ copyCount + " copy count!");
}
List<StoreNode> snList = new ArrayList<StoreNode>();
for (int i = 0; i < copyCount; i++) {
if (type.equals(OperationEnum.READ) || type.equals(OperationEnum.WRITE)
|| type.equals(OperationEnum.MULTIREAD)) {// 写操作一定要取得一个node,读操作只有在可读时才取node
int logicId = rlc.getVpmrList().get(i).getNodeByKey(key);
List<List<StoreNode>> mainNodeList = rlc.getMainStoreNodeList();
List<StoreNode> seqNodeList = mainNodeList.get(i);
StoreNode sn = seqNodeList.get(logicId);
snList.add(sn);
}
}
if (snList.isEmpty()) {
throw new DorisRouterException("No store node can be used!");
}
// //////////////////////////////////////////////////////////TODO for debug
// List<StoreNode> tl = new ArrayList<StoreNode>();
// for (StoreNode tsn : snList) {
// tl.add(tsn);
// }
// //////////////////////////////////////////////////////
anylizeNode(type, snList, key);
if (snList.isEmpty()) {
// log.error("No store node for operation:" + tl + " become:" + snList);
throw new DorisRouterException("No store node for operation");
}
if (type.equals(OperationEnum.WRITE) && snList.size() < copyCount) {
// log.error("No enough store node for write:" + tl + " become:" + snList);
throw new DorisRouterException("No enough store node for write");
}
if (OperationEnum.MULTIREAD.equals(type)) {
return snList;
}
// if read ,then find one node
if (OperationEnum.READ.equals(type)) {
StoreNode sn = snList.get(getHashIndex(snList.size(), key));
List<StoreNode> tempList = new ArrayList<StoreNode>(1);
tempList.add(sn);
return tempList;
}
int size = snList.size();
// if write, must not be all backup nodes,
if (OperationEnum.WRITE.equals(type)) {
boolean backTag = true;
List<StoreNode> tempList = new ArrayList<StoreNode>(size);
// 保证节点在访问时,同一个key会以相同的序列来访问所有node;访问顺序必须同read的顺序一致;
// 以便保证写操作的原子性;
for (int i = 0; i < size; i++) {
int index = getHashIndex(size - i, key);
StoreNode sn = snList.get(index);
snList.remove(index);
if (!sn.getSequence().equals(StoreNodeSequenceEnum.TEMP_SEQUENCE)) {// 只要有一个节点不是备用节点
backTag = false;
}
tempList.add(sn);
}
if (backTag) {
throw new DorisRouterException("All nodes is backup node.");
}
return tempList;
}
return snList;
}
public void onConfigChange(RouteConfigChangeEvent event) {
buildConfig(event.getRouteTable().getMainStoreNodeList(), event.getRouteTable().getBackupStoreNodeList());
}
private void buildConfig(List<List<StoreNode>> mainStoreNodeList, List<StoreNode> backupStoreNodeList) {
if (mainStoreNodeList == null || mainStoreNodeList.isEmpty()) {
if (log.isErrorEnabled()) {
log.error("normal sequence is null.");
}
return;
}
for (List<StoreNode> snList : mainStoreNodeList) {
if (snList == null || snList.isEmpty()) {
if (log.isErrorEnabled()) {
log.error("normal sequence is null.");
}
return;
}
}
List<RouteAlgorithm> tempList = new ArrayList<RouteAlgorithm>();
// 对正常序列进行排序,这个顺序用于路由版本更新时请求重定向。
for (int i = 1; i < mainStoreNodeList.size(); i++) {
for (int j = i; j < mainStoreNodeList.size(); j++) {
if (mainStoreNodeList.get(j).get(0).getSequence().getValue() < mainStoreNodeList.get(j - 1).get(0).getSequence().getValue()) {
List<StoreNode> ls = mainStoreNodeList.get(j - 1);
mainStoreNodeList.set(j - 1, mainStoreNodeList.get(j));
mainStoreNodeList.set(j, ls);
}
}
}
for (List<StoreNode> list : mainStoreNodeList) {
RouteAlgorithm vpmr = new VpmRouterAlgorithm(list.size(), virtualNum);
tempList.add(vpmr);
}
// 这几个List要同时完成变更,需要一个容器包装
RouterListContainer tc = new RouterListContainer();
if (backupStoreNodeList != null && !backupStoreNodeList.isEmpty()) {
RouteAlgorithm tempVpmr = new VpmRouterAlgorithm(backupStoreNodeList.size(), virtualNum);
tc.setBackupVpmr(tempVpmr);
tc.setBackupStoreNodeList(backupStoreNodeList);
}
tc.setVpmrList(tempList);
tc.setMainStoreNodeList(mainStoreNodeList);
// log.error("old: " + rlc);
rlc = tc;
// log.error("new: " + rlc);
}
/**
* client各种情况的路由策略
*
* @param type
* @param snList
* @param key
* @throws DorisRouterException
*/
private void anylizeNode(OperationEnum type, List<StoreNode> snList, String key) throws DorisRouterException {
if (type.equals(OperationEnum.READ) || type.equals(OperationEnum.MULTIREAD)) {
Iterator<StoreNode> i = snList.iterator();
while (i.hasNext()) {
NodeRouteStatus status = i.next().getStatus();
if (status.equals(NodeRouteStatus.TEMP_FAILED)) {
i.remove();
}
}
}
if (type.equals(OperationEnum.WRITE)) {
for (int i = 0; i < snList.size(); i++) {
NodeRouteStatus status = snList.get(i).getStatus();
if (status.equals(NodeRouteStatus.TEMP_FAILED)) {
// 用备份节点写
snList.set(i, findBackupNode(key));
}
}
}
}
/**
* 查找失效节点的替代节点,调用前该节点经过admin仲裁为临时失效
*
* @param type
* @param copyCount
* @param key
* @param sn 失效节点
* @return
* @throws DorisRouterException
*/
public StoreNode findFailoverNode(OperationEnum type, int copyCount, String key, StoreNode sn)
throws DorisRouterException {
// log.error(sn + " is failed by admin check.");
sn.setStatus(NodeRouteStatus.TEMP_FAILED);// 一定是临时失效,这个修改是client的临时修改,在新的配置实例生效前使用
List<StoreNode> snList = findNodes(type, copyCount, key);// 重新获取节点列表,因为刚修改过一个节点状态,这里可能因为都是备用节点而导致写操作路由异常。
if (snList == null || snList.isEmpty()) {
log.error("Current RouterListContainer is:" + rlc);
throw new DorisRouterException("can't find failover node.");
}
// 读操作,重新获取读节点
if (type.equals(OperationEnum.READ) || type.equals(OperationEnum.MULTIREAD)) {
return snList.get(getHashIndex(snList.size(), key));
}
// 写操作,查找备用节点
if (type.equals(OperationEnum.WRITE)) {
return findBackupNode(key);
}
log.error("not support this type of operation:" + type.name());
throw new DorisRouterException("not support this type of operation:" + type.name());
}
/**
* @deprecated
* @param sn
* @param key
* @return
* @throws DorisRouterException
*/
private StoreNode findBackupNode(StoreNode sn, String key) throws DorisRouterException {
// 该失效节点本身是备用节点
if (sn.getSequence().equals(StoreNodeSequenceEnum.TEMP_SEQUENCE)) {
return findNextStoreNode(rlc.getBackupStoreNodeList(), sn);
}
// 该失效节点是正常节点,返回备用节点
if (sn.getSequence().equals(StoreNodeSequenceEnum.TEMP_SEQUENCE)) {
return findBackupNode(key);
}
return null;
}
private int getRandomIndex(int size) throws DorisRouterException {
if (size <= 0) {
log.error("Current RouterListContainer is:" + rlc);
throw new DorisRouterException("Fatal error! size is " + size);
}
return RandomNumUtil.getRandomNumMath() % size;
}
/**
* 确保指定的key,每次都会路由到相同的节点;<br>
* 对于写多份的场景,也每次都以相同的顺序写入多个节点;
*
* @param size
* @param key
* @return
*/
public static int getHashIndex(int size, String key) throws DorisRouterException {
if (size <= 0) {
// log.error("Current RouterListContainer is:" + rlc);
throw new DorisRouterException("Fatal error! size is " + size);
}
int hash = key.hashCode() % size;
return hash < 0 ? -hash : hash;
}
/**
* 在节点列表中查找失效节点的下一个节点
*
* @deprecated
* @param snList
* @param sn
* @return
*/
private StoreNode findNextStoreNode(List<StoreNode> snList, StoreNode sn) throws DorisRouterException {
for (int i = 0; i < snList.size(); i++) {
StoreNode snt = snList.get(i);
if (snt.equals(sn)) {// 找到失效节点
if (snList.size() == 1) {
log.error("Current RouterListContainer is:" + rlc);
throw new DorisRouterException("no store node can be read!");// 列表只有一个节点,且是当前失效节点,再无可用节点
}
// 把节点列表当作一个环,获得失效节点的下一个节点
StoreNode snr = null;
if (i < (snList.size() - 1)) {
snr = snList.get(i + 1);//
} else {
snr = snList.get(0);
}
return snr;
}
}
// 节点列表不包含失效节点,路由表发生变化,返回新路由表随机节点
return snList.get(getRandomIndex(snList.size()));
}
/**
* 查找备用节点<br>
* 需要处理备用节点失效的情况,返回下一OK节点<br>
* 当对等序列在两个以上时,同时失效节点在一个以上时,临时节点存在失效节点时,多个失效节点数据可能会写在一个临时节点上,暂不考虑
*
* @param key
* @return
*/
private StoreNode findBackupNode(String key) throws DorisRouterException {
if (rlc.getBackupStoreNodeList() == null || rlc.getBackupVpmr() == null
|| rlc.getBackupStoreNodeList().isEmpty()) {
log.error("Current RouterListContainer is:" + rlc);
throw new DorisRouterException("rlc's temp node is null. can't find useful backup node.");
}
int k = rlc.getBackupVpmr().getNodeByKey(key);
StoreNode sn = rlc.getBackupStoreNodeList().get(k);
if (sn.getStatus().equals(NodeRouteStatus.OK)) {
return sn;
}
int x = rlc.getBackupStoreNodeList().size();
for (int i = 0; i < x - 1; i++) {
if (k < x - 1) {
k++;
} else {
k = 0;
}
sn = rlc.getBackupStoreNodeList().get(k);
if (sn.getStatus().equals(NodeRouteStatus.OK)) {
return sn;
}
}
log.error("Current backup list is:" + rlc.getBackupStoreNodeList());
for (int i = 0; i < rlc.getBackupStoreNodeList().size(); i++) {
log.error("all backup nodes were failed:" + sn + ":" + rlc.getBackupStoreNodeList().get(i).getStatus());
}
throw new DorisRouterException("at last. can't find useful backup node." + sn.getStatus());
}
public void setRouteAlgorithm(RouteAlgorithm routeAlgorithm) {
// TODO: use configed routeAlgorithm from configManager.getProperties()
}
public void initConfig() {
}
public void setConfigManager(ConfigManager configManager) {
this.configManager = configManager;
}
}