/** * Dianping.com Inc. * Copyright (c) 2003-2013 All Rights Reserved. */ package com.dianping.pigeon.remoting.invoker.route.balance; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.StringUtils; 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.registry.RegistryManager; import com.dianping.pigeon.registry.listener.RegistryEventListener; import com.dianping.pigeon.registry.listener.ServiceProviderChangeEvent; import com.dianping.pigeon.registry.listener.ServiceProviderChangeListener; import com.dianping.pigeon.remoting.common.util.Constants; import com.dianping.pigeon.remoting.invoker.Client; import com.dianping.pigeon.remoting.invoker.config.InvokerConfig; import com.dianping.pigeon.remoting.invoker.domain.ConnectInfo; import com.dianping.pigeon.remoting.invoker.listener.ClusterListener; import com.dianping.pigeon.remoting.invoker.listener.ClusterListenerManager; import com.dianping.pigeon.remoting.invoker.route.statistics.CapacityChecker; import com.dianping.pigeon.remoting.invoker.route.statistics.ServiceStatisticsHolder; import com.dianping.pigeon.threadpool.DefaultThreadPool; import com.dianping.pigeon.threadpool.ThreadPool; import com.dianping.pigeon.util.ClassUtils; import com.dianping.pigeon.util.ServiceUtils; import com.dianping.pigeon.util.ThreadPoolUtils; public class LoadBalanceManager { private static final Logger logger = LoggerLoader.getLogger(LoadBalanceManager.class); private static Map<String, LoadBalance> loadBalanceMap = new ConcurrentHashMap<String, LoadBalance>(); private static ConfigManager configManager = ConfigManagerLoader.getConfigManager(); public static final String DEFAULT_LOADBALANCE = configManager.getStringValue(Constants.KEY_LOADBALANCE, WeightedAutoawareLoadBalance.NAME); private static ConcurrentHashMap<String, WeightFactor> weightFactors = new ConcurrentHashMap<String, WeightFactor>(); private static ConcurrentHashMap<String, Integer> weights = new ConcurrentHashMap<String, Integer>(); private static int initialFactor = configManager.getIntValue("pigeon.loadbalance.initialFactor", 0); private static int defaultFactor = configManager.getIntValue("pigeon.loadbalance.defaultFactor", 100); private static long interval = configManager.getLongValue("pigeon.loadbalance.interval", 200); private static int step = configManager.getIntValue("pigeon.loadbalance.step", 1); private static String stepTicks = configManager .getStringValue( "pigeon.loadbalance.stepticks", "0:15;1:15;2:15;3:15;4:15;5:10;6:10;7:10;8:10;9:10;10:7;11:7;12:7;13:7;14:7;15:5;16:5;17:5;18:5;19:5;20:3;21:3;22:3;23:3;24:3;25:2;26:2;27:2;28:2;29:2"); private static Map<Integer, Integer> stepTicksMap = new HashMap<Integer, Integer>(); private static ThreadPool loadbalanceThreadPool = new DefaultThreadPool("Pigeon-Client-Loadbalance-ThreadPool"); private static volatile int errorLogSeed = 0; static { LoadBalanceManager.register(RandomLoadBalance.NAME, null, RandomLoadBalance.instance); LoadBalanceManager.register(AutoawareLoadBalance.NAME, null, AutoawareLoadBalance.instance); LoadBalanceManager.register(RoundRobinLoadBalance.NAME, null, RoundRobinLoadBalance.instance); LoadBalanceManager.register(WeightedAutoawareLoadBalance.NAME, null, WeightedAutoawareLoadBalance.instance); } /** * * * @param invokerConfig * @param callType * @return */ public static LoadBalance getLoadBalance(InvokerConfig<?> invokerConfig, int callType) { String serviceId = ServiceUtils.getServiceId(invokerConfig.getUrl(), invokerConfig.getSuffix()); LoadBalance loadBalance = loadBalanceMap.get(serviceId); if (loadBalance != null) { return loadBalance; } loadBalance = loadBalanceMap.get(invokerConfig.getLoadbalance()); if (loadBalance != null) { return loadBalance; } if (DEFAULT_LOADBALANCE != null) { loadBalance = loadBalanceMap.get(DEFAULT_LOADBALANCE); if (loadBalance != null) { loadBalanceMap.put(invokerConfig.getLoadbalance(), loadBalance); return loadBalance; } else { logError( "the loadbalance[" + DEFAULT_LOADBALANCE + "] is invalid, only support " + loadBalanceMap.keySet() + ".", null); } } return loadBalance; } @SuppressWarnings("unchecked") public static void register(String serviceName, String suffix, Object loadBalance) { String serviceId = ServiceUtils.getServiceId(serviceName, suffix); LoadBalance loadBlanceObj = null; if (loadBalance instanceof LoadBalance) { loadBlanceObj = (LoadBalance) loadBalance; } else if (loadBalance instanceof String && StringUtils.isNotBlank((String) loadBalance)) { if (!loadBalanceMap.containsKey(loadBalance)) { try { Class<? extends LoadBalance> loadbalanceClass = (Class<? extends LoadBalance>) ClassUtils .loadClass((String) loadBalance); loadBlanceObj = loadbalanceClass.newInstance(); } catch (Throwable e) { throw new IllegalArgumentException("failed to register loadbalance[service=" + serviceId + ",class=" + loadBalance + "]", e); } } else { loadBlanceObj = loadBalanceMap.get(loadBalance); } } else if (loadBalance instanceof Class) { try { Class<? extends LoadBalance> loadbalanceClass = (Class<? extends LoadBalance>) loadBalance; loadBlanceObj = loadbalanceClass.newInstance(); } catch (Throwable e) { throw new IllegalArgumentException("failed to register loadbalance[service=" + serviceId + ",class=" + loadBalance + "]", e); } } if (loadBlanceObj != null) { loadBalanceMap.put(serviceId, loadBlanceObj); } } public static int getEffectiveWeight(String clientAddress) { Integer w = weights.get(clientAddress); if (w == null) { w = 1; } WeightFactor wf = weightFactors.get(clientAddress); if (wf == null) { return w * defaultFactor; } else { return w * wf.getFactor(); } } public static void destroy() throws Exception { ThreadPoolUtils.shutdown(loadbalanceThreadPool.getExecutor()); } public static void init() { WeightFactorMaintainer weightFactorMaintainer = new WeightFactorMaintainer(); RegistryEventListener.addListener(weightFactorMaintainer); ClusterListenerManager.getInstance().addListener(weightFactorMaintainer); loadbalanceThreadPool.execute(weightFactorMaintainer); CapacityChecker serviceStatisticsChecker = new CapacityChecker(); loadbalanceThreadPool.execute(serviceStatisticsChecker); // initialize step ticks if (StringUtils.isNotBlank(stepTicks)) { try { String[] ticks = stepTicks.split(";"); for (String tick : ticks) { if (StringUtils.isNotBlank(tick)) { String[] kv = tick.split(":"); if (kv.length == 2) { stepTicksMap.put(Integer.valueOf(kv[0]), Integer.valueOf(kv[1])); } } } } catch (RuntimeException e) { logger.error("", e); } } } private static void logError(String message, Throwable t) { if (errorLogSeed++ % 1000 == 0) { if (t != null) { logger.warn(message, t); } else { logger.warn(message); } errorLogSeed = 0; } } public static class WeightFactor { private int factor; private int currentStepTicks; public WeightFactor(int initialFactor) { factor = initialFactor; } public int getFactor() { return factor; } public void setFactor(int factor) { this.factor = factor; } public int getCurrentStepTicks() { return currentStepTicks; } public void setCurrentStepTicks(int currentStepTicks) { this.currentStepTicks = currentStepTicks; } public String toString() { return factor + ":" + currentStepTicks; } } private static class WeightFactorMaintainer implements Runnable, ServiceProviderChangeListener, ClusterListener { public WeightFactorMaintainer() { if (initialFactor < 0) { initialFactor = 0; } if (interval < 0) { interval = 1000; } if (initialFactor > defaultFactor || step < 0) { throw new IllegalArgumentException("Invalid weight factor params"); } } @Override public void run() { while (!Thread.interrupted()) { try { Thread.sleep(interval); adjustFactor(); } catch (InterruptedException e) { } catch (RuntimeException e) { logger.warn("error with weight factor maintainer:" + e.getMessage()); } } } private void adjustFactor() { for (Entry<String, WeightFactor> entry : weightFactors.entrySet()) { WeightFactor weightFactor = entry.getValue(); if (weightFactor.getFactor() < defaultFactor) { Integer ticks = stepTicksMap.get(weightFactor.getFactor()); if (ticks == null) { ticks = 1; } int currentStepTicks = weightFactor.getCurrentStepTicks(); weightFactor.setCurrentStepTicks(currentStepTicks + 1); if (weightFactor.getCurrentStepTicks() >= ticks) { int factor = Math.min(defaultFactor, weightFactor.getFactor() + step); weightFactor.setFactor(factor); weightFactor.setCurrentStepTicks(0); } entry.setValue(weightFactor); } } } @Override public void providerAdded(ServiceProviderChangeEvent event) { addWeight(event.getConnect(), event.getWeight()); } @Override public void providerRemoved(ServiceProviderChangeEvent event) { removeWeight(event.getConnect()); } @Override public void hostWeightChanged(ServiceProviderChangeEvent event) { Integer originalWeight = weights.get(event.getConnect()); weights.put(event.getConnect(), event.getWeight()); if ((originalWeight == null || originalWeight == 0) && event.getWeight() > 0) { weightFactors.put(event.getConnect(), new WeightFactor(initialFactor)); } } @Override public void addConnect(ConnectInfo cmd) { addWeight(cmd.getConnect(), RegistryManager.getInstance().getServiceWeight(cmd.getConnect())); } @Override public void removeConnect(Client client) { removeWeight(client.getAddress()); } @Override public void doNotUse(String serviceName, String host, int port) { removeWeight(host + ":" + port); } private void addWeight(String address, int weight) { weights.put(address, weight); weightFactors.put(address, new WeightFactor(initialFactor)); } private void removeWeight(String address) { weights.remove(address); weightFactors.remove(address); ServiceStatisticsHolder.removeCapacityBucket(address); } } public static Map<String, WeightFactor> getWeightFactors() { return weightFactors; } }