dubbo源码深度解读六之cluster模块
来源:互联网 发布:高中生软件培训学校 编辑:程序博客网 时间:2024/06/05 07:01
前言:这是集群模块,将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
下图描述了dubbo调用过程中的对于集群,负载等的调用关系,根据该图一步步进行解读。
一,Cluster
将Directory中的多个Invoker伪装成一个Invoker, 对上层透明,包含集群的容错机制
@SPI(FailoverCluster.NAME)public interface Cluster { @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进行重新选择。
接下来看看其他的集群方案:
在配置时候,我们可以这样配置
<dubbo:service cluster="failsafe" /> 服务提供方<dubbo:reference cluster="failsafe" /> 服务消费方
二,Directory
1,这是集群目录服务,代表多个invoker,相当于List,它的值可能是动态变化的,比如注册中心推送变更,集群选择调用服务时通过目录服务找到所有服务。
public interface Directory<T> extends Node { //服务的类型 Class<T> getInterface(); //返回所有服务的可执行对象 List<Invoker<T>> list(Invocation invocation) throws RpcException;}
2,两个实现类StaticDirectory和RegistryDirectory
(1)StaticDirectory
静态目录服务, 它的所有Invoker通过构造函数传入, 服务消费方引用服务的时候, 服务对多注册中心的引用,将Invokers集合直接传入 StaticDirectory构造器,再由Cluster伪装成一个Invoker
(2)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> { URL getUrl(); <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;}
路由主要有下面两个
(1)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 { 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(); 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); } }
步骤大致如下:
1)从url根据RULE_KEY获取路由条件路由内容
2)rule.indexOf(“=>”) 分割路由内容
3)分别调用parseRule(rule) 解析路由为whenRule和thenRules
接下来看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 { if (! matchWhen(url)) { 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; } for (Invoker<T> invoker : invokers) { if (matchThen(invoker.getUrl(), url)) { result.add(invoker); } } if (result.size() > 0) { return result; } 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; }
逻辑大致如下:
1)如果url不满足when条件即过来条件, 不过滤返回所有invokers
2)遍历所有invokers判断是否满足then条件, 将满足条件的加入集合result
3)Result不为空,有满足条件的invokers返回
4)Result为空, 没有满足条件的invokers, 判断参数FORCE_KEY是否强制过来,如果强制过滤返回空, 不是返回所有即不过滤
(2)ScriptRouter: 脚本路由
通过url的RULE_KEY参数获取脚本内容,然后通过Java的脚本引擎执行脚本代码
接下来先看构造函数
public ScriptRouter(URL url) { this.url = url; String type = url.getParameter(Constants.TYPE_KEY); this.priority = url.getParameter(Constants.PRIORITY_KEY, 0); 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)); } 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; }
1)从url获取脚本类型javascript, groovy等等
2)从url根据RULE_KEY获取路由规则内容
3)根据脚本类型获取java支持的脚本执行引擎
接下来看route方法
@SuppressWarnings("unchecked") 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 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; } }
大致逻辑如下:
1)执行引擎创建参数绑定
2)绑定执行的参数
3)执行引擎编译路由规则得到执行函数CompiledScript
4)CompiledScript.eval(binds) 根据参数执行路由规则
Dubbo也支持通过FileRouterFactory从文件读取路由规则,将读取的规则设置到url的RULE_KEY参数上, 文件的后缀代表了路由的类型,选择具体的路由工厂 ConditionRouterFactory,ScriptRouterFactory来创建路由规则。
(四)LoadBalance
负载均衡, 负责从多个 Invokers中选出具体的一个Invoker用于本次调用,调用过程中包含了负载均衡的算法,调用失败后需要重新选择。
@SPI(RandomLoadBalance.NAME)public interface LoadBalance { //Select方法设配类通过url的参数选择具体的算法, 在从invokers集合中根据具体的算法选择一个invoker @Adaptive("loadbalance") <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;}
类注解@SPI说明可以基于Dubbo的扩展机制进行自定义的负责均衡算法实现,默认是随机算法
方法注解@Adaptive说明能够生成设配方法
(1) RandomLoadBalance: 随机访问策略,按权重设置随机概率,是默认策略
1)获取所有invokers的个数
2)遍历所有Invokers, 获取计算每个invokers的权重,并把权重累计加起来
每相邻的两个invoker比较他们的权重是否一样,有一个不一样说明权重不均等
3)总权重大于零且权重不均等的情况下按总权重获取随机数offset = random.netx(totalWeight);遍历invokers确定随机数offset落在哪个片段(invoker上)
4)权重相同或者总权重为0, 根据invokers个数均等选择invokers.get(random.nextInt(length))
(2)RoundRobinLoadBalance:轮询,按公约后的权重设置轮询比率
1)获取轮询key 服务名+方法名,获取可供调用的invokers个数length
设置最大权重的默认值maxWeight=0,设置最小权重的默认值minWeight=Integer.MAX_VALUE
2)遍历所有Inokers,比较出得出maxWeight和minWeight
3)如果权重是不一样的,根据key获取自增序列,自增序列加一与最大权重取模默认得到currentWeigth,遍历所有invokers筛选出大于currentWeight的invokers,设置可供调用的invokers的个数length
4)自增序列加一并与length取模,从invokers获取invoker
(3)LeastActiveLoadBalance: 最少活跃调用数, 相同的活跃的随机选择,活跃数是指调用前后的计数差, 使慢的提供者收到更少的请求,因为越慢的提供者前后的计数差越大。
(4)ConsistentHashLoadBalance:一致性hash, 相同参数的请求总是发到同一个提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
- dubbo源码深度解读六之cluster模块
- dubbo源码深度解读一之common模块
- dubbo源码深度解读二之config模块
- dubbo源码深度解读三之container模块
- dubbo源码深度解读四之remoting模块
- dubbo源码深度解读五之rpc模块
- dubbo源码深度解读七之registery模块
- dubbo源码深度解读一之common模块
- Dubbo源码分析(六):Dubbo内核实现之动态编译
- Dubbo ExtensionLoader源码解读
- dubbo源码解读
- dubbo 源码学习笔记 (六) —— 集群模块
- spark源码学习(六):standalone模式的cluster集群源码解读
- Caffe源码解读(六): Caffe的I/O模块
- 深度解剖dubbo源码
- 深度解剖dubbo源码
- 深度解剖dubbo源码
- elasticsearch之cluster模块
- 单目标定程序
- Android 位置,大小,旋转,透明度改变的补间动画
- Python 线程同步 线程优先级
- 位域
- Notes on LaTex: lshshort chapter 2 文本格式设置
- dubbo源码深度解读六之cluster模块
- iOS 音视频专栏(一)视频流H264分析以及坑
- 工具列表
- java.util.BitSet
- Maven小总
- 嵌入式实时操作系统μC OS-3.pdf
- 堆和栈的区别(转过无数次的文章)
- STM32 BSRR BRR ODR 寄存器解析
- Python 常用库