Dubbo源码分析(六):Cluster
Dubbo的集群模块,将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
上图描述了dubbo调用过程中的对于集群,负载等的调用关系,根据该图一步步进行解读。
Cluster
将Directory中的多个Invoker伪装成一个Invoker, 对上层透明,包含集群的容错机制
@SPI(FailoverCluster.NAME)
public interface Cluster {
/**
* Merge the directory invokers to a virtual invoker.
*
* @param <T>
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
Cluster类似于一个工厂类,根据不同的集群策略生成不同的cluster。从上可以看出默认的策略是FailoverCluster,当调用失败时候,重试其他服务,通常用于读操作,但重试会带来更长延迟。
public class FailoverCluster implements Cluster {
public final static String NAME = "failover";
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
}
}
可以看出是通过创建一个FailoverClusterInvoker对象。在FailoverClusterInvoker中,会对列表中的invoker进行重新选择。
接下来看看其他的集群方案:
Directory
这是集群目录服务,代表多个invoker,相当于List,它的值可能是动态变化的,比如注册中心推送变更,集群选择调用服务时通过目录服务找到所有服务。
public interface Directory<T> extends Node {
/**
* get service type.
*
* @return service type.
*/
// 服务的类型
Class<T> getInterface();
/**
* list invokers.
*
* @return invokers
*/
// 返回所有服务的可执行对象
List<Invoker<T>> list(Invocation invocation) throws RpcException;
}
两个实现类StaticDirectory和RegistryDirectory
StaticDirectory
静态目录服务, 它的所有Invoker通过构造函数传入, 服务消费方引用服务的时候, 服务对多注册中心的引用,将Invokers集合直接传入 StaticDirectory构造器,再由Cluster伪装成一个Invoker
RegistryDirectory:
注册目录服务, 它的Invoker集合是从注册中心获取的, 它实现了NotifyListener接口实现了回调接口notify(List)。 比如消费方要调用某远程服务,会向注册中心订阅这个服务的所有服务提供方,订阅时和服务提供方数据有变动时回调消费方的NotifyListener服务的notify方法NotifyListener.notify(List) 回调接口传入所有服务的提供方的url地址然后将urls转化为invokers, 也就是refer应用远程服务(zookeeper也因此很适合做为注册中心)
Router
服务路由, 根据路由规则从多个Invoker中选出一个子集。AbstractDirectory是所有目录服务实现的上层抽象, 它在list列举出所有invokers后,会在通过Router服务进行路由过滤。
public interface Router extends Comparable<Router> {
/**
* get the router url.
*
* @return url
*/
URL getUrl();
/**
* route.
*
* @param invokers
* @param url refer url
* @param invocation
* @return routed invokers
* @throws RpcException
*/
<T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
两个实现类ConditionRouter和ScriptRouter
ConditionRouter:条件路由
public ConditionRouter(URL url) {
this.url = url;
this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
this.force = url.getParameter(Constants.FORCE_KEY, false);
try {
// 从url根据RULE_KEY获取路由条件路由内容
String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
if (rule == null || rule.trim().length() == 0) {
throw new IllegalArgumentException("Illegal route rule!");
}
rule = rule.replace("consumer.", "").replace("provider.", "");
// 分割路由内容
int i = rule.indexOf("=>");
String whenRule = i < 0 ? null : rule.substring(0, i).trim();
String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
// 分别调用parseRule(rule) 解析路由为whenRule和thenRules
Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
// NOTE: When条件是允许为空的,外部业务来保证类似的约束条件
this.whenCondition = when;
this.thenCondition = then;
} catch (ParseException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
接下来看route方法
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
throws RpcException {
if (invokers == null || invokers.size() == 0) {
return invokers;
}
try {
// 如果url不满足when条件即过来条件, 不过滤返回所有invokers
if (!matchWhen(url, invocation)) {
return invokers;
}
List<Invoker<T>> result = new ArrayList<Invoker<T>>();
if (thenCondition == null) {
logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
return result;
}
// 遍历所有invokers判断是否满足then条件, 将满足条件的加入集合result
for (Invoker<T> invoker : invokers) {
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
// Result不为空,有满足条件的invokers返回
if (result.size() > 0) {
return result;
// Result为空, 没有满足条件的invokers, 判断参数FORCE_KEY是否强制过来,如果强制过滤返回空, 不是返回所有即不过滤
} else if (force) {
logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(Constants.RULE_KEY));
return result;
}
} catch (Throwable t) {
logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
}
return invokers;
}
ScriptRouter: 脚本路由
public ScriptRouter(URL url) {
this.url = url;
// 从url获取脚本类型javascript, groovy等等
String type = url.getParameter(Constants.TYPE_KEY);
this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
// 从url根据RULE_KEY获取路由规则内容
String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
if (type == null || type.length() == 0) {
type = Constants.DEFAULT_SCRIPT_TYPE_KEY;
}
if (rule == null || rule.length() == 0) {
throw new IllegalStateException(new IllegalStateException("route rule can not be empty. rule:" + rule));
}
// 根据脚本类型获取java支持的脚本执行引擎
ScriptEngine engine = engines.get(type);
if (engine == null) {
engine = new ScriptEngineManager().getEngineByName(type);
if (engine == null) {
throw new IllegalStateException(new IllegalStateException("Unsupported route rule type: " + type + ", rule: " + rule));
}
engines.put(type, engine);
}
this.engine = engine;
this.rule = rule;
}
接下来看route方法
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
try {
List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers);
Compilable compilable = (Compilable) engine;
// 执行引擎创建参数绑定
Bindings bindings = engine.createBindings();
// // 绑定执行的参数
bindings.put("invokers", invokersCopy);
bindings.put("invocation", invocation);
bindings.put("context", RpcContext.getContext());
// 执行引擎编译路由规则得到执行函数CompiledScript
CompiledScript function = compilable.compile(rule);
// 根据参数执行路由规则
Object obj = function.eval(bindings);
if (obj instanceof Invoker[]) {
invokersCopy = Arrays.asList((Invoker<T>[]) obj);
} else if (obj instanceof Object[]) {
invokersCopy = new ArrayList<Invoker<T>>();
for (Object inv : (Object[]) obj) {
invokersCopy.add((Invoker<T>) inv);
}
} else {
invokersCopy = (List<Invoker<T>>) obj;
}
return invokersCopy;
} catch (ScriptException e) {
//fail then ignore rule .invokers.
logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
return invokers;
}
}
Dubbo也支持通过FileRouterFactory从文件读取路由规则,将读取的规则设置到url的RULE_KEY参数上, 文件的后缀代表了路由的类型,选择具体的路由工厂 ConditionRouterFactory,ScriptRouterFactory来创建路由规则。
LoadBalance
负载均衡, 负责从多个 Invokers中选出具体的一个Invoker用于本次调用,调用过程中包含了负载均衡的算法,调用失败后需要重新选择。
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* select one invoker in list.
*
* @param invokers invokers.
* @param url refer url
* @param invocation invocation.
* @return selected invoker.
*/
// Select方法设配类通过url的参数选择具体的算法, 在从invokers集合中根据具体的算法选择一个invoker
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
类注解@SPI说明可以基于Dubbo的扩展机制进行自定义的负责均衡算法实现,默认是随机算法
方法注解@Adaptive说明能够生成设配方法
RandomLoadBalance(默认)如下:
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
private final Random random = new Random();
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size(); // 总个数
int totalWeight = 0; // 总权重
boolean sameWeight = true; // 权重是否都一样
// 遍历所有Invokers, 获取计算每个invokers的权重,并把权重累计加起来
每相邻的两个invoker比较他们的权重是否一样,有一个不一样说明权重不均等
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
totalWeight += weight; // 累计总权重
if (sameWeight && i > 0
&& weight != getWeight(invokers.get(i - 1), invocation)) {
sameWeight = false; // 计算所有权重是否一样
}
}
// 总权重大于零且权重不均等的情况下按总权重获取随机数offset = random.netx(totalWeight);遍历invokers确定随机数offset落在哪个片段(invoker上)
if (totalWeight > 0 && !sameWeight) {
// 如果权重不相同且权重大于0则按总权重数随机
int offset = random.nextInt(totalWeight);
// 并确定随机值落在哪个片断上
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);
if (offset < 0) {
return invokers.get(i);
}
}
}
// 如果权重相同或权重为0则均等随机
return invokers.get(random.nextInt(length));
}
}
RoundRobinLoadBalance:轮询,按公约后的权重设置轮询比率。代码如下:
public class RoundRobinLoadBalance extends AbstractLoadBalance {
public static final String NAME = "roundrobin";
private final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 获取轮询key 服务名+方法名,获取可供调用的invokers个数length
设置最大权重的默认值maxWeight=0,设置最小权重的默认值minWeight=Integer.MAX_VALUE
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
int length = invokers.size(); // 总个数
int maxWeight = 0; // 最大权重
int minWeight = Integer.MAX_VALUE; // 最小权重
final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
int weightSum = 0;
// 遍历所有Inokers,比较出得出maxWeight和minWeight
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
maxWeight = Math.max(maxWeight, weight); // 累计最大权重
minWeight = Math.min(minWeight, weight); // 累计最小权重
if (weight > 0) {
invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
weightSum += weight;
}
}
// 如果权重是不一样的,根据key获取自增序列,自增序列加一与最大权重取模默认得到currentWeigth,遍历所有invokers筛选出大于currentWeight的invokers,设置可供调用的invokers的个数length
AtomicPositiveInteger sequence = sequences.get(key);
if (sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
sequence = sequences.get(key);
}
int currentSequence = sequence.getAndIncrement();
if (maxWeight > 0 && minWeight < maxWeight) { // 权重不一样
int mod = currentSequence % weightSum;
for (int i = 0; i < maxWeight; i++) {
for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
final Invoker<T> k = each.getKey();
final IntegerWrapper v = each.getValue();
if (mod == 0 && v.getValue() > 0) {
return k;
}
if (v.getValue() > 0) {
v.decrement();
mod--;
}
}
}
}
// 自增序列加一并与length取模,从invokers获取invoker
// 取模轮循
return invokers.get(currentSequence % length);
}
private static final class IntegerWrapper {
private int value;
public IntegerWrapper(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public void decrement() {
this.value--;
}
}
}
LeastActiveLoadBalance:最少活跃调用数, 相同的活跃的随机选择,活跃数是指调用前后的计数差, 使慢的提供者收到更少的请求,因为越慢的提供者前后的计数差越大。代码如下:
public class LeastActiveLoadBalance extends AbstractLoadBalance {
public static final String NAME = "leastactive";
private final Random random = new Random();
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size(); // 总个数
int leastActive = -1; // 最小的活跃数
int leastCount = 0; // 相同最小活跃数的个数
int[] leastIndexs = new int[length]; // 相同最小活跃数的下标
int totalWeight = 0; // 总权重
int firstWeight = 0; // 第一个权重,用于于计算是否相同
boolean sameWeight = true; // 是否所有权重相同
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // 活跃数
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // 权重
if (leastActive == -1 || active < leastActive) { // 发现更小的活跃数,重新开始
leastActive = active; // 记录最小活跃数
leastCount = 1; // 重新统计相同最小活跃数的个数
leastIndexs[0] = i; // 重新记录最小活跃数下标
totalWeight = weight; // 重新累计总权重
firstWeight = weight; // 记录第一个权重
sameWeight = true; // 还原权重相同标识
} else if (active == leastActive) { // 累计相同最小的活跃数
leastIndexs[leastCount++] = i; // 累计相同最小活跃数下标
totalWeight += weight; // 累计总权重
// 判断所有权重是否一样
if (sameWeight && i > 0
&& weight != firstWeight) {
sameWeight = false;
}
}
}
// assert(leastCount > 0)
if (leastCount == 1) {
// 如果只有一个最小则直接返回
return invokers.get(leastIndexs[0]);
}
if (!sameWeight && totalWeight > 0) {
// 如果权重不相同且权重大于0则按总权重数随机
int offsetWeight = random.nextInt(totalWeight);
// 并确定随机值落在哪个片断上
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexs[i];
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
if (offsetWeight <= 0)
return invokers.get(leastIndex);
}
}
// 如果权重相同或权重为0则均等随机
return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}
}
ConsistentHashLoadBalance:一致性hash, 相同参数的请求总是发到同一个提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。代码如下:
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();
@SuppressWarnings("unchecked")
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
int identityHashCode = System.identityHashCode(invokers);
ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
if (selector == null || selector.identityHashCode != identityHashCode) {
selectors.put(key, new ConsistentHashSelector<T>(invokers, invocation.getMethodName(), identityHashCode));
selector = (ConsistentHashSelector<T>) selectors.get(key);
}
return selector.select(invocation);
}
private static final class ConsistentHashSelector<T> {
private final TreeMap<Long, Invoker<T>> virtualInvokers;
private final int replicaNumber;
private final int identityHashCode;
private final int[] argumentIndex;
ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
this.identityHashCode = identityHashCode;
URL url = invokers.get(0).getUrl();
this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
argumentIndex = new int[index.length];
for (int i = 0; i < index.length; i++) {
argumentIndex[i] = Integer.parseInt(index[i]);
}
for (Invoker<T> invoker : invokers) {
String address = invoker.getUrl().getAddress();
for (int i = 0; i < replicaNumber / 4; i++) {
byte[] digest = md5(address + i);
for (int h = 0; h < 4; h++) {
long m = hash(digest, h);
virtualInvokers.put(m, invoker);
}
}
}
}
public Invoker<T> select(Invocation invocation) {
String key = toKey(invocation.getArguments());
byte[] digest = md5(key);
return selectForKey(hash(digest, 0));
}
private String toKey(Object[] args) {
StringBuilder buf = new StringBuilder();
for (int i : argumentIndex) {
if (i >= 0 && i < args.length) {
buf.append(args[i]);
}
}
return buf.toString();
}
private Invoker<T> selectForKey(long hash) {
Invoker<T> invoker;
Long key = hash;
if (!virtualInvokers.containsKey(key)) {
SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key);
if (tailMap.isEmpty()) {
key = virtualInvokers.firstKey();
} else {
key = tailMap.firstKey();
}
}
invoker = virtualInvokers.get(key);
return invoker;
}
private long hash(byte[] digest, int number) {
return (((long) (digest[3 + number * 4] & 0xFF) << 24)
| ((long) (digest[2 + number * 4] & 0xFF) << 16)
| ((long) (digest[1 + number * 4] & 0xFF) << 8)
| (digest[number * 4] & 0xFF))
& 0xFFFFFFFFL;
}
private byte[] md5(String value) {
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e.getMessage(), e);
}
md5.reset();
byte[] bytes;
try {
bytes = value.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e.getMessage(), e);
}
md5.update(bytes);
return md5.digest();
}
}
}
转载于:https://my.oschina.net/jzgycq/blog/1591871
上一篇: 他是燕国谋臣,后为燕国的未来谋成一件大事
下一篇: 数据结构——链式栈模板类实现
推荐阅读
-
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
-
Mybaits 源码解析 (六)----- 全网最详细:Select 语句的执行过程分析(上篇)(Mapper方法是如何调用到XML中的SQL的?)
-
Tomcat源码分析 (六)----- Tomcat 启动过程(一)
-
Dubbo源码分析之 SPI(一)
-
dubbo源码分析01:SPI机制
-
Android源码分析(六)-----蓝牙Bluetooth源码目录分析
-
[Abp 源码分析]六、工作单元的实现
-
vuex 源码分析(六) 辅助函数 详解
-
Dubbo源码分析-寻找注册中心、服务提供者、服务消费者功能入口
-
源码分析 Sentinel 之 Dubbo 适配原理