package com.dianping.pigeon.remoting.invoker.route.quality;
import com.dianping.pigeon.config.ConfigChangeListener;
import com.dianping.pigeon.config.ConfigManager;
import com.dianping.pigeon.config.ConfigManagerLoader;
import com.dianping.pigeon.log.Logger;
import com.dianping.pigeon.log.LoggerLoader;
import com.dianping.pigeon.remoting.common.domain.InvocationRequest;
import com.dianping.pigeon.remoting.invoker.Client;
import com.dianping.pigeon.remoting.invoker.domain.InvokerContext;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by chenchongze on 16/5/20.
*/
public enum RequestQualityManager {
INSTANCE;
private RequestQualityManager() {
ConfigManagerLoader.getConfigManager().registerConfigChangeListener(new InnerConfigChangeListener());
}
private static final Logger logger = LoggerLoader.getLogger(RequestQualityManager.class);
private static final ConfigManager configManager = ConfigManagerLoader.getConfigManager();
private static final String KEY_REQUEST_QUALITY_AUTO = "pigeon.invoker.request.quality.auto.active";
private static final String KEY_REQUEST_QUALITY_FAILED_PERCENT_GOOD = "pigeon.invoker.request.quality.failed.percent.good";
private static final String KEY_REQUEST_QUALITY_FAILED_PERCENT_NORMAL = "pigeon.invoker.request.quality.failed.percent.normal";
private static final String KEY_REQUEST_QUALITY_THRESHOLD_TOTAL = "pigeon.invoker.request.quality.threshold.total";
private volatile static boolean isReqQualityEnable = configManager.getBooleanValue(KEY_REQUEST_QUALITY_AUTO, false);
private volatile static Float reqQualityFailedPercentGood = configManager.getFloatValue(KEY_REQUEST_QUALITY_FAILED_PERCENT_GOOD, 1f);
private volatile static Float reqQualityFailedPercentNormal = configManager.getFloatValue(KEY_REQUEST_QUALITY_FAILED_PERCENT_NORMAL, 5f);
private volatile static int reqQualityThresholdTotal = configManager.getIntValue(KEY_REQUEST_QUALITY_THRESHOLD_TOTAL, 20);
// hosts --> ( requestUrl:serviceName#method --> second --> { total, failed } )
private ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<Integer, Quality>>>
addrReqUrlSecondQualities = new ConcurrentHashMap<String, ConcurrentMap<String, ConcurrentMap<Integer, Quality>>>();
// hosts --> ( requestUrl:serviceName#method --> { total, failed } )
private volatile ConcurrentMap<String, ConcurrentMap<String, Quality>> addrReqUrlQualities = null;
public ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<Integer, Quality>>> getAddrReqUrlSecondQualities() {
return addrReqUrlSecondQualities;
}
public ConcurrentMap<String, ConcurrentMap<String, Quality>> getAddrReqUrlQualities() {
return addrReqUrlQualities;
}
public void setAddrReqUrlQualities(ConcurrentMap<String, ConcurrentMap<String, Quality>> addrReqUrlQualities) {
this.addrReqUrlQualities = addrReqUrlQualities;
}
public void addClientRequest(InvokerContext context, boolean failed) {
if (isReqQualityEnable && context.getClient() != null) {
String address = context.getClient().getAddress();
ConcurrentMap<String, ConcurrentMap<Integer, Quality>>
requestSecondQuality = addrReqUrlSecondQualities.get(address);
if (requestSecondQuality == null) {
requestSecondQuality = new ConcurrentHashMap<String, ConcurrentMap<Integer, Quality>>();
ConcurrentMap<String, ConcurrentMap<Integer, Quality>>
last = addrReqUrlSecondQualities.putIfAbsent(address, requestSecondQuality);
if (last != null) {
requestSecondQuality = last;
}
}
String requestUrl = getRequestUrl(context);
ConcurrentMap<Integer, Quality> secondQuality = requestSecondQuality.get(requestUrl);
if (secondQuality == null) {
secondQuality = new ConcurrentHashMap<Integer, Quality>();
ConcurrentMap<Integer, Quality> last = requestSecondQuality.putIfAbsent(requestUrl, secondQuality);
if (last != null) {
secondQuality = last;
}
}
int currentSecond = Calendar.getInstance().get(Calendar.SECOND);
Quality quality = secondQuality.get(currentSecond);
if (quality == null) {
quality = new Quality(0, 0);
Quality last = secondQuality.putIfAbsent(currentSecond, quality);
if (last != null) {
quality = last;
}
}
quality.total.incrementAndGet();
if (failed) {
quality.failed.incrementAndGet();
}
}
}
public void removeClientQualities(String address) {
addrReqUrlSecondQualities.remove(address);
}
private String getRequestUrl(InvokerContext context) {
return context.getInvokerConfig().getUrl() + "#" + context.getMethodName();
}
private String getRequestUrl(InvocationRequest request) {
return request.getServiceName() + "#" + request.getMethodName();
}
/**
* 根据方法的服务质量过滤,优先保留服务质量good的clients,数量低于least时加入服务质量normal的clients
* 存在的问题,如果一个节点是个死节点,那么他永远是优秀的节点
*
* @param clientList
* @param request
* @param least 最少保留个数
* @return
*/
@Deprecated
public List<Client> getQualityPreferClients(List<Client> clientList, InvocationRequest request, float least) {
// 筛选good,normal,bad clients
// 直接进行服务质量路由,先只保留服务质量good的,如果不够(比如少于1个),加入服务质量normal的
if (!CollectionUtils.isEmpty(addrReqUrlQualities)) {
String requestUrl = getRequestUrl(request);
Map<RequrlQuality, List<Client>> filterQualityClientsMap = new HashMap<RequrlQuality, List<Client>>();
for (RequrlQuality reqQuality : RequrlQuality.values()) {
filterQualityClientsMap.put(reqQuality, new ArrayList<Client>());
}
for (Client client : clientList) {
if (addrReqUrlQualities.containsKey(client.getAddress())) {
ConcurrentMap<String, Quality> reqUrlQualities = addrReqUrlQualities.get(client.getAddress());
if (reqUrlQualities.containsKey(requestUrl)) {
Quality quality = reqUrlQualities.get(requestUrl);
switch (quality.getQuality()) {
case REQURL_QUALITY_GOOD:
filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_GOOD).add(client);
break;
case REQURL_QUALITY_NORNAL:
filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_NORNAL).add(client);
break;
case REQURL_QUALITY_BAD:
filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_BAD).add(client);
break;
default:
// never be here
break;
}
}
}
}
List<Client> filterQualityClients = new ArrayList<Client>();
filterQualityClients.addAll(filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_GOOD));
if (filterQualityClients.size() < least) {
filterQualityClients.addAll(filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_NORNAL));
}
return filterQualityClients;
}
return clientList;
}
public boolean isEnableRequestQualityRoute() {
return isReqQualityEnable;
}
public int adjustWeightWithQuality(int weight, String clientAddress, InvocationRequest request) {
if (isEnableRequestQualityRoute()) {
if (!CollectionUtils.isEmpty(addrReqUrlQualities)) {
ConcurrentMap<String, Quality> reqUrlQualities = addrReqUrlQualities.get(clientAddress);
if (reqUrlQualities != null) {
Quality quality = reqUrlQualities.get(getRequestUrl(request));
if (quality != null) {
weight /= quality.getQualityValue(); // int 多数会归零
}
}
}
}
return weight;
}
public static class Quality {
private RequrlQuality quality = RequrlQuality.REQURL_QUALITY_GOOD;
private AtomicInteger failed = new AtomicInteger();
private AtomicInteger total = new AtomicInteger();
public Quality() {
}
public Quality(int total, int failed) {
this.total.set(total);
this.failed.set(failed);
}
public AtomicInteger getFailed() {
return failed;
}
public int getFailedValue() {
return failed.get();
}
public void setFailed(int failed) {
this.failed.set(failed);
}
public AtomicInteger getTotal() {
return total;
}
public int getTotalValue() {
return total.get();
}
public void setTotal(int total) {
this.total.set(total);
}
public float getFailedPercent() {
if (total.get() > 0) {
return failed.get() * 100 / total.get();
} else {
return 0;
}
}
public void clear() {
total.set(0);
failed.set(0);
quality = RequrlQuality.REQURL_QUALITY_GOOD;
}
public RequrlQuality getQuality() {
if (getTotalValue() > reqQualityThresholdTotal) {
float failedRate = getFailedPercent();
if (failedRate < reqQualityFailedPercentGood) {
quality = RequrlQuality.REQURL_QUALITY_GOOD;
} else if (failedRate >= reqQualityFailedPercentGood && failedRate < reqQualityFailedPercentNormal) {
quality = RequrlQuality.REQURL_QUALITY_NORNAL;
} else if (failedRate >= reqQualityFailedPercentNormal) {
quality = RequrlQuality.REQURL_QUALITY_BAD;
}
}
return quality;
}
public int getQualityValue() {
return getQuality().getValue();
}
}
private enum RequrlQuality {
REQURL_QUALITY_GOOD(1),
REQURL_QUALITY_NORNAL(10),
REQURL_QUALITY_BAD(100);
private int value;
RequrlQuality(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
private class InnerConfigChangeListener implements ConfigChangeListener {
@Override
public void onKeyUpdated(String key, String value) {
if (key.endsWith(KEY_REQUEST_QUALITY_AUTO)) {
try {
isReqQualityEnable = Boolean.valueOf(value);
logger.info("set request quality switch to " + value);
} catch (RuntimeException e) {
logger.warn("set request quality switch failed!", e);
}
} else if (key.endsWith(KEY_REQUEST_QUALITY_FAILED_PERCENT_GOOD)) {
try {
reqQualityFailedPercentGood = Float.valueOf(value);
logger.info("set req quality failed percent good to " + value);
} catch (RuntimeException e) {
logger.warn("set req quality failed percent good failed!", e);
}
} else if (key.endsWith(KEY_REQUEST_QUALITY_FAILED_PERCENT_NORMAL)) {
try {
reqQualityFailedPercentNormal = Float.valueOf(value);
logger.info("set req quality failed percent normal to " + value);
} catch (RuntimeException e) {
logger.warn("set req quality failed percent normal failed!", e);
}
} else if (key.endsWith(KEY_REQUEST_QUALITY_THRESHOLD_TOTAL)) {
try {
reqQualityThresholdTotal = Integer.valueOf(value);
logger.info("set req quality threshold total to " + value);
} catch (RuntimeException e) {
logger.warn("set req quality threshold total failed!", e);
}
}
}
@Override
public void onKeyAdded(String key, String value) {
}
@Override
public void onKeyRemoved(String key) {
}
}
}