package com.dianping.pigeon.remoting.invoker.route.region;
import com.dianping.pigeon.config.ConfigChangeListener;
import com.dianping.pigeon.config.ConfigManager;
import com.dianping.pigeon.config.ConfigManagerLoader;
import com.dianping.pigeon.log.LoggerLoader;
import com.dianping.pigeon.monitor.Monitor;
import com.dianping.pigeon.monitor.MonitorLoader;
import com.dianping.pigeon.registry.RegistryManager;
import com.dianping.pigeon.remoting.common.domain.InvocationRequest;
import com.dianping.pigeon.remoting.invoker.Client;
import com.dianping.pigeon.remoting.invoker.route.quality.RequestQualityManager;
import com.google.common.collect.Lists;
import com.dianping.pigeon.log.Logger;
import java.util.*;
/**
* Created by chenchongze on 16/4/15.
*/
public class AutoSwitchRegionPolicy implements RegionPolicy {
public final static AutoSwitchRegionPolicy INSTANCE = new AutoSwitchRegionPolicy();
public static final String NAME = "autoSwitch";
private final Logger logger = LoggerLoader.getLogger(this.getClass());
private final RegionPolicyManager regionPolicyManager = RegionPolicyManager.INSTANCE;
private final RequestQualityManager requestQualityManager = RequestQualityManager.INSTANCE;
private final RegistryManager registryManager = RegistryManager.getInstance();
private final ConfigManager configManager = ConfigManagerLoader.getConfigManager();
private final Monitor monitor = MonitorLoader.getMonitor();
private static final String KEY_REGION_THRESHOLD_RATIO = "pigeon.regions.switchratio";
private static final String KEY_IDC_FILTER_ENABLE = "pigeon.regions.autoswitch.idc.enable";
private static final String KEY_IDC_FILTER_THRESHOLD_LEAST = "pigeon.regions.autoswitch.idc.threshold.least";
private static final String KEY_IDC_FILTER_THRESHOLD_RATIO = "pigeon.regions.autoswitch.idc.threshold.ratio";
private volatile float regionSwitchRatio = configManager.getFloatValue(KEY_REGION_THRESHOLD_RATIO, 0.5f);
private volatile boolean isIdcFilterEnable = configManager.getBooleanValue(KEY_IDC_FILTER_ENABLE, false);
private volatile int idcFilterThresholdLeast = configManager.getIntValue(KEY_IDC_FILTER_THRESHOLD_LEAST, 2);
private volatile float idcFilterThresHoldRatio = configManager.getFloatValue(KEY_IDC_FILTER_THRESHOLD_RATIO, 0.2f);
private AutoSwitchRegionPolicy() {
configManager.registerConfigChangeListener(new InnerConfigChangeListener());
}
@Override
public List<Client> getPreferRegionClients(List<Client> clientList, InvocationRequest request) {
return getRegionActiveClients(clientList, request);
}
private List<Client> getRegionActiveClients(List<Client> clientList, InvocationRequest request) {
int sizeBefore = clientList.size();
Map<Region, InnerRegionStat> regionStats = new HashMap<Region, InnerRegionStat>();
List<Region> regionArrays = Lists.newArrayList(regionPolicyManager.getRegionArray());
for (Region region : regionArrays) { // 缓存每个region的统计信息
regionStats.put(region, new InnerRegionStat());
}
for (Client client : clientList) { // 分发client的region统计信息
try {
InnerRegionStat regionStat = regionStats.get(client.getRegion());
if (regionStat != null) {
regionStat.addTotal();
if (client.isActive() && registryManager.getServiceWeightFromCache(client.getAddress()) > 0) {
regionStat.addActive();
regionStat.addClient(client);
}
}
} catch (Throwable t) {
logger.error(t);
}
}
for (int i = 0; i < regionArrays.size(); ++i) {// 优先级大小按数组大小排列
Region region = regionArrays.get(i);
try {
InnerRegionStat regionStat = regionStats.get(region);
int total = regionStat.getTotal();
int active = regionStat.getActive();
List<Client> regionClientList = regionStat.getClientList();
if (i == 0 && isIdcFilterEnable && regionClientList.size() > 0) { // 开启本地idc(region(0))优先
int idcTotal = 0;
int idcActive = 0;
List<Client> idcClientList = new ArrayList<Client>();
for (Client client : regionClientList) {
if (RegionUtils.isInLocalIdc(client.getHost())) {
idcClientList.add(client);
++ idcTotal;
if (client.isActive() && registryManager.getServiceWeightFromCache(client.getAddress()) > 0) {
++ idcActive;
}
}
}
float idcLeast = idcFilterThresHoldRatio * idcTotal;
if (idcTotal > 0 && idcActive > 0 && idcActive >= idcLeast) { // idc可用client比例合格
if (idcActive > idcFilterThresholdLeast || idcActive > idcFilterThresHoldRatio * active) {
// idc可用client数量合格 或 idc可用client数量占region可用client数量的比例合格
monitor.logEvent("PigeonCall.idc",
request.getServiceName() + "#" + RegionUtils.getLocalIdc(), "");
return idcClientList;
}
}
}
float least = regionSwitchRatio * total;
if (total > 0 && active > 0 && active >= least) {
if (logger.isDebugEnabled()) {
logger.debug("b: " + sizeBefore + ", a:" + regionClientList.size());
}
return regionClientList;
} else {
if (logger.isDebugEnabled()) {
logger.debug(request.getServiceName() + " skipped region " + region.getName()
+ ", available clients less than " + least);
}
monitor.logEvent("PigeonCall.regionUnavailable",
request.getServiceName() + "#" + region.getName(), "");
}
} catch (Throwable t) {
logger.error(t);
} finally {
//todo if force region, maybe here
}
}
return clientList;
}
private class InnerRegionStat {
private int active = 0;
private int total = 0;
private List<Client> clientList = new ArrayList<Client>();
public List<Client> getClientList() {
return clientList;
}
public void addClient(Client client) {
clientList.add(client);
}
public int getActive() {
return active;
}
public void addActive() {
++active;
}
public int getTotal() {
return total;
}
public void addTotal() {
++total;
}
}
private class InnerConfigChangeListener implements ConfigChangeListener {
@Override
public void onKeyUpdated(String key, String value) {
if (key.endsWith(KEY_IDC_FILTER_ENABLE)) {
try {
isIdcFilterEnable = Boolean.valueOf(value);
logger.info("set " + KEY_IDC_FILTER_ENABLE + " value: " + value);
} catch (RuntimeException e) {
logger.warn("set " + KEY_IDC_FILTER_ENABLE + " failed!", e);
}
} else if (key.endsWith(KEY_IDC_FILTER_THRESHOLD_LEAST)) {
try {
idcFilterThresholdLeast = Integer.valueOf(value);
logger.info("set " + KEY_IDC_FILTER_THRESHOLD_LEAST + " value: " + value);
} catch (RuntimeException e) {
logger.warn("set " + KEY_IDC_FILTER_THRESHOLD_LEAST + " failed!", e);
}
} else if (key.endsWith(KEY_IDC_FILTER_THRESHOLD_RATIO)) {
try {
idcFilterThresHoldRatio = Float.valueOf(value);
logger.info("set " + KEY_IDC_FILTER_THRESHOLD_RATIO + " value: " + value);
} catch (RuntimeException e) {
logger.warn("set " + KEY_IDC_FILTER_THRESHOLD_RATIO + " failed!", e);
}
} else if (key.endsWith(KEY_REGION_THRESHOLD_RATIO)) {
try {
regionSwitchRatio = Float.valueOf(value);
logger.info("set " + KEY_REGION_THRESHOLD_RATIO + " value: " + value);
} catch (RuntimeException e) {
logger.warn("set " + KEY_REGION_THRESHOLD_RATIO + " failed!", e);
}
}
}
@Override
public void onKeyAdded(String key, String value) {
}
@Override
public void onKeyRemoved(String key) {
}
}
}