SegmentFault Dubbo源码分析最新的文章
2020-03-02T07:43:56+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
从2.7.0-2.7.5版本,Dubbo调用链路是如何提升30%性能的
https://segmentfault.com/a/1190000021886082
2020-03-02T07:43:56+08:00
2020-03-02T07:43:56+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<p><img src="/img/bVbDYLz" alt="head.GIF" title="head.GIF"></p>
<h2>前言</h2>
<p>Dubbo 2.7.5版本发布也有快两个月了,从2.7.0到2.7.5,Dubbo在性能优化上也做了不少事情,据官方的压测结果,在QPS层面,从2.7.0到2.7.5,Dubbo 单次RPC调用链路性能提升了 30%,本文就带大家看一看Dubbo做了哪些改动来提升RPC 调用链路性能。</p>
<h2>服务元数据静态化,减少链路计算</h2>
<h3>consumer端缓存ConsumerModel</h3>
<p>我们知道当一个服务启动时,如果配置了服务引用相关的配置时,在consumer端会生成所引用服务的代理对象,有关服务引用过程的源码解析可以直接扫下方二维码关注公众号【加点代码调调味】获取。在生成服务的代理对象前会做一些校验配置以及更新配置等工作,它们主要是在ReferenceConfig的#checkAndUpdateSubConfigs()方法中实现的。在2.7.5版本中,该方法被新增了一些加载服务元数据的逻辑,下面来看看下面这部分源码:</p>
<pre><code class="java">public void checkAndUpdateSubConfigs() {
/**
* 省略无关代码
*/
// part1-start
//init serivceMetadata
serviceMetadata.setVersion(version);
serviceMetadata.setGroup(group);
serviceMetadata.setDefaultGroup(group);
serviceMetadata.setServiceType(getActualInterface());
serviceMetadata.setServiceInterfaceName(interfaceName);
// TODO, uncomment this line once service key is unified
serviceMetadata.setServiceKey(URL.buildKey(interfaceName, group, version));
// part2-start
ServiceRepository repository = ApplicationModel.getServiceRepository();
ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass);
repository.registerConsumer(
serviceMetadata.getServiceKey(),
serviceDescriptor,
this,
null,
serviceMetadata);
// part2-end
/**
* 省略无关代码
*/
}</code></pre>
<h3>provider端缓存ProviderModel</h3>
<p>当服务启动时,必然会经过doExportUrls方法,具体的服务暴露过程源码分析可以直接扫下方二维码关注公众号【加点代码调调味】获取。跟consumer端一样,在新版本中添加了加载和计算元数据的逻辑。看下面源码:</p>
<pre><code class="java">private void doExportUrls() {
// part1-start
ServiceRepository repository = ApplicationModel.getServiceRepository();
ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
repository.registerProvider(
getUniqueServiceName(),
ref,
serviceDescriptor,
this,
serviceMetadata
);
// part1-end
/**
* 省略无关代码
*/
}</code></pre>
<p>看到这里还是一头雾水吧,不要放弃,接着往下看:</p>
<p>可以看到consumer端的part1部分只是赋值一些服务相关的元数据,而consumer端的part2部分以及provider端的part1部分则是引入了ServiceRepository,ServiceRepository是服务仓库,它封装了服务相关的元数据、consumer端的元数据模型ConsumerModel以及provider端的元数据模型ProviderModel,ConsumerModel和ProviderModel两个模型,分别封装了consumer端和provider端的配置,比如ConsumerModel就封装了referenceConfig、methodConfig等。在part2部分最重要的就是实例化了一个ServiceRepository对象,然后将version、group、服务类型、服务接口名等元数据放入ServiceRepository对象中,保证在进程启动阶段服务元数据尽量做一些计算,做一些元数据以及计算结果的缓存,(比如生成方法参数描述parameterDesc数据等),从而在RPC调用链路上需要用到相关元数据时可以直接能直接拿到计算结果。在下面几个地方用到了ServiceRepository中缓存的元数据计算结果。</p>
<ul>
<li>初始化RpcInvocation时:无论是consumer端的调用链路中还是provider端的调用链路中,RpcInvocation一直都是整个调用链路内携带元数据的载体,举个例子:可以看源码中RpcInvocation有一个方法initParameterDesc(),这其中是赋值parameterDesc、compatibleParamSignatures、returnTypes这三个属性,但是这三个数据都是直接从ServiceRepository中直接获取的,并不是在初始化RpcInvocation时再计算这三个值。</li>
<li>解码时:当provider端对请求进行解码时,会解析需要调用的方法签名,包括方法的参数类型、返回类型,现在这些内容都已经做了缓存,所以无需再重新解析,只要直接从ServiceRepository中获取即可,具体的源码可以看DecodeableRpcInvocation的#decode(Channel channel, InputStream input)方法。</li>
</ul>
<h2>减少调用过程中的 URL 操作产生的内存分配</h2>
<p>除了在consumer端以及provider端在各自的调用链路中提升性能外,减少调用过程中的URL操作产生的内存分配动作也是一个优化的点。在2.7.x以前的版本,也就是还没有元数据中心的版本,统一模型URL携带的内容极其多,这会导致在网络中数据的传输中数据包太大,影响响应时间。而在2.7.x版本缩减了URL中的相关内容,让URL只关注服务定位相关的元数据,比如protocol、host、port等。下面来看看在最新的版本中是如何做到减少调用过程中的 URL 操作产生的内存分配的。主要从以下几个方面着手:</p>
<ul>
<li>减少了URL对于方法级别元数据获取操作的内存分配</li>
<li>减少URL.getAddress的对象分配</li>
</ul>
<h3>减少了URL对于方法级别元数据获取操作的内存分配</h3>
<pre><code class="java">public class URL implements Serializable {
/**
* 省略无关代码
*/
private final Map<String, String> parameters;
private final Map<String, Map<String, String>> methodParameters;
private volatile transient Map<String, Map<String, Number>> methodNumbers;
public static Map<String, Map<String, String>> toMethodParameters(Map<String, String> parameters) {
Map<String, Map<String, String>> methodParameters = new HashMap<>();
if (parameters == null) {
return methodParameters;
}
String methodsString = parameters.get(METHODS_KEY);
if (StringUtils.isNotEmpty(methodsString)) {
String[] methods = methodsString.split(",");
for (Map.Entry<String, String> entry : parameters.entrySet()) {
String key = entry.getKey();
for (String method : methods) {
String methodPrefix = method + '.';
if (key.startsWith(methodPrefix)) {
String realKey = key.substring(methodPrefix.length());
URL.putMethodParameter(method, realKey, entry.getValue(), methodParameters);
}
}
}
} else {
for (Map.Entry<String, String> entry : parameters.entrySet()) {
String key = entry.getKey();
int methodSeparator = key.indexOf('.');
if (methodSeparator > 0) {
String method = key.substring(0, methodSeparator);
String realKey = key.substring(methodSeparator + 1);
URL.putMethodParameter(method, realKey, entry.getValue(), methodParameters);
}
}
}
return methodParameters;
}
/**
* 省略无关代码
*/
}
</code></pre>
<p>在url中parameters属性携带着服务相关的一些元数据配置,比如group、timeout等配置,包括方法级别的配置,还有服务相关的methods、interface等元数据,下图是我跑的一个demo,其中展示了parameters的一些内容:</p>
<p><img src="/img/bVbDZiJ" alt="parameters示例.png" title="parameters示例.png"></p>
<p>在新版本中将方法相关的元数据通过一个map维护在url中,减少对字符串的操作,因为每次需要获取方法的元数据时,都需要从parameters中获取对应的值,比如获取sayHello.timeout的值,然后还要根据“.”分割获取该配置的key为timeout,最终才能获取到sayHello这个方法的timeout配置,现在方法级别的元数据直接维护在url中,就不需要每次都进行字符串操作,并且还添加了缓存methodNumbers,加快二次获取的速度。</p>
<h3>减少URL.getAddress的对象分配</h3>
<p><strong>旧版本代码</strong></p>
<pre><code class="java">public class URL implements Serializable {
/**
* 省略无关代码
*/
private final String host;
private final int port;
public String getAddress(String host, int port) {
return port <= 0 ? host : host + ':' + port;
}
/**
* 省略无关代码
*/
}</code></pre>
<p><strong>新版本代码</strong></p>
<pre><code class="java">public class URL implements Serializable {
/**
* 省略无关代码
*/
private transient String address;
private final String host;
private final int port;
private static String getAddress(String host, int port) {
return port <= 0 ? host : host + ':' + port;
}
public String getAddress() {
if (address == null) {
address = getAddress(host, port);
}
return address;
}
/**
* 省略无关代码
*/
}</code></pre>
<p>从源码看,在URL中新增了address这个属性,在获取address时只做一次对象分配,而不需要像原来每次调用getAddress方法时都做一些字符串拼接,由于拼接的host和port都是一个String类型的对象,所以在拼接的时候并不会被在编译期间就优化,而是会创建一个StringBuilder对象来进行拼接,这样每次获取address就会带来对象的内存分配的性能损耗。</p>
<p>除了对于URL.getAddress的对象分配的优化外,在2.7.5版本发布后,还有几个pull request也是针对对象分配的优化,原理和这个差不多,这里就不一一列举了。</p>
<h2>送福利区域</h2>
<p>扫描下方二维码关注公众号【加点代码调调味】,点击菜单栏获取免费49篇的《Dubbo源码解析》系列文章<br><img src="/img/bVbDIOM" alt="公众号二维码.png" title="公众号二维码.png"><br><img src="/img/bVbDZhN" alt="foot.gif" title="foot.gif"></p>
Dubbo Roadmap与未来展望
https://segmentfault.com/a/1190000021821028
2020-02-23T23:42:39+08:00
2020-02-23T23:42:39+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
1
<h2>前言</h2>
<p>前几天看了刘军关于《从 2019 到 2020,Apache Dubbo 年度总结与展望》线上直播,打算对此写个观后感。</p>
<p>首先介绍一下刘军,他是Apache Dubbo PMC,项目核心维护者,见证了 Dubbo 从重启开源到 Apache 毕业的整个流程。现任职阿里巴巴中间件团队,参与服务框架、微服务相关工作,目前主要在推动 Dubbo 开源的云原生化。</p>
<p>整个线上直播大概一个多小时,总体讲了三个方面,分别是:</p>
<ul>
<li>Dubbo社区发展回顾</li>
<li>重要功能解析</li>
<li>Roadmap与未来展望</li>
</ul>
<p>重要功能解析主要是关于2.7.x的特性,这部分内容我会将每一块分为一个主题在后续的文章中一一介绍,本文大致介绍一下直播中讲到的未来展望这部分内容。</p>
<h2>Roadmap与未来展望</h2>
<p>在未来规划里主要分为四个发力点,分别是:</p>
<ul>
<li>微服务相关功能的增强</li>
<li>Dubbo 3.0 协议升级</li>
<li>服务自省</li>
<li>云原生</li>
</ul>
<h3>微服务相关功能的增强</h3>
<p>目前微服务相关功能正在做的有Dubbo的鉴权功能、内置熔断能力以及 TLS,在2.7.5版本中已经集成来鉴权功能、链路的安全传输。涉及到业务开发的功能,dubbo在后续还会做进一步增强。</p>
<h3>Dubbo 3.0 协议升级</h3>
<p>在未来会针对Dubbo协议做全面升级,该升级主要涉及到以下几个重要的方向:</p>
<ul>
<li>Reactive Stream的支持:前面提到的在协议层面对Reactive Stream的支持,以支持消费端的流式请求,服务端的流式响应或者双向的流式通信。</li>
<li>HTTP/2的支持:因为在微服务云原生场景下,基于 HTTP/2 构建的通信协议具有更好的通用性和穿透性,所以在3.0协议上会对HTTP/2的支持。</li>
<li>跨语言的支持:针对跨语言的支持,更多的是序列化方面发力。</li>
<li>流量控制的支持:比如Reactive编程里面的request (n)或者 HTTP/2 中提供的流控机制等。</li>
<li>协议升级:因为要考虑到老版本协议如何升级到新版本的协议,所以高版本的协议要提供一定的协议协商机制,类似于 HTTP/2 引入的协议协商机制,高版本的协议还要能够对低版本的协议有一定的包容。</li>
<li>可扩展性增强:主要是Header的metadata部分,需要区分协议扩展和RPC方法的扩展,因为dubbo原来的协议中attachments更多的是为了支持方法的扩展,在新版本的协议中需要支持协议的扩展。</li>
<li>Mesh:新版本的协议需要对mesh更友好,方便完成跟mesh的协作。</li>
<li>通用性强:协议设计上要兼顾通用性和性能,可在各种设备上运行。</li>
</ul>
<h3>服务自省</h3>
<p>dubbo之前都是基于接口粒度,之后会规划增加应用粒度的服务发现、治理以及选址,因为通过增加应用粒度的支持,可以与主流微服务和云原生模型对齐,并且解决接口带来的性能问题。接下来应用粒度有几个方面可以发力:</p>
<ul>
<li>以应用粒度注册、注册中心只关注地址变更。</li>
<li>
<p>元数据服务提供额外信息。</p>
<ul>
<li>接口列表、方法列表、方法签名等;</li>
<li>实例特有配置;</li>
</ul>
</li>
<li>
<p>保持RPC编程风格不变</p>
<ul>
<li>继续面向接口编程,无需额外改造</li>
<li>仅通过Registry配置,区别新老服务发现模型;</li>
</ul>
</li>
</ul>
<h3>云原生</h3>
<ul>
<li>服务发现:dubbo的服务发现能力作为sdk在以前的基础设施上可以正常工作,未来需要解决在Kubernetes的一些基础设施上该如何工作,未来希望可以复用这些基础设施,基于容器调度的能力来复用底层的服务发现能力。</li>
<li>云上、云下互通:未来会考虑云上云下dubbo应用的互通。</li>
<li>平滑迁移:未来希望能将普通运用可以平滑迁移到容器中。</li>
<li>Talk to xDS:在mesh场景下,dubbo可能会考虑对具备xDS协议的标准式配置的下发能力的sdk的支持。</li>
<li>生命周期对齐:未来需要考虑的是dubbo的生命周期如何和容器相关的生命周期进行绑定。 服务治理机制适配:在k8s下,对pod的调度有一套自己的调度机制,它的ip是不停的变化,dubbo有很多治理规则都是强绑定ip的,这些并不适合云原生的服务治理,所以在未来需要解决dubbo在服务治理机制上如何适配的问题。</li>
</ul>
<p><img src="/img/bVbDIOM" alt="加点代码调调味.png" title="加点代码调调味.png"></p>
Dubbo源码解析(四十八)异步化改造
https://segmentfault.com/a/1190000019960031
2019-08-03T09:24:54+08:00
2019-08-03T09:24:54+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
3
<h2>2.7大揭秘——异步化改造</h2>
<blockquote>目标:从源码的角度分析2.7的新特性中对于异步化的改造原理。</blockquote>
<h3>前言</h3>
<p>dubbo中提供了很多类型的协议,关于协议的系列可以查看下面的文章:</p>
<blockquote><ul>
<li><a href="https://segmentfault.com/a/1190000017973639">dubbo源码解析(二十四)远程调用——dubbo协议</a></li>
<li><a href="https://segmentfault.com/a/1190000017998711">dubbo源码解析(二十五)远程调用——hessian协议</a></li>
<li><a href="https://segmentfault.com/a/1190000018002784">dubbo源码解析(二十六)远程调用——http协议</a></li>
<li><a href="https://segmentfault.com/a/1190000018016406">dubbo源码解析(二十七)远程调用——injvm本地调用</a></li>
<li><a href="https://segmentfault.com/a/1190000018034217">dubbo源码解析(二十八)远程调用——memcached协议</a></li>
<li><a href="https://segmentfault.com/a/1190000018045851">dubbo源码解析(二十九)远程调用——redis协议</a></li>
<li><a href="https://segmentfault.com/a/1190000018054361">dubbo源码解析(三十)远程调用——rest协议</a></li>
<li><a href="https://segmentfault.com/a/1190000018098414">dubbo源码解析(三十一)远程调用——rmi协议</a></li>
<li><a href="https://segmentfault.com/a/1190000018070746">dubbo源码解析(三十二)远程调用——thrift协议</a></li>
<li><a href="https://segmentfault.com/a/1190000018079811">dubbo源码解析(三十三)远程调用——webservice协议</a></li>
</ul></blockquote>
<p>官方推荐的是使用dubbo协议,而异步调用的支持也是在dubbo协议中实现的。</p>
<p>看了我之前写的2.7新特性的文章,应该对于异步化改造有个大致的印象。要弄懂异步在什么时候起作用,先要弄懂dubbo 的服务暴露和引用过程以及消费端发送请求过程和服务端处理请求过程。我在前四篇文章已经讲述了相关内容,异步请求只是dubbo的一种请求方式,基于 dubbo 底层的异步 NIO 实现异步调用,对于 Provider 响应时间较长的场景是必须的,它能有效利用 Consumer 端的资源,相对于 Consumer 端使用多线程来说开销较小。可以让消费者无需阻塞等待返回结果。</p>
<p>经过改良后,Provider端也支持异步处理请求,引用官网的话就是现在Provider端异步执行和Consumer端异步调用是相互独立的,你可以任意正交组合两端配置。</p>
<p>如何开启和使用异步可以查看以下链接:</p>
<blockquote>Provider异步执行:<a href="https://link.segmentfault.com/?enc=c93mx7XxrI0XsfdO%2F0ADNA%3D%3D.jbrZmRKlS1AttxJA%2BDnxkrwEWZlZm7amN8TFvL6ne3aVCz%2F52Lvn1yKyvZ3KbIfBOFW3nHDyy12YaP0nnhhaOu6FVdOy9bswgNNo2M3Vogk%3D" rel="nofollow">http://dubbo.apache.org/zh-cn/docs/user/demos/async-execute-on-provider.html</a><p>Consumer异步调用:<a href="https://link.segmentfault.com/?enc=ZFyuVg%2FQLSCZvoQhuRwSdg%3D%3D.tXAYuE3gtrjRTwFczGO%2FMFiNeMZJfgXHYrtq619yX9iON50m9RA2MXkZd1S8H30mKKwxMek6UwDk1JoH4C4OfQ%3D%3D" rel="nofollow">http://dubbo.apache.org/zh-cn/docs/user/demos/async-call.html</a></p>
</blockquote>
<h3>异步的改造</h3>
<h4>Listener做为Filter的内部接口</h4>
<p>从设计上</p>
<ol>
<li>废弃了Filter原先的onResponse()方法</li>
<li>
<p>在Filter接口新增了内部接口Listener,相关接口设计如下。</p>
<ul><li>优点:职责划分更加明确,进行逻辑分组,增强可读性,Filter本身应仅是传递调用的响应,而所有回调都放入Listener。这样做以后可以把之前回调的逻辑从invoke里面剥离出来,放到Listener的onResponse或者onError中。</li></ul>
</li>
</ol>
<pre><code class="java">interface Listener {
/**
* 回调正常的调用结果
* @param appResponse
* @param invoker
* @param invocation
*/
void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation);
/**
* 回调异常结果
* @param t
* @param invoker
* @param invocation
*/
void onError(Throwable t, Invoker<?> invoker, Invocation invocation);
}</code></pre>
<ol><li>新增抽象类ListenableFilter,实现了Filter接口,其中只记录了一个该过滤器的内部Listener实例。</li></ol>
<pre><code class="java">public abstract class ListenableFilter implements Filter {
protected Listener listener = null;
public Listener listener() {
// 提供该过滤器的内部类listener
return listener;
}
}</code></pre>
<h4>异步转同步,新增InvokeMode</h4>
<p>不变的是配置来决定调用方式,变的是在何时去做同步异步的不同逻辑处理。看<a href="https://segmentfault.com/a/1190000019387309">《dubbo源码解析(四十六)消费端发送请求过程》</a>讲到的(十四)DubboInvoker的doInvoke,在以前的逻辑会直接在doInvoke方法中根据配置区分同步、异步、单向调用。现在只单独做了单向调用和需要返回结果的区分,统一先使用AsyncRpcResult来表示结果,也就是说一开始统一都是异步调用,然后在调用回到AsyncToSyncInvoker的invoke中时,才对同步异步做区分,这里新增了InvokeMode,InvokeMode现在有三种模式:SYNC, ASYNC, FUTURE。前两种很显而易见,后面一种是调用的返回类型是Future类型,代表调用的方法的返回类型是CompletableFuture类型,这种模式专门用来支持服务端异步的。看下面的源码。</p>
<pre><code class="java">public static InvokeMode getInvokeMode(URL url, Invocation inv) {
// 如果返回类型是future
if (isReturnTypeFuture(inv)) {
return InvokeMode.FUTURE;
} else if (isAsync(url, inv)) {
// 如果是异步调用
return InvokeMode.ASYNC;
} else {
// 如果是同步
return InvokeMode.SYNC;
}
}</code></pre>
<p>参考<a href="https://segmentfault.com/a/1190000019387309">《dubbo源码解析(四十六)消费端发送请求过程》</a>的(十二)AsyncToSyncInvoker的invoke逻辑,如果是同步模式,就会阻塞调用get方法。直到调用成功有结果返回。如果不是同步模式,就直接返回。</p>
<h4>ResponseFuture改为CompletableFuture</h4>
<p>关于ResponseFuture可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的(六)ResponseFuture。具体的可以看它的两个实现(七)DefaultFuture和(八)SimpleFuture。</p>
<p>在这次改造中,最小JDK版本从以前的1.6变成了1.8。当然也要用到1.8中新特性,其中就包括CompletableFuture。dubbo的通信主要有两处,一处是Consumer发送请求消息给Provider,另一处就是Provider把结果发送给Consumer。在Consumer发送请求消息给Provider的时候,Consumer不会一直处于等待,而是生成ResponseFuture会抛给下游去做其他操作,等到Provider把结果返回放入ResponseFuture,Consumer可以通过get方法获得结果,或者它也支持回调。但是这就暴露了一些问题,也就是为在新特性里提到的缺陷:</p>
<ul>
<li>Future只支持阻塞式的get()接口获取结果。因为future.get()会导致线程阻塞。</li>
<li>Future接口无法实现自动回调,而自定义ResponseFuture虽支持callback回调但支持的异步场景有限,如不支持Future间的相互协调或组合等;</li>
</ul>
<p>针对以上两个不足,CompletableFuture可以很好的解决它们。</p>
<ul>
<li>针对第一点不足,因为CompletableFuture实现了CompletionStage和Future接口,所以它还是可以像以前一样通过阻塞或者轮询的方式获得结果。这一点就能保证阻塞式获得结果,也就是同步调用不会被抛弃。当然本身也不是很建议用get()这样阻塞的方式来获取结果。</li>
<li>针对第二点不足,首先是自动回调,CompletableFuture提供了良好的回调方法。比如下面四个方法有关计算结果完成时的处理:</li>
</ul>
<pre><code class="java">public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)</code></pre>
<p>当计算完成后,就会执行该方法中的action方法。相比于ResponseFuture,不再需要自己去做回调注册的编码,更加易于理解。</p>
<ul><li>还是针对第二点,自定义的ResponseFuture不支持Future间的相互协调或组合,CompletableFuture很好的解决了这个问题,在CompletableFuture中以下三个方法实现了future之间转化的功能:</li></ul>
<pre><code class="java">public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)</code></pre>
<p>由于回调风格的实现,我们不必因为等待一个计算完成而阻塞着调用线程,而是告诉CompletableFuture当计算完成的时候请执行某个function。而且我们还可以将这些操作串联起来,或者将CompletableFuture组合起来。这一组函数的功能是当原来的CompletableFuture计算完后,将结果传递给函数fn,将fn的结果作为新的CompletableFuture计算结果。因此它的功能相当于将CompletableFuture<T>转换成CompletableFuture<U>。</p>
<p>除了转化之外,还有future之间组合的支持,例如以下三个方法:</p>
<pre><code class="java">public <U> CompletableFuture<U> thenCompose(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn, Executor executor)</code></pre>
<p>这一组方法接受一个Function作为参数,这个Function的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture,这个新的CompletableFuture会组合原来的CompletableFuture和函数返回的CompletableFuture。</p>
<p>现在就能看出CompletableFuture的强大了,它解决了自定义ResponseFuture的许多问题,该类有几十个方法,感兴趣的可以去一一尝试。</p>
<h4>随处可见的CompletableFuture</h4>
<p>可以看到以前的版本只能在RpcContext中进行获取。而经过改良后,首先RpcContext一样能过获取,其次在过滤器链返回的Result中也能获取,可以从最新的代码中看到,原先的RpcResult类已经被去除,而在AsyncRpcResult也继承了CompletableFuture<Result>类,也就是说有AsyncRpcResult的地方,就有CompletableFuture。并且在后续的dubbo3.0中,AsyncRpcResult将会内置CompletableFuture类型的变量,CompletableFuture的获取方式也会大大增加。</p>
<h5>AsyncRpcResult全面替代RpcResult</h5>
<p>接下来我就来讲解一下AsyncRpcResult类。</p>
<pre><code class="java">/**
* 当相同的线程用于执行另一个RPC调用时,并且回调发生时,原来的RpcContext可能已经被更改。
* 所以我们应该保留当前RpcContext实例的引用,并在执行回调之前恢复它。
* 存储当前的RpcContext
*/
private RpcContext storedContext;
/**
* 存储当前的ServerContext
*/
private RpcContext storedServerContext;
/**
* 会话域
*/
private Invocation invocation;
public AsyncRpcResult(Invocation invocation) {
// 设置会话域
this.invocation = invocation;
// 获得当前线程内代表消费者端的Context
this.storedContext = RpcContext.getContext();
// 获得当前线程内代表服务端的Context
this.storedServerContext = RpcContext.getServerContext();
}
/**
* 转换成新的AsyncRpcResult
* @param asyncRpcResult
*/
public AsyncRpcResult(AsyncRpcResult asyncRpcResult) {
this.invocation = asyncRpcResult.getInvocation();
this.storedContext = asyncRpcResult.getStoredContext();
this.storedServerContext = asyncRpcResult.getStoredServerContext();
}</code></pre>
<p>上面的是AsyncRpcResult核心的变量以及构造函数,storedContext和storedServerContext存储了相关的RpcContext实例的引用,为的就是防止在回调的时候由于相同的线程用于执行另一个RPC调用导致原来的RpcContext可能已经被更改。所以存储下来后,我们需要在执行回调之前恢复它。具体的可以看下面的thenApplyWithContext方法。</p>
<pre><code class="java">@Override
public Object getValue() {
// 获得计算的结果
return getAppResponse().getValue();
}
@Override
public void setValue(Object value) {
// 创建一个AppResponse实例
AppResponse appResponse = new AppResponse();
// 把结果放入AppResponse
appResponse.setValue(value);
// 标志该future完成,并且把携带结果的appResponse设置为该future的结果
this.complete(appResponse);
}
@Override
public Throwable getException() {
// 获得抛出的异常信息
return getAppResponse().getException();
}
@Override
public void setException(Throwable t) {
// 创建一个AppResponse实例
AppResponse appResponse = new AppResponse();
// 把异常放入appResponse
appResponse.setException(t);
// 标志该future完成,并且把携带异常的appResponse设置为该future的结果
this.complete(appResponse);
}
@Override
public boolean hasException() {
// 设置是否有抛出异常
return getAppResponse().hasException();
}
public Result getAppResponse() {
// 如果该结果计算完成,则直接调用get方法获得结果
try {
if (this.isDone()) {
return this.get();
}
} catch (Exception e) {
// This should never happen;
logger.error("Got exception when trying to fetch the underlying result from AsyncRpcResult.", e);
}
// 创建AppResponse
return new AppResponse();
}
</code></pre>
<p>这些实现了Result接口的方法,可以发现其中都是调用了AppResponse的方法,AppResponse跟AsyncRpcResult一样也继承了AbstractResult,不过它是作为回调的数据结构。AppResponse我会在异步化过滤器链回调中讲述。</p>
<pre><code class="java">@Override
public Object recreate() throws Throwable {
// 强制类型转化
RpcInvocation rpcInvocation = (RpcInvocation) invocation;
// 如果返回的是future类型
if (InvokeMode.FUTURE == rpcInvocation.getInvokeMode()) {
// 创建AppResponse实例
AppResponse appResponse = new AppResponse();
// 创建future
CompletableFuture<Object> future = new CompletableFuture<>();
// appResponse设置future值,因为返回的就是CompletableFuture类型
appResponse.setValue(future);
// 当该AsyncRpcResult完成的时候,把结果放入future中,这样返回的就是CompletableFuture包裹的结果
this.whenComplete((result, t) -> {
if (t != null) {
if (t instanceof CompletionException) {
t = t.getCause();
}
future.completeExceptionally(t);
} else {
if (result.hasException()) {
future.completeExceptionally(result.getException());
} else {
future.complete(result.getValue());
}
}
});
// 重置
return appResponse.recreate();
} else if (this.isDone()) {
// 如果完成,则直接重置
return this.get().recreate();
}
// 如果返回类型不是CompletableFuture,则调用AppResponse的重置
return (new AppResponse()).recreate();
}</code></pre>
<p>该方法是重置,本来也是直接调用了AppResponse的方法,不过因为支持了以CompletableFuture为返回类型的服务方法调用,所以这里做了一些额外的逻辑,也就是把结果用CompletableFuture包裹,作为返回的结果放入AppResponse实例中。可以对标使用了CompletableFuture签名的服务。</p>
<pre><code class="java">@Override
public Result thenApplyWithContext(Function<Result, Result> fn) {
// 当该AsyncRpcResult完成后,结果作为参数先执行beforeContext,再执行fn,最后执行andThen
this.thenApply(fn.compose(beforeContext).andThen(afterContext));
// You may need to return a new Result instance representing the next async stage,
// like thenApply will return a new CompletableFuture.
return this;
}
/**
* tmp context to use when the thread switch to Dubbo thread.
* 临时的RpcContext,当用户线程切换为Dubbo线程时候使用
*/
/**
* 临时的RpcContext
*/
private RpcContext tmpContext;
private RpcContext tmpServerContext;
private Function<Result, Result> beforeContext = (appResponse) -> {
// 获得当前线程消费者端的RpcContext
tmpContext = RpcContext.getContext();
// 获得当前线程服务端的RpcContext
tmpServerContext = RpcContext.getServerContext();
// 重新设置消费者端的RpcContext
RpcContext.restoreContext(storedContext);
// 重新设置服务端的RpcContext
RpcContext.restoreServerContext(storedServerContext);
return appResponse;
};
private Function<Result, Result> afterContext = (appResponse) -> {
// 重新把临时的RpcContext设置回去
RpcContext.restoreContext(tmpContext);
RpcContext.restoreServerContext(tmpServerContext);
return appResponse;
};</code></pre>
<p>把这几部分代码放在一起时因为当用户线程切换为Dubbo线程时候需要用到临时的RpcContext来记录,如何使用该thenApplyWithContext方法,我也会在异步化过滤器链回调中讲到。</p>
<p>其他的方法比较好理解,我就不一一讲解。</p>
<h4>异步化过滤器链回调</h4>
<p>如果看过前两篇关于发送请求和处理请求的过程,应该就知道在整个调用链中有许多的过滤器,而Consumer和Provider分别都有各自的过滤器来做一些功能增强。过滤器有执行链,也有回调链,如果整一个链路都是同步的,那么过滤器一旦增多,链路增长,就会带来请求响应时间的增加,这当然是最不想看到的事情。那如果把过滤器的调用链异步化,那么我们就可以用一个future来代替结果抛给下游,让下游不再阻塞。这样就大大降低了响应时间,节省资源,提升RPC响应性能。而这里的future就是下面要介绍的AppResponse。那我先来介绍一下如何实现异步化过滤器链回调。我就拿消费端发送请求过程来举例子说明。</p>
<p>参考<a href="https://segmentfault.com/a/1190000019387309">《dubbo源码解析(四十六)消费端发送请求过程》</a>的(六)ProtocolFilterWrapper的内部类CallbackRegistrationInvoker的invoke,可以看到当所有的过滤器执行完后,会遍历每一个过滤器链,获得上面所说的内部接口Listener实现类,进行异步回调,因为请求已经在(十四)DubboInvoker的doInvoke中进行了发送,返回给下游一个AsyncRpcResult,而AsyncRpcResult内包裹的是AppResponse,可以看<a href="https://segmentfault.com/a/1190000019420778">《dubbo源码解析(四十七)服务端处理请求过程》</a>的(二十三)AbstractProxyInvoker的invoke,当代理类执行相关方法后,会创建一个AppResponse,把结果放入AppResponse中。所以AsyncRpcResult中包裹的是AppResponse,然后调用回调方法onResponse。并且会执行thenApplyWithContext把回调结果放入上下文中。而这个上下文如何避免相同的线程用于执行另一个RPC调用导致原来的RpcContext可能已经被更改的情况,我也在上面已经说明。</p>
<h5>新增AppResponse</h5>
<p>AppResponse继承了AbstractResult,同样也是CompletableFuture<Result>类型,但是AppResponse跟AsyncRpcResult职能不一样,AsyncRpcResult作为一个future,而AppResponse可以说是作为rpc调用结果的一个数据结构,它的实现很简单,就是封装了以下三个属性和对应的一些方法。</p>
<pre><code class="java">/**
* 调用结果
*/
private Object result;
/**
* rpc调用时的异常
*/
private Throwable exception;
/**
* 附加值
*/
private Map<String, String> attachments = new HashMap<String, String>();</code></pre>
<p>前面我也讲了,Provider处理请求完成后,会把结果放在AppResponse内,在整个链路调用过程中AsyncRpcResult内部必然会有一个AppResponse存在,而为上文提到的过滤器内置接口Listener的onResponse方法中的appResponse就是AppResponse类型的,它作为一个回调的数据类型。</p>
<h3>后记</h3>
<p>该文章讲解了dubbo 2.7.x版本对于异步化改造的介绍,上面只是罗列了所有改动的点,没有具体讲述在哪些新增功能上的应用,如果感兴趣,可以参考前几篇的调用过程文章,来看看新增的功能点如何运用上述的设计的,比如Provider异步,有一种实现方式就用到了上述的InvokeMode。接下来一篇我会讲述元数据的改造。</p>
Dubbo源码解析(四十七)服务端处理请求过程
https://segmentfault.com/a/1190000019420778
2019-06-08T22:54:25+08:00
2019-06-08T22:54:25+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
3
<h2>2.7大揭秘——服务端处理请求过程</h2>
<blockquote>目标:从源码的角度分析服务端接收到请求后的一系列操作,最终把客户端需要的值返回。</blockquote>
<h3>前言</h3>
<p>上一篇讲到了消费端发送请求的过程,该篇就要将服务端处理请求的过程。也就是当服务端收到请求数据包后的一系列处理以及如何返回最终结果。我们也知道消费端在发送请求的时候已经做了编码,所以我们也需要在服务端接收到数据包后,对协议头和协议体进行解码。不过本篇不讲解如何解码。有兴趣的可以翻翻我以前的文章,有讲到关于解码的逻辑。接下来就开始讲解服务端收到请求后的逻辑。</p>
<h3>处理过程</h3>
<p>假设远程通信的实现还是用netty4,解码器将数据包解析成 Request 对象后,NettyHandler 的 messageReceived 方法紧接着会收到这个对象,所以第一步就是NettyServerHandler的channelRead。</p>
<h4>(一)NettyServerHandler的channelRead</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017553202">《dubbo源码解析(十七)远程通信——Netty4》</a>的(三)NettyServerHandler</p>
<pre><code class="java">public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 看是否在缓存中命中,如果没有命中,则创建NettyChannel并且缓存。
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
try {
// 接受消息
handler.received(channel, msg);
} finally {
// 如果通道不活跃或者断掉,则从缓存中清除
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}</code></pre>
<p>NettyServerHandler是基于netty4实现的服务端通道处理实现类,而该方法就是用来接收请求,接下来就是执行AbstractPeer的received。</p>
<h4>(二)AbstractPeer的received</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>的(一)AbstractPeer</p>
<pre><code class="java">public void received(Channel ch, Object msg) throws RemotingException {
// 如果通道已经关闭,则直接返回
if (closed) {
return;
}
handler.received(ch, msg);
}</code></pre>
<p>该方法比较简单,之前也讲过AbstractPeer类就做了装饰模式中装饰角色,只是维护了通道的正在关闭和关闭完成两个状态。然后到了MultiMessageHandler的received</p>
<h4>(三)MultiMessageHandler的received</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>的(八)MultiMessageHandler</p>
<pre><code class="java">public void received(Channel channel, Object message) throws RemotingException {
// 如果消息是MultiMessage类型的,也就是多消息类型
if (message instanceof MultiMessage) {
// 强制转化为MultiMessage
MultiMessage list = (MultiMessage) message;
// 把各个消息进行发送
for (Object obj : list) {
handler.received(channel, obj);
}
} else {
// 直接发送
handler.received(channel, message);
}
}</code></pre>
<p>该方法也比较简单,就是对于多消息的处理。</p>
<h4>(四)HeartbeatHandler的received</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的(二十)HeartbeatHandler。其中就是对心跳事件做了处理。如果不是心跳请求,那么接下去走到AllChannelHandler的received。</p>
<h4>(五)AllChannelHandler的received</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>的(十一)AllChannelHandler。该类处理的是连接、断开连接、捕获异常以及接收到的所有消息都分发到线程池。所以这里的received方法就是把请求分发到线程池,让线程池去执行该请求。</p>
<p>还记得我在之前文章里面讲到到Dispatcher接口吗,它是一个线程派发器。分别有五个实现:</p>
<table>
<thead><tr>
<th>Dispatcher实现类</th>
<th>对应的handler</th>
<th>用途</th>
</tr></thead>
<tbody>
<tr>
<td>AllDispatcher</td>
<td>AllChannelHandler</td>
<td>所有消息都派发到线程池,包括请求,响应,连接事件,断开事件等</td>
</tr>
<tr>
<td>ConnectionOrderedDispatcher</td>
<td>ConnectionOrderedChannelHandler</td>
<td>在 IO 线程上,将连接和断开事件放入队列,有序逐个执行,其它消息派发到线程池</td>
</tr>
<tr>
<td>DirectDispatcher</td>
<td>无</td>
<td>所有消息都不派发到线程池,全部在 IO 线程上直接执行</td>
</tr>
<tr>
<td>ExecutionDispatcher</td>
<td>ExecutionChannelHandler</td>
<td>只有请求消息派发到线程池,不含响应。其它消息均在 IO 线程上执行</td>
</tr>
<tr>
<td>MessageOnlyDispatcher</td>
<td>MessageOnlyChannelHandler</td>
<td>只有请求和响应消息派发到线程池,其它消息均在 IO 线程上执行</td>
</tr>
</tbody>
</table>
<p>这些Dispatcher的实现类以及对应的Handler都可以在<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>中查看相关实现。dubbo默认all为派发策略。所以我在这里讲了AllChannelHandler的received。把消息送到线程池后,可以看到首先会创建一个ChannelEventRunnable实体。那么接下来就是线程接收并且执行任务了。</p>
<h4>(六)ChannelEventRunnable的run</h4>
<p>ChannelEventRunnable实现了Runnable接口,主要是用来接收消息事件,并且根据事件的种类来分别执行不同的操作。来看看它的run方法:</p>
<pre><code class="java">public void run() {
// 如果是接收的消息
if (state == ChannelState.RECEIVED) {
try {
// 直接调用下一个received
handler.received(channel, message);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is " + message, e);
}
} else {
switch (state) {
//如果是连接事件请求
case CONNECTED:
try {
// 执行连接
handler.connected(channel);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
// 如果是断开连接事件请求
case DISCONNECTED:
try {
// 执行断开连接
handler.disconnected(channel);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
// 如果是发送消息
case SENT:
try {
// 执行发送消息
handler.sent(channel, message);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is " + message, e);
}
break;
// 如果是异常
case CAUGHT:
try {
// 执行异常捕获
handler.caught(channel, exception);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is: " + message + ", exception is " + exception, e);
}
break;
default:
logger.warn("unknown state: " + state + ", message is " + message);
}
}
}</code></pre>
<p>可以看到把消息分为了几种类别,因为请求和响应消息出现频率明显比其他类型消息高,也就是RECEIVED,所以单独先做处理,根据不同的类型的消息,会被执行不同的逻辑,我们这里主要看state为RECEIVED的,那么如果是RECEIVED,则会执行下一个received方法。</p>
<h4>(七)DecodeHandler的received</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>的(七)DecodeHandler。可以看到received方法中根据消息的类型进行不同的解码。而DecodeHandler 存在的意义就是保证请求或响应对象可在线程池中被解码,解码完成后,就会分发到HeaderExchangeHandler的received。</p>
<h4>(八)HeaderExchangeHandler的received</h4>
<pre><code class="java">public void received(Channel channel, Object message) throws RemotingException {
// 设置接收到消息的时间戳
channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
// 获得通道
final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
try {
// 如果消息是Request类型
if (message instanceof Request) {
// handle request.
// 强制转化为Request
Request request = (Request) message;
// 如果该请求是事件心跳事件或者只读事件
if (request.isEvent()) {
// 执行事件
handlerEvent(channel, request);
} else {
// 如果是正常的调用请求,且需要响应
if (request.isTwoWay()) {
// 处理请求
handleRequest(exchangeChannel, request);
} else {
// 如果不需要响应,则继续下一步
handler.received(exchangeChannel, request.getData());
}
}
} else if (message instanceof Response) {
// 处理响应
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
// 如果是telnet相关的请求
if (isClientSide(channel)) {
// 如果是客户端侧,则直接抛出异常,因为客户端侧不支持telnet
Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
logger.error(e.getMessage(), e);
} else {
// 如果是服务端侧,则执行telnet命令
String echo = handler.telnet(channel, (String) message);
if (echo != null && echo.length() > 0) {
channel.send(echo);
}
}
} else {
// 如果都不是,则继续下一步
handler.received(exchangeChannel, message);
}
} finally {
// 移除关闭或者不活跃的通道
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}</code></pre>
<p>该方法中就对消息进行了细分,事件请求、正常的调用请求、响应、telnet命令请求等,并且针对不同的消息类型做了不同的逻辑调用。我们这里主要看正常的调用请求。见下一步。</p>
<h4>(九)HeaderExchangeHandler的handleRequest</h4>
<pre><code class="java">void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {
// 创建一个Response实例
Response res = new Response(req.getId(), req.getVersion());
// 如果请求被破坏了
if (req.isBroken()) {
// 获得请求的数据包
Object data = req.getData();
String msg;
// 如果数据为空
if (data == null) {
//消息设置为空
msg = null;
// 如果在这之前已经出现异常,也就是数据为Throwable类型
} else if (data instanceof Throwable) {
// 响应消息把异常信息返回
msg = StringUtils.toString((Throwable) data);
} else {
// 返回请求数据
msg = data.toString();
}
res.setErrorMessage("Fail to decode request due to: " + msg);
// 设置错误请求的状态码
res.setStatus(Response.BAD_REQUEST);
// 发送该消息
channel.send(res);
return;
}
// find handler by message class.
// 获得请求数据 也就是 RpcInvocation 对象
Object msg = req.getData();
try {
// 继续向下调用 返回一个future
CompletionStage<Object> future = handler.reply(channel, msg);
future.whenComplete((appResult, t) -> {
try {
if (t == null) {
//设置调用结果状态为成功
res.setStatus(Response.OK);
// 把结果放入响应
res.setResult(appResult);
} else {
// 如果服务调用有异常,则设置结果状态码为服务错误
res.setStatus(Response.SERVICE_ERROR);
// 把报错信息放到响应中
res.setErrorMessage(StringUtils.toString(t));
}
// 发送该响应
channel.send(res);
} catch (RemotingException e) {
logger.warn("Send result to consumer failed, channel is " + channel + ", msg is " + e);
} finally {
// HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
});
} catch (Throwable e) {
// 如果在执行中抛出异常,则也算服务异常
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(e));
channel.send(res);
}
}</code></pre>
<p>该方法是处理正常的调用请求,主要做了正常、异常调用的情况处理,并且加入了状态码,然后发送执行后的结果给客户端。接下来看下一步。</p>
<h4>(十)DubboProtocol的requestHandler实例的reply</h4>
<p>这里我默认是使用dubbo协议,所以执行的是DubboProtocol的requestHandler的reply方法。可以参考<a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>的(三)DubboProtocol</p>
<pre><code class="java">public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {
// 如果请求消息不属于会话域,则抛出异常
if (!(message instanceof Invocation)) {
throw new RemotingException(channel, "Unsupported request: "
+ (message == null ? null : (message.getClass().getName() + ": " + message))
+ ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}
//强制类型转化
Invocation inv = (Invocation) message;
// 获得暴露的服务invoker
Invoker<?> invoker = getInvoker(channel, inv);
// need to consider backward-compatibility if it's a callback
// 如果是回调服务
if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
// 获得 方法定义
String methodsStr = invoker.getUrl().getParameters().get("methods");
boolean hasMethod = false;
// 如果只有一个方法定义
if (methodsStr == null || !methodsStr.contains(",")) {
// 设置会话域中是否有一致的方法定义标志
hasMethod = inv.getMethodName().equals(methodsStr);
} else {
// 分割不同的方法
String[] methods = methodsStr.split(",");
// 如果方法不止一个,则分割后遍历查询,找到了则设置为true
for (String method : methods) {
if (inv.getMethodName().equals(method)) {
hasMethod = true;
break;
}
}
}
// 如果没有该方法,则打印告警日志
if (!hasMethod) {
logger.warn(new IllegalStateException("The methodName " + inv.getMethodName()
+ " not found in callback service interface ,invoke will be ignored."
+ " please update the api interface. url is:"
+ invoker.getUrl()) + " ,invocation is :" + inv);
return null;
}
}
// 设置远程地址
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
// 调用下一个调用链
Result result = invoker.invoke(inv);
// 返回CompletableFuture<Result>
return result.completionFuture().thenApply(Function.identity());
}</code></pre>
<p>上述代码有些变化,是最新的代码,加入了CompletableFuture,这个我会在后续的异步化改造中讲到。这里主要关注的是又开始跟客户端发请求一样执行invoke调用链了。</p>
<h4>(十一)ProtocolFilterWrapper的CallbackRegistrationInvoker的invoke</h4>
<p>可以直接参考<a href="https://segmentfault.com/a/1190000019387309">《dubbo源码解析(四十六)消费端发送请求过程》</a>的(六)ProtocolFilterWrapper的内部类CallbackRegistrationInvoker的invoke。</p>
<h4>(十二)ProtocolFilterWrapper的buildInvokerChain方法中的invoker实例的invoke方法。</h4>
<p>可以直接参考<a href="https://segmentfault.com/a/1190000019387309">《dubbo源码解析(四十六)消费端发送请求过程》</a>的(七)ProtocolFilterWrapper的buildInvokerChain方法中的invoker实例的invoke方法。</p>
<h4>(十三)EchoFilter的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017815616">《dubbo源码解析(二十)远程调用——Filter》</a>的(八)EchoFilter</p>
<pre><code class="java">public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
// 如果调用的方法是回声测试的方法 则直接返回结果,否则 调用下一个调用链
if (inv.getMethodName().equals($ECHO) && inv.getArguments() != null && inv.getArguments().length == 1) {
// 创建一个默认的AsyncRpcResult返回
return AsyncRpcResult.newDefaultAsyncResult(inv.getArguments()[0], inv);
}
return invoker.invoke(inv);
}</code></pre>
<p>该过滤器就是对回声测试的调用进行拦截,上述源码跟连接中源码唯一区别就是改为了AsyncRpcResult。</p>
<h4>(十四)ClassLoaderFilter的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017815616">《dubbo源码解析(二十)远程调用——Filter》</a>的(三)ClassLoaderFilter,用来做类加载器的切换。</p>
<h4>(十五)GenericFilter的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017815616">《dubbo源码解析(二十)远程调用——Filter》</a>的(十一)GenericFilter。</p>
<pre><code class="java">public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
// 如果是泛化调用
if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !GenericService.class.isAssignableFrom(invoker.getInterface())) {
// 获得请求名字
String name = ((String) inv.getArguments()[0]).trim();
// 获得请求参数类型
String[] types = (String[]) inv.getArguments()[1];
// 获得请求参数
Object[] args = (Object[]) inv.getArguments()[2];
try {
// 获得方法
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
// 获得该方法的参数类型
Class<?>[] params = method.getParameterTypes();
if (args == null) {
args = new Object[params.length];
}
// 获得附加值
String generic = inv.getAttachment(GENERIC_KEY);
if (StringUtils.isBlank(generic)) {
generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
}
// 如果附加值为空,在用上下文携带的附加值
if (StringUtils.isEmpty(generic)
|| ProtocolUtils.isDefaultGenericSerialization(generic)) {
// 直接进行类型转化
args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
for (int i = 0; i < args.length; i++) {
if (byte[].class == args[i].getClass()) {
try (UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i])) {
// 使用nativejava方式反序列化
args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension(GENERIC_SERIALIZATION_NATIVE_JAVA)
.deserialize(null, is).readObject();
} catch (Exception e) {
throw new RpcException("Deserialize argument [" + (i + 1) + "] failed.", e);
}
} else {
throw new RpcException(
"Generic serialization [" +
GENERIC_SERIALIZATION_NATIVE_JAVA +
"] only support message type " +
byte[].class +
" and your message type is " +
args[i].getClass());
}
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof JavaBeanDescriptor) {
// 用JavaBean方式反序列化
args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);
} else {
throw new RpcException(
"Generic serialization [" +
GENERIC_SERIALIZATION_BEAN +
"] only support message type " +
JavaBeanDescriptor.class.getName() +
" and your message type is " +
args[i].getClass().getName());
}
}
} else if (ProtocolUtils.isProtobufGenericSerialization(generic)) {
// as proto3 only accept one protobuf parameter
if (args.length == 1 && args[0] instanceof String) {
try (UnsafeByteArrayInputStream is =
new UnsafeByteArrayInputStream(((String) args[0]).getBytes())) {
// 用protobuf-json进行反序列化
args[0] = ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension("" + GENERIC_SERIALIZATION_PROTOBUF)
.deserialize(null, is).readObject(method.getParameterTypes()[0]);
} catch (Exception e) {
throw new RpcException("Deserialize argument failed.", e);
}
} else {
throw new RpcException(
"Generic serialization [" +
GENERIC_SERIALIZATION_PROTOBUF +
"] only support one" + String.class.getName() +
" argument and your message size is " +
args.length + " and type is" +
args[0].getClass().getName());
}
}
return invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
} catch (NoSuchMethodException e) {
throw new RpcException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new RpcException(e.getMessage(), e);
}
}
return invoker.invoke(inv);
}
static class GenericListener implements Listener {
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation inv) {
// 如果是泛化调用
if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !GenericService.class.isAssignableFrom(invoker.getInterface())) {
// 获得序列化方式
String generic = inv.getAttachment(GENERIC_KEY);
// 如果为空,默认获取会话域中的配置
if (StringUtils.isBlank(generic)) {
generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
}
// 如果回调有异常,直接设置异常
if (appResponse.hasException() && !(appResponse.getException() instanceof GenericException)) {
appResponse.setException(new GenericException(appResponse.getException()));
}
// 如果是native java形式序列化
if (ProtocolUtils.isJavaGenericSerialization(generic)) {
try {
UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
// 使用native java形式序列化
ExtensionLoader.getExtensionLoader(Serialization.class).getExtension(GENERIC_SERIALIZATION_NATIVE_JAVA).serialize(null, os).writeObject(appResponse.getValue());
// 加入结果
appResponse.setValue(os.toByteArray());
} catch (IOException e) {
throw new RpcException(
"Generic serialization [" +
GENERIC_SERIALIZATION_NATIVE_JAVA +
"] serialize result failed.", e);
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
// 用JavaBean方式序列化
appResponse.setValue(JavaBeanSerializeUtil.serialize(appResponse.getValue(), JavaBeanAccessor.METHOD));
} else if (ProtocolUtils.isProtobufGenericSerialization(generic)) {
try {
UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
// 用protobuf-json进行序列化
ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension(GENERIC_SERIALIZATION_PROTOBUF)
.serialize(null, os).writeObject(appResponse.getValue());
appResponse.setValue(os.toString());
} catch (IOException e) {
throw new RpcException("Generic serialization [" +
GENERIC_SERIALIZATION_PROTOBUF +
"] serialize result failed.", e);
}
} else {
// 直接进行类型转化并且设置值
appResponse.setValue(PojoUtils.generalize(appResponse.getValue()));
}
}
}
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
}
}</code></pre>
<p>跟连接内的代码对比,第一个就是对过滤器设计发生了变化,这个我在异步化改造里面会讲到,第二个是新增了protobuf-json的泛化序列化方式。</p>
<h4>(十六)ContextFilter的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017815616">《dubbo源码解析(二十)远程调用——Filter》</a>的(六)ContextFilter,最新代码几乎差不多,除了因为对Filter的设计做了修改以外,还有新增了tag路由的相关逻辑,tag相关部分我会在后续文章中讲解,该类主要是做了初始化rpc上下文。</p>
<h4>(十七)TraceFilter的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>的(十三)TraceFilter,该过滤器是增强的功能是通道的跟踪,会在通道内把最大的调用次数和现在的调用数量放进去。方便使用telnet来跟踪服务的调用次数等。</p>
<h4>(十八)TimeoutFilter的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017815616">《dubbo源码解析(二十)远程调用——Filter》</a>的(十三)TimeoutFilter,该过滤器是当服务调用超时的时候,记录告警日志。</p>
<h4>(十九)MonitorFilter的invoke</h4>
<pre><code class="java">public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 如果开启监控
if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
// 设置开始监控时间
invocation.setAttachment(MONITOR_FILTER_START_TIME, String.valueOf(System.currentTimeMillis()));
// 对同时在线数量加1
getConcurrent(invoker, invocation).incrementAndGet(); // count up
}
return invoker.invoke(invocation); // proceed invocation chain
}</code></pre>
<pre><code class="java">class MonitorListener implements Listener {
@Override
public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
// 如果开启监控
if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
// 收集监控对数据,并且更新监控数据
collect(invoker, invocation, result, RpcContext.getContext().getRemoteHost(), Long.valueOf(invocation.getAttachment(MONITOR_FILTER_START_TIME)), false);
// 同时在线监控数减1
getConcurrent(invoker, invocation).decrementAndGet(); // count down
}
}
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
// 收集监控对数据,并且更新监控数据
collect(invoker, invocation, null, RpcContext.getContext().getRemoteHost(), Long.valueOf(invocation.getAttachment(MONITOR_FILTER_START_TIME)), true);
// 同时在线监控数减1
getConcurrent(invoker, invocation).decrementAndGet(); // count down
}
}
private void collect(Invoker<?> invoker, Invocation invocation, Result result, String remoteHost, long start, boolean error) {
try {
// 获得监控的url
URL monitorUrl = invoker.getUrl().getUrlParameter(MONITOR_KEY);
// 通过该url获得Monitor实例
Monitor monitor = monitorFactory.getMonitor(monitorUrl);
if (monitor == null) {
return;
}
// 创建一个统计的url
URL statisticsURL = createStatisticsUrl(invoker, invocation, result, remoteHost, start, error);
// 把收集的信息更新并且发送信息
monitor.collect(statisticsURL);
} catch (Throwable t) {
logger.warn("Failed to monitor count service " + invoker.getUrl() + ", cause: " + t.getMessage(), t);
}
}
private URL createStatisticsUrl(Invoker<?> invoker, Invocation invocation, Result result, String remoteHost, long start, boolean error) {
// ---- service statistics ----
// 调用服务消耗的时间
long elapsed = System.currentTimeMillis() - start; // invocation cost
// 获得同时监控的数量
int concurrent = getConcurrent(invoker, invocation).get(); // current concurrent count
String application = invoker.getUrl().getParameter(APPLICATION_KEY);
// 获得服务名
String service = invoker.getInterface().getName(); // service name
// 获得调用的方法名
String method = RpcUtils.getMethodName(invocation); // method name
// 获得组
String group = invoker.getUrl().getParameter(GROUP_KEY);
// 获得版本号
String version = invoker.getUrl().getParameter(VERSION_KEY);
int localPort;
String remoteKey, remoteValue;
// 如果是消费者端的监控
if (CONSUMER_SIDE.equals(invoker.getUrl().getParameter(SIDE_KEY))) {
// ---- for service consumer ----
// 本地端口为0
localPort = 0;
// key为provider
remoteKey = MonitorService.PROVIDER;
// value为服务ip
remoteValue = invoker.getUrl().getAddress();
} else {
// ---- for service provider ----
// 端口为服务端口
localPort = invoker.getUrl().getPort();
// key为consumer
remoteKey = MonitorService.CONSUMER;
// value为远程地址
remoteValue = remoteHost;
}
String input = "", output = "";
if (invocation.getAttachment(INPUT_KEY) != null) {
input = invocation.getAttachment(INPUT_KEY);
}
if (result != null && result.getAttachment(OUTPUT_KEY) != null) {
output = result.getAttachment(OUTPUT_KEY);
}
// 返回一个url
return new URL(COUNT_PROTOCOL, NetUtils.getLocalHost(), localPort, service + PATH_SEPARATOR + method, MonitorService.APPLICATION, application, MonitorService.INTERFACE, service, MonitorService.METHOD, method, remoteKey, remoteValue, error ? MonitorService.FAILURE : MonitorService.SUCCESS, "1", MonitorService.ELAPSED, String.valueOf(elapsed), MonitorService.CONCURRENT, String.valueOf(concurrent), INPUT_KEY, input, OUTPUT_KEY, output, GROUP_KEY, group, VERSION_KEY, version);
}
}</code></pre>
<p>在invoke里面只是做了记录开始监控的时间以及对同时监控的数量加1操作,当结果回调时,会对结果数据做搜集计算,最后通过监控服务记录和发送最新信息。</p>
<h4>(二十)ExceptionFilter的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017815616">《dubbo源码解析(二十)远程调用——Filter》</a>的(九)ExceptionFilter,该过滤器主要是对异常的处理。</p>
<h4>(二十一)InvokerWrapper的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017854954">《dubbo源码解析(二十二)远程调用——Protocol》</a>的(五)InvokerWrapper。该类用了装饰模式,不过并没有实现实际的功能增强。</p>
<h4>(二十二)DelegateProviderMetaDataInvoker的invoke</h4>
<pre><code class="java">public Result invoke(Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}</code></pre>
<p>该类也是用了装饰模式,不过该类是invoker和配置中心的适配类,其中也没有进行实际的功能增强。</p>
<h4>(二十三)AbstractProxyInvoker的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017892690">《dubbo源码解析(二十三)远程调用——Proxy》</a>的(二)AbstractProxyInvoker。不过代码已经有更新了,下面贴出最新的代码。</p>
<pre><code class="java">public Result invoke(Invocation invocation) throws RpcException {
try {
// 执行下一步
Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
// 把返回结果用CompletableFuture包裹
CompletableFuture<Object> future = wrapWithFuture(value, invocation);
// 创建AsyncRpcResult实例
AsyncRpcResult asyncRpcResult = new AsyncRpcResult(invocation);
future.whenComplete((obj, t) -> {
AppResponse result = new AppResponse();
// 如果抛出异常
if (t != null) {
// 属于CompletionException异常
if (t instanceof CompletionException) {
// 设置异常信息
result.setException(t.getCause());
} else {
// 直接设置异常
result.setException(t);
}
} else {
// 如果没有异常,则把结果放入异步结果内
result.setValue(obj);
}
// 完成
asyncRpcResult.complete(result);
});
return asyncRpcResult;
} catch (InvocationTargetException e) {
if (RpcContext.getContext().isAsyncStarted() && !RpcContext.getContext().stopAsync()) {
logger.error("Provider async started, but got an exception from the original method, cannot write the exception back to consumer because an async result may have returned the new thread.", e);
}
return AsyncRpcResult.newDefaultAsyncResult(null, e.getTargetException(), invocation);
} catch (Throwable e) {
throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
}
}</code></pre>
<p>这里主要是因为异步化改造而出现的代码变化,我会在异步化改造中讲到这部分。现在主要来看下一步。</p>
<h4>(二十四)JavassistProxyFactory的getInvoker方法中匿名类的doInvoke</h4>
<p>这里默认代理实现方式是Javassist。可以参考<a href="https://segmentfault.com/a/1190000017892690">《dubbo源码解析(二十三)远程调用——Proxy》</a>的(六)JavassistProxyFactory。其中Wrapper 是一个抽象类,其中 invokeMethod 是一个抽象方法。dubbo 会在运行时通过 Javassist 框架为 Wrapper 生成实现类,并实现 invokeMethod 方法,该方法最终会根据调用信息调用具体的服务。以 DemoServiceImpl 为例,Javassist 为其生成的代理类如下。</p>
<pre><code class="java">/** Wrapper0 是在运行时生成的,大家可使用 Arthas 进行反编译 */
public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
public static String[] pns;
public static Map pts;
public static String[] mns;
public static String[] dmns;
public static Class[] mts0;
// 省略其他方法
public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {
DemoService demoService;
try {
// 类型转换
demoService = (DemoService)object;
}
catch (Throwable throwable) {
throw new IllegalArgumentException(throwable);
}
try {
// 根据方法名调用指定的方法
if ("sayHello".equals(string) && arrclass.length == 1) {
return demoService.sayHello((String)arrobject[0]);
}
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.alibaba.dubbo.demo.DemoService.").toString());
}
}
</code></pre>
<p>然后就是直接调用的是对应的方法了。到此,方法执行完成了。</p>
<h3>结果返回</h3>
<p>可以看到我上述讲到的(八)HeaderExchangeHandler的received和(九)HeaderExchangeHandler的handleRequest有好几处channel.send方法的调用,也就是当结果返回的返回的时候,会主动发送执行结果给客户端。当然发送的时候还是会对结果Response 对象进行编码,编码逻辑我就先不在这里阐述。</p>
<p>当客户端接收到这个返回的消息时候,进行解码后,识别为Response 对象,将该对象派发到线程池中,该过程跟服务端接收到调用请求到逻辑是一样的,可以参考上述的解析,区别在于到(八)HeaderExchangeHandler的received方法的时候,执行的是handleResponse方法。</p>
<h4>(九)HeaderExchangeHandler的handleResponse</h4>
<pre><code class="java">static void handleResponse(Channel channel, Response response) throws RemotingException {
// 如果响应不为空,并且不是心跳事件的响应,则调用received
if (response != null && !response.isHeartbeat()) {
DefaultFuture.received(channel, response);
}
}</code></pre>
<h4>(十)DefaultFuture的received</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的(七)DefaultFuture,不过该类的继承的是CompletableFuture,因为对异步化的改造,该类已经做了一些变化。</p>
<pre><code class="java">public static void received(Channel channel, Response response) {
received(channel, response, false);
}
public static void received(Channel channel, Response response, boolean timeout) {
try {
// future集合中移除该请求的future,(响应id和请求id一一对应的)
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
//获得超时
Timeout t = future.timeoutCheckTask;
// 如果没有超时,则取消timeoutCheckTask
if (!timeout) {
// decrease Time
t.cancel();
}
// 接收响应结果
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at "
+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
+ ", response " + response
+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()));
}
} finally {
// 通道集合移除该请求对应的通道,代表着这一次请求结束
CHANNELS.remove(response.getId());
}
}</code></pre>
<p>该方法中主要是对超时的处理,还有对应的请求和响应的匹配,也就是返回相应id的future。</p>
<h4>(十一)DefaultFuture的doReceived</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的(七)DefaultFuture,不过因为运用了CompletableFuture,所以该方法完全重写了。这部分的改造我也会在异步化改造中讲述到。</p>
<pre><code class="java">private void doReceived(Response res) {
// 如果结果为空,则抛出异常
if (res == null) {
throw new IllegalStateException("response cannot be null");
}
// 如果结果的状态码为ok
if (res.getStatus() == Response.OK) {
// 则future调用完成
this.complete(res.getResult());
} else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
// 如果超时,则返回一个超时异常
this.completeExceptionally(new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()));
} else {
// 否则返回一个RemotingException
this.completeExceptionally(new RemotingException(channel, res.getErrorMessage()));
}
}</code></pre>
<p>随后用户线程即可从 DefaultFuture 实例中获取到相应结果。</p>
<h3>后记</h3>
<p>该文章讲解了dubbo调服务端接收到请求后一系列处理的所有步骤以及服务端怎么把结果发送给客户端,是目前最新代码的解析。因为里面涉及到很多流程,所以可以自己debug一步一步看一看整个过程。可以看到本文涉及到异步化改造已经很多了,这也是我在讲解异步化改造前想先讲解这些过程的原因。只有弄清楚这些过程,才能更加理解对于异步化改造的意义。下一篇文将讲解异步化改造。</p>
Dubbo源码解析(四十六)消费端发送请求过程
https://segmentfault.com/a/1190000019387309
2019-06-04T17:53:55+08:00
2019-06-04T17:53:55+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
5
<h2>2.7大揭秘——消费端发送请求过程</h2>
<blockquote>目标:从源码的角度分析一个服务方法调用经历怎么样的磨难以后到达服务端。</blockquote>
<h3>前言</h3>
<p>前一篇文章讲到的是引用服务的过程,引用服务无非就是创建出一个代理。供消费者调用服务的相关方法。本节将从调用方法开始讲解内部的整个调用链。我们就拿dubbo内部的例子讲。</p>
<pre><code class="java">ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
DemoService demoService = context.getBean("demoService", DemoService.class);
String hello = demoService.sayHello("world");
System.out.println("result: " + hello);</code></pre>
<p>这是dubbo-demo-xml-consumer内的实例代码。接下来我们就开始来看调用demoService.sayHello方法的时候,dubbo执行了哪些操作。</p>
<h3>执行过程</h3>
<h4>(一)InvokerInvocationHandler的invoke</h4>
<pre><code class="java">public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获得方法名称
String methodName = method.getName();
// 获得方法参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
// 如果该方法所在的类是Object类型,则直接调用invoke。
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
// 如果这个方法是toString,则直接调用invoker.toString()
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
// 如果这个方法是hashCode直接调用invoker.hashCode()
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
// 如果这个方法是equals,直接调用invoker.equals(args[0])
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
// 调用invoke
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}</code></pre>
<p>可以看到上面的源码,首先对Object的方法进行了处理,如果调用的方法不是这些方法,则先会 创建RpcInvocation,然后再调用invoke。</p>
<h5>RpcInvocation的构造方法</h5>
<pre><code class="java">public RpcInvocation(Method method, Object[] arguments) {
this(method.getName(), method.getParameterTypes(), arguments, null, null);
}
</code></pre>
<pre><code class="java">public RpcInvocation(String methodName, Class<?>[] parameterTypes, Object[] arguments, Map<String, String> attachments, Invoker<?> invoker) {
// 设置方法名
this.methodName = methodName;
// 设置参数类型
this.parameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
// 设置参数
this.arguments = arguments == null ? new Object[0] : arguments;
// 设置附加值
this.attachments = attachments == null ? new HashMap<String, String>() : attachments;
// 设置invoker实体
this.invoker = invoker;
}</code></pre>
<p>创建完RpcInvocation后,就是调用invoke。先进入的是ListenerInvokerWrapper的invoke。</p>
<h4>(二)MockClusterInvoker的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000018154297">《dubbo源码解析(四十一)集群——Mock》</a>的(二)MockClusterInvoker,降级后的返回策略的实现,根据配置的不同来决定不用降级还是强制服务降级还是失败后再服务降级。</p>
<h4>(三)AbstractClusterInvoker的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000018099552">《dubbo源码解析(三十五)集群——cluster》</a>的(一)AbstractClusterInvoker,该类是一个抽象类,其中封装了一些公用的方法,AbstractClusterInvoker的invoke也只是做了一些公用操作。主要的逻辑在doInvoke中。</p>
<h4>(四)FailoverClusterInvoker的doInvoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000018099552">《dubbo源码解析(三十五)集群——cluster》</a>的(十二)FailoverClusterInvoker,该类实现了失败重试的容错策略。</p>
<h4>(五)InvokerWrapper的invoke</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017854954">《dubbo源码解析(二十二)远程调用——Protocol》</a>的(五)InvokerWrapper。该类用了装饰模式,不过并没有实现实际的功能增强。</p>
<h4>(六)ProtocolFilterWrapper的内部类CallbackRegistrationInvoker的invoke</h4>
<pre><code class="java">public Result invoke(Invocation invocation) throws RpcException {
// 调用拦截器链的invoke
Result asyncResult = filterInvoker.invoke(invocation);
// 把异步返回的结果加入到上下文中
asyncResult.thenApplyWithContext(r -> {
// 循环各个过滤器
for (int i = filters.size() - 1; i >= 0; i--) {
Filter filter = filters.get(i);
// onResponse callback
// 如果该过滤器是ListenableFilter类型的
if (filter instanceof ListenableFilter) {
// 强制类型转化
Filter.Listener listener = ((ListenableFilter) filter).listener();
if (listener != null) {
// 如果内部类listener不为空,则调用回调方法onResponse
listener.onResponse(r, filterInvoker, invocation);
}
} else {
// 否则,直接调用filter的onResponse,做兼容。
filter.onResponse(r, filterInvoker, invocation);
}
}
// 返回异步结果
return r;
});
// 返回异步结果
return asyncResult;
}</code></pre>
<p>这里看到先是调用拦截器链的invoke方法。下面的逻辑是把异步返回的结果放到上下文中,具体的ListenableFilter以及内部类的设计,还有thenApplyWithContext等方法我会在异步的实现中讲到。</p>
<h4>(七)ProtocolFilterWrapper的buildInvokerChain方法中的invoker实例的invoke方法。</h4>
<pre><code class="java">public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
try {
// 依次调用各个过滤器,获得最终的返回结果
asyncResult = filter.invoke(next, invocation);
} catch (Exception e) {
// onError callback
// 捕获异常,如果该过滤器是ListenableFilter类型的
if (filter instanceof ListenableFilter) {
// 获得内部类Listener
Filter.Listener listener = ((ListenableFilter) filter).listener();
if (listener != null) {
//调用onError,回调错误信息
listener.onError(e, invoker, invocation);
}
}
// 抛出异常
throw e;
}
// 返回结果
return asyncResult;
}</code></pre>
<p>该方法中是对异常的捕获,调用内部类Listener的onError来回调错误信息。接下来看它经过了哪些拦截器。</p>
<h4>(八)ConsumerContextFilter的invoke</h4>
<pre><code class="java">public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获得上下文,设置invoker,会话域,本地地址和原创地址
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
.setLocalAddress(NetUtils.getLocalHost(), 0)
.setRemoteAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
// 如果会话域是RpcInvocation,则设置invoker
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(invoker);
}
try {
// 移除服务端的上下文
RpcContext.removeServerContext();
// 调用下一个过滤器
return invoker.invoke(invocation);
} finally {
// 清空上下文
RpcContext.removeContext();
}
}</code></pre>
<pre><code class="java">static class ConsumerContextListener implements Listener {
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
// 把结果中的附加值放入到上下文中
RpcContext.getServerContext().setAttachments(appResponse.getAttachments());
}
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
// 不做任何处理
}
}</code></pre>
<p>可以参考<a href="https://segmentfault.com/a/1190000017815616">《dubbo源码解析(二十)远程调用——Filter》</a>,不过上面的源码是最新的,而链接内的源码是2.6.x的,虽然做了一些变化,比如内部类的的设计,后续的过滤器也有同样的实现,但是ConsumerContextFilter作用没有变化,它依旧是在当前的RpcContext中记录本地调用的一次状态信息。该过滤器执行完成后,会回到ProtocolFilterWrapper的invoke中的</p>
<pre><code class="java">Result result = filter.invoke(next, invocation);
</code></pre>
<p>然后继续调用下一个过滤器FutureFilter。</p>
<h4>(九)FutureFilter的invoke</h4>
<pre><code class="java">public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
// 该方法是真正的调用方法的执行
fireInvokeCallback(invoker, invocation);
// need to configure if there's return value before the invocation in order to help invoker to judge if it's
// necessary to return future.
return invoker.invoke(invocation);
}
</code></pre>
<pre><code class="java">class FutureListener implements Listener {
@Override
public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
if (result.hasException()) {
// 处理异常结果
fireThrowCallback(invoker, invocation, result.getException());
} else {
// 处理正常结果
fireReturnCallback(invoker, invocation, result.getValue());
}
}
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
}
}
</code></pre>
<p>可以参考<a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>中的(十四)FutureFilter,其中会有部分结构不一样,跟ConsumerContextFilter一样,因为后续版本对Filter接口进行了新的设计,增加了onResponse方法,把返回的执行逻辑放到onResponse中去了。其他逻辑没有很大变化。等该过滤器执行完成后,还是回到ProtocolFilterWrapper的invoke中的,继续调用下一个过滤器MonitorFilter。</p>
<h4>(十)MonitorFilter的invoke</h4>
<pre><code class="java">public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 如果开启监控
if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
// 设置监控开始时间
invocation.setAttachment(MONITOR_FILTER_START_TIME, String.valueOf(System.currentTimeMillis()));
// 获得当前的调用数,并且增加
getConcurrent(invoker, invocation).incrementAndGet(); // count up
}
return invoker.invoke(invocation); // proceed invocation chain
}
</code></pre>
<pre><code class="java">class MonitorListener implements Listener {
@Override
public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
// 如果开启监控
if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
// 执行监控,搜集数据
collect(invoker, invocation, result, RpcContext.getContext().getRemoteHost(), Long.valueOf(invocation.getAttachment(MONITOR_FILTER_START_TIME)), false);
// 减少当前调用数
getConcurrent(invoker, invocation).decrementAndGet(); // count down
}
}
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
// 如果开启监控
if (invoker.getUrl().hasParameter(MONITOR_KEY)) {
// 执行监控,搜集数据
collect(invoker, invocation, null, RpcContext.getContext().getRemoteHost(), Long.valueOf(invocation.getAttachment(MONITOR_FILTER_START_TIME)), true);
// 减少当前调用数
getConcurrent(invoker, invocation).decrementAndGet(); // count down
}
}
</code></pre>
<p>可以看到该过滤器实际用来做监控,监控服务的调用数量等。其中监控的逻辑不是本文重点,所以不细讲。接下来调用的是ListenerInvokerWrapper的invoke。</p>
<h4>(十一)ListenerInvokerWrapper的invoke</h4>
<pre><code class="java">public Result invoke(Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
</code></pre>
<p>可以参考<a href="https://segmentfault.com/a/1190000017827073">《dubbo源码解析(二十一)远程调用——Listener》</a>,这里用到了装饰者模式,直接调用了invoker。该类里面做了服务启动的监听器。我们直接关注下一个invoke。</p>
<h4>(十二)AsyncToSyncInvoker的invoke</h4>
<pre><code class="java">public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult = invoker.invoke(invocation);
try {
// 如果是同步的调用
if (InvokeMode.SYNC == ((RpcInvocation)invocation).getInvokeMode()) {
// 从异步结果中get结果
asyncResult.get();
}
} catch (InterruptedException e) {
throw new RpcException("Interrupted unexpectedly while waiting for remoting result to return! method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof TimeoutException) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} else if (t instanceof RemotingException) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
} catch (Throwable e) {
throw new RpcException(e.getMessage(), e);
}
// 返回异步结果
return asyncResult;
}
</code></pre>
<p>AsyncToSyncInvoker类从名字上就很好理解,它的作用是把异步结果转化为同步结果。新的改动中每个调用只要不是oneway方式调用都会先以异步调用开始,然后根据配置的情况如果是同步调用,则会在这个类中进行异步结果转同步的处理。当然,这里先是执行了invoke,然后就进入下一个AbstractInvoker的invoke了。</p>
<h4>(十三)AbstractInvoker的invoke</h4>
<pre><code class="java">public Result invoke(Invocation inv) throws RpcException {
// if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceed
// 如果服务引用销毁,则打印告警日志,但是通过
if (destroyed.get()) {
logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
+ ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
}
RpcInvocation invocation = (RpcInvocation) inv;
// 会话域中加入该调用链
invocation.setInvoker(this);
// 把附加值放入会话域
if (CollectionUtils.isNotEmptyMap(attachment)) {
invocation.addAttachmentsIfAbsent(attachment);
}
// 把上下文的附加值放入会话域
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (CollectionUtils.isNotEmptyMap(contextAttachments)) {
/**
* invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
* because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
* by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
* a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
*/
invocation.addAttachments(contextAttachments);
}
// 从配置中得到是什么模式的调用,一共有FUTURE、ASYNC和SYNC
invocation.setInvokeMode(RpcUtils.getInvokeMode(url, invocation));
// 加入编号
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
try {
// 执行调用链
return doInvoke(invocation);
} catch (InvocationTargetException e) { // biz exception
// 获得异常
Throwable te = e.getTargetException();
if (te == null) {
// 创建默认的异常异步结果
return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
} else {
if (te instanceof RpcException) {
// 设置异常码
((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
}
// 创建默认的异常异步结果
return AsyncRpcResult.newDefaultAsyncResult(null, te, invocation);
}
} catch (RpcException e) {
if (e.isBiz()) {
return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
} else {
throw e;
}
} catch (Throwable e) {
return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
}
}
</code></pre>
<p>可以参考<a href="https://segmentfault.com/a/1190000017854954">《dubbo源码解析(二十二)远程调用——Protocol》</a>的(三)AbstractInvoker。该方法做了一些公共的操作,比如服务引用销毁的检测,加入附加值,加入调用链实体域到会话域中等。然后执行了doInvoke抽象方法。各协议自己去实现。然后就是执行到doInvoke方法了。使用的协议不一样,doInvoke的逻辑也有所不同,我这里举的例子是使用dubbo协议,所以我就介绍DubboInvoker的doInvoke,其他自行查看具体的实现。此次的异步改造加入了InvokeMode,我会在后续中介绍这个。</p>
<h4>(十四)DubboInvoker的doInvoke</h4>
<pre><code class="java">protected Result doInvoke(final Invocation invocation) throws Throwable {
// rpc会话域
RpcInvocation inv = (RpcInvocation) invocation;
// 获得方法名
final String methodName = RpcUtils.getMethodName(invocation);
// 把path放入到附加值中
inv.setAttachment(PATH_KEY, getUrl().getPath());
// 把版本号放入到附加值
inv.setAttachment(VERSION_KEY, version);
// 当前的客户端
ExchangeClient currentClient;
// 如果数组内就一个客户端,则直接取出
if (clients.length == 1) {
currentClient = clients[0];
} else {
// 取模轮询 从数组中取,当取到最后一个时,从头开始
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
// 是否是单向发送
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
// 获得超时时间
int timeout = getUrl().getMethodParameter(methodName, TIMEOUT_KEY, DEFAULT_TIMEOUT);
// 如果是单向发送
if (isOneway) {
// 是否等待消息发送,默认不等待消息发出,将消息放入 IO 队列,即刻返回。
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
// 单向发送只负责发送消息,不等待服务端应答,所以没有返回值
currentClient.send(inv, isSent);
// 设置future为null,因为单向发送没有返回值
RpcContext.getContext().setFuture(null);
// 创建一个默认的AsyncRpcResult
return AsyncRpcResult.newDefaultAsyncResult(invocation);
} else {
// 否则直接创建AsyncRpcResult
AsyncRpcResult asyncRpcResult = new AsyncRpcResult(inv);
// 异步调用,返回CompletableFuture类型的future
CompletableFuture<Object> responseFuture = currentClient.request(inv, timeout);
// 当调用结果完成时
responseFuture.whenComplete((obj, t) -> {
// 如果有异常
if (t != null) {
// 抛出一个异常
asyncRpcResult.completeExceptionally(t);
} else {
// 完成调用
asyncRpcResult.complete((AppResponse) obj);
}
});
// 异步返回结果用CompletableFuture包装,把future放到上下文中,
RpcContext.getContext().setFuture(new FutureAdapter(asyncRpcResult));
// 返回结果
return asyncRpcResult;
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
</code></pre>
<p>可以参考<a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>的(一)DubboInvoker,不过链接内的文章的源码是2.6.x版本的,而上述的源码是最新版本的,其中就有对于异步的改动,比如加入了异步返回结果、除了单向调用,一律都先处理成AsyncRpcResult等。具体的AsyncRpcResult以及其中用到的CompletableFuture我会在下文介绍。</p>
<p>上述源码中执行currentClient.request或者currentClient.send,代表把请求放入channel中,交给channel来处理请求。最后来看一个currentClient.request,因为这其中涉及到了Future的构建。</p>
<h4>(十五)ReferenceCountExchangeClient的request</h4>
<pre><code class="java">public CompletableFuture<Object> request(Object request, int timeout) throws RemotingException {
return client.request(request, timeout);
}
</code></pre>
<p>ReferenceCountExchangeClient是一个记录请求数的类,用了适配器模式,对ExchangeClient做了功能增强。</p>
<p>可以参考<a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>的(八)ReferenceCountExchangeClient。</p>
<h4>(十六)HeaderExchangeClient的request</h4>
<pre><code class="java">public CompletableFuture<Object> request(Object request, int timeout) throws RemotingException {
return channel.request(request, timeout);
}
</code></pre>
<p>该类也是用了适配器模式,该类主要的作用就是增加了心跳功能,可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的(四)HeaderExchangeClient。然后进入HeaderExchangeChannel的request。</p>
<h4>(十七)HeaderExchangeChannel的request</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的(二)HeaderExchangeChannel,在这个request方法中就可以看到</p>
<pre><code class="java"> // 创建DefaultFuture对象,可以从future中主动获得请求对应的响应信息
DefaultFuture future = new DefaultFuture(channel, req, timeout);
</code></pre>
<p>生成了需要的future。异步请求结果就是从这个future中获取。关于DefaultFuture也可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的(七)DefaultFuture。</p>
<p>后面channel.send方法就是跟远程通信有关了,例如使用netty作为通信实现,则会使用netty实现的客户端进行通信。</p>
<h4>(十八)AbstractPeer的send</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>的(一)AbstractPeer,其中send方法比较简单,根据sent配置项去做消息发送。接下来看AbstractClient的send</p>
<h4>(十九)AbstractClient的send</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>的(四)AbstractClient。</p>
<pre><code class="java">public void send(Object message, boolean sent) throws RemotingException {
// 如果需要重连或者没有链接,则连接
if (needReconnect && !isConnected()) {
connect();
}
// 获得通道
Channel channel = getChannel();
//TODO Can the value returned by getChannel() be null? need improvement.
if (channel == null || !channel.isConnected()) {
throw new RemotingException(this, "message can not send, because channel is closed . url:" + getUrl());
}
// 通过通道发送消息
channel.send(message, sent);
}
</code></pre>
<p>该方法中做了重连的逻辑,然后就是通过通道发送消息,dubbo有几种通信的实现,我这里就按照默认的netty4实现来讲解,所以下一步走到了NettyChannel的send。</p>
<h4>(二十)NettyChannel的send</h4>
<p>可以参考<a href="https://segmentfault.com/a/1190000017553202">《dubbo源码解析(十七)远程通信——Netty4》</a>的(一)NettyChannel。这里其中先执行了下面父类AbstractChannel的send,检查了一下通道是否关闭,然后再走下面的逻辑。当执行writeAndFlush方法后,消息就被发送。</p>
<p>dubbo数据包可以查看<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的(二十五)ExchangeCodec,后续关于netty发送消息,以及netty出站数据在发出之前还需要进行编码操作我就先不做介绍,主要是跟netty知识点强相关,只是dubbo做了一些自己的编码,以及集成了各类序列化方式。</p>
<h3>后记</h3>
<p>该文章讲解了dubbo调用服务的方法所经历的所有步骤,直到调用消息发送到服务端为止,是目前最新代码的解析。下一篇文将讲解服务端收到方法调用的请求后,如何处理以及如何把调用结果返回的过程。</p>
Congratulations Apache® Dubbo™ as a TLP
https://segmentfault.com/a/1190000019245932
2019-05-21T11:48:07+08:00
2019-05-21T11:48:07+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2><strong>The Apache Software Foundation Announces Apache® Dubbo™ as a Top-Level Project</strong></h2>
<p>This article aims to record the important moment of Apache® Dubbo™.</p>
<p><strong>Wakefield, MA —20 May 2019—</strong> The Apache Software Foundation (ASF), the all-volunteer developers, stewards, and incubators of more than 350 Open Source projects and initiatives, announced today Apache® Dubbo™ as a Top-Level Project (TLP).</p>
<p>This is really exciting news, congratulations!</p>
<blockquote>link:<a href="https://link.segmentfault.com/?enc=fPKqgAe5pqcqEdnKykyAvg%3D%3D.%2FawK0SodEZ3ezqhFJnR5ZpoNA26C%2Fj%2Fwc8sxcpkCjXNBBA9Ddd8y2GQhNFxhJoaWzzKtxXLLxvL1NUUPTrDak7Or%2BMtYo924iPpiHxxqVk0ugKOZwZucYwIgK6KgFiAw" rel="nofollow">https://blogs.apache.org/foun...</a>
</blockquote>
Dubbo源码解析(四十五)服务引用过程
https://segmentfault.com/a/1190000018999555
2019-04-26T16:02:00+08:00
2019-04-26T16:02:00+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
6
<h2>dubbo服务引用过程</h2>
<blockquote>目标:从源码的角度分析服务引用过程。</blockquote>
<h3>前言</h3>
<p>前面服务暴露过程的文章讲解到,服务引用有两种方式,一种就是直连,也就是直接指定服务的地址来进行引用,这种方式更多的时候被用来做服务测试,不建议在生产环境使用这样的方法,因为直连不适合服务治理,dubbo本身就是一个服务治理的框架,提供了很多服务治理的功能。所以更多的时候,我们都不会选择绕过注册中心,而是通过注册中心的方式来进行服务引用。</p>
<h3>服务引用过程</h3>
<p><img src="/img/remote/1460000018999558" alt="dubbo-refer" title="dubbo-refer"></p>
<p>大致可以分为三个步骤:</p>
<ol>
<li>配置加载</li>
<li>创建invoker</li>
<li>创建服务接口代理类</li>
</ol>
<h4>引用起点</h4>
<p>dubbo服务的引用起点就类似于bean加载。dubbo中有一个类ReferenceBean,它实现了FactoryBean接口,继承了ReferenceConfig,所以ReferenceBean作为dubbo中能生产对象的工厂Bean,而我们要引用服务,也就是要有一个该服务的对象。</p>
<p>服务引用被触发有两个时机:</p>
<ul>
<li>Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务(饿汉式)</li>
<li>在 ReferenceBean 对应的服务被注入到其他类中时引用(懒汉式)</li>
</ul>
<p>默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 <dubbo:reference> 的 init 属性开启。</p>
<p>因为ReferenceBean实现了FactoryBean接口的getObject()方法,所以在加载bean的时候,会调用ReferenceBean的getObject()方法</p>
<h6>ReferenceBean的getObject()</h6>
<pre><code class="java">public Object getObject() {
return get();
}</code></pre>
<p>这个get方法是ReferenceConfig的get()方法</p>
<h6>ReferenceConfig的get()</h6>
<pre><code class="java">public synchronized T get() {
// 检查并且更新配置
checkAndUpdateSubConfigs();
// 如果被销毁,则抛出异常
if (destroyed) {
throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
}
// 检测 代理对象ref 是否为空,为空则通过 init 方法创建
if (ref == null) {
// 用于处理配置,以及调用 createProxy 生成代理类
init();
}
return ref;
}</code></pre>
<p>关于checkAndUpdateSubConfigs()方法前一篇文章已经讲了,我就不再讲述。这里关注init方法。该方法也是处理各类配置的开始。</p>
<h4>配置加载(1)</h4>
<h6>ReferenceConfig的init()</h6>
<pre><code class="java">private void init() {
// 如果已经初始化过,则结束
if (initialized) {
return;
}
// 设置初始化标志为true
initialized = true;
// 本地存根合法性校验
checkStubAndLocal(interfaceClass);
// mock合法性校验
checkMock(interfaceClass);
// 用来存放配置
Map<String, String> map = new HashMap<String, String>();
// 存放这是消费者侧
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
// 添加 协议版本、发布版本,时间戳 等信息到 map 中
appendRuntimeParameters(map);
// 如果是泛化调用
if (!isGeneric()) {
// 获得版本号
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
// 设置版本号
map.put(Constants.REVISION_KEY, revision);
}
// 获得所有方法
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
// 把所有方法签名拼接起来放入map
map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), Constants.COMMA_SEPARATOR));
}
}
// 加入服务接口名称
map.put(Constants.INTERFACE_KEY, interfaceName);
// 添加metrics、application、module、consumer、protocol的所有信息到map
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);
Map<String, Object> attributes = null;
if (CollectionUtils.isNotEmpty(methods)) {
attributes = new HashMap<String, Object>();
// 遍历方法配置
for (MethodConfig methodConfig : methods) {
// 把方法配置加入map
appendParameters(map, methodConfig, methodConfig.getName());
// 生成重试的配置key
String retryKey = methodConfig.getName() + ".retry";
// 如果map中已经有该配置,则移除该配置
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
// 如果配置为false,也就是不重试,则设置重试次数为0次
if ("false".equals(retryValue)) {
map.put(methodConfig.getName() + ".retries", "0");
}
}
// 设置异步配置
attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig));
}
}
// 获取服务消费者 ip 地址
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
// 如果为空,则获取本地ip
if (StringUtils.isEmpty(hostToRegistry)) {
hostToRegistry = NetUtils.getLocalHost();
}
// 设置消费者ip
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
// 创建代理对象
ref = createProxy(map);
// 生产服务key
String serviceKey = URL.buildKey(interfaceName, group, version);
// 根据服务名,ReferenceConfig,代理类构建 ConsumerModel,
// 并将 ConsumerModel 存入到 ApplicationModel 中
ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
}</code></pre>
<p>该方法大致分为以下几个步骤:</p>
<ol>
<li>检测本地存根和mock合法性。</li>
<li>添加协议版本、发布版本,时间戳、metrics、application、module、consumer、protocol等的所有信息到 map 中</li>
<li>单独处理方法配置,设置重试次数配置以及设置该方法对异步配置信息。</li>
<li>添加消费者ip地址到map</li>
<li>创建代理对象</li>
<li>生成ConsumerModel存入到 ApplicationModel 中</li>
</ol>
<p>在这里处理配置到逻辑比较清晰。下面就是看ReferenceConfig的createProxy()方法。</p>
<h4>创建invoker</h4>
<h6>ReferenceConfig的createProxy()</h6>
<pre><code class="java">private T createProxy(Map<String, String> map) {
// 根据配置检查是否为本地调用
if (shouldJvmRefer(map)) {
// 生成url,protocol使用的是injvm
URL url = new URL(Constants.LOCAL_PROTOCOL, Constants.LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
// 利用InjvmProtocol 的 refer 方法生成 InjvmInvoker 实例
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
// 如果url不为空,则用户可能想进行直连来调用
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
// 当需要配置多个 url 时,可用分号进行分割,这里会进行切分
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
// 遍历所有的url
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (StringUtils.isEmpty(url.getPath())) {
// 设置接口全限定名为 url 路径
url = url.setPath(interfaceName);
}
// 检测 url 协议是否为 registry,若是,表明用户想使用指定的注册中心
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
// 将 map 转换为查询字符串,并作为 refer 参数的值添加到 url 中
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
// 合并 url,移除服务提供者的一些配置(这些配置来源于用户配置的 url 属性),
// 比如线程池相关配置。并保留服务提供者的部分配置,比如版本,group,时间戳等
// 最后将合并后的配置设置为 url 查询字符串中。
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // assemble URL from register center's configuration
// 校验注册中心
checkRegistry();
// 加载注册中心的url
List<URL> us = loadRegistries(false);
if (CollectionUtils.isNotEmpty(us)) {
// 遍历所有的注册中心
for (URL u : us) {
// 生成监控url
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
// 加入监控中心url的配置
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
// 添加 refer 参数到 url 中,并将 url 添加到 urls 中
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
// 如果urls为空,则抛出异常
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
// 如果只有一个注册中心,则直接调用refer方法
if (urls.size() == 1) {
// 调用 RegistryProtocol 的 refer 构建 Invoker 实例
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
// 遍历所有的注册中心url
for (URL url : urls) {
// 通过 refprotocol 调用 refer 构建 Invoker,
// refprotocol 会在运行时根据 url 协议头加载指定的 Protocol 实例,并调用实例的 refer 方法
// 把生成的Invoker加入到集合中
invokers.add(refprotocol.refer(interfaceClass, url));
// 如果是注册中心的协议
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
// 则设置registryURL
registryURL = url; // use last registry url
}
}
// 优先用注册中心的url
if (registryURL != null) { // registry url is available
// use RegistryAwareCluster only when register's cluster is available
// 只有当注册中心当链接可用当时候,采用RegistryAwareCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, RegistryAwareCluster.NAME);
// The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
// 由集群进行多个invoker合并
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url, must be direct invoke.
// 直接进行合并
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
// 如果需要核对该服务是否可用,并且该服务不可用
if (shouldCheck() && !invoker.isAvailable()) {
// make it possible for consumer to retry later if provider is temporarily unavailable
// 修改初始化标志为false
initialized = false;
// 抛出异常
throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
/**
* @since 2.7.0
* ServiceData Store
*/
// 元数据中心服务
MetadataReportService metadataReportService = null;
// 加载元数据服务,如果成功
if ((metadataReportService = getMetadataReportService()) != null) {
// 生成url
URL consumerURL = new URL(Constants.CONSUMER_PROTOCOL, map.remove(Constants.REGISTER_IP_KEY), 0, map.get(Constants.INTERFACE_KEY), map);
// 把消费者配置加入到元数据中心中
metadataReportService.publishConsumer(consumerURL);
}
// create service proxy
// 创建服务代理
return (T) proxyFactory.getProxy(invoker);
}</code></pre>
<p>该方法的大致逻辑可用分为以下几步:</p>
<ol>
<li>如果是本地调用,则直接使用InjvmProtocol 的 refer 方法生成 Invoker 实例。</li>
<li>如果不是本地调用,但是是选择直连的方式来进行调用,则分割配置的多个url。如果协议是配置是registry,则表明用户想使用指定的注册中心,配置url后将url并且保存到urls里面,否则就合并url,并且保存到urls。</li>
<li>如果是通过注册中心来进行调用,则先校验所有的注册中心,然后加载注册中心的url,遍历每个url,加入监控中心url配置,最后把每个url保存到urls。</li>
<li>针对urls集合的数量,如果是单注册中心,直接引用RegistryProtocol 的 refer 构建 Invoker 实例,如果是多注册中心,则对每个url都生成Invoker,利用集群进行多个Invoker合并。</li>
<li>最终输出一个invoker。</li>
</ol>
<p>Invoker 是 Dubbo 的核心模型,代表一个可执行体。在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来。关于这几个接口的定义介绍可以参考<a href="https://segmentfault.com/a/1190000017787521">《dubbo源码解析(十九)远程调用——开篇》</a>,Protocol 实现类有很多,下面会分析 RegistryProtocol 和 DubboProtocol,我们可以看到上面的源码中讲到,当只有一个注册中心的时候,会直接使用RegistryProtocol。所以先来看看RegistryProtocol的refer()方法。</p>
<h5>RegistryProtocol生成invoker</h5>
<h6>RegistryProtocol的refer()</h6>
<pre><code class="java">public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 取 registry 参数值,并将其设置为协议头,默认是dubbo
url = URLBuilder.from(url)
.setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
.removeParameter(REGISTRY_KEY)
.build();
// 获得注册中心实例
Registry registry = registryFactory.getRegistry(url);
// 如果是注册中心服务,则返回注册中心服务的invoker
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
// 将 url 查询字符串转为 Map
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
// 获得group值
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
// 如果有多个组,或者组配置为*,则使用MergeableCluster,并调用 doRefer 继续执行服务引用逻辑
if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
// 只有一个组或者没有组配置,则直接执行doRefer
return doRefer(cluster, registry, type, url);
}</code></pre>
<p>上面的逻辑比较简单,如果是注册服务中心,则直接创建代理。如果不是,先处理组配置,根据组配置来决定Cluster的实现方式,然后调用doRefer方法。</p>
<h6>RegistryProtocol的doRefer()</h6>
<pre><code class="java">private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 创建 RegistryDirectory 实例
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
// 设置注册中心
directory.setRegistry(registry);
// 设置协议
directory.setProtocol(protocol);
// all attributes of REFER_KEY
// 所有属性放到map中
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
// 生成服务消费者链接
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
// 注册服务消费者,在 consumers 目录下新节点
if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
// 注册服务消费者
registry.register(directory.getRegisteredConsumerUrl());
}
// 创建路由规则链
directory.buildRouterChain(subscribeUrl);
// 订阅 providers、configurators、routers 等节点数据
directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
// 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个,生成一个invoker
Invoker invoker = cluster.join(directory);
// 在服务提供者处注册消费者
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}</code></pre>
<p>该方法大致可以分为以下步骤:</p>
<ol>
<li>创建一个 RegistryDirectory 实例,然后生成服务者消费者链接。</li>
<li>向注册中心进行注册。</li>
<li>紧接着订阅 providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。</li>
<li>由于一个服务可能部署在多台服务器上,这样就会在 providers 产生多个节点,这个时候就需要 Cluster 将多个服务节点合并为一个,并生成一个 Invoker。关于 RegistryDirectory 和 Cluster,可以看我前面写的一些文章介绍。</li>
</ol>
<h5>DubboProtocol生成invoker</h5>
<p>首先还是从DubboProtocol的refer()开始。</p>
<h6>DubboProtocol的refer()</h6>
<pre><code class="java">public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// create rpc invoker.
// 创建一个DubboInvoker实例
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
// 加入到集合中
invokers.add(invoker);
return invoker;
}</code></pre>
<p>创建DubboInvoker比较简单,调用了构造方法,这里主要讲这么生成ExchangeClient,也就是getClients方法。</p>
<h6>DubboProtocol的getClients()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>的(三)DubboProtocol中的源码分析。最新版本基本没有什么变化,只是因为加入了配置中心,配置的优先级更加明确了,所以增加了xml配置优先级高于properties配置的代码逻辑,都比较容易理解。</p>
<p>其中如果是配置的共享,则获得共享客户端对象,也就是getSharedClient()方法,否则新建客户端也就是initClient()方法。</p>
<h6>DubboProtocol的getSharedClient()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>的(三)DubboProtocol中的源码分析,该方法比较简单,先访问缓存,若缓存未命中,则通过 initClient 方法创建新的 ExchangeClient 实例,并将该实例传给 ReferenceCountExchangeClient 构造方法创建一个带有引用计数功能的 ExchangeClient 实例。</p>
<h6>DubboProtocol的initClient()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>的(三)DubboProtocol中的源码分析,initClient 方法首先获取用户配置的客户端类型,最新版本已经改为默认 netty4。然后设置用户心跳配置,然后检测用户配置的客户端类型是否存在,不存在则抛出异常。最后根据 lazy 配置决定创建什么类型的客户端。这里的 LazyConnectExchangeClient 代码并不是很复杂,该类会在 request 方法被调用时通过 Exchangers 的 connect 方法创建 ExchangeClient 客户端。下面我们分析一下 Exchangers 的 connect 方法。</p>
<h6>Exchangers的connect()</h6>
<pre><code class="java">public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// 获取 Exchanger 实例,默认为 HeaderExchangeClient
return getExchanger(url).connect(url, handler);
}</code></pre>
<p>getExchanger 会通过 SPI 加载 HeaderExchangeClient 实例,这个方法比较简单。接下来分析 HeaderExchangeClient 的connect的实现。</p>
<h6>HeaderExchangeClient 的connect()</h6>
<pre><code class="java">public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
// 创建 HeaderExchangeHandler 对象
// 创建 DecodeHandler 对象
// 通过 Transporters 构建 Client 实例
// 创建 HeaderExchangeClient 对象
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}</code></pre>
<p>其中HeaderExchangeHandler、DecodeHandler等可以参考<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>和<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>的分析。这里重点关注Transporters 构建 Client,也就是Transporters的connect方法。</p>
<h6>Transporters的connect()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017274525">《dubbo源码解析(八)远程通信——开篇》</a>的(十)Transporters中源码分析。其中获得自适应拓展类,该类会在运行时根据客户端类型加载指定的 Transporter 实现类。若用户未配置客户端类型,则默认加载 NettyTransporter,并调用该类的 connect 方法。假设是netty4的实现,则执行以下代码。</p>
<pre><code class="java">public Client connect(URL url, ChannelHandler listener) throws RemotingException {
return new NettyClient(url, listener);
}</code></pre>
<p>到这里为止,DubboProtocol生成invoker过程也结束了。再回到createProxy方法的最后一句代码,根据invoker创建服务代理对象。</p>
<h4>创建代理</h4>
<p>为服务接口生成代理对象。有了代理对象,即可进行远程调用。首先来看AbstractProxyFactory 的 getProxy()方法。</p>
<h6>AbstractProxyFactory 的 getProxy()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017892690">《dubbo源码解析(二十三)远程调用——Proxy》</a>的(一)AbstractProxyFactory的源码分析。可以看到第二个getProxy方法其实就是获取 interfaces 数组,调用到第三个getProxy方法时,该getProxy是个抽象方法,由子类来实现,我们还是默认它的代理实现方式为Javassist。所以可以看JavassistProxyFactory的getProxy方法。</p>
<h6>JavassistProxyFactory的getProxy()</h6>
<pre><code class="java">public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
// 生成 Proxy 子类(Proxy 是抽象类)。并调用 Proxy 子类的 newInstance 方法创建 Proxy 实例
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}</code></pre>
<p>我们重点看Proxy的getProxy方法。</p>
<pre><code class="java">/**
* Get proxy.
*
* @param ics interface class array.
* @return Proxy instance.
*/
public static Proxy getProxy(Class<?>... ics) {
// 获得Proxy的类加载器来进行生成代理类
return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);
}
/**
* Get proxy.
*
* @param cl class loader.
* @param ics interface class array.
* @return Proxy instance.
*/
public static Proxy getProxy(ClassLoader cl, Class<?>... ics) {
if (ics.length > Constants.MAX_PROXY_COUNT) {
throw new IllegalArgumentException("interface limit exceeded");
}
StringBuilder sb = new StringBuilder();
// 遍历接口列表
for (int i = 0; i < ics.length; i++) {
String itf = ics[i].getName();
// 检测是否是接口,如果不是,则抛出异常
if (!ics[i].isInterface()) {
throw new RuntimeException(itf + " is not a interface.");
}
Class<?> tmp = null;
try {
// 重新加载接口类
tmp = Class.forName(itf, false, cl);
} catch (ClassNotFoundException e) {
}
// 检测接口是否相同,这里 tmp 有可能为空,也就是该接口无法被类加载器加载的。
if (tmp != ics[i]) {
throw new IllegalArgumentException(ics[i] + " is not visible from class loader");
}
// 拼接接口全限定名,分隔符为 ;
sb.append(itf).append(';');
}
// use interface class name list as key.
// 使用拼接后的接口名作为 key
String key = sb.toString();
// get cache by class loader.
Map<String, Object> cache;
// 把该类加载器加到本地缓存
synchronized (ProxyCacheMap) {
cache = ProxyCacheMap.computeIfAbsent(cl, k -> new HashMap<>());
}
Proxy proxy = null;
synchronized (cache) {
do {
// 从缓存中获取 Reference<Proxy> 实例
Object value = cache.get(key);
if (value instanceof Reference<?>) {
proxy = (Proxy) ((Reference<?>) value).get();
if (proxy != null) {
return proxy;
}
}
// 并发控制,保证只有一个线程可以进行后续操作
if (value == PendingGenerationMarker) {
try {
// 其他线程在此处进行等待
cache.wait();
} catch (InterruptedException e) {
}
} else {
// 放置标志位到缓存中,并跳出 while 循环进行后续操作
cache.put(key, PendingGenerationMarker);
break;
}
}
while (true);
}
long id = PROXY_CLASS_COUNTER.getAndIncrement();
String pkg = null;
ClassGenerator ccp = null, ccm = null;
try {
// 创建 ClassGenerator 对象
ccp = ClassGenerator.newInstance(cl);
Set<String> worked = new HashSet<>();
List<Method> methods = new ArrayList<>();
for (int i = 0; i < ics.length; i++) {
// 检测接口访问级别是否为 protected 或 privete
if (!Modifier.isPublic(ics[i].getModifiers())) {
// 获取接口包名
String npkg = ics[i].getPackage().getName();
if (pkg == null) {
pkg = npkg;
} else {
// 非 public 级别的接口必须在同一个包下,否者抛出异常
if (!pkg.equals(npkg)) {
throw new IllegalArgumentException("non-public interfaces from different packages");
}
}
}
// 添加接口到 ClassGenerator 中
ccp.addInterface(ics[i]);
// 遍历接口方法
for (Method method : ics[i].getMethods()) {
// 获取方法描述,可理解为方法签名
String desc = ReflectUtils.getDesc(method);
// 如果方法描述字符串已在 worked 中,则忽略。考虑这种情况,
// A 接口和 B 接口中包含一个完全相同的方法
if (worked.contains(desc)) {
continue;
}
worked.add(desc);
int ix = methods.size();
// 获取方法返回值类型
Class<?> rt = method.getReturnType();
// 获取参数列表
Class<?>[] pts = method.getParameterTypes();
// 生成 Object[] args = new Object[1...N]
StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
for (int j = 0; j < pts.length; j++) {
// 生成 args[1...N] = ($w)$1...N;
code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
}
// 生成 InvokerHandler 接口的 invoker 方法调用语句,如下:
// Object ret = handler.invoke(this, methods[1...N], args);
code.append(" Object ret = handler.invoke(this, methods[").append(ix).append("], args);");
// 返回值不为 void
if (!Void.TYPE.equals(rt)) {
// 生成返回语句,形如 return (java.lang.String) ret;
code.append(" return ").append(asArgument(rt, "ret")).append(";");
}
methods.add(method);
// 添加方法名、访问控制符、参数列表、方法代码等信息到 ClassGenerator 中
ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
}
}
if (pkg == null) {
pkg = PACKAGE_NAME;
}
// create ProxyInstance class.
// 构建接口代理类名称:pkg + ".proxy" + id,比如 org.apache.dubbo.proxy0
String pcn = pkg + ".proxy" + id;
ccp.setClassName(pcn);
ccp.addField("public static java.lang.reflect.Method[] methods;");
// 生成 private java.lang.reflect.InvocationHandler handler;
ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
// 为接口代理类添加带有 InvocationHandler 参数的构造方法,比如:
// porxy0(java.lang.reflect.InvocationHandler arg0) {
// handler=$1;
// }
ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");
// 为接口代理类添加默认构造方法
ccp.addDefaultConstructor();
// 生成接口代理类
Class<?> clazz = ccp.toClass();
clazz.getField("methods").set(null, methods.toArray(new Method[0]));
// create Proxy class.
// 构建 Proxy 子类名称,比如 Proxy1,Proxy2 等
String fcn = Proxy.class.getName() + id;
ccm = ClassGenerator.newInstance(cl);
ccm.setClassName(fcn);
ccm.addDefaultConstructor();
ccm.setSuperClass(Proxy.class);
// 为 Proxy 的抽象方法 newInstance 生成实现代码,形如:
// public Object newInstance(java.lang.reflect.InvocationHandler h) {
// return new org.apache.dubbo.proxy0($1);
// }
ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
Class<?> pc = ccm.toClass();
// 生成 Proxy 实现类
proxy = (Proxy) pc.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
// release ClassGenerator
if (ccp != null) {
// 释放资源
ccp.release();
}
if (ccm != null) {
ccm.release();
}
synchronized (cache) {
if (proxy == null) {
cache.remove(key);
} else {
// 写缓存
cache.put(key, new WeakReference<Proxy>(proxy));
}
// 唤醒其他等待线程
cache.notifyAll();
}
}
return proxy;
}</code></pre>
<p>代码比较多,大致可以分为以下几步:</p>
<ol>
<li>对接口进行校验,检查是否是一个接口,是否不能被类加载器加载。</li>
<li>做并发控制,保证只有一个线程可以进行后续的代理生成操作。</li>
<li>创建cpp,用作为服务接口生成代理类。首先对接口定义以及包信息进行处理。</li>
<li>对接口的方法进行处理,包括返回类型,参数类型等。最后添加方法名、访问控制符、参数列表、方法代码等信息到 ClassGenerator 中。</li>
<li>创建接口代理类的信息,比如名称,默认构造方法等。</li>
<li>生成接口代理类。</li>
<li>创建ccm,ccm 则是用于为 org.apache.dubbo.common.bytecode.Proxy 抽象类生成子类,主要是实现 Proxy 类的抽象方法。</li>
<li>设置名称、创建构造方法、添加方法</li>
<li>生成 Proxy 实现类。</li>
<li>释放资源</li>
<li>创建弱引用,写入缓存,唤醒其他线程。</li>
</ol>
<p>到这里,接口代理类生成后,服务引用也就结束了。</p>
<h3>后记</h3>
<blockquote>参考官方文档:<a href="https://link.segmentfault.com/?enc=RrXJchyKWJbW%2BcXwE05nWw%3D%3D.kWGxyhDHhlR%2Bf12rQyIoQNcE30DeoNz8s1cHnH4n1PmndbVmxKp1mKc9rxyJGowiYSJbkWZD%2B2E%2BirvYnnM0pBZAORqEYF7OmsCMwwj%2BEE8%3D" rel="nofollow">https://dubbo.apache.org/zh-c...</a>
</blockquote>
<p>该文章讲解了dubbo的服务引用过程,下一篇就讲解服务方法调用过程。</p>
Dubbo源码解析(四十四)服务暴露过程
https://segmentfault.com/a/1190000018953699
2019-04-23T06:59:40+08:00
2019-04-23T06:59:40+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
10
<h2>dubbo服务暴露过程</h2>
<blockquote>目标:从源码的角度分析服务暴露过程。</blockquote>
<h3>前言</h3>
<p>本来这一篇一个写异步化改造的内容,但是最近我一直在想,某一部分的优化改造该怎么去撰写才能更加的让读者理解。我觉得还是需要先从整个调用链入手,先弄清楚了该功能在哪一个时机发生的,说通俗一点,这块代码是什么时候或者什么场景被执行的,然后再去分析内部是如何实现,最后阐述这样改造的好处。</p>
<p>我在前面的文章都很少提及各个调用链的关系,各模块之间也没有串起来,并且要讲解异步化改造我认为先弄懂服务的暴露和引用过程是非常有必要的,所以我将用两片文章来讲解服务暴露和服务引用的过程。</p>
<h3>服务暴露过程</h3>
<p>服务暴露过程大致可分为三个部分:</p>
<ol>
<li>前置工作,主要用于检查参数,组装 URL。</li>
<li>导出服务,包含暴露服务到本地 (JVM),和暴露服务到远程两个过程。</li>
<li>向注册中心注册服务,用于服务发现。</li>
</ol>
<h4>暴露起点</h4>
<p>Spring中有一个ApplicationListener接口,其中定义了一个onApplicationEvent()方法,在当容器内发生任何事件时,此方法都会被触发。</p>
<p>Dubbo中ServiceBean类实现了该接口,并且实现了onApplicationEvent方法:</p>
<pre><code class="java">@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 如果服务没有被暴露并且服务没有被取消暴露,则打印日志
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
// 导出
export();
}
}</code></pre>
<p>只要服务没有被暴露并且服务没有被取消暴露,就暴露服务。执行export方法。接下来就是跟官网的时序图有关,对照着时序图来看下面的过程。</p>
<p><img src="/img/remote/1460000018953709" alt="dubbo-export" title="dubbo-export"></p>
<p>我会在下面的小标题加上时序图的每一步操作范围,比如下面要讲到的前置工作其实就是时序图中的1:export(),那我会在标题括号里面写1。</p>
<p>其中服务暴露的第八步已经没有了。</p>
<h4>前置工作(1)</h4>
<p>前置工作主要包含两个部分,分别是配置检查,以及 URL 装配。在暴露服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成后,接下来需要根据这些配置组装 URL。在 Dubbo 中,URL 的作用十分重要。Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。</p>
<h5>配置检查</h5>
<p>在调用export方法之后,执行的是ServiceConfig中的export方法。</p>
<pre><code class="java">public synchronized void export() {
//检查并且更新配置
checkAndUpdateSubConfigs();
// 如果不应该暴露,则直接结束
if (!shouldExport()) {
return;
}
// 如果使用延迟加载,则延迟delay时间后暴露服务
if (shouldDelay()) {
delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
} else {
// 暴露服务
doExport();
}
}</code></pre>
<p>可以看到首先做的就是对配置的检查和更新,执行的是ServiceConfig中的checkAndUpdateSubConfigs方法。然后检测是否应该暴露,如果不应该暴露,则直接结束,然后检测是否配置了延迟加载,如果是,则使用定时器来实现延迟加载的目的。</p>
<h6>checkAndUpdateSubConfigs()</h6>
<pre><code class="java">public void checkAndUpdateSubConfigs() {
// Use default configs defined explicitly on global configs
// 用于检测 provider、application 等核心配置类对象是否为空,
// 若为空,则尝试从其他配置类对象中获取相应的实例。
completeCompoundConfigs();
// Config Center should always being started first.
// 开启配置中心
startConfigCenter();
// 检测 provider 是否为空,为空则新建一个,并通过系统变量为其初始化
checkDefault();
// 检查application是否为空
checkApplication();
// 检查注册中心是否为空
checkRegistry();
// 检查protocols是否为空
checkProtocol();
this.refresh();
// 核对元数据中心配置是否为空
checkMetadataReport();
// 服务接口名不能为空,否则抛出异常
if (StringUtils.isEmpty(interfaceName)) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
// 检测 ref 是否为泛化服务类型
if (ref instanceof GenericService) {
// 设置interfaceClass为GenericService
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
// 设置generic = true
generic = Boolean.TRUE.toString();
}
} else {
try {
// 获得接口类型
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 对 interfaceClass,以及 <dubbo:method> 标签中的必要字段进行检查
checkInterfaceAndMethods(interfaceClass, methods);
// 对 ref 合法性进行检测
checkRef();
generic = Boolean.FALSE.toString();
}
// stub local一样都是配置本地存根
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
// 本地存根合法性校验
checkStubAndLocal(interfaceClass);
// mock合法性校验
checkMock(interfaceClass);
}</code></pre>
<p>可以看到,该方法中是对各类配置的校验,并且更新部分配置。其中检查的细节我就不展开,因为服务暴露整个过程才是本文重点。</p>
<p>在经过shouldExport()和shouldDelay()两个方法检测后,会执行ServiceConfig的doExport()方法</p>
<h6>doExport()</h6>
<pre><code class="java">protected synchronized void doExport() {
// 如果调用不暴露的方法,则unexported值为true
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
// 如果服务已经暴露了,则直接结束
if (exported) {
return;
}
// 设置已经暴露
exported = true;
// 如果path为空,则赋值接口名称
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
// 多协议多注册中心暴露服务
doExportUrls();
}</code></pre>
<p>该方法就是对于服务是否暴露在一次校验,然后会执行ServiceConfig的doExportUrls()方法,对于多协议多注册中心暴露服务进行支持。</p>
<h6>doExportUrls()</h6>
<pre><code class="java">private void doExportUrls() {
// 加载注册中心链接
List<URL> registryURLs = loadRegistries(true);
// 遍历 protocols,并在每个协议下暴露服务
for (ProtocolConfig protocolConfig : protocols) {
// 以path、group、version来作为服务唯一性确定的key
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
// 组装 URL
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}</code></pre>
<p>从该方法可以看到:</p>
<ul>
<li>loadRegistries()方法是加载注册中心链接。</li>
<li>服务的唯一性是通过path、group、version一起确定的。</li>
<li>doExportUrlsFor1Protocol()方法开始组装URL。</li>
</ul>
<h6>loadRegistries()</h6>
<pre><code class="java">protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
// 如果registries为空,直接返回空集合
if (CollectionUtils.isNotEmpty(registries)) {
// 遍历注册中心配置集合registries
for (RegistryConfig config : registries) {
// 获得地址
String address = config.getAddress();
// 若地址为空,则设置为0.0.0.0
if (StringUtils.isEmpty(address)) {
address = Constants.ANYHOST_VALUE;
}
// 如果地址为N/A,则跳过
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
// 添加 ApplicationConfig 中的字段信息到 map 中
appendParameters(map, application);
// 添加 RegistryConfig 字段信息到 map 中
appendParameters(map, config);
// 添加path
map.put(Constants.PATH_KEY, RegistryService.class.getName());
// 添加 协议版本、发布版本,时间戳 等信息到 map 中
appendRuntimeParameters(map);
// 如果map中没有protocol,则默认为使用dubbo协议
if (!map.containsKey(Constants.PROTOCOL_KEY)) {
map.put(Constants.PROTOCOL_KEY, Constants.DUBBO_PROTOCOL);
}
// 解析得到 URL 列表,address 可能包含多个注册中心 ip,因此解析得到的是一个 URL 列表
List<URL> urls = UrlUtils.parseURLs(address, map);
// 遍历URL 列表
for (URL url : urls) {
// 将 URL 协议头设置为 registry
url = URLBuilder.from(url)
.addParameter(Constants.REGISTRY_KEY, url.getProtocol())
.setProtocol(Constants.REGISTRY_PROTOCOL)
.build();
// 通过判断条件,决定是否添加 url 到 registryList 中,条件如下:
// 如果是服务提供者,并且是注册中心服务 或者 是消费者端,并且是订阅服务
// 则加入到registryList
if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}</code></pre>
<h5>组装URL</h5>
<p>我在以前的文章也提到过,dubbo内部用URL来携带各类配置,贯穿整个调用链,它就是配置的载体。服务的配置被组装到URL中就是从这里开始,上面提到遍历每个协议配置,在每个协议下都暴露服务,就会执行ServiceConfig的doExportUrlsFor1Protocol()方法,该方法前半部分实现了组装URL的逻辑,后半部分实现了暴露dubbo服务等逻辑,其中为用分割线分隔了。</p>
<h6>doExportUrlsFor1Protocol()</h6>
<pre><code class="java">private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 获取协议名
String name = protocolConfig.getName();
// 如果为空,则是默认的dubbo
if (StringUtils.isEmpty(name)) {
name = Constants.DUBBO;
}
Map<String, String> map = new HashMap<String, String>();
// 设置服务提供者册
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
// 添加 协议版本、发布版本,时间戳 等信息到 map 中
appendRuntimeParameters(map);
// 添加metrics、application、module、provider、protocol的所有信息到map
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
// 如果method的配置列表不为空
if (CollectionUtils.isNotEmpty(methods)) {
// 遍历method配置列表
for (MethodConfig method : methods) {
// 把方法名加入map
appendParameters(map, method, method.getName());
// 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。
// 比如存储 <dubbo:method name="sayHello" retries="2"> 对应的 MethodConfig,
// 键 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
// 如果retryValue为false,则不重试,设置值为0
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
// 获得ArgumentConfig列表
List<ArgumentConfig> arguments = method.getArguments();
if (CollectionUtils.isNotEmpty(arguments)) {
// 遍历ArgumentConfig列表
for (ArgumentConfig argument : arguments) {
// convert argument type
// // 检测 type 属性是否为空,或者空串
if (argument.getType() != null && argument.getType().length() > 0) {
// 利用反射获取该服务的所有方法集合
Method[] methods = interfaceClass.getMethods();
// visit all methods
if (methods != null && methods.length > 0) {
// 遍历所有方法
for (int i = 0; i < methods.length; i++) {
// 获得方法名
String methodName = methods[i].getName();
// target the method, and get its signature
// 找到目标方法
if (methodName.equals(method.getName())) {
// 通过反射获取目标方法的参数类型数组 argtypes
Class<?>[] argtypes = methods[i].getParameterTypes();
// one callback in the method
// 如果下标为-1
if (argument.getIndex() != -1) {
// 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
// 添加 ArgumentConfig 字段信息到 map 中
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
// 不一致,则抛出异常
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
} else {
// multiple callbacks in the method
// 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
for (int j = 0; j < argtypes.length; j++) {
Class<?> argclazz = argtypes[j];
if (argclazz.getName().equals(argument.getType())) {
// 如果找到,则添加 ArgumentConfig 字段信息到 map 中
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
}
}
}
}
}
}
} else if (argument.getIndex() != -1) {
// 用户未配置 type 属性,但配置了 index 属性,且 index != -1,则直接添加到map
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
// 抛出异常
throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
}
} // end of methods for
}
// 如果是泛化调用,则在map中设置generic和methods
if (ProtocolUtils.isGeneric(generic)) {
map.put(Constants.GENERIC_KEY, generic);
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
// 获得版本号
String revision = Version.getVersion(interfaceClass, version);
// 放入map
if (revision != null && revision.length() > 0) {
map.put(Constants.REVISION_KEY, revision);
}
// 获得方法集合
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
// 如果为空,则告警
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
// 设置method为*
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
// 否则加入方法集合
map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
// 把token 的值加入到map中
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
// export service
// 获得地址
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
// 获得端口号
Integer port = this.findConfigedPorts(protocolConfig, name, map);
// 生成 URL
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
// —————————————————————————————————————分割线———————————————————————————————————————
// 加载 ConfiguratorFactory,并生成 Configurator 实例,判断是否有该协议的实现存在
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
// 通过实例配置 url
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
// // 如果 scope = none,则什么都不做
if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
// // scope != remote,暴露到本地
if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
// 暴露到本地
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
// // scope != local,导出到远程
if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
// 如果注册中心链接集合不为空
if (CollectionUtils.isNotEmpty(registryURLs)) {
// 遍历注册中心
for (URL registryURL : registryURLs) {
// 添加dynamic配置
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
// 加载监视器链接
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
// 添加监视器配置
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
// 获得代理方式
String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
// 添加代理方式到注册中心到url
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
}
// 为服务提供类(ref)生成 Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 暴露服务,并且生成Exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
// 加入到暴露者集合中
exporters.add(exporter);
}
} else {
// 不存在注册中心,则仅仅暴露服务,不会记录暴露到地址
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
// 如果元数据中心服务不为空,则发布该服务,也就是在元数据中心记录url中到部分配置
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
}
this.urls.add(url);
}</code></pre>
<p>先看分割线上面部分,就是组装URL的全过程,我觉得大致可以分为一下步骤:</p>
<ol>
<li>它把metrics、application、module、provider、protocol等所有配置都放入map中,</li>
<li>针对method都配置,先做签名校验,先找到该服务是否有配置的方法存在,然后该方法签名是否有这个参数存在,都核对成功才将method的配置加入map。</li>
<li>将泛化调用、版本号、method或者methods、token等信息加入map</li>
<li>获得服务暴露地址和端口号,利用map内数据组装成URL。</li>
</ol>
<h4>创建invoker(2,3)</h4>
<p>暴露到远程的源码直接看doExportUrlsFor1Protocol()方法分割线下半部分。当生成暴露者的时候,服务已经暴露,接下来会细致的分析这暴露内部的过程。可以发现无论暴露到本地还是远程,都会通过代理工厂创建invoker。这个时候就走到了上述时序图的ProxyFactory。我在这篇文章中有讲到invoker:<a href="https://segmentfault.com/a/1190000017787521">dubbo源码解析(十九)远程调用——开篇</a>,首先我们来看看invoker如何诞生的。Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory。JavassistProxyFactory中有一个getInvoker()方法。</p>
<h5>获取invoker方法</h5>
<h6>getInvoker()</h6>
<pre><code class="java">public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
// // 为目标类创建 Wrapper
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
// 创建匿名 Invoker 类对象,并实现 doInvoke 方法。
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}</code></pre>
<p>可以看到,该方法就是创建了一个匿名的Invoker类对象,在doInvoke()方法中调用wrapper.invokeMethod()方法。Wrapper 是一个抽象类,仅可通过 getWrapper(Class) 方法创建子类。在创建 Wrapper 子类的过程中,子类代码生成逻辑会对 getWrapper 方法传入的 Class 对象进行解析,拿到诸如类方法,类成员变量等信息。以及生成 invokeMethod 方法代码和其他一些方法代码。代码生成完毕后,通过 Javassist 生成 Class 对象,最后再通过反射创建 Wrapper 实例。那么我们先来看看getWrapper()方法:</p>
<h6>getWrapper()</h6>
<pre><code class="java">public static Wrapper getWrapper(Class<?> c) {
while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.
{
// 返回该对象的超类
c = c.getSuperclass();
}
// 如果超类就是Object,则返回子类Wrapper
if (c == Object.class) {
return OBJECT_WRAPPER;
}
// 从缓存中获取 Wrapper 实例
Wrapper ret = WRAPPER_MAP.get(c);
// 如果没有命中,则创建 Wrapper
if (ret == null) {
// 创建Wrapper
ret = makeWrapper(c);
// 写入缓存
WRAPPER_MAP.put(c, ret);
}
return ret;
}</code></pre>
<p>该方法只是对Wrapper 做了缓存。主要的逻辑在makeWrapper()。</p>
<h6>makeWrapper()</h6>
<pre><code class="java"> // 检测 c 是否为基本类型,若是则抛出异常
if (c.isPrimitive()) {
throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);
}
// 获得类名
String name = c.getName();
// 获得类加载器
ClassLoader cl = ClassHelper.getClassLoader(c);
// c1 用于存储 setPropertyValue 方法代码
StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
// c2 用于存储 getPropertyValue 方法代码
StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
// c3 用于存储 invokeMethod 方法代码
StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");
// 生成类型转换代码及异常捕捉代码,比如:
// DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }
c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
// pts 用于存储成员变量名和类型
Map<String, Class<?>> pts = new HashMap<>(); // <property name, property types>
// ms 用于存储方法描述信息(可理解为方法签名)及 Method 实例
Map<String, Method> ms = new LinkedHashMap<>(); // <method desc, Method instance>
// mns 为方法名列表
List<String> mns = new ArrayList<>(); // method names.
// dmns 用于存储“定义在当前类中的方法”的名称
List<String> dmns = new ArrayList<>(); // declaring method names.
// get all public field.
// 获取 public 访问级别的字段,并为所有字段生成条件判断语句
for (Field f : c.getFields()) {
String fn = f.getName();
Class<?> ft = f.getType();
if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) {
// 忽略关键字 static 或 transient 修饰的变量
continue;
}
// 生成条件判断及赋值语句,比如:
// if( $2.equals("name") ) { w.name = (java.lang.String) $3; return;}
// if( $2.equals("age") ) { w.age = ((Number) $3).intValue(); return;}
c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");
// 生成条件判断及返回语句,比如:
// if( $2.equals("name") ) { return ($w)w.name; }
c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");
// 存储 <字段名, 字段类型> 键值对到 pts 中
pts.put(fn, ft);
}
// 获得c类的所有方法
Method[] methods = c.getMethods();
// get all public method.
// 检测 c 中是否包含在当前类中声明的方法
boolean hasMethod = hasMethods(methods);
// 如果包含
if (hasMethod) {
c3.append(" try{");
for (Method m : methods) {
//ignore Object's method.
// 忽略 Object 中定义的方法
if (m.getDeclaringClass() == Object.class) {
continue;
}
// 获得方法的名称
String mn = m.getName();
// 生成方法名判断语句,比如:
// if ( "sayHello".equals( $2 )
c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
int len = m.getParameterTypes().length;
// 生成“运行时传入的参数数量与方法参数列表长度”判断语句,比如:
// && $3.length == 2
c3.append(" && ").append(" $3.length == ").append(len);
boolean override = false;
for (Method m2 : methods) {
// 检测方法是否存在重载情况,条件为:方法对象不同 && 方法名相同
if (m != m2 && m.getName().equals(m2.getName())) {
override = true;
break;
}
}
// 对重载方法进行处理,考虑下面的方法:
// 1. void sayHello(Integer, String)
// 2. void sayHello(Integer, Integer)
// 方法名相同,参数列表长度也相同,因此不能仅通过这两项判断两个方法是否相等。
// 需要进一步判断方法的参数类型
if (override) {
if (len > 0) {
for (int l = 0; l < len; l++) {
c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")
.append(m.getParameterTypes()[l].getName()).append("\")");
}
}
}
// 添加 ) {,完成方法判断语句,此时生成的代码可能如下(已格式化):
// if ("sayHello".equals($2)
// && $3.length == 2
// && $3[0].getName().equals("java.lang.Integer")
// && $3[1].getName().equals("java.lang.String")) {
c3.append(" ) { ");
// 根据返回值类型生成目标方法调用语句
if (m.getReturnType() == Void.TYPE) {
// w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;
c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
} else {
// return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);
c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");
}
c3.append(" }");
// 添加方法名到 mns 集合中
mns.add(mn);
// 检测当前方法是否在 c 中被声明的
if (m.getDeclaringClass() == c) {
// 若是,则将当前方法名添加到 dmns 中
dmns.add(mn);
}
ms.put(ReflectUtils.getDesc(m), m);
}
// 添加异常捕捉语句
c3.append(" } catch(Throwable e) { ");
c3.append(" throw new java.lang.reflect.InvocationTargetException(e); ");
c3.append(" }");
}
// 添加NoSuchMethodException异常
c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }");
// deal with get/set method.
Matcher matcher;
// 处理 get/set 方法
for (Map.Entry<String, Method> entry : ms.entrySet()) {
// 获得方法名称
String md = entry.getKey();
// 获得Method方法
Method method = entry.getValue();
// 如果是get开头的方法
if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
// 获取属性名
String pn = propertyName(matcher.group(1));
// 生成属性判断以及返回语句,示例如下:
// if( $2.equals("name") ) { return ($w).w.getName(); }
c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
pts.put(pn, method.getReturnType());
} else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
String pn = propertyName(matcher.group(1));
c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
// 存储属性名和返回类型到pts
pts.put(pn, method.getReturnType());
} else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
// 如果是set开头的方法
// 获得参数类型
Class<?> pt = method.getParameterTypes()[0];
// 获得属性名
String pn = propertyName(matcher.group(1));
// 生成属性判断以及 setter 调用语句,示例如下:
// if( $2.equals("name") ) { w.setName((java.lang.String)$3); return; }
c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
pts.put(pn, pt);
}
}
// 添加 NoSuchPropertyException 异常抛出代码
c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ".\"); }");
c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ".\"); }");
// make class
long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
// 创建类生成器
ClassGenerator cc = ClassGenerator.newInstance(cl);
// 设置类名及超类
cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
cc.setSuperClass(Wrapper.class);
// 添加默认的构造函数
cc.addDefaultConstructor();
// 添加字段
cc.addField("public static String[] pns;"); // property name array.
cc.addField("public static " + Map.class.getName() + " pts;"); // property type map.
cc.addField("public static String[] mns;"); // all method name array.
cc.addField("public static String[] dmns;"); // declared method name array.
for (int i = 0, len = ms.size(); i < len; i++) {
cc.addField("public static Class[] mts" + i + ";");
}
// 添加方法
cc.addMethod("public String[] getPropertyNames(){ return pns; }");
cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
cc.addMethod("public String[] getMethodNames(){ return mns; }");
cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
cc.addMethod(c1.toString());
cc.addMethod(c2.toString());
cc.addMethod(c3.toString());
try {
// 生成类
Class<?> wc = cc.toClass();
// setup static field.
// 设置字段值
wc.getField("pts").set(null, pts);
wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
wc.getField("mns").set(null, mns.toArray(new String[0]));
wc.getField("dmns").set(null, dmns.toArray(new String[0]));
int ix = 0;
for (Method m : ms.values()) {
wc.getField("mts" + ix++).set(null, m.getParameterTypes());
}
// 创建 Wrapper 实例
return (Wrapper) wc.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
cc.release();
ms.clear();
mns.clear();
dmns.clear();
}
}</code></pre>
<p>该方法有点长,大致可以分为几个步骤:</p>
<ol>
<li>初始化了c1、c2、c3、pts、ms、mns、dmns变量,向 c1、c2、c3 中添加方法定义和类型转换代码。</li>
<li>为 public 级别的字段生成条件判断取值与赋值代码</li>
<li>为定义在当前类中的方法生成判断语句,和方法调用语句。</li>
<li>处理 getter、setter 以及以 is/has/can 开头的方法。处理方式是通过正则表达式获取方法类型(get/set/is/...),以及属性名。之后为属性名生成判断语句,然后为方法生成调用语句。</li>
<li>通过 ClassGenerator 为刚刚生成的代码构建 Class 类,并通过反射创建对象。ClassGenerator 是 Dubbo 自己封装的,该类的核心是 toClass() 的重载方法 toClass(ClassLoader, ProtectionDomain),该方法通过 javassist 构建 Class。</li>
</ol>
<h4>服务暴露</h4>
<p>服务暴露分为暴露到本地 (JVM),和暴露到远程。doExportUrlsFor1Protocol()方法分割线下半部分就是服务暴露的逻辑。根据scope的配置分为:</p>
<ul>
<li>scope = none,不暴露服务</li>
<li>scope != remote,暴露到本地</li>
<li>scope != local,暴露到远程</li>
</ul>
<h5>暴露到本地</h5>
<p>导出本地执行的是ServiceConfig中的exportLocal()方法。</p>
<h6>exportLocal()(4)</h6>
<pre><code class="java">private void exportLocal(URL url) {
// 如果协议不是injvm
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
// 生成本地的url,分别把协议改为injvm,设置host和port
URL local = URLBuilder.from(url)
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
// 通过代理工程创建invoker
// 再调用export方法进行暴露服务,生成Exporter
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
// 把生成的暴露者加入集合
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}</code></pre>
<p>本地暴露调用的是injvm协议方法,也就是InjvmProtocol 的 export()方法。</p>
<h6>export()(5)</h6>
<pre><code class="java">public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}</code></pre>
<p>该方法只是创建了一个,因为暴露到本地,所以在同一个jvm中。所以不需要其他操作。</p>
<h5>暴露到远程</h5>
<p>暴露到远程的逻辑要比本地复杂的多,它大致可以分为服务暴露和服务注册两个过程。先来看看服务暴露。我们知道dubbo有很多协议实现,在doExportUrlsFor1Protocol()方法分割线下半部分中,生成了Invoker后,就需要调用protocol 的 export()方法,很多人会认为这里的export()就是配置中指定的协议实现中的方法,但这里是不对的。因为暴露到远程后需要进行服务注册,而RegistryProtocol的 export()方法就是实现了服务暴露和服务注册两个过程。所以这里的export()调用的是RegistryProtocol的 export()。</p>
<h6>export()</h6>
<pre><code class="java">public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 获得注册中心的url
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
//获得已经注册的服务提供者url
URL providerUrl = getProviderUrl(originInvoker);
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
// the same service. Because the subscribed is cached key with the name of the service, it causes the
// subscription information to cover.
// 获取override订阅 URL
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
// 创建override的监听器
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
// 把监听器添加到集合
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// 根据override的配置来覆盖原来的url,使得配置是最新的。
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
//export invoker
// 服务暴露
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// url to registry
// 根据 URL 加载 Registry 实现类,比如ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
// 返回注册到注册表的url并过滤url参数一次
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
// 生成ProviderInvokerWrapper,它会保存服务提供方和消费方的调用地址和代理对象
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
// ————————————————————————————————分割线——————————————————————————————————————
//to judge if we need to delay publish
// 获取 register 参数
boolean register = registeredProviderUrl.getParameter("register", true);
// 如果需要注册服务
if (register) {
// 向注册中心注册服务
register(registryUrl, registeredProviderUrl);
// 设置reg为true,表示服务注册
providerInvokerWrapper.setReg(true);
}
// Deprecated! Subscribe to override rules in 2.6.x or before.
// 向注册中心进行订阅 override 数据
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
// 设置注册中心url
exporter.setRegisterUrl(registeredProviderUrl);
// 设置override数据订阅的url
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
// 创建并返回 DestroyableExporter
return new DestroyableExporter<>(exporter);
}</code></pre>
<p>从代码上看,我用分割线分成两部分,分别是服务暴露和服务注册。该方法的逻辑大致分为以下几个步骤:</p>
<ol>
<li>获得服务提供者的url,再通过override数据重新配置url,然后执行doLocalExport()进行服务暴露。</li>
<li>加载注册中心实现类,向注册中心注册服务。</li>
<li>向注册中心进行订阅 override 数据。</li>
<li>创建并返回 DestroyableExporter</li>
</ol>
<p>服务暴露先调用的是RegistryProtocol的doLocalExport()方法</p>
<pre><code class="java">private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker);
// 加入缓存
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
// 创建 Invoker 为委托类对象
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
// 调用 protocol 的 export 方法暴露服务
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}</code></pre>
<p>这里的逻辑比较简单,主要是在这里根据不同的协议配置,调用不同的protocol实现。跟暴露到本地的时候实现InjvmProtocol一样。我这里假设配置选用的是dubbo协议,来继续下面的介绍。</p>
<h6>DubboProtocol的export()</h6>
<p><a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>中的(三)DubboProtocol有export()相关的源码分析, 从源码中可以看出做了一些本地存根的处理,关键的就是openServer,来启动服务器。</p>
<h6>DubboProtocol的openServer()</h6>
<p><a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>中的(三)DubboProtocol有openServer()相关的源码分析, 不过该文章中的代码是2.6.x的代码,最新的版本中加入了 <strong>DCL</strong>。其中reset方法则是重置服务器的一些配置。例如在同一台机器上(单网卡),同一个端口上仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时则调用 reset 方法重置服务器的一些配置。主要来看其中的createServer()方法。</p>
<h6>DubboProtocol的createServer()</h6>
<p><a href="https://segmentfault.com/a/1190000017973639">《dubbo源码解析(二十四)远程调用——dubbo协议》</a>中的(三)DubboProtocol有createServer()相关的源码分析,其中最新版本的默认远程通讯服务端实现方式已经改为netty4。该方法大致可以分为以下几个步骤:</p>
<ol>
<li>对服务端远程通讯服务端实现方式配置是否支持的检测</li>
<li>创建服务器实例,也就是调用bind()方法</li>
<li>对服务端远程通讯客户端实现方式配置是否支持的检测</li>
</ol>
<h6>Exchangers的bind()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>中的(二十一)Exchangers。</p>
<pre><code class="java">public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// 获取 Exchanger,默认为 HeaderExchanger。
// 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例
return getExchanger(url).bind(url, handler);
}</code></pre>
<h6>HeaderExchanger的bind()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>(十六)HeaderExchanger,其中bind()方法做了大致以下步骤:</p>
<ol>
<li>创建HeaderExchangeHandler</li>
<li>创建DecodeHandler</li>
<li>Transporters.bind(),创建服务器实例。</li>
<li>创建HeaderExchangeServer</li>
</ol>
<p>其中HeaderExchangeHandler、DecodeHandler、HeaderExchangeServer可以参考<a href="https://segmentfault.com/a/1190000017467343">《dubbo源码解析(十)远程通信——Exchange层》</a>中的讲解。</p>
<h6>Transporters的bind()(6)</h6>
<pre><code class="java">public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
// 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器
handler = new ChannelHandlerDispatcher(handlers);
}
// 获取自适应 Transporter 实例,并调用实例方法
return getTransporter().bind(url, handler);
}</code></pre>
<p>getTransporter() 方法获取的 Transporter 是在运行时动态创建的,类名为 TransporterAdaptive,也就是自适应拓展类。TransporterAdaptive 会在运行时根据传入的 URL 参数决定加载什么类型的 Transporter,默认为基于Netty4的实现。假设是 NettyTransporter 的 bind 方法。</p>
<h6>NettyTransporter的bind()(6)</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017553202">《dubbo源码解析(十七)远程通信——Netty4》</a>的(六)NettyTransporter。</p>
<pre><code class="java">public Server bind(URL url, ChannelHandler listener) throws RemotingException {
// 创建NettyServer
return new NettyServer(url, listener);
}</code></pre>
<h6>NettyServer的构造方法(7)</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017553202">《dubbo源码解析(十七)远程通信——Netty4》</a>的(五)NettyServer</p>
<pre><code class="java">public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}</code></pre>
<p>调用的是父类AbstractServer构造方法</p>
<h6>AbstractServer的构造方法(7)</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>的(三)AbstractServer中的构造方法。</p>
<p>服务器实例创建完以后,就是开启服务器了,AbstractServer中的doOpen是抽象方法,还是拿netty4来讲解,也就是看NettyServer的doOpen()的方法。</p>
<h6>NettyServer的doOpen()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017553202">《dubbo源码解析(十七)远程通信——Netty4》</a>中的(五)NettyServer中的源码分析。这里执行完成后,服务器被开启,服务也暴露出来了。接下来就是讲解服务注册的内容。</p>
<h5>服务注册(9)</h5>
<p>dubbo服务注册并不是必须的,因为dubbo支持直连的方式就可以绕过注册中心。直连的方式很多时候用来做服务测试。</p>
<p>回过头去看一下RegistryProtocol的 export()方法的分割线下面部分。其中服务注册先调用的是register()方法。</p>
<h6>RegistryProtocol的register()</h6>
<pre><code class="java">public void register(URL registryUrl, URL registeredProviderUrl) {
// 获取 Registry
Registry registry = registryFactory.getRegistry(registryUrl);
// 注册服务
registry.register(registeredProviderUrl);
}</code></pre>
<p>所以服务注册大致可以分为两步:</p>
<ol>
<li>获得注册中心实例</li>
<li>注册服务</li>
</ol>
<p>获得注册中心首先执行的是AbstractRegistryFactory的getRegistry()方法</p>
<h6>AbstractRegistryFactory的getRegistry()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000016905715">《dubbo源码解析(三)注册中心——开篇》</a>的(七)support包下的AbstractRegistryFactory中的源码解析。大概的逻辑就是先从缓存中取,如果没有命中,则创建注册中心实例,这里的createRegistry()是一个抽象方法,具体的实现逻辑由子类完成,假设这里使用zookeeper作为注册中心,则调用的是ZookeeperRegistryFactory的createRegistry()。</p>
<h6>ZookeeperRegistryFactory的createRegistry()</h6>
<pre><code class="java">public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}</code></pre>
<p>就是创建了一个ZookeeperRegistry,执行了ZookeeperRegistry的构造方法。</p>
<h6>ZookeeperRegistry的构造方法</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017132620">《dubbo源码解析(七)注册中心——zookeeper》</a>的(一)ZookeeperRegistry中的源码分析。大致的逻辑可以分为以下几个步骤:</p>
<ol>
<li>创建zookeeper客户端</li>
<li>添加监听器</li>
</ol>
<p>主要看ZookeeperTransporter的connect方法,因为当connect方法执行完后,注册中心创建过程就结束了。首先执行的是AbstractZookeeperTransporter的connect方法。</p>
<h6>AbstractZookeeperTransporter的connect()</h6>
<pre><code class="java">public ZookeeperClient connect(URL url) {
ZookeeperClient zookeeperClient;
// 获得所有url地址
List<String> addressList = getURLBackupAddress(url);
// The field define the zookeeper server , including protocol, host, port, username, password
// 从缓存中查找可用的客户端,如果有,则直接返回
if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
logger.info("find valid zookeeper client from the cache for address: " + url);
return zookeeperClient;
}
// avoid creating too many connections, so add lock
synchronized (zookeeperClientMap) {
if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
logger.info("find valid zookeeper client from the cache for address: " + url);
return zookeeperClient;
}
// 创建客户端
zookeeperClient = createZookeeperClient(toClientURL(url));
logger.info("No valid zookeeper client found from cache, therefore create a new client for url. " + url);
// 加入缓存
writeToClientMap(addressList, zookeeperClient);
}
return zookeeperClient;
}</code></pre>
<p>看上面的源码,主要是执行了createZookeeperClient()方法,而该方法是一个抽象方法,由子类实现,这里是CuratorZookeeperTransporter的createZookeeperClient()</p>
<h6>CuratorZookeeperTransporter的createZookeeperClient()</h6>
<pre><code class="java">public ZookeeperClient createZookeeperClient(URL url) {
return new CuratorZookeeperClient(url);
}</code></pre>
<p>这里就是执行了CuratorZookeeperClient的构造方法。</p>
<h6>CuratorZookeeperClient的构造方法</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017565522">《dubbo源码解析(十八)远程通信——Zookeeper》</a>的(四)CuratorZookeeperClient中的源码分析,其中逻辑主要用于创建和启动 CuratorFramework 实例,基本都是调用Curator框架的API。</p>
<p>创建完注册中心的实例后,我们就要进行注册服务了。也就是调用的是FailbackRegistry的register()方法。</p>
<h6>FailbackRegistry的register()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000016905715">《dubbo源码解析(三)注册中心——开篇》</a>的(六)support包下的FailbackRegistry中的源码分析。可以看到关键是执行了doRegister()方法,该方法是抽象方法,由子类完成。这里因为假设是zookeeper,所以执行的是ZookeeperRegistry的doRegister()。</p>
<h6>ZookeeperRegistry的doRegister()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017132620">《dubbo源码解析(七)注册中心——zookeeper》</a>的(一)ZookeeperRegistry中的源代码,可以看到逻辑就是调用Zookeeper 客户端创建服务节点。节点路径由 toUrlPath 方法生成。而这里create方法执行的是AbstractZookeeperClient的create() 方法</p>
<h6>AbstractZookeeperClient的create()</h6>
<p>可以参考<a href="https://segmentfault.com/a/1190000017565522">dubbo源码解析(十八)远程通信——Zookeeper》</a>的(二)AbstractZookeeperClient中的源代码分析。createEphemeral()和createPersistent()是抽象方法,具体实现由子类完成,也就是CuratorZookeeperClient类。代码逻辑比较简单。我就不再赘述。到这里为止,服务也就注册完成。</p>
<p>关于向注册中心进行订阅 override 数据的规则在最新版本有一些大变动,跟2.6.x及以前的都不一样。所以这部分内容在新特性中去讲解。</p>
<h3>后记</h3>
<blockquote>参考官方文档:<a href="https://link.segmentfault.com/?enc=cBggShpRIImc3O7RqPWwUg%3D%3D.wZqj5JERhOqWruSjwp8LOd2Th9jAqxEGEETgjXqlTsINCW5XHTv4CMEM4wFJG%2BTlf8nq5o1qiBFriv436SZ9c1C6xVvRkcZCVmYOBOUcEWU%3D" rel="nofollow">https://dubbo.apache.org/zh-c...</a>
</blockquote>
<p>该文章讲解了dubbo的服务暴露过程,也是为了之后讲2.7新特性做铺垫,下一篇讲解服务的引用过程。</p>
Dubbo源码解析(四十三)2.7新特性
https://segmentfault.com/a/1190000018657457
2019-03-26T12:24:44+08:00
2019-03-26T12:24:44+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
3
<h2>DUBBO——2.7大揭秘</h2>
<blockquote>目标:了解2.7的新特性,以及版本升级的引导。</blockquote>
<h3>前言</h3>
<p>我们知道Dubbo在2011年开源,停止更新了一段时间。在2017 年 9 月 7 日,Dubbo 悄悄的在 GitHub 发布了 2.5.4 版本。随后,版本发布的非常迅速,Dubbo项目被重启了,经过大半年的更新,在2018年2月15日,Dubbo 获得了 14 张赞成票,在无弃权和反对票的情况下,正式通过投票,顺利成为 Apache 基金会孵化项目。现在的Dubbo社区非常活跃,版本进度也非常的快。</p>
<p><img src="/img/remote/1460000018657460" alt="dubbo社区" title="dubbo社区"></p>
<p>从上图就可以看出dubbo现在的活跃度。</p>
<p>现在dubbo项目有以下几个分支:</p>
<ol>
<li>2.5.x:该分支在近期投票决定不再维护。</li>
<li>2.6.x:该分支现在还在维护中,包名前缀是com.alibaba,也是贡献给 Apache 之前的版本。</li>
<li>2.7.1-release:一个临时分支。</li>
<li>3.x-dev:将以 Streaming 为内核,重点的改变在服务治理和编程模型上。具体我也还没有深入研究,我也会跟踪该分支的变动,敬请期待吧。</li>
<li>master:目前版本是2.7.x,包名前缀是:org.apache,也是 Dubbo 贡献给 Apache 的开发版本,接下来的分析也会在2.7.1上进行分析。</li>
</ol>
<p>关注dubbo社区的朋友应该也知道在2019.3.23在南京举办了Meetup,其中有一个专题就是讲2.7新特性介绍。我就在分享者的基础上讲解一下自己的理解。</p>
<h3>正文</h3>
<h4>(一)JDK版本</h4>
<p>在所需的最小JDK版本从以前的1.6变成了1.8。</p>
<h4>(二)包重命名</h4>
<p>com.alibaba.dubbo - > org.apache.dubbo</p>
<h4>(三)异步支持优化</h4>
<p>我们知道dubbo协议本身支持三种发送请求方式:</p>
<ol>
<li>单向发送:执行方法不需要返回结果</li>
<li>同步发送:执行方法后,等待结果返回,否则一直阻塞.</li>
<li>
<p>异步发送:也就是当我发送调用后,我不阻塞等待结果,直接返回,将返回的future保存到上下文,方便后期使用。在异步发送中有两种方式分别是</p>
<ol>
<li>future:当请求有响应后,通过future.get()来获得响应结果,但是future.get()会导致线程阻塞,future从RpcContext获取。</li>
<li>callback:设置一个回调线程,当接收到响应时,自动执行,不会对当前线程造成阻塞,自定义ResponseFuture支持callback。</li>
</ol>
</li>
</ol>
<p>2.6.x版本的异步方式提供了一些异步能力,包括Consumer端异步调用、参数回调、事件通知等。但当前的异步方式存在以下问题:</p>
<ul>
<li>Future获取方式不够直接,只能在RpcContext中进行获取;</li>
<li>Future只支持阻塞式的get()接口获取结果。</li>
<li>Future接口无法实现自动回调,而自定义ResponseFuture虽支持callback回调但支持的异步场景有限,如不支持Future间的相互协调或组合等;</li>
<li>不支持Provider端异步</li>
</ul>
<p>具体的可以参考该文章<a href="https://segmentfault.com/a/1190000017973639">dubbo源码解析(二十四)远程调用——dubbo协议</a>中的源码分析来理解其中存在的问题。</p>
<p>那么在2.7.x版本,由于JDK版本升级到了1.8,引入了JDK1.8 中的CompletableFuture接口,CompletableFuture支持 future 和 callback 两种调用方式。关于CompletableFuture怎么被运用到dubbo中我会在后续的文章介绍。引入该接口后,做了以下优化:</p>
<ul>
<li>支持Provider端异步</li>
<li>支持直接定义返回CompletableFuture的服务接口。通过这种类型的接口,我们可以更自然的实现Consumer、Provider端的异步编程。</li>
</ul>
<pre><code class="java">public interface AsyncService {
CompletableFuture<String> sayHello(String name);
}</code></pre>
<ul><li>如果你不想将接口的返回值定义为Future类型,或者存在定义好的同步类型接口,则可以额外定义一个异步接口并提供Future类型的方法。</li></ul>
<pre><code class="java">public interface GreetingsService {
String sayHi(String name);
}</code></pre>
<pre><code class="java">@AsyncFor(GreetingsService.class)
public interface GrettingServiceAsync extends GreetingsService {
CompletableFuture<String> sayHiAsync(String name);
}</code></pre>
<ul><li>如果你的原始接口定义不是Future类型的返回值,Provider端异步也提供了类似Servlet3.0里的Async Servlet的编程接口: <code>RpcContext.startAsync()</code>
</li></ul>
<pre><code class="java">public interface AsyncService {
String sayHello(String name);
}</code></pre>
<pre><code class="java">public class AsyncServiceImpl implements AsyncService {
public String sayHello(String name) {
final AsyncContext asyncContext = RpcContext.startAsync();
new Thread(() -> {
asyncContext.write("Hello " + name + ", response from provider.");
}).start();
return null;
}
}</code></pre>
<ul><li>异步过滤器链回调。</li></ul>
<p>具体的实现原理我在后续文章中结合源码来讲解,<strong>注意</strong>:这些改动都仅仅支持dubbo协议。</p>
<h4>(四)元数据改造</h4>
<p>我们知道2.7以前的版本只有注册中心,注册中心的URL有数十个key/value的键值对,包含了一个服务所有的元数据。在越来越多的功能被增加,元数据也变得异常庞大,就出现了下面的问题:</p>
<ul>
<li>注册中心存储的URL过长:这会导致存储的压力骤增,数据庞大导致在修改元数据后的通知效率也下降,并且增加了消费者对于元数据解析的压力,尤其是在大规模场景下的内存增长显著</li>
<li>注册中心承担了过多的服务治理配置的功能:初始配置的同步、存储各种运行期配置规则加剧了注册中心的压力,配置规则的灵活性也有所限制,阻碍了市场上的一些优秀微服务配置中心的集成和扩展。</li>
<li>属性的功能定位不清晰:methods,pid,owner虽然是为了查询服务而注册的属性,但是这些简陋的信息很难满足查询服务治理需求,所以需要更加丰富的注册数据。例如methods,虽然方法列表的内容已经很长,但是在ops开发服务测试/mock功能时,发现需要的方法签名等数据还是无法获取。</li>
</ul>
<p>针对以上问题,在2.7中,将URL中的元数据划分了三个部分:</p>
<ul>
<li>元数据信息:接口的完整定义,包含接口名,接口所含的方法,以及方法所含的出入参信息。对于服务测试和服务mock有很重要作用。</li>
<li>执行链路上数据:需要将参数从provider端传递给consume端,让consume端感知的到,比如token、timeout等</li>
<li>服务自己持有的配置&Ops需求:只有provider端自己需要或者consume端自己需要的数据,比如executes、document等</li>
</ul>
<p>改造后,分别形成三大中心:</p>
<ul>
<li>注册中心:理想情况下,注册中心将只用于关键服务信息(核心链路)的同步,进一步减轻注册中心的存储压力,提高地址同步效率,同时缓解当前由于URL冗余在大规模推送时造成的Consumer端内存计算压力。</li>
<li>配置中心:解决当前配置和地址信息耦合的问题,通过抽象动态配置层,让开发者可以对接微服务场景下更常用的、更专业的配置中心,如Nacos, Apollo, Consul, Etcd等;提供更灵活的、更丰富的配置规则,包括服务、应用不同粒度的配置,更丰富的路由规则,集中式管理的动态参数规则等</li>
<li>服务查询治理中心:对于纯粹的服务查询相关的数据,包括Consumer的服务订阅数据,往往都是注册后不可变的并且不需要节点间的同步,如当前URL可以看到的methods、owner等key以及所有的Consumer端URL,目前支持 redis(推荐),zookeeper,将作为<a href="https://link.segmentfault.com/?enc=54UqfpUdB0sOTeOvvvzAZQ%3D%3D.Zmaa%2BeG1q41Pp7syIZ46XDzthcFvYFPWcxtbnrKTbi%2BSpf8eACPSy8l9iUq5BVO4" rel="nofollow">Dubbo-Admin</a>支持的服务测试,模拟和其他服务治理功能的基础。</li>
</ul>
<h4>(五)服务治理规则增强</h4>
<h5>路由规则的增强</h5>
<p>Dubbo 提供了具有一定扩展性的路由规则,其中具有代表性的是条件路由和脚本路由。2.6.x及以下版本存在的问题:</p>
<ul>
<li>路由规则存储在注册中心</li>
<li>只支持服务粒度的路由,应用级别无法定义路由规则</li>
<li>支持路由缓存,但基本不具有扩展性</li>
<li>一个服务或应用允许定义多条路由规则,服务治理无法管控</li>
<li>实现上,每条规则生成一个Router实例并动态加载</li>
</ul>
<p>在2.7.x版本中,对路由规则做了增强:</p>
<ul><li>
<p>丰富的路由规则。</p>
<ul>
<li>条件路由:支持应用程序级别和服务级别条件。</li>
<li>标记路由:新引入以更好地支持流量隔离,例如灰色部署</li>
</ul>
</li></ul>
<h5>配置中心对服务治理的加成</h5>
<ul>
<li>将治理规则与注册表分离,也就是出现了配置中心,使配置中心更容易扩展。有Apollo和Zookeeper,2.7.1还支持了consul和etcd。</li>
<li>应用程序级动态配置支持。</li>
<li>使用YAML作为配置语言,更易于阅读和使用</li>
</ul>
<h4>(六)新增配置中心</h4>
<p>配置中心(v2.7.0)在Dubbo中承担两个职责:</p>
<ul>
<li>外部化配置:启动配置的集中式存储 (简单理解为dubbo.properties的外部化存储)外部化配置目的之一是实现配置的集中式管理,这部分业界已经有很多成熟的专业配置系统如Apollo, Nacos等,Dubbo所做的主要是保证能配合这些系统正常工作。外部化配置和其他本地配置在内容和格式上并无区别,可以简单理解为<code>dubbo.properties</code>的外部化存储,配置中心更适合将一些公共配置如注册中心、元数据中心配置等抽取以便做集中管理</li>
<li>服务治理:服务治理规则的存储与通知。</li>
</ul>
<p>配置的操作可以查看官方文档,由于现在dubbo支持多种配置方式,所以这里需要强调的是配置覆盖的优先级,从上至下优先级依此降低:</p>
<p><img src="/img/remote/1460000018657461" alt="configuration (1)" title="configuration (1)"></p>
<h4>(七)序列化扩展</h4>
<p>新增了Protobuf序列化支持。</p>
<h4>(八)其他</h4>
<p>其他的bug修复以及一些小细节优化请查看github上的Issues或者PR。</p>
<h3>后记</h3>
<blockquote>升级2.7.0的引导请查看以下链接:<a href="https://link.segmentfault.com/?enc=f4V1jMkmUp%2FtMlzE%2BaLvMQ%3D%3D.lwcJjjoLsvsnYCcnvrvi%2FFT%2F3QES96mJZ2zFeff6Fl5XuhcN8PoTu1rVTWI%2BwawL%2B1LUOhry%2B9%2BMaYHYCSEY9y00p3RvaXOyHu9rfwcMg5Q%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<p>该文章讲解了dubbo2.7的新特性,现在2.7.1已经发布,有兴趣的可以去看看2.7.1新增了什么。下一篇我就先从源码的角度来讲讲这个异步化的改造。</p>
Dubbo源码解析(四十二)序列化——开篇
https://segmentfault.com/a/1190000018505481
2019-03-14T16:26:03+08:00
2019-03-14T16:26:03+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
4
<h2>序列化——开篇</h2>
<blockquote>目标:介绍dubbo中序列化的内容,对dubbo中支持的序列化方式做对比,介绍dubbo-serialization-api下的源码</blockquote>
<h3>前言</h3>
<p>序列化就是将对象转成字节流,用于网络传输,以及将字节流转为对象,用于在收到字节流数据后还原成对象。序列化的好处我就不多说了,无非就是安全性更好、可跨平台等。网上有很多总结的很好,我在这里主要讲讲dubbo中序列化的设计和实现了哪些序列化方式。</p>
<p>dubbo在2.6.x版本中,支持五种序列化方式,分别是</p>
<ol>
<li>fastjson:依赖阿里的fastjson库,功能强大(支持普通JDK类包括任意Java Bean Class、Collection、Map、Date或enum)</li>
<li>fst:完全兼容JDK序列化协议的系列化框架,序列化速度大概是JDK的4-10倍,大小是JDK大小的1/3左右。</li>
<li>hessian2:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubbo RPC默认启用的序列化方式</li>
<li>jdk:JDK自带的Java序列化实现。</li>
<li>kryo:是一个快速序列化/反序列化工具,其使用了字节码生成机制(底层依赖了 ASM 库),因此具有比较好的运行速度,速度快,序列化后体积小,跨语言支持较复杂</li>
</ol>
<p>在dubbo最新的2.7.0版本中支持了protostuff,之前的版本dubbo还实现了自己的dubbo序列化,但是由于还不够成熟,所有暂时移除了dubbo序列化的实现。</p>
<p>从性能上对比,fst和kryo>hessian2>fastjson>jdk。</p>
<p>他们具体的实现我不讲解,因为很多都直接使用了对应的依赖裤,我只讲解dubbo序列化的接口设计。</p>
<p><img src="/img/remote/1460000018505484" alt="serialization" title="serialization"></p>
<h3>源码分析</h3>
<h4>(一)DataInput</h4>
<pre><code class="java">public interface DataInput {
/**
* Read boolean.
* 读取布尔类型
* @return boolean.
* @throws IOException
*/
boolean readBool() throws IOException;
/**
* Read byte.
* 读取字节
* @return byte value.
* @throws IOException
*/
byte readByte() throws IOException;
/**
* Read short integer.
* 读取short类型
* @return short.
* @throws IOException
*/
short readShort() throws IOException;
/**
* Read integer.
* 读取integer类型
* @return integer.
* @throws IOException
*/
int readInt() throws IOException;
/**
* Read long.
* 读取long类型
* @return long.
* @throws IOException
*/
long readLong() throws IOException;
/**
* Read float.
* 读取float类型
* @return float.
* @throws IOException
*/
float readFloat() throws IOException;
/**
* Read double.
* 读取double类型
* @return double.
* @throws IOException
*/
double readDouble() throws IOException;
/**
* Read UTF-8 string.
* 读取UTF-8 string
* @return string.
* @throws IOException
*/
String readUTF() throws IOException;
/**
* Read byte array.
* 读取byte数组
* @return byte array.
* @throws IOException
*/
byte[] readBytes() throws IOException;
}</code></pre>
<p>该接口是数据输入接口,可以看到定义了从 InputStream 中各类数据类型的读取方法。</p>
<h4>(二)DataOutput</h4>
<pre><code class="java">public interface DataOutput {
/**
* Write boolean.
* 输出boolean类型
* @param v value.
* @throws IOException
*/
void writeBool(boolean v) throws IOException;
/**
* Write byte.
* 输出byte类型
* @param v value.
* @throws IOException
*/
void writeByte(byte v) throws IOException;
/**
* Write short.
* 输出short类型
* @param v value.
* @throws IOException
*/
void writeShort(short v) throws IOException;
/**
* Write integer.
* 输出integer类型
* @param v value.
* @throws IOException
*/
void writeInt(int v) throws IOException;
/**
* Write long.
* 输出long类型
* @param v value.
* @throws IOException
*/
void writeLong(long v) throws IOException;
/**
* Write float.
* 输出float类型
* @param v value.
* @throws IOException
*/
void writeFloat(float v) throws IOException;
/**
* Write double.
* 输出double类型
* @param v value.
* @throws IOException
*/
void writeDouble(double v) throws IOException;
/**
* Write string.
* 输出string类型
* @param v value.
* @throws IOException
*/
void writeUTF(String v) throws IOException;
/**
* Write byte array.
* 输出byte数组
* @param v value.
* @throws IOException
*/
void writeBytes(byte[] v) throws IOException;
/**
* Write byte array.
* 输出byte数组中部分数据
* @param v value.
* @param off offset.
* @param len length.
* @throws IOException
*/
void writeBytes(byte[] v, int off, int len) throws IOException;
/**
* Flush buffer.
* 刷新缓冲区
* @throws IOException
*/
void flushBuffer() throws IOException;
}</code></pre>
<p>该接口是数据输出接口,可以看到定义了向 InputStream 中,写入基本类型的数据。</p>
<h4>(三)ObjectOutput</h4>
<pre><code class="java">public interface ObjectOutput extends DataOutput {
/**
* write object.
* 输入object类型
* @param obj object.
*/
void writeObject(Object obj) throws IOException;
}</code></pre>
<p>在 DataOutput 的基础上,增加写入object类型的数据。</p>
<h4>(四)ObjectInput</h4>
<pre><code class="java">public interface ObjectInput extends DataInput {
/**
* read object.
* 读取object类型数据
* @return object.
*/
Object readObject() throws IOException, ClassNotFoundException;
/**
* read object.
* 根据class类型读取object类型数据
* @param cls object type.
* @return object.
*/
<T> T readObject(Class<T> cls) throws IOException, ClassNotFoundException;
/**
* read object.
* 取object类型数据
* @param cls object type.
* @return object.
*/
<T> T readObject(Class<T> cls, Type type) throws IOException, ClassNotFoundException;
}</code></pre>
<p>该接口是继承了DataInput 接口,在 DataInput 的基础上,增加读取object类型的数据。</p>
<h4>(五)Cleanable</h4>
<pre><code class="java">public interface Cleanable {
/**
* 清理
*/
void cleanup();
}</code></pre>
<p>该接口是清理接口,定义了一个清理方法。目前只有kryo实现的时候,完成序列化或反序列化,需要做清理。通过实现该接口,执行清理的逻辑。</p>
<h4>(六)Serialization</h4>
<pre><code class="java">@SPI("hessian2")
public interface Serialization {
/**
* get content type id
* 获得内容类型编号
* @return content type id
*/
byte getContentTypeId();
/**
* get content type
* 获得内容类型名
* @return content type
*/
String getContentType();
/**
* create serializer
* 创建 ObjectOutput 对象,序列化输出到 OutputStream
* @param url
* @param output
* @return serializer
* @throws IOException
*/
@Adaptive
ObjectOutput serialize(URL url, OutputStream output) throws IOException;
/**
* create deserializer
* 创建 ObjectInput 对象,从 InputStream 反序列化
* @param url
* @param input
* @return deserializer
* @throws IOException
*/
@Adaptive
ObjectInput deserialize(URL url, InputStream input) throws IOException;
}</code></pre>
<p>该接口是序列化接口,该接口也是可扩展接口,默认是使用hessian2序列化方式。其中定义了序列化和反序列化等方法</p>
<h4>(七)SerializableClassRegistry</h4>
<pre><code class="java">public abstract class SerializableClassRegistry {
/**
* 可序列化类类的集合
*/
private static final Set<Class> registrations = new LinkedHashSet<Class>();
/**
* only supposed to be called at startup time
* 把可序列化的类加入到集合
*/
public static void registerClass(Class clazz) {
registrations.add(clazz);
}
/**
* 获得可序列化的类的集合
* @return
*/
public static Set<Class> getRegisteredClasses() {
return registrations;
}
}</code></pre>
<p>该类提供一个序列化统一的注册中心,其实就是封装了可序列化类的集合</p>
<h4>(八)SerializationOptimizer</h4>
<pre><code class="java">public interface SerializationOptimizer {
/**
* 需要序列化的类的集合
* @return
*/
Collection<Class> getSerializableClasses();
}</code></pre>
<p>该接口序列化优化器接口,在 Kryo 、FST 中,支持配置需要优化的类。业务系统中,可以实现自定义的 SerializationOptimizer,进行配置。或者使用文件来配置也是一个选择。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=xhH%2Fd7Xy6oNexUx7FA18Ug%3D%3D.JXByHbT5p5DS3l59V%2F1JfXldvQVYVWr8UElPtTi9xLySia49fffJqz9uLjjunn74VnW8ZwO%2Fz%2F7Lk1m%2BzzZpy%2FOAm5h20qEhzo2PTE83mcBzcRkdQLeWYNiWkqtSsTMdfy56lgX5poHeIBXgXr1uqCPXoZ0EmQZLyKFJ1pyK1XcC7u2PW%2FNxOYYzxQXhnhosuUxTdwSi%2FeqgGLwcemmR2w%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了dubbo支持的几种序列化方式,介绍了序列化的接口设计,具体的实现我不再讲述,因为大部分都是调用了不同的依赖库。接下来我会说一个分割线,我讲开始讲解2.7.x版本的新特性,然后分析新特性的实现,下一篇就先讲解一下dubbo2.7.0的大改动。</p>
Dubbo源码解析(四十一)集群——Mock
https://segmentfault.com/a/1190000018154297
2019-02-14T17:31:17+08:00
2019-02-14T17:31:17+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
4
<h2>集群——Mock</h2>
<blockquote>目标:介绍dubbo中集群的Mock,介绍dubbo-cluster下关于服务降级和本地伪装的源码。</blockquote>
<h3>前言</h3>
<p>本文讲解两块内容,分别是本地伪装和服务降级,本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。而服务降级则是临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。</p>
<h3>源码分析</h3>
<h4>(一)MockClusterWrapper</h4>
<pre><code class="java">public class MockClusterWrapper implements Cluster {
private Cluster cluster;
public MockClusterWrapper(Cluster cluster) {
this.cluster = cluster;
}
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建MockClusterInvoker
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
}</code></pre>
<p>该类是服务降级的装饰器类,对Cluster进行了功能增强,增强了服务降级的功能。</p>
<h4>(二)MockClusterInvoker</h4>
<p>该类是服务降级中定义降级后的返回策略的实现。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(MockClusterInvoker.class);
/**
* 目录
*/
private final Directory<T> directory;
/**
* invoker对象
*/
private final Invoker<T> invoker;</code></pre>
<h5>2.invoke</h5>
<pre><code class="java">@Override
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
// 获得 "mock" 配置项,有多种配置方式
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
// 如果没有mock
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
//no mock
// 直接调用
result = this.invoker.invoke(invocation);
// 如果强制服务降级
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
//force:direct mock
// 直接调用 Mock Invoker ,执行本地 Mock 逻辑
result = doMockInvoke(invocation, null);
} else {
//fail-mock
// 失败服务降级
try {
// 否则正常调用
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}
// 如果调用失败,则服务降级
result = doMockInvoke(invocation, e);
}
}
}
return result;
}</code></pre>
<p>该方法是定义降级后的返回策略的实现,根据配置的不同来决定不用降级还是强制服务降级还是失败后再服务降级。</p>
<h5>3.doMockInvoke</h5>
<pre><code class="java">@SuppressWarnings({"unchecked", "rawtypes"})
private Result doMockInvoke(Invocation invocation, RpcException e) {
Result result = null;
Invoker<T> minvoker;
// 路由匹配 Mock Invoker 集合
List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
// 如果mockInvokers为空,则创建一个MockInvoker
if (mockInvokers == null || mockInvokers.isEmpty()) {
// 创建一个MockInvoker
minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());
} else {
// 取出第一个
minvoker = mockInvokers.get(0);
}
try {
// 调用invoke
result = minvoker.invoke(invocation);
} catch (RpcException me) {
// 如果抛出异常,则返回异常结果
if (me.isBiz()) {
result = new RpcResult(me.getCause());
} else {
throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause());
}
} catch (Throwable me) {
throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
}
return result;
}</code></pre>
<p>该方法是执行本地Mock,服务降级。</p>
<h5>4.selectMockInvoker</h5>
<pre><code class="java">private List<Invoker<T>> selectMockInvoker(Invocation invocation) {
List<Invoker<T>> invokers = null;
//TODO generic invoker?
if (invocation instanceof RpcInvocation) {
//Note the implicit contract (although the description is added to the interface declaration, but extensibility is a problem. The practice placed in the attachement needs to be improved)
// 注意隐式契约(尽管描述被添加到接口声明中,但是可扩展性是一个问题。附件中的做法需要改进)
((RpcInvocation) invocation).setAttachment(Constants.INVOCATION_NEED_MOCK, Boolean.TRUE.toString());
//directory will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is present in invocation, otherwise, a list of mock invokers will return.
// 如果调用中存在Constants.INVOCATION_NEED_MOCK,则目录将返回正常调用者列表,否则,将返回模拟调用者列表。
try {
invokers = directory.list(invocation);
} catch (RpcException e) {
if (logger.isInfoEnabled()) {
logger.info("Exception when try to invoke mock. Get mock invokers error for service:"
+ directory.getUrl().getServiceInterface() + ", method:" + invocation.getMethodName()
+ ", will contruct a new mock with 'new MockInvoker()'.", e);
}
}
}
return invokers;
}</code></pre>
<p>该方法是路由匹配 Mock Invoker 集合。</p>
<h4>(三)MockInvokersSelector</h4>
<p>该类是路由选择器实现类。</p>
<h5>1.route</h5>
<pre><code class="java">@Override
public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
URL url, final Invocation invocation) throws RpcException {
// 如果附加值为空,则直接
if (invocation.getAttachments() == null) {
// 获得普通的invoker集合
return getNormalInvokers(invokers);
} else {
// 获得是否需要降级的值
String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
// 如果为空,则获得普通的Invoker集合
if (value == null)
return getNormalInvokers(invokers);
else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
// 获得MockedInvoker集合
return getMockedInvokers(invokers);
}
}
return invokers;
}</code></pre>
<p>该方法是根据配置来决定选择普通的invoker集合还是mockInvoker集合。</p>
<h5>2.getMockedInvokers</h5>
<pre><code class="java">private <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) {
// 如果没有MockedInvoker,则返回null
if (!hasMockProviders(invokers)) {
return null;
}
// 找到MockedInvoker,往sInvokers中加入,并且返回
List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1);
for (Invoker<T> invoker : invokers) {
if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) {
sInvokers.add(invoker);
}
}
return sInvokers;
}</code></pre>
<p>该方法是获得MockedInvoker集合。</p>
<h5>3.getNormalInvokers</h5>
<pre><code class="java">private <T> List<Invoker<T>> getNormalInvokers(final List<Invoker<T>> invokers) {
// 如果没有MockedInvoker,则返回普通的Invoker 集合
if (!hasMockProviders(invokers)) {
return invokers;
} else {
// 否则 去除MockedInvoker,把普通的Invoker 集合返回
List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(invokers.size());
for (Invoker<T> invoker : invokers) {
// 把不是MockedInvoker的invoker加入sInvokers,返回
if (!invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) {
sInvokers.add(invoker);
}
}
return sInvokers;
}
}</code></pre>
<p>该方法是获得普通的Invoker集合,不包含mock的。</p>
<h5>4.hasMockProviders</h5>
<pre><code class="java">private <T> boolean hasMockProviders(final List<Invoker<T>> invokers) {
boolean hasMockProvider = false;
for (Invoker<T> invoker : invokers) {
// 如果有一个是MockInvoker,则返回true
if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) {
hasMockProvider = true;
break;
}
}
return hasMockProvider;
}</code></pre>
<p>该方法是判断是否有MockInvoker。</p>
<p>以上三个类是对服务降级功能的实现,下面两个类是对本地伪装的实现。</p>
<h4>(四)MockProtocol</h4>
<p>该类实现了AbstractProtocol接口,是服务</p>
<pre><code class="java">final public class MockProtocol extends AbstractProtocol {
@Override
public int getDefaultPort() {
return 0;
}
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
throw new UnsupportedOperationException();
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 创建MockInvoker
return new MockInvoker<T>(url);
}
}</code></pre>
<h4>(五)MockInvoker</h4>
<p>本地伪装的invoker实现类。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 代理工厂
*/
private final static ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
/**
* mock 与 Invoker 对象的映射缓存
*/
private final static Map<String, Invoker<?>> mocks = new ConcurrentHashMap<String, Invoker<?>>();
/**
* 异常集合
*/
private final static Map<String, Throwable> throwables = new ConcurrentHashMap<String, Throwable>();
/**
* url对象
*/
private final URL url;</code></pre>
<h5>2.parseMockValue</h5>
<pre><code class="java">public static Object parseMockValue(String mock, Type[] returnTypes) throws Exception {
Object value = null;
// 如果mock为empty,则
if ("empty".equals(mock)) {
// 获得空的对象
value = ReflectUtils.getEmptyObject(returnTypes != null && returnTypes.length > 0 ? (Class<?>) returnTypes[0] : null);
} else if ("null".equals(mock)) {
// 如果为null,则返回null
value = null;
} else if ("true".equals(mock)) {
// 如果为true,则返回true
value = true;
} else if ("false".equals(mock)) {
// 如果为false,则返回false
value = false;
} else if (mock.length() >= 2 && (mock.startsWith("\"") && mock.endsWith("\"")
|| mock.startsWith("\'") && mock.endsWith("\'"))) {
// 使用 '' 或 "" 的字符串,截取掉头尾
value = mock.subSequence(1, mock.length() - 1);
} else if (returnTypes != null && returnTypes.length > 0 && returnTypes[0] == String.class) {
// 字符串
value = mock;
} else if (StringUtils.isNumeric(mock)) {
// 是数字
value = JSON.parse(mock);
} else if (mock.startsWith("{")) {
// 是map类型的
value = JSON.parseObject(mock, Map.class);
} else if (mock.startsWith("[")) {
// 是数组类型
value = JSON.parseObject(mock, List.class);
} else {
value = mock;
}
if (returnTypes != null && returnTypes.length > 0) {
value = PojoUtils.realize(value, (Class<?>) returnTypes[0], returnTypes.length > 1 ? returnTypes[1] : null);
}
return value;
}</code></pre>
<p>该方法是解析mock值</p>
<h5>3.invoke</h5>
<pre><code class="java">@Override
public Result invoke(Invocation invocation) throws RpcException {
// 获得 `"mock"` 配置项,方法级 > 类级
String mock = getUrl().getParameter(invocation.getMethodName() + "." + Constants.MOCK_KEY);
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(this);
}
// 如果mock为空
if (StringUtils.isBlank(mock)) {
// 获得mock值
mock = getUrl().getParameter(Constants.MOCK_KEY);
}
// 如果还是为空。则抛出异常
if (StringUtils.isBlank(mock)) {
throw new RpcException(new IllegalAccessException("mock can not be null. url :" + url));
}
// 标准化 `"mock"` 配置项
mock = normalizeMock(URL.decode(mock));
// 等于 "return " ,返回值为空的 RpcResult 对象
if (mock.startsWith(Constants.RETURN_PREFIX)) {
// 分割
mock = mock.substring(Constants.RETURN_PREFIX.length()).trim();
try {
// 获得返回类型
Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
// 解析mock值
Object value = parseMockValue(mock, returnTypes);
return new RpcResult(value);
} catch (Exception ew) {
throw new RpcException("mock return invoke error. method :" + invocation.getMethodName()
+ ", mock:" + mock + ", url: " + url, ew);
}
// 如果是throw
} else if (mock.startsWith(Constants.THROW_PREFIX)) {
// 根据throw分割
mock = mock.substring(Constants.THROW_PREFIX.length()).trim();
// 如果为空,则抛出异常
if (StringUtils.isBlank(mock)) {
throw new RpcException("mocked exception for service degradation.");
} else { // user customized class
// 创建自定义异常
Throwable t = getThrowable(mock);
// 抛出业务类型的 RpcException 异常
throw new RpcException(RpcException.BIZ_EXCEPTION, t);
}
} else { //impl mock
try {
// 否则直接获得invoker
Invoker<T> invoker = getInvoker(mock);
// 调用
return invoker.invoke(invocation);
} catch (Throwable t) {
throw new RpcException("Failed to create mock implementation class " + mock, t);
}
}
}</code></pre>
<p>该方法是本地伪装的核心实现,mock分三种,分别是return、throw、自定义的mock类。</p>
<h5>4.normalizedMock</h5>
<pre><code class="java">public static String normalizeMock(String mock) {
// 若为空,直接返回
if (mock == null) {
return mock;
}
mock = mock.trim();
if (mock.length() == 0) {
return mock;
}
if (Constants.RETURN_KEY.equalsIgnoreCase(mock)) {
return Constants.RETURN_PREFIX + "null";
}
// 若果为 "true" "default" "fail" "force" 四种字符串,返回default
if (ConfigUtils.isDefault(mock) || "fail".equalsIgnoreCase(mock) || "force".equalsIgnoreCase(mock)) {
return "default";
}
// fail:throw/return foo => throw/return
if (mock.startsWith(Constants.FAIL_PREFIX)) {
mock = mock.substring(Constants.FAIL_PREFIX.length()).trim();
}
// force:throw/return foo => throw/return
if (mock.startsWith(Constants.FORCE_PREFIX)) {
mock = mock.substring(Constants.FORCE_PREFIX.length()).trim();
}
// 如果是return或者throw,替换`为"
if (mock.startsWith(Constants.RETURN_PREFIX) || mock.startsWith(Constants.THROW_PREFIX)) {
mock = mock.replace('`', '"');
}
return mock;
}</code></pre>
<p>该方法是规范化mock值。</p>
<h5>5.getThrowable</h5>
<pre><code class="java">public static Throwable getThrowable(String throwstr) {
// 从异常集合中取出异常
Throwable throwable = throwables.get(throwstr);
// 如果不为空,则抛出异常
if (throwable != null) {
return throwable;
}
try {
Throwable t;
// 获得异常类
Class<?> bizException = ReflectUtils.forName(throwstr);
Constructor<?> constructor;
// 获得构造方法
constructor = ReflectUtils.findConstructor(bizException, String.class);
// 创建 Throwable 对象
t = (Throwable) constructor.newInstance(new Object[]{"mocked exception for service degradation."});
// 添加到缓存中
if (throwables.size() < 1000) {
throwables.put(throwstr, t);
}
return t;
} catch (Exception e) {
throw new RpcException("mock throw error :" + throwstr + " argument error.", e);
}
}</code></pre>
<p>该方法是获得异常。</p>
<h5>6.getInvoker</h5>
<pre><code class="java">private Invoker<T> getInvoker(String mockService) {
// 从缓存中,获得 Invoker 对象,如果有,直接缓存。
Invoker<T> invoker = (Invoker<T>) mocks.get(mockService);
if (invoker != null) {
return invoker;
}
// 获得服务类型
Class<T> serviceType = (Class<T>) ReflectUtils.forName(url.getServiceInterface());
// 获得MockObject
T mockObject = (T) getMockObject(mockService, serviceType);
// 创建invoker
invoker = proxyFactory.getInvoker(mockObject, serviceType, url);
if (mocks.size() < 10000) {
// 加入集合
mocks.put(mockService, invoker);
}
return invoker;
}</code></pre>
<p>该方法是获得invoker。</p>
<h5>7.getMockObject</h5>
<pre><code class="java">public static Object getMockObject(String mockService, Class serviceType) {
if (ConfigUtils.isDefault(mockService)) {
mockService = serviceType.getName() + "Mock";
}
// 获得类型
Class<?> mockClass = ReflectUtils.forName(mockService);
if (!serviceType.isAssignableFrom(mockClass)) {
throw new IllegalStateException("The mock class " + mockClass.getName() +
" not implement interface " + serviceType.getName());
}
try {
// 初始化
return mockClass.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException("No default constructor from mock class " + mockClass.getName(), e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
</code></pre>
<p>该方法是获得mock对象。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=wzrLPrhkNSNzbn1p26wfTg%3D%3D.2PTFR%2FQ4dvx2sM1Uj7dQe9q6u15ru3B8jt%2BoW72MnrLiB9MJ8QyT%2FOAaipbv5odFadhb%2BUi90PMiEJgG0J0azXNev42ZazNMmYDqgGXg2SDh0fTLxLRaoYvixCZht2llx5NBVwhtCMyRv1whZaZC2zC1KtoGDc1%2Fh3vLLfcbZ3E3mZ5iBh6Ggsn%2B1lDrxyIo" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了集群中关于mock实现的部分,到这里为止,集群部分就全部讲完了,这是2.6.x版本的集群,那在2.7中对于路由和配置规则都有相应的大改动,我会在之后2.7版本的讲解中讲到。接下来我将开始对序列化模块进行讲解。</p>
Dubbo源码解析(四十)集群——router
https://segmentfault.com/a/1190000018141200
2019-02-13T16:45:01+08:00
2019-02-13T16:45:01+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2>集群——router</h2>
<blockquote>目标:介绍dubbo中集群的路由,介绍dubbo-cluster下router包的源码。</blockquote>
<h3>前言</h3>
<p>路由规则 决定一次 dubbo 服务调用的目标服务器,分为条件路由规则和脚本路由规则,并且支持可扩展 。</p>
<h3>源码分析</h3>
<h4>(一)ConditionRouterFactory</h4>
<pre><code class="java">public class ConditionRouterFactory implements RouterFactory {
public static final String NAME = "condition";
@Override
public Router getRouter(URL url) {
// 创建一个ConditionRouter
return new ConditionRouter(url);
}
}</code></pre>
<p>该类是基于条件表达式规则路由工厂类。</p>
<h4>(二)ConditionRouter</h4>
<p>该类是基于条件表达式的路由实现类。关于给予条件表达式的路由规则,可以查看官方文档:</p>
<blockquote>官方文档地址:<a href="https://link.segmentfault.com/?enc=EeR0Jsj07Ddfy9Lr7C6asw%3D%3D.DzrUHIEwogILgwwbTjgWrWMm7Tss0ej94BRr6t8HfgqrSPRbfWQZSyumylita6HR31AmnPJSI1%2Fq3pMc3cUlE8ZIug17Ir70JHOYpZKX4II%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(ConditionRouter.class);
/**
* 分组正则匹配
*/
private static Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
/**
* 路由规则 URL
*/
private final URL url;
/**
* 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
*/
private final int priority;
/**
* 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false 。
*/
private final boolean force;
/**
* 消费者匹配条件集合,通过解析【条件表达式 rule 的 `=>` 之前半部分】
*/
private final Map<String, MatchPair> whenCondition;
/**
* 提供者地址列表的过滤条件,通过解析【条件表达式 rule 的 `=>` 之后半部分】
*/
private final Map<String, MatchPair> thenCondition;</code></pre>
<h5>2.构造方法</h5>
<pre><code class="java">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: It should be determined on the business level whether the `When condition` can be empty or not.
this.whenCondition = when;
this.thenCondition = then;
} catch (ParseException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}</code></pre>
<h5>3.MatchPair</h5>
<pre><code class="java">private static final class MatchPair {
/**
* 匹配的值的集合
*/
final Set<String> matches = new HashSet<String>();
/**
* 不匹配的值的集合
*/
final Set<String> mismatches = new HashSet<String>();
/**
* 判断value是否匹配matches或者mismatches
* @param value
* @param param
* @return
*/
private boolean isMatch(String value, URL param) {
// 只匹配 matches
if (!matches.isEmpty() && mismatches.isEmpty()) {
for (String match : matches) {
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
// 匹配上了返回true
return true;
}
}
// 没匹配上则为false
return false;
}
// 只匹配 mismatches
if (!mismatches.isEmpty() && matches.isEmpty()) {
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
// 如果匹配上了,则返回false
return false;
}
}
// 没匹配上,则为true
return true;
}
// 匹配 matches和mismatches
if (!matches.isEmpty() && !mismatches.isEmpty()) {
//when both mismatches and matches contain the same value, then using mismatches first
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
// 匹配上则为false
return false;
}
}
for (String match : matches) {
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
// 匹配上则为true
return true;
}
}
return false;
}
return false;
}
}</code></pre>
<p>该类是内部类,封装了匹配的值,每个属性条件。并且提供了判断是否匹配的方法。</p>
<h5>4.parseRule</h5>
<pre><code class="java">private static Map<String, MatchPair> parseRule(String rule)
throws ParseException {
Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
// 如果规则为空,则直接返回空
if (StringUtils.isBlank(rule)) {
return condition;
}
// Key-Value pair, stores both match and mismatch conditions
MatchPair pair = null;
// Multiple values
Set<String> values = null;
// 正则表达式匹配
final Matcher matcher = ROUTE_PATTERN.matcher(rule);
// 一个一个匹配
while (matcher.find()) { // Try to match one by one
String separator = matcher.group(1);
String content = matcher.group(2);
// Start part of the condition expression.
// 开始条件表达式
if (separator == null || separator.length() == 0) {
pair = new MatchPair();
// 保存条件
condition.put(content, pair);
}
// The KV part of the condition expression
else if ("&".equals(separator)) {
// 把参数的条件表达式放入condition
if (condition.get(content) == null) {
pair = new MatchPair();
condition.put(content, pair);
} else {
pair = condition.get(content);
}
}
// The Value in the KV part.
// 把值放入values
else if ("=".equals(separator)) {
if (pair == null)
throw new ParseException("Illegal route rule \""
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
values = pair.matches;
values.add(content);
}
// The Value in the KV part.
// 把不等于的条件限制也放入values
else if ("!=".equals(separator)) {
if (pair == null)
throw new ParseException("Illegal route rule \""
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
values = pair.mismatches;
values.add(content);
}
// The Value in the KV part, if Value have more than one items.
// 如果以.分隔的也放入values
else if (",".equals(separator)) { // Should be seperateed by ','
if (values == null || values.isEmpty())
throw new ParseException("Illegal route rule \""
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
values.add(content);
} else {
throw new ParseException("Illegal route rule \"" + rule
+ "\", The error char '" + separator + "' at index "
+ matcher.start() + " before \"" + content + "\".", matcher.start());
}
}
return condition;
}</code></pre>
<p>该方法是根据规则解析路由配置内容。具体的可以参照官网的配置规则来解读这里每一个分割取值作为条件的过程。</p>
<h5>5.route</h5>
<pre><code class="java">@Override
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
throws RpcException {
// 为空,直接返回空 Invoker 集合
if (invokers == null || invokers.isEmpty()) {
return invokers;
}
try {
// 如果不匹配 `whenCondition` ,直接返回 `invokers` 集合,因为不需要走 `whenThen` 的匹配
if (!matchWhen(url, invocation)) {
return invokers;
}
List<Invoker<T>> result = new ArrayList<Invoker<T>>();
// 如果thenCondition为空,则直接返回空
if (thenCondition == null) {
logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
return result;
}
// 遍历invokers
for (Invoker<T> invoker : invokers) {
// 如果thenCondition匹配,则加入result
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
if (!result.isEmpty()) {
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;
}</code></pre>
<p>该方法是进行路由规则的匹配,分别对消费者和提供者进行匹配。</p>
<h5>6.matchCondition</h5>
<pre><code class="java">private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {
Map<String, String> sample = url.toMap();
// 是否匹配
boolean result = false;
// 遍历条件
for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {
String key = matchPair.getKey();
String sampleValue;
//get real invoked method name from invocation
// 获得方法名
if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) {
sampleValue = invocation.getMethodName();
} else {
//
sampleValue = sample.get(key);
if (sampleValue == null) {
sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key);
}
}
if (sampleValue != null) {
// 如果不匹配条件值,返回false
if (!matchPair.getValue().isMatch(sampleValue, param)) {
return false;
} else {
// 匹配则返回true
result = true;
}
} else {
//not pass the condition
// 如果匹配的集合不为空
if (!matchPair.getValue().matches.isEmpty()) {
// 返回false
return false;
} else {
// 返回true
result = true;
}
}
}
return result;
}</code></pre>
<p>该方法是匹配条件的主要逻辑。</p>
<h4>(三)ScriptRouterFactory</h4>
<p>该类是基于脚本的路由规则工厂类。</p>
<pre><code class="java">public class ScriptRouterFactory implements RouterFactory {
public static final String NAME = "script";
@Override
public Router getRouter(URL url) {
// 创建ScriptRouter
return new ScriptRouter(url);
}
}</code></pre>
<h4>(四)ScriptRouter</h4>
<p>该类是基于脚本的路由实现类</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(ScriptRouter.class);
/**
* 脚本类型 与 ScriptEngine 的映射缓存
*/
private static final Map<String, ScriptEngine> engines = new ConcurrentHashMap<String, ScriptEngine>();
/**
* 脚本
*/
private final ScriptEngine engine;
/**
* 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0 。
*/
private final int priority;
/**
* 路由规则
*/
private final String rule;
/**
* 路由规则 URL
*/
private final URL url;</code></pre>
<h5>2.route</h5>
<pre><code class="java">@Override
@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();
// 设置invokers、invocation、context
bindings.put("invokers", invokersCopy);
bindings.put("invocation", invocation);
bindings.put("context", RpcContext.getContext());
// 编译脚本
CompiledScript function = compilable.compile(rule);
// 执行脚本
Object obj = function.eval(bindings);
// 根据结果类型,转换成 (List<Invoker<T>> 类型返回
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.
// 发生异常,忽略路由规则,返回全 `invokers` 集合
logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
return invokers;
}
}</code></pre>
<p>该方法是根据路由规则选择invoker的实现逻辑。</p>
<h4>(五)FileRouterFactory</h4>
<p>该类是装饰者,对RouterFactory进行了功能增强,增加了从文件中读取规则。</p>
<pre><code class="java">public class FileRouterFactory implements RouterFactory {
public static final String NAME = "file";
/**
* 路由工厂
*/
private RouterFactory routerFactory;
public void setRouterFactory(RouterFactory routerFactory) {
this.routerFactory = routerFactory;
}
@Override
public Router getRouter(URL url) {
try {
// Transform File URL into Script Route URL, and Load
// file:///d:/path/to/route.js?router=script ==> script:///d:/path/to/route.js?type=js&rule=<file-content>
// 获得 router 配置项,默认为 script
String protocol = url.getParameter(Constants.ROUTER_KEY, ScriptRouterFactory.NAME); // Replace original protocol (maybe 'file') with 'script'
String type = null; // Use file suffix to config script type, e.g., js, groovy ...
// 获得path
String path = url.getPath();
// 获得类型
if (path != null) {
int i = path.lastIndexOf('.');
if (i > 0) {
type = path.substring(i + 1);
}
}
// 读取规则
String rule = IOUtils.read(new FileReader(new File(url.getAbsolutePath())));
boolean runtime = url.getParameter(Constants.RUNTIME_KEY, false);
// 获得脚本路由url
URL script = url.setProtocol(protocol).addParameter(Constants.TYPE_KEY, type).addParameter(Constants.RUNTIME_KEY, runtime).addParameterAndEncoded(Constants.RULE_KEY, rule);
// 获得路由
return routerFactory.getRouter(script);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}</code></pre>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=hKIW%2FNNRQEAVToYXZnxRzg%3D%3D.uspnCiQeCGjY3AzDHxdKjtfhY1jjxYJnkC2IJYzqkjMBkxZ9Mlt9cgKecC2gmGKFM8htoiIK1OuyJUjGzIiI2qdverSlXTgkfH8ReZSVDmHbbU9R%2B6mjVXYLAg3agsttHH5Fk1tj2SD3fRhBq0hCXwMdlTRA2456BPThHBWyTAg%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了集群中关于路由规则实现的部分。接下来我将开始对集群模块关于Mock部分进行讲解。</p>
Dubbo源码解析(三十九)集群——merger
https://segmentfault.com/a/1190000018121914
2019-02-12T06:50:40+08:00
2019-02-12T06:50:40+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2>集群——merger</h2>
<blockquote>目标:介绍dubbo中集群的分组聚合,介绍dubbo-cluster下merger包的源码。</blockquote>
<h3>前言</h3>
<p>按组合并返回结果 ,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。这个时候就要用到分组聚合。</p>
<h3>源码分析</h3>
<h4>(一)MergeableCluster</h4>
<pre><code class="java">public class MergeableCluster implements Cluster {
public static final String NAME = "mergeable";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建MergeableClusterInvoker
return new MergeableClusterInvoker<T>(directory);
}
}</code></pre>
<p>该类实现了Cluster接口,是分组集合的集群实现。</p>
<h4>(二)MergeableClusterInvoker</h4>
<p>该类是分组聚合的实现类,其中最关机的就是invoke方法。</p>
<pre><code class="java">@Override
@SuppressWarnings("rawtypes")
public Result invoke(final Invocation invocation) throws RpcException {
// 获得invoker集合
List<Invoker<T>> invokers = directory.list(invocation);
/**
* 获得是否merger
*/
String merger = getUrl().getMethodParameter(invocation.getMethodName(), Constants.MERGER_KEY);
// 如果没有设置需要聚合,则只调用一个invoker的
if (ConfigUtils.isEmpty(merger)) { // If a method doesn't have a merger, only invoke one Group
// 只要有一个可用就返回
for (final Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
return invokers.iterator().next().invoke(invocation);
}
// 返回类型
Class<?> returnType;
try {
// 获得返回类型
returnType = getInterface().getMethod(
invocation.getMethodName(), invocation.getParameterTypes()).getReturnType();
} catch (NoSuchMethodException e) {
returnType = null;
}
// 结果集合
Map<String, Future<Result>> results = new HashMap<String, Future<Result>>();
// 循环invokers
for (final Invoker<T> invoker : invokers) {
// 获得每次调用的future
Future<Result> future = executor.submit(new Callable<Result>() {
@Override
public Result call() throws Exception {
// 回调,把返回结果放入future
return invoker.invoke(new RpcInvocation(invocation, invoker));
}
});
// 加入集合
results.put(invoker.getUrl().getServiceKey(), future);
}
Object result = null;
List<Result> resultList = new ArrayList<Result>(results.size());
// 获得超时时间
int timeout = getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 遍历每一个结果
for (Map.Entry<String, Future<Result>> entry : results.entrySet()) {
Future<Result> future = entry.getValue();
try {
// 获得调用返回的结果
Result r = future.get(timeout, TimeUnit.MILLISECONDS);
if (r.hasException()) {
log.error("Invoke " + getGroupDescFromServiceKey(entry.getKey()) +
" failed: " + r.getException().getMessage(),
r.getException());
} else {
// 加入集合
resultList.add(r);
}
} catch (Exception e) {
throw new RpcException("Failed to invoke service " + entry.getKey() + ": " + e.getMessage(), e);
}
}
// 如果为空,则返回空的结果
if (resultList.isEmpty()) {
return new RpcResult((Object) null);
} else if (resultList.size() == 1) {
// 如果只有一个结果,则返回该结果
return resultList.iterator().next();
}
// 如果返回类型是void,也就是没有返回值,那么返回空结果
if (returnType == void.class) {
return new RpcResult((Object) null);
}
// 根据方法来合并,将调用返回结果的指定方法进行合并
if (merger.startsWith(".")) {
merger = merger.substring(1);
Method method;
try {
// 获得方法
method = returnType.getMethod(merger, returnType);
} catch (NoSuchMethodException e) {
throw new RpcException("Can not merge result because missing method [ " + merger + " ] in class [ " +
returnType.getClass().getName() + " ]");
}
// 有 Method ,进行合并
if (!Modifier.isPublic(method.getModifiers())) {
method.setAccessible(true);
}
// 从集合中移除
result = resultList.remove(0).getValue();
try {
// 方法返回类型匹配,合并时,修改 result
if (method.getReturnType() != void.class
&& method.getReturnType().isAssignableFrom(result.getClass())) {
for (Result r : resultList) {
result = method.invoke(result, r.getValue());
}
} else {
// 方法返回类型不匹配,合并时,不修改 result
for (Result r : resultList) {
method.invoke(result, r.getValue());
}
}
} catch (Exception e) {
throw new RpcException("Can not merge result: " + e.getMessage(), e);
}
} else {
// 基于 Merger
Merger resultMerger;
// 如果是默认的方式
if (ConfigUtils.isDefault(merger)) {
// 获得该类型的合并方式
resultMerger = MergerFactory.getMerger(returnType);
} else {
// 如果不是默认的,则配置中指定获得Merger的实现类
resultMerger = ExtensionLoader.getExtensionLoader(Merger.class).getExtension(merger);
}
if (resultMerger != null) {
List<Object> rets = new ArrayList<Object>(resultList.size());
// 遍历返回结果
for (Result r : resultList) {
// 加入到rets
rets.add(r.getValue());
}
// 合并
result = resultMerger.merge(
rets.toArray((Object[]) Array.newInstance(returnType, 0)));
} else {
throw new RpcException("There is no merger to merge result.");
}
}
// 返回结果
return new RpcResult(result);
}</code></pre>
<p>前面部分在讲获得调用的结果,后面部分是对结果的合并,合并有两种方式,根据配置不同可用分为基于方法的合并和基于merger的合并。</p>
<h4>(三)MergerFactory</h4>
<p>Merger 工厂类,获得指定类型的Merger 对象。</p>
<pre><code class="java">public class MergerFactory {
/**
* Merger 对象缓存
*/
private static final ConcurrentMap<Class<?>, Merger<?>> mergerCache =
new ConcurrentHashMap<Class<?>, Merger<?>>();
/**
* 获得指定类型的Merger对象
* @param returnType
* @param <T>
* @return
*/
public static <T> Merger<T> getMerger(Class<T> returnType) {
Merger result;
// 如果类型是集合
if (returnType.isArray()) {
// 获得类型
Class type = returnType.getComponentType();
// 从缓存中获得该类型的Merger对象
result = mergerCache.get(type);
// 如果为空,则
if (result == null) {
// 初始化所有的 Merger 扩展对象,到 mergerCache 缓存中。
loadMergers();
// 从集合中取出对应的Merger对象
result = mergerCache.get(type);
}
// 如果结果为空,则直接返回ArrayMerger的单例
if (result == null && !type.isPrimitive()) {
result = ArrayMerger.INSTANCE;
}
} else {
// 否则直接从mergerCache中取出
result = mergerCache.get(returnType);
// 如果为空
if (result == null) {
// 初始化所有的 Merger 扩展对象,到 mergerCache 缓存中。
loadMergers();
// 从集合中取出
result = mergerCache.get(returnType);
}
}
return result;
}
/**
* 初始化所有的 Merger 扩展对象,到 mergerCache 缓存中。
*/
static void loadMergers() {
// 获得Merger所有的扩展对象名
Set<String> names = ExtensionLoader.getExtensionLoader(Merger.class)
.getSupportedExtensions();
// 遍历
for (String name : names) {
// 加载每一个扩展实现,然后放入缓存。
Merger m = ExtensionLoader.getExtensionLoader(Merger.class).getExtension(name);
mergerCache.putIfAbsent(ReflectUtils.getGenericClass(m.getClass()), m);
}
}
}</code></pre>
<p>逻辑比较简单。</p>
<h4>(四)ArrayMerger</h4>
<p>因为不同的类型有不同的Merger实现,我们可以来看看这个图片:</p>
<p><img src="/img/remote/1460000018121917?w=668&h=512" alt="merger" title="merger"></p>
<p>可以看到有好多好多,我就讲解其中的一种,偷懒一下,其他的麻烦有兴趣的去看看源码了。</p>
<pre><code class="java">public class ArrayMerger implements Merger<Object[]> {
/**
* 单例
*/
public static final ArrayMerger INSTANCE = new ArrayMerger();
@Override
public Object[] merge(Object[]... others) {
// 如果长度为0 则直接返回
if (others.length == 0) {
return null;
}
// 总长
int totalLen = 0;
// 遍历所有需要合并的对象
for (int i = 0; i < others.length; i++) {
Object item = others[i];
// 如果为数组
if (item != null && item.getClass().isArray()) {
// 累加数组长度
totalLen += Array.getLength(item);
} else {
throw new IllegalArgumentException((i + 1) + "th argument is not an array");
}
}
if (totalLen == 0) {
return null;
}
// 获得数组类型
Class<?> type = others[0].getClass().getComponentType();
// 创建长度
Object result = Array.newInstance(type, totalLen);
int index = 0;
// 遍历需要合并的对象
for (Object array : others) {
// 遍历每个数组中的数据
for (int i = 0; i < Array.getLength(array); i++) {
// 加入到最终结果中
Array.set(result, index++, Array.get(array, i));
}
}
return (Object[]) result;
}
}</code></pre>
<p>是不是很简单,就是循环合并就可以了。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=dRR5o4TJf1mIug3NxzzTyA%3D%3D.11Q1FC0ehyK06AUI%2FCFhx1R%2Bt2LtT6TGpG1zTnv%2BBQe7uop%2BoDzP8IDDOR91wlwBzyFwms5OdOiGxRilsEcUG5kw35pDIbGLVIuDCYnv8vuW8x95%2FH7jev5Bob52NG0SVZJv9rHDPCghtMSmhatgO6%2F6dfSldh8zWPZqYvlSPDE%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了集群中关于分组聚合实现的部分。接下来我将开始对集群模块关于路由部分进行讲解。</p>
Dubbo源码解析(三十八)集群——LoadBalance
https://segmentfault.com/a/1190000018105767
2019-02-07T14:19:11+08:00
2019-02-07T14:19:11+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
6
<h2>集群——LoadBalance</h2>
<blockquote>目标:介绍dubbo中集群的负载均衡,介绍dubbo-cluster下loadBalance包的源码。</blockquote>
<h3>前言</h3>
<p>负载均衡,说的通俗点就是要一碗水端平。在这个时代,公平是很重要的,在网络请求的时候同样是这个道理,我们有很多机器,但是请求老是到某个服务器上,而某些服务器又常年空闲,导致了资源的浪费,也增加了服务器因为压力过载而宕机的风险。这个时候就需要负载均衡的出现。它就相当于是一个天秤,通过各种策略,可以让每台服务器获取到适合自己处理能力的负载,这样既能够为高负载的服务器分流,还能避免资源浪费。负载均衡分为软件的负载均衡和硬件负载均衡,我们这里讲到的是软件负载均衡,在dubbo中,需要对消费者的调用请求进行分配,避免少数服务提供者负载过大,其他服务空闲的情况,因为负载过大会导致服务请求超时。这个时候就需要负载均衡起作用了。Dubbo 提供了4种负载均衡实现:</p>
<ol>
<li>RandomLoadBalance:基于权重随机算法</li>
<li>LeastActiveLoadBalance:基于最少活跃调用数算法</li>
<li>ConsistentHashLoadBalance:基于 hash 一致性</li>
<li>RoundRobinLoadBalance:基于加权轮询算法</li>
</ol>
<p>具体的实现看下面解析。</p>
<h3>源码分析</h3>
<h4>(一)AbstractLoadBalance</h4>
<p>该类实现了LoadBalance接口,是负载均衡的抽象类,提供了权重计算的功能。</p>
<h5>1.select</h5>
<pre><code class="java">@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 如果invokers为空则返回空
if (invokers == null || invokers.isEmpty())
return null;
// 如果invokers只有一个服务提供者,则返回一个
if (invokers.size() == 1)
return invokers.get(0);
// 调用doSelect进行选择
return doSelect(invokers, url, invocation);
}</code></pre>
<p>该方法是选择一个invoker,关键的选择还是调用了doSelect方法,不过doSelect是一个抽象方法,由上述四种负载均衡策略来各自实现。</p>
<h5>2.getWeight</h5>
<pre><code class="java">protected int getWeight(Invoker<?> invoker, Invocation invocation) {
// 获得 weight 配置,即服务权重。默认为 100
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);
if (weight > 0) {
// 获得启动时间戳
long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
// 获得启动总时长
int uptime = (int) (System.currentTimeMillis() - timestamp);
// 获得预热需要总时长。默认为10分钟
int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);
// 如果服务运行时间小于预热时间,则重新计算服务权重,即降权
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight(uptime, warmup, weight);
}
}
}
return weight;
}</code></pre>
<p>该方法是获得权重的方法,计算权重在calculateWarmupWeight方法中实现,该方法考虑到了jvm预热的过程。</p>
<h5>3.calculateWarmupWeight</h5>
<pre><code class="java">static int calculateWarmupWeight(int uptime, int warmup, int weight) {
// 计算权重 (uptime / warmup) * weight,进度百分比 * 权重
int ww = (int) ((float) uptime / ((float) warmup / (float) weight));
// 权重范围为 [0, weight] 之间
return ww < 1 ? 1 : (ww > weight ? weight : ww);
}</code></pre>
<p>该方法是计算权重的方法,其中计算公式是(uptime / warmup) <em> weight,含义就是进度百分比 </em> 权重值。</p>
<h4>(二)RandomLoadBalance</h4>
<p>该类是基于权重随机算法的负载均衡实现类,我们先来讲讲原理,比如我有有一组服务器 servers = [A, B, C],他们他们对应的权重为 weights = [6, 3, 1],权重总和为10,现在把这些权重值平铺在一维坐标值上,分别出现三个区域,A区域为[0,6),B区域为[6,9),C区域为[9,10),然后产生一个[0, 10)的随机数,看该数字落在哪个区间内,就用哪台服务器,这样权重越大的,被击中的概率就越大。</p>
<pre><code class="java">public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
/**
* 随机数产生器
*/
private final Random random = new Random();
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 获得服务长度
int length = invokers.size(); // Number of invokers
// 总的权重
int totalWeight = 0; // The sum of weights
// 是否有相同的权重
boolean sameWeight = true; // Every invoker has the same weight?
// 遍历每个服务,计算相应权重
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// 计算总的权重值
totalWeight += weight; // Sum
// 如果前一个服务的权重值不等于后一个则sameWeight为false
if (sameWeight && i > 0
&& weight != getWeight(invokers.get(i - 1), invocation)) {
sameWeight = false;
}
}
// 如果每个服务权重都不同,并且总的权重值不为0
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
int offset = random.nextInt(totalWeight);
// Return a invoker based on the random value.
// 循环让 offset 数减去服务提供者权重值,当 offset 小于0时,返回相应的 Invoker。
// 举例说明一下,我们有 servers = [A, B, C],weights = [6, 3, 1],offset = 7。
// 第一次循环,offset - 6 = 1 > 0,即 offset > 6,
// 表明其不会落在服务器 A 对应的区间上。
// 第二次循环,offset - 3 = -2 < 0,即 6 < offset < 9,
// 表明其会落在服务器 B 对应的区间上
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);
if (offset < 0) {
return invokers.get(i);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
// 如果所有服务提供者权重值相同,此时直接随机返回一个即可
return invokers.get(random.nextInt(length));
}
}</code></pre>
<p>该算法比较好理解,当然 RandomLoadBalance 也存在一定的缺点,当调用次数比较少时,Random 产生的随机数可能会比较集中,此时多数请求会落到同一台服务器上,不过影响不大。</p>
<h4>(三)LeastActiveLoadBalance</h4>
<p>该负载均衡策略基于最少活跃调用数算法,某个服务活跃调用数越小,表明该服务提供者效率越高,也就表明单位时间内能够处理的请求更多。此时应该选择该类服务器。实现很简单,就是每一个服务都有一个活跃数active来记录该服务的活跃值,每收到一个请求,该active就会加1,,没完成一个请求,active就会减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求。除了最小活跃数,还引入了权重值,也就是当活跃数一样的时候,选择利用权重法来进行选择,如果权重也一样,那么随机选择一个。</p>
<pre><code class="java">public class LeastActiveLoadBalance extends AbstractLoadBalance {
public static final String NAME = "leastactive";
/**
* 随机器
*/
private final Random random = new Random();
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 获得服务长度
int length = invokers.size(); // Number of invokers
// 最小的活跃数
int leastActive = -1; // The least active value of all invokers
// 具有相同“最小活跃数”的服务者提供者(以下用 Invoker 代称)数量
int leastCount = 0; // The number of invokers having the same least active value (leastActive)
// leastIndexs 用于记录具有相同“最小活跃数”的 Invoker 在 invokers 列表中的下标信息
int[] leastIndexs = new int[length]; // The index of invokers having the same least active value (leastActive)
// 总的权重
int totalWeight = 0; // The sum of with warmup weights
// 第一个最小活跃数的 Invoker 权重值,用于与其他具有相同最小活跃数的 Invoker 的权重进行对比,
// 以检测是否“所有具有相同最小活跃数的 Invoker 的权重”均相等
int firstWeight = 0; // Initial value, used for comparision
// 是否权重相同
boolean sameWeight = true; // Every invoker has the same weight value?
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
// 获取 Invoker 对应的活跃数
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // Active number
// 获得该服务的权重
int afterWarmup = getWeight(invoker, invocation); // Weight
// 发现更小的活跃数,重新开始
if (leastActive == -1 || active < leastActive) { // Restart, when find a invoker having smaller least active value.
// 记录当前最小的活跃数
leastActive = active; // Record the current least active value
// 更新 leastCount 为 1
leastCount = 1; // Reset leastCount, count again based on current leastCount
// 记录当前下标值到 leastIndexs 中
leastIndexs[0] = i; // Reset
totalWeight = afterWarmup; // Reset
firstWeight = afterWarmup; // Record the weight the first invoker
sameWeight = true; // Reset, every invoker has the same weight value?
// 如果当前 Invoker 的活跃数 active 与最小活跃数 leastActive 相同
} else if (active == leastActive) { // If current invoker's active value equals with leaseActive, then accumulating.
// 在 leastIndexs 中记录下当前 Invoker 在 invokers 集合中的下标
leastIndexs[leastCount++] = i; // Record index number of this invoker
// 累加权重
totalWeight += afterWarmup; // Add this invoker's weight to totalWeight.
// If every invoker has the same weight?
if (sameWeight && i > 0
&& afterWarmup != firstWeight) {
sameWeight = false;
}
}
}
// assert(leastCount > 0)
// 当只有一个 Invoker 具有最小活跃数,此时直接返回该 Invoker 即可
if (leastCount == 1) {
// If we got exactly one invoker having the least active value, return this invoker directly.
return invokers.get(leastIndexs[0]);
}
// 有多个 Invoker 具有相同的最小活跃数,但它们之间的权重不同
if (!sameWeight && totalWeight > 0) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
// 随机生成一个数字
int offsetWeight = random.nextInt(totalWeight) + 1;
// Return a invoker based on the random value.
// 相关算法可以参考RandomLoadBalance
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexs[i];
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
if (offsetWeight <= 0)
return invokers.get(leastIndex);
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
// 如果权重一样,则随机取一个
return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}
}</code></pre>
<p>前半部分在进行最小活跃数的策略,后半部分在进行权重的随机策略,可以参见RandomLoadBalance。</p>
<h4>(四)ConsistentHashLoadBalance</h4>
<p>该类是负载均衡基于 hash 一致性的逻辑实现。一致性哈希算法由麻省理工学院的 Karger 及其合作者于1997年提供出的,一开始被大量运用于缓存系统的负载均衡。它的工作原理是这样的:首先根据 ip 或其他的信息为缓存节点生成一个 hash,在dubbo中使用参数进行计算hash。并将这个 hash 投射到 [0, 232 - 1] 的圆环上,当有查询或写入请求时,则生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。大致效果如下图所示(引用一下官网的图)</p>
<p>每个缓存节点在圆环上占据一个位置。如果缓存项的 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项,这里有两个概念不要弄混,缓存节点就好比dubbo中的服务提供者,会有很多的服务提供者,而缓存项就好比是服务引用的消费者。比如下面绿色点对应的缓存项也就是服务消费者将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项也就是服务消费者最终会存储到 cache-4 节点中,也就是调用cache-4 这个服务提供者。</p>
<p><img src="/img/remote/1460000018105770" alt="consistent-hash" title="consistent-hash"></p>
<p>但是在hash一致性算法并不能够保证hash算法的平衡性,就拿上面的例子来看,cache-3挂掉了,那该节点下的所有缓存项都要存储到 cache-4 节点中,这就导致hash值低的一直往高的存储,会面临一个不平衡的现象,见下图:</p>
<p><img src="/img/remote/1460000018105771" alt="consistent-hash-data-incline" title="consistent-hash-data-incline"></p>
<p>可以看到最后会变成类似不平衡的现象,那我们应该怎么避免这样的事情,做到平衡性,那就需要引入虚拟节点,虚拟节点是实际节点在 hash 空间的复制品,“虚拟节点”在 hash 空间中以hash值排列。比如下图:</p>
<p><img src="/img/remote/1460000018105772" alt="consistent-hash-invoker" title="consistent-hash-invoker"></p>
<p>可以看到各个节点都被均匀分布在圆环上,而某一个服务提供者居然有多个节点存在,分别跟其他节点交错排列,这样做的目的就是避免数据倾斜问题,也就是由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。类似第二张图的情况。</p>
<p>看完原理,接下来我们来看看代码</p>
<h5>1.doSelect</h5>
<pre><code class="java">protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 获得方法名
String methodName = RpcUtils.getMethodName(invocation);
// 获得key
String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;
// 获取 invokers 原始的 hashcode
int identityHashCode = System.identityHashCode(invokers);
// 从一致性 hash 选择器集合中获得一致性 hash 选择器
ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
// 如果等于空或者选择器的hash值不等于原始的值,则新建一个一致性 hash 选择器,并且加入到集合
if (selector == null || selector.identityHashCode != identityHashCode) {
selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, identityHashCode));
selector = (ConsistentHashSelector<T>) selectors.get(key);
}
// 选择器选择一个invoker
return selector.select(invocation);
}</code></pre>
<p>该方法也做了一些invokers 列表是不是变动过,以及创建 ConsistentHashSelector等工作,然后调用selector.select来进行选择。</p>
<h5>2.ConsistentHashSelector</h5>
<pre><code class="java">private static final class ConsistentHashSelector<T> {
/**
* 存储 Invoker 虚拟节点
*/
private final TreeMap<Long, Invoker<T>> virtualInvokers;
/**
* 每个Invoker 对应的虚拟节点数
*/
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();
// 获取虚拟节点数,默认为160
this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
// 获取参与 hash 计算的参数下标值,默认对第一个参数进行 hash 运算
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]);
}
// 遍历invokers
for (Invoker<T> invoker : invokers) {
String address = invoker.getUrl().getAddress();
for (int i = 0; i < replicaNumber / 4; i++) {
// 对 address + i 进行 md5 运算,得到一个长度为16的字节数组
byte[] digest = md5(address + i);
// // 对 digest 部分字节进行4次 hash 运算,得到四个不同的 long 型正整数
for (int h = 0; h < 4; h++) {
// h = 0 时,取 digest 中下标为 0 ~ 3 的4个字节进行位运算
// h = 1 时,取 digest 中下标为 4 ~ 7 的4个字节进行位运算
// h = 2, h = 3 时过程同上
long m = hash(digest, h);
// 将 hash 到 invoker 的映射关系存储到 virtualInvokers 中,
// virtualInvokers 需要提供高效的查询操作,因此选用 TreeMap 作为存储结构
virtualInvokers.put(m, invoker);
}
}
}
}
/**
* 选择一个invoker
* @param invocation
* @return
*/
public Invoker<T> select(Invocation invocation) {
// 将参数转为 key
String key = toKey(invocation.getArguments());
// 对参数 key 进行 md5 运算
byte[] digest = md5(key);
// 取 digest 数组的前四个字节进行 hash 运算,再将 hash 值传给 selectForKey 方法,
// 寻找合适的 Invoker
return selectForKey(hash(digest, 0));
}
/**
* 将参数转为 key
* @param args
* @return
*/
private String toKey(Object[] args) {
StringBuilder buf = new StringBuilder();
// 遍历参数下标
for (int i : argumentIndex) {
if (i >= 0 && i < args.length) {
// 拼接参数,生成key
buf.append(args[i]);
}
}
return buf.toString();
}
/**
* 通过hash选择invoker
* @param hash
* @return
*/
private Invoker<T> selectForKey(long hash) {
// 到 TreeMap 中查找第一个节点值大于或等于当前 hash 的 Invoker
Map.Entry<Long, Invoker<T>> entry = virtualInvokers.tailMap(hash, true).firstEntry();
// 如果 hash 大于 Invoker 在圆环上最大的位置,此时 entry = null,
// 需要将 TreeMap 的头节点赋值给 entry
if (entry == null) {
entry = virtualInvokers.firstEntry();
}
// 返回选择的invoker
return entry.getValue();
}
/**
* 计算hash值
* @param digest
* @param number
* @return
*/
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;
}
/**
* md5
* @param value
* @return
*/
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();
}
}</code></pre>
<p>该类是内部类,是一致性 hash 选择器,首先看它的属性,利用TreeMap来存储 Invoker 虚拟节点,因为需要提供高效的查询操作。再看看它的构造方法,执行了一系列的初始化逻辑,比如从配置中获取虚拟节点数以及参与 hash 计算的参数下标,默认情况下只使用第一个参数进行 hash,并且ConsistentHashLoadBalance 的负载均衡逻辑只受参数值影响,具有相同参数值的请求将会被分配给同一个服务提供者。还有一个select方法,比较简单,先进行md5运算。然后hash,最后选择出对应的invoker。</p>
<h4>(五)RoundRobinLoadBalance</h4>
<p>该类是负载均衡基于加权轮询算法的实现。那么什么是加权轮询,轮询很好理解,比如我第一个请求分配给A服务器,第二个请求分配给B服务器,第三个请求分配给C服务器,第四个请求又分配给A服务器,这就是轮询,但是这只适合每台服务器性能相近的情况,这种是一种非常理想的情况,那更多的是每台服务器的性能都会有所差异,这个时候性能差的服务器被分到等额的请求,就会需要承受压力大宕机的情况,这个时候我们需要对轮询加权,我举个例子,服务器 A、B、C 权重比为 6:3:1,那么在10次请求中,服务器 A 将收到其中的6次请求,服务器 B 会收到其中的3次请求,服务器 C 则收到其中的1次请求,也就是说每台服务器能够收到的请求归结于它的权重。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 回收间隔
*/
private static int RECYCLE_PERIOD = 60000;</code></pre>
<h5>2.WeightedRoundRobin</h5>
<pre><code class="java">protected static class WeightedRoundRobin {
/**
* 权重
*/
private int weight;
/**
* 当前已经有多少请求落在该服务提供者身上,也可以看成是一个动态的权重
*/
private AtomicLong current = new AtomicLong(0);
/**
* 最后一次更新时间
*/
private long lastUpdate;
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
current.set(0);
}
public long increaseCurrent() {
return current.addAndGet(weight);
}
public void sel(int total) {
current.addAndGet(-1 * total);
}
public long getLastUpdate() {
return lastUpdate;
}
public void setLastUpdate(long lastUpdate) {
this.lastUpdate = lastUpdate;
}
}</code></pre>
<p>该内部类是一个加权轮询器,它记录了某一个服务提供者的一些数据,比如权重、比如当前已经有多少请求落在该服务提供者上等。</p>
<h5>3.doSelect</h5>
<pre><code class="java">@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// key = 全限定类名 + "." + 方法名,比如 com.xxx.DemoService.sayHello
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
if (map == null) {
methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<String, WeightedRoundRobin>());
map = methodWeightMap.get(key);
}
// 权重总和
int totalWeight = 0;
// 最小权重
long maxCurrent = Long.MIN_VALUE;
// 获得现在的时间戳
long now = System.currentTimeMillis();
// 创建已经选择的invoker
Invoker<T> selectedInvoker = null;
// 创建加权轮询器
WeightedRoundRobin selectedWRR = null;
// 下面这个循环主要做了这样几件事情:
// 1. 遍历 Invoker 列表,检测当前 Invoker 是否有
// 相应的 WeightedRoundRobin,没有则创建
// 2. 检测 Invoker 权重是否发生了变化,若变化了,
// 则更新 WeightedRoundRobin 的 weight 字段
// 3. 让 current 字段加上自身权重,等价于 current += weight
// 4. 设置 lastUpdate 字段,即 lastUpdate = now
// 5. 寻找具有最大 current 的 Invoker,以及 Invoker 对应的 WeightedRoundRobin,
// 暂存起来,留作后用
// 6. 计算权重总和
for (Invoker<T> invoker : invokers) {
// 获得identify的值
String identifyString = invoker.getUrl().toIdentityString();
// 获得加权轮询器
WeightedRoundRobin weightedRoundRobin = map.get(identifyString);
// 计算权重
int weight = getWeight(invoker, invocation);
// 如果权重小于0,则设置0
if (weight < 0) {
weight = 0;
}
// 如果加权轮询器为空
if (weightedRoundRobin == null) {
// 创建加权轮询器
weightedRoundRobin = new WeightedRoundRobin();
// 设置权重
weightedRoundRobin.setWeight(weight);
// 加入集合
map.putIfAbsent(identifyString, weightedRoundRobin);
weightedRoundRobin = map.get(identifyString);
}
// 如果权重跟之前的权重不一样,则重新设置权重
if (weight != weightedRoundRobin.getWeight()) {
//weight changed
weightedRoundRobin.setWeight(weight);
}
// 计数器加1
long cur = weightedRoundRobin.increaseCurrent();
// 更新最后一次更新时间
weightedRoundRobin.setLastUpdate(now);
// 当落在该服务提供者的统计数大于最大可承受的数
if (cur > maxCurrent) {
// 赋值
maxCurrent = cur;
// 被选择的selectedInvoker赋值
selectedInvoker = invoker;
// 被选择的加权轮询器赋值
selectedWRR = weightedRoundRobin;
}
// 累加
totalWeight += weight;
}
// 如果更新锁不能获得并且invokers的大小跟map大小不匹配
// 对 <identifyString, WeightedRoundRobin> 进行检查,过滤掉长时间未被更新的节点。
// 该节点可能挂了,invokers 中不包含该节点,所以该节点的 lastUpdate 长时间无法被更新。
// 若未更新时长超过阈值后,就会被移除掉,默认阈值为60秒。
if (!updateLock.get() && invokers.size() != map.size()) {
if (updateLock.compareAndSet(false, true)) {
try {
// copy -> modify -> update reference
ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<String, WeightedRoundRobin>();
// 复制
newMap.putAll(map);
Iterator<Entry<String, WeightedRoundRobin>> it = newMap.entrySet().iterator();
// 轮询
while (it.hasNext()) {
Entry<String, WeightedRoundRobin> item = it.next();
// 如果大于回收时间,则进行回收
if (now - item.getValue().getLastUpdate() > RECYCLE_PERIOD) {
// 从集合中移除
it.remove();
}
}
// 加入集合
methodWeightMap.put(key, newMap);
} finally {
updateLock.set(false);
}
}
}
// 如果被选择的selectedInvoker不为空
if (selectedInvoker != null) {
// 设置总的权重
selectedWRR.sel(totalWeight);
return selectedInvoker;
}
// should not happen here
return invokers.get(0);
}</code></pre>
<p>该方法是选择的核心,其实关键是一些数据记录,在每次请求都会记录落在该服务上的请求数,然后在根据权重来分配,并且会有回收时间来处理一些长时间未被更新的节点。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=MK6j18hVFGQE9yhV%2FuBLvQ%3D%3D.%2BPjxVT1Mc98VtzTNEBA69zgTj7aXqK1rPwEpezACl0V1DmN9zftYaNzdexWDptNakYo58cDFa3fo37Q1AF0KKV09D9TWumkuja0Y1g%2FBK%2BJGoKFgrX6obi2sKprOuC7zDA1cM7w1FlKys6atJDplsU8mXIwlxoA34xc1spok3J9eyP8a6y9in9P72CzyqH8T" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了集群中关于负载均衡实现的部分,每个算法都是现在很普遍的负载均衡算法,希望大家细细品味。接下来我将开始对集群模块关于分组聚合部分进行讲解。</p>
Dubbo源码解析(三十七)集群——directory
https://segmentfault.com/a/1190000018102784
2019-02-06T08:56:17+08:00
2019-02-06T08:56:17+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
3
<h2>集群——directory</h2>
<blockquote>目标:介绍dubbo中集群的目录,介绍dubbo-cluster下directory包的源码。</blockquote>
<h3>前言</h3>
<p>我在前面的文章中也提到了Directory可以看成是多个Invoker的集合,Directory 的用途是保存 Invoker,其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Inovker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Inovker,那在之前文章中我忽略了RegistryDirectory的源码分析,在本文中来补充。</p>
<h3>源码分析</h3>
<h4>(一)AbstractDirectory</h4>
<p>该类实现了Directory接口,</p>
<h5>1.属性</h5>
<pre><code class="java">// logger
private static final Logger logger = LoggerFactory.getLogger(AbstractDirectory.class);
/**
* url对象
*/
private final URL url;
/**
* 是否销毁
*/
private volatile boolean destroyed = false;
/**
* 消费者端url
*/
private volatile URL consumerUrl;
/**
* 路由集合
*/
private volatile List<Router> routers;</code></pre>
<h5>2.list</h5>
<pre><code class="java">@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
// 如果销毁,则抛出异常
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
// 调用doList来获得Invoker集合
List<Invoker<T>> invokers = doList(invocation);
// 获得路由集合
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && !localRouters.isEmpty()) {
// 遍历路由
for (Router router : localRouters) {
try {
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
// 根据路由规则选择符合规则的invoker集合
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}</code></pre>
<p>该方法是生成invoker集合的逻辑实现。其中doList是抽象方法,交由子类来实现。</p>
<h5>3.setRouters</h5>
<pre><code class="java">protected void setRouters(List<Router> routers) {
// copy list
// 复制路由集合
routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers);
// append url router
// 获得路由的配置
String routerkey = url.getParameter(Constants.ROUTER_KEY);
if (routerkey != null && routerkey.length() > 0) {
// 加载路由工厂
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
// 加入集合
routers.add(routerFactory.getRouter(url));
}
// append mock invoker selector
// 加入服务降级路由
routers.add(new MockInvokersSelector());
// 排序
Collections.sort(routers);
this.routers = routers;
}</code></pre>
<h4>(二)StaticDirectory</h4>
<p>静态 Directory 实现类,将传入的 invokers 集合,封装成静态的 Directory 对象。</p>
<pre><code class="java">public class StaticDirectory<T> extends AbstractDirectory<T> {
private final List<Invoker<T>> invokers;
public StaticDirectory(List<Invoker<T>> invokers) {
this(null, invokers, null);
}
public StaticDirectory(List<Invoker<T>> invokers, List<Router> routers) {
this(null, invokers, routers);
}
public StaticDirectory(URL url, List<Invoker<T>> invokers) {
this(url, invokers, null);
}
public StaticDirectory(URL url, List<Invoker<T>> invokers, List<Router> routers) {
super(url == null && invokers != null && !invokers.isEmpty() ? invokers.get(0).getUrl() : url, routers);
if (invokers == null || invokers.isEmpty())
throw new IllegalArgumentException("invokers == null");
this.invokers = invokers;
}
@Override
public Class<T> getInterface() {
return invokers.get(0).getInterface();
}
@Override
public boolean isAvailable() {
if (isDestroyed()) {
return false;
}
// 遍历invokers,如果有一个可用,则可用
for (Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
return true;
}
}
return false;
}
@Override
public void destroy() {
if (isDestroyed()) {
return;
}
super.destroy();
// 遍历invokers,销毁所有的invoker
for (Invoker<T> invoker : invokers) {
invoker.destroy();
}
// 清除集合
invokers.clear();
}
@Override
protected List<Invoker<T>> doList(Invocation invocation) throws RpcException {
return invokers;
}
}</code></pre>
<p>该类我就不多讲解,比较简单。</p>
<h4>(三)RegistryDirectory</h4>
<p>该类继承了AbstractDirectory类,是基于注册中心的动态 Directory 实现类,会根据注册中心的推送变更 List<Invoker></p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(RegistryDirectory.class);
/**
* cluster实现类对象
*/
private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
/**
* 路由工厂
*/
private static final RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension();
/**
* 配置规则工厂
*/
private static final ConfiguratorFactory configuratorFactory = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getAdaptiveExtension();
/**
* 服务key
*/
private final String serviceKey; // Initialization at construction time, assertion not null
/**
* 服务类型
*/
private final Class<T> serviceType; // Initialization at construction time, assertion not null
/**
* 消费者URL的配置项 Map
*/
private final Map<String, String> queryMap; // Initialization at construction time, assertion not null
/**
* 原始的目录 URL
*/
private final URL directoryUrl; // Initialization at construction time, assertion not null, and always assign non null value
/**
* 服务方法集合
*/
private final String[] serviceMethods;
/**
* 是否使用多分组
*/
private final boolean multiGroup;
/**
* 协议
*/
private Protocol protocol; // Initialization at the time of injection, the assertion is not null
/**
* 注册中心
*/
private Registry registry; // Initialization at the time of injection, the assertion is not null
/**
* 是否禁止访问
*/
private volatile boolean forbidden = false;
/**
* 覆盖目录的url
*/
private volatile URL overrideDirectoryUrl; // Initialization at construction time, assertion not null, and always assign non null value
/**
* override rules
* Priority: override>-D>consumer>provider
* Rule one: for a certain provider <ip:port,timeout=100>
* Rule two: for all providers <* ,timeout=5000>
* 配置规则数组
*/
private volatile List<Configurator> configurators; // The initial value is null and the midway may be assigned to null, please use the local variable reference
// Map<url, Invoker> cache service url to invoker mapping.
/**
* url与服务提供者 Invoker 集合的映射缓存
*/
private volatile Map<String, Invoker<T>> urlInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference
// Map<methodName, Invoker> cache service method to invokers mapping.
/**
* 方法名和服务提供者 Invoker 集合的映射缓存
*/
private volatile Map<String, List<Invoker<T>>> methodInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference
// Set<invokerUrls> cache invokeUrls to invokers mapping.
/**
* 服务提供者Invoker 集合缓存
*/
private volatile Set<URL> cachedInvokerUrls; // The initial value is null and the midway may be assigned to null, please use the local variable reference</code></pre>
<h5>2.toConfigurators</h5>
<pre><code class="java">public static List<Configurator> toConfigurators(List<URL> urls) {
// 如果为空,则返回空集合
if (urls == null || urls.isEmpty()) {
return Collections.emptyList();
}
List<Configurator> configurators = new ArrayList<Configurator>(urls.size());
// 遍历url集合
for (URL url : urls) {
//如果是协议是empty的值,则清空配置集合
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
configurators.clear();
break;
}
// 覆盖的参数集合
Map<String, String> override = new HashMap<String, String>(url.getParameters());
//The anyhost parameter of override may be added automatically, it can't change the judgement of changing url
// 覆盖的anyhost参数可以自动添加,也不能改变更改url的判断
override.remove(Constants.ANYHOST_KEY);
// 如果需要覆盖添加的值为0,则清空配置
if (override.size() == 0) {
configurators.clear();
continue;
}
// 加入配置规则集合
configurators.add(configuratorFactory.getConfigurator(url));
}
// 排序
Collections.sort(configurators);
return configurators;
}</code></pre>
<p>该方法是处理配置规则url集合,转换覆盖url映射以便在重新引用时使用,每次发送所有规则,网址将被重新组装和计算。</p>
<h5>3.destroy</h5>
<pre><code class="java">@Override
public void destroy() {
// 如果销毁了,则返回
if (isDestroyed()) {
return;
}
// unsubscribe.
try {
if (getConsumerUrl() != null && registry != null && registry.isAvailable()) {
// 取消订阅
registry.unsubscribe(getConsumerUrl(), this);
}
} catch (Throwable t) {
logger.warn("unexpeced error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t);
}
super.destroy(); // must be executed after unsubscribing
try {
// 清空所有的invoker
destroyAllInvokers();
} catch (Throwable t) {
logger.warn("Failed to destroy service " + serviceKey, t);
}
}</code></pre>
<p>该方法是销毁方法。</p>
<h5>4.destroyAllInvokers</h5>
<pre><code class="java">private void destroyAllInvokers() {
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
// 如果invoker集合不为空
if (localUrlInvokerMap != null) {
// 遍历
for (Invoker<T> invoker : new ArrayList<Invoker<T>>(localUrlInvokerMap.values())) {
try {
// 销毁invoker
invoker.destroy();
} catch (Throwable t) {
logger.warn("Failed to destroy service " + serviceKey + " to provider " + invoker.getUrl(), t);
}
}
// 清空集合
localUrlInvokerMap.clear();
}
methodInvokerMap = null;
}</code></pre>
<p>该方法是关闭所有的invoker服务。</p>
<h5>5.notify</h5>
<pre><code class="java">@Override
public synchronized void notify(List<URL> urls) {
List<URL> invokerUrls = new ArrayList<URL>();
List<URL> routerUrls = new ArrayList<URL>();
List<URL> configuratorUrls = new ArrayList<URL>();
// 遍历url
for (URL url : urls) {
// 获得协议
String protocol = url.getProtocol();
// 获得类别
String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
// 如果是路由规则
if (Constants.ROUTERS_CATEGORY.equals(category)
|| Constants.ROUTE_PROTOCOL.equals(protocol)) {
// 则在路由规则集合中加入
routerUrls.add(url);
} else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
|| Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
// 如果是配置规则,则加入配置规则集合
configuratorUrls.add(url);
} else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
// 如果是服务提供者,则加入服务提供者集合
invokerUrls.add(url);
} else {
logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
}
}
// configurators
if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
// 处理配置规则url集合
this.configurators = toConfigurators(configuratorUrls);
}
// routers
if (routerUrls != null && !routerUrls.isEmpty()) {
// 处理路由规则 URL 集合
List<Router> routers = toRouters(routerUrls);
if (routers != null) { // null - do nothing
// 并且设置路由集合
setRouters(routers);
}
}
List<Configurator> localConfigurators = this.configurators; // local reference
// merge override parameters
this.overrideDirectoryUrl = directoryUrl;
if (localConfigurators != null && !localConfigurators.isEmpty()) {
// 遍历配置规则集合 逐个进行配置
for (Configurator configurator : localConfigurators) {
this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
}
}
// providers
// 处理服务提供者 URL 集合
refreshInvoker(invokerUrls);
}
</code></pre>
<p>当服务有变化的时候,执行该方法。首先将url根据路由规则、服务提供者和配置规则三种类型分开,分别放入三个集合,然后对每个集合进行修改或者通知</p>
<h5>6.refreshInvoker</h5>
<pre><code class="java">private void refreshInvoker(List<URL> invokerUrls) {
if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
// 设置禁止访问
this.forbidden = true; // Forbid to access
// methodInvokerMap 置空
this.methodInvokerMap = null; // Set the method invoker map to null
// 关闭所有的invoker
destroyAllInvokers(); // Close all invokers
} else {
// 关闭禁止访问
this.forbidden = false; // Allow to access
// 引用老的 urlInvokerMap
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
// 传入的 invokerUrls 为空,说明是路由规则或配置规则发生改变,此时 invokerUrls 是空的,直接使用 cachedInvokerUrls 。
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
// 否则把所有的invokerUrls加入缓存
this.cachedInvokerUrls = new HashSet<URL>();
this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
}
// 如果invokerUrls为空,则直接返回
if (invokerUrls.isEmpty()) {
return;
}
// 将传入的 invokerUrls ,转成新的 urlInvokerMap
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
// 转换出新的 methodInvokerMap
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
// state change
// If the calculation is wrong, it is not processed.
// 如果为空,则打印错误日志并且返回
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
return;
}
// 若服务引用多 group ,则按照 method + group 聚合 Invoker 集合
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
this.urlInvokerMap = newUrlInvokerMap;
try {
// 销毁不再使用的 Invoker 集合
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
}
</code></pre>
<p>该方法是处理服务提供者 URL 集合。根据 invokerURL 列表转换为 invoker 列表。转换规则如下:</p>
<ol>
<li>如果 url 已经被转换为 invoker ,则不在重新引用,直接从缓存中获取,注意如果 url 中任何一个参数变更也会重新引用。</li>
<li>如果传入的 invoker 列表不为空,则表示最新的 invoker 列表。</li>
<li>如果传入的 invokerUrl 列表是空,则表示只是下发的 override 规则或 route 规则,需要重新交叉对比,决定是否需要重新引用。</li>
</ol>
<h5>7.toMergeMethodInvokerMap</h5>
<pre><code class="java">private Map<String, List<Invoker<T>>> toMergeMethodInvokerMap(Map<String, List<Invoker<T>>> methodMap) {
// 循环方法,按照 method + group 聚合 Invoker 集合
Map<String, List<Invoker<T>>> result = new HashMap<String, List<Invoker<T>>>();
// 遍历方法集合
for (Map.Entry<String, List<Invoker<T>>> entry : methodMap.entrySet()) {
// 获得方法
String method = entry.getKey();
// 获得invoker集合
List<Invoker<T>> invokers = entry.getValue();
// 获得组集合
Map<String, List<Invoker<T>>> groupMap = new HashMap<String, List<Invoker<T>>>();
// 遍历invoker集合
for (Invoker<T> invoker : invokers) {
// 获得url携带的组配置
String group = invoker.getUrl().getParameter(Constants.GROUP_KEY, "");
// 获得该组对应的invoker集合
List<Invoker<T>> groupInvokers = groupMap.get(group);
// 如果为空,则新创建一个,然后加入集合
if (groupInvokers == null) {
groupInvokers = new ArrayList<Invoker<T>>();
groupMap.put(group, groupInvokers);
}
groupInvokers.add(invoker);
}
// 如果只有一个组
if (groupMap.size() == 1) {
// 返回该组的invoker集合
result.put(method, groupMap.values().iterator().next());
} else if (groupMap.size() > 1) {
// 如果不止一个组
List<Invoker<T>> groupInvokers = new ArrayList<Invoker<T>>();
// 遍历组
for (List<Invoker<T>> groupList : groupMap.values()) {
// 每次从集群中选择一个invoker加入groupInvokers
groupInvokers.add(cluster.join(new StaticDirectory<T>(groupList)));
}
// 加入需要返回的集合
result.put(method, groupInvokers);
} else {
result.put(method, invokers);
}
}
return result;
}
</code></pre>
<p>该方法是通过按照 method + group 来聚合 Invoker 集合。</p>
<h5>8.toRouters</h5>
<pre><code class="java">private List<Router> toRouters(List<URL> urls) {
List<Router> routers = new ArrayList<Router>();
// 如果为空,则直接返回空集合
if (urls == null || urls.isEmpty()) {
return routers;
}
if (urls != null && !urls.isEmpty()) {
// 遍历url集合
for (URL url : urls) {
// 如果为empty协议,则直接跳过
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
continue;
}
// 获得路由规则
String routerType = url.getParameter(Constants.ROUTER_KEY);
if (routerType != null && routerType.length() > 0) {
// 设置协议
url = url.setProtocol(routerType);
}
try {
// 获得路由
Router router = routerFactory.getRouter(url);
if (!routers.contains(router))
// 加入集合
routers.add(router);
} catch (Throwable t) {
logger.error("convert router url to router error, url: " + url, t);
}
}
}
return routers;
}
</code></pre>
<p>该方法是对url集合进行路由的解析,返回路由集合。</p>
<h5>9.toInvokers</h5>
<pre><code class="java">private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
// 如果为空,则返回空集合
if (urls == null || urls.isEmpty()) {
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<String>();
// 获得引用服务的协议
String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
// 遍历url
for (URL providerUrl : urls) {
// If protocol is configured at the reference side, only the matching protocol is selected
// 如果在参考侧配置协议,则仅选择匹配协议
if (queryProtocols != null && queryProtocols.length() > 0) {
boolean accept = false;
// 分割协议
String[] acceptProtocols = queryProtocols.split(",");
// 遍历协议
for (String acceptProtocol : acceptProtocols) {
// 如果匹配,则是接受的协议
if (providerUrl.getProtocol().equals(acceptProtocol)) {
accept = true;
break;
}
}
if (!accept) {
continue;
}
}
// 如果协议是empty,则跳过
if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
continue;
}
// 如果该协议不是dubbo支持的,则打印错误日志,跳过
if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()
+ ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions()));
continue;
}
// 合并url参数
URL url = mergeUrl(providerUrl);
String key = url.toFullString(); // The parameter urls are sorted
if (keys.contains(key)) { // Repeated url
continue;
}
// 添加到keys
keys.add(key);
// Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again
// 如果服务端 URL 发生变化,则重新 refer 引用
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
if (invoker == null) { // Not in the cache, refer again
try {
// 判断是否开启
boolean enabled = true;
// 获得enabled配置
if (url.hasParameter(Constants.DISABLED_KEY)) {
enabled = !url.getParameter(Constants.DISABLED_KEY, false);
} else {
enabled = url.getParameter(Constants.ENABLED_KEY, true);
}
// 若开启,创建 Invoker 对象
if (enabled) {
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
}
// 添加到 newUrlInvokerMap 中
if (invoker != null) { // Put new invoker in cache
newUrlInvokerMap.put(key, invoker);
}
} else {
newUrlInvokerMap.put(key, invoker);
}
}
// 清空 keys
keys.clear();
return newUrlInvokerMap;
}
</code></pre>
<p>该方法是将url转换为调用者,如果url已被引用,则不会重新引用。</p>
<h5>10.mergeUrl</h5>
<pre><code class="java">private URL mergeUrl(URL providerUrl) {
// 合并消费端参数
providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // Merge the consumer side parameters
// 合并配置规则
List<Configurator> localConfigurators = this.configurators; // local reference
if (localConfigurators != null && !localConfigurators.isEmpty()) {
for (Configurator configurator : localConfigurators) {
providerUrl = configurator.configure(providerUrl);
}
}
// 不检查连接是否成功,总是创建 Invoker
providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); // Do not check whether the connection is successful or not, always create Invoker!
// The combination of directoryUrl and override is at the end of notify, which can't be handled here
// 合并提供者参数,因为 directoryUrl 与 override 合并是在 notify 的最后,这里不能够处理
this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters()); // Merge the provider side parameters
// 1.0版本兼容
if ((providerUrl.getPath() == null || providerUrl.getPath().length() == 0)
&& "dubbo".equals(providerUrl.getProtocol())) { // Compatible version 1.0
//fix by tony.chenl DUBBO-44
String path = directoryUrl.getParameter(Constants.INTERFACE_KEY);
if (path != null) {
int i = path.indexOf('/');
if (i >= 0) {
path = path.substring(i + 1);
}
i = path.lastIndexOf(':');
if (i >= 0) {
path = path.substring(0, i);
}
providerUrl = providerUrl.setPath(path);
}
}
return providerUrl;
}
</code></pre>
<p>该方法是合并 URL 参数,优先级为配置规则 > 服务消费者配置 > 服务提供者配置.</p>
<h5>11.toMethodInvokers</h5>
<pre><code class="java">private Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) {
Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>();
// According to the methods classification declared by the provider URL, the methods is compatible with the registry to execute the filtered methods
List<Invoker<T>> invokersList = new ArrayList<Invoker<T>>();
if (invokersMap != null && invokersMap.size() > 0) {
// 遍历调用者列表
for (Invoker<T> invoker : invokersMap.values()) {
String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY);
// 按服务提供者 URL 所声明的 methods 分类
if (parameter != null && parameter.length() > 0) {
// 分割参数得到方法集合
String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter);
if (methods != null && methods.length > 0) {
// 遍历方法集合
for (String method : methods) {
if (method != null && method.length() > 0
&& !Constants.ANY_VALUE.equals(method)) {
// 获得该方法对应的invoker,如果为空,则创建
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
if (methodInvokers == null) {
methodInvokers = new ArrayList<Invoker<T>>();
newMethodInvokerMap.put(method, methodInvokers);
}
methodInvokers.add(invoker);
}
}
}
}
invokersList.add(invoker);
}
}
// 根据路由规则,匹配合适的 Invoker 集合。
List<Invoker<T>> newInvokersList = route(invokersList, null);
// 添加 `newInvokersList` 到 `newMethodInvokerMap` 中,表示该服务提供者的全量 Invoker 集合
newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList);
if (serviceMethods != null && serviceMethods.length > 0) {
// 循环方法,获得每个方法路由匹配的invoker集合
for (String method : serviceMethods) {
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
if (methodInvokers == null || methodInvokers.isEmpty()) {
methodInvokers = newInvokersList;
}
newMethodInvokerMap.put(method, route(methodInvokers, method));
}
}
// sort and unmodifiable
// 循环排序每个方法的 Invoker 集合,排序
for (String method : new HashSet<String>(newMethodInvokerMap.keySet())) {
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method);
Collections.sort(methodInvokers, InvokerComparator.getComparator());
newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers));
}
// 设置为不可变
return Collections.unmodifiableMap(newMethodInvokerMap);
}
</code></pre>
<p>该方法是将调用者列表转换为与方法的映射关系。</p>
<h5>12.destroyUnusedInvokers</h5>
<pre><code class="java">private void destroyUnusedInvokers(Map<String, Invoker<T>> oldUrlInvokerMap, Map<String, Invoker<T>> newUrlInvokerMap) {
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
destroyAllInvokers();
return;
}
// check deleted invoker
// 记录已经删除的invoker
List<String> deleted = null;
if (oldUrlInvokerMap != null) {
Collection<Invoker<T>> newInvokers = newUrlInvokerMap.values();
// 遍历旧的invoker集合
for (Map.Entry<String, Invoker<T>> entry : oldUrlInvokerMap.entrySet()) {
if (!newInvokers.contains(entry.getValue())) {
if (deleted == null) {
deleted = new ArrayList<String>();
}
// 加入该invoker
deleted.add(entry.getKey());
}
}
}
if (deleted != null) {
// 遍历需要删除的invoker url集合
for (String url : deleted) {
if (url != null) {
// 移除该url
Invoker<T> invoker = oldUrlInvokerMap.remove(url);
if (invoker != null) {
try {
// 销毁invoker
invoker.destroy();
if (logger.isDebugEnabled()) {
logger.debug("destroy invoker[" + invoker.getUrl() + "] success. ");
}
} catch (Exception e) {
logger.warn("destroy invoker[" + invoker.getUrl() + "] faild. " + e.getMessage(), e);
}
}
}
}
}
}
</code></pre>
<p>该方法是销毁不再使用的 Invoker 集合。</p>
<h5>13.doList</h5>
<pre><code class="java">@Override
public List<Invoker<T>> doList(Invocation invocation) {
// 如果禁止访问,则抛出异常
if (forbidden) {
// 1. No service provider 2. Service providers are disabled
throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
"No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost()
+ " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist).");
}
List<Invoker<T>> invokers = null;
Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference
if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
// 获得方法名
String methodName = RpcUtils.getMethodName(invocation);
// 获得参数名
Object[] args = RpcUtils.getArguments(invocation);
if (args != null && args.length > 0 && args[0] != null
&& (args[0] instanceof String || args[0].getClass().isEnum())) {
// 根据第一个参数枚举路由
invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter
}
if (invokers == null) {
// 根据方法名获得 Invoker 集合
invokers = localMethodInvokerMap.get(methodName);
}
if (invokers == null) {
// 使用全量 Invoker 集合。例如,`#$echo(name)` ,回声方法
invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
}
if (invokers == null) {
// 使用 `methodInvokerMap` 第一个 Invoker 集合。防御性编程。
Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
if (iterator.hasNext()) {
invokers = iterator.next();
}
}
}
return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
}
</code></pre>
<p>该方法是通过会话域来获得Invoker集合。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=hNhAfUSdmsoQpXJ17xF5oQ%3D%3D.JsGsRVEfAqioSrq4OGg9w277V6wvC0Qu0RiF82B11a3Bu3rwJ4dJBPwo6XFEMUWoDzecRSD6wtzw7UidR02uaPn4n%2BH0KCgb%2B3ItVe2yrCt82iGJm9OVYhnN8zePVbQvnZqqFrV%2FpDTpq%2BFbVbb8piLYUUFe55zvCnD6q890m9aXK8mkpCX9vx4kUpeCwqJ0" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了集群中关于directory实现的部分,关键是RegistryDirectory,其中涉及到众多方法,需要好好品味。接下来我将开始对集群模块关于loadbalance部分进行讲解。</p>
Dubbo源码解析(三十六)集群——configurator
https://segmentfault.com/a/1190000018100997
2019-02-05T11:22:57+08:00
2019-02-05T11:22:57+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2>集群——configurator</h2>
<blockquote>目标:介绍dubbo中集群的配置规则,介绍dubbo-cluster下configurator包的源码。</blockquote>
<h3>前言</h3>
<p>向注册中心写入动态配置覆盖规则 。该功能通常由监控中心或治理中心的页面完成。在最新的2.7.0版本中有新的配置规则,我会在后续讲解2.7.0新特性的时候提到。这里还是根据旧版本中配置规则来讲解,可以参考官方文档:</p>
<blockquote><a href="https://link.segmentfault.com/?enc=5DxuUAeMCJfvoScxTNKEdQ%3D%3D.0r%2Fddl%2B4xMHfkwSzvp6rYbdb3Ru%2BE3RGMyaaQ5dR3o0S1MIVwxN93hQRsqFiY1byLtDbwebFuvf3BDuQdYPwfOYEUosVg3LDa3swGVbAawA%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a></blockquote>
<h3>源码分析</h3>
<h4>(一)AbstractConfigurator</h4>
<p>该类实现了Configurator接口,是配置规则 抽象类,配置有两种方式,一种是没有时添加配置,这种暂时没有用到,另一种是覆盖配置。</p>
<h5>1.configure</h5>
<pre><code class="java">@Override
public URL configure(URL url) {
if (configuratorUrl == null || configuratorUrl.getHost() == null
|| url == null || url.getHost() == null) {
return url;
}
// If override url has port, means it is a provider address. We want to control a specific provider with this override url, it may take effect on the specific provider instance or on consumers holding this provider instance.
// 如果覆盖url具有端口,则表示它是提供者地址。我们希望使用此覆盖URL控制特定提供程序,它可以在提供端生效 也可以在消费端生效。
if (configuratorUrl.getPort() != 0) {
if (url.getPort() == configuratorUrl.getPort()) {
return configureIfMatch(url.getHost(), url);
}
} else {// override url don't have a port, means the ip override url specify is a consumer address or 0.0.0.0
// 1.If it is a consumer ip address, the intention is to control a specific consumer instance, it must takes effect at the consumer side, any provider received this override url should ignore;
// 2.If the ip is 0.0.0.0, this override url can be used on consumer, and also can be used on provider
// 配置规则,URL 没有端口,意味着override 输入消费端地址 或者 0.0.0.0
if (url.getParameter(Constants.SIDE_KEY, Constants.PROVIDER).equals(Constants.CONSUMER)) {
// 如果它是一个消费者ip地址,目的是控制一个特定的消费者实例,它必须在消费者一方生效,任何提供者收到这个覆盖url应该忽略;
return configureIfMatch(NetUtils.getLocalHost(), url);// NetUtils.getLocalHost is the ip address consumer registered to registry.
} else if (url.getParameter(Constants.SIDE_KEY, Constants.CONSUMER).equals(Constants.PROVIDER)) {
// 如果ip为0.0.0.0,则此覆盖url可以在使用者上使用,也可以在提供者上使用
return configureIfMatch(Constants.ANYHOST_VALUE, url);// take effect on all providers, so address must be 0.0.0.0, otherwise it won't flow to this if branch
}
}
return url;
}</code></pre>
<p>该方法是规则配置到URL中,但是关键逻辑在configureIfMatch方法中。</p>
<h5>2.configureIfMatch</h5>
<pre><code class="java">private URL configureIfMatch(String host, URL url) {
// 匹配 Host
if (Constants.ANYHOST_VALUE.equals(configuratorUrl.getHost()) || host.equals(configuratorUrl.getHost())) {
String configApplication = configuratorUrl.getParameter(Constants.APPLICATION_KEY,
configuratorUrl.getUsername());
String currentApplication = url.getParameter(Constants.APPLICATION_KEY, url.getUsername());
// 匹配 "application"
if (configApplication == null || Constants.ANY_VALUE.equals(configApplication)
|| configApplication.equals(currentApplication)) {
Set<String> conditionKeys = new HashSet<String>();
// 配置 URL 中的条件 KEYS 集合。其中下面四个 KEY ,不算是条件,而是内置属性。考虑到下面要移除,所以添加到该集合中。
conditionKeys.add(Constants.CATEGORY_KEY);
conditionKeys.add(Constants.CHECK_KEY);
conditionKeys.add(Constants.DYNAMIC_KEY);
conditionKeys.add(Constants.ENABLED_KEY);
// 判断传入的 url 是否匹配配置规则 URL 的条件。
for (Map.Entry<String, String> entry : configuratorUrl.getParameters().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 除了 "application" 和 "side" 之外,带有 `"~"` 开头的 KEY ,也是条件。
if (key.startsWith("~") || Constants.APPLICATION_KEY.equals(key) || Constants.SIDE_KEY.equals(key)) {
// 添加搭配条件集合
conditionKeys.add(key);
if (value != null && !Constants.ANY_VALUE.equals(value)
&& !value.equals(url.getParameter(key.startsWith("~") ? key.substring(1) : key))) {
return url;
}
}
}
// 移除条件 KEYS 集合,并配置到 URL 中
return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
}
}
return url;
}</code></pre>
<p>该方法是当条件匹配时,才对url进行配置。</p>
<h5>3.compareTo</h5>
<pre><code class="java">@Override
public int compareTo(Configurator o) {
if (o == null) {
return -1;
}
// // host 升序
int ipCompare = getUrl().getHost().compareTo(o.getUrl().getHost());
// 如果host相同,则根据priority降序来对比
if (ipCompare == 0) {//host is the same, sort by priority
int i = getUrl().getParameter(Constants.PRIORITY_KEY, 0),
j = o.getUrl().getParameter(Constants.PRIORITY_KEY, 0);
return i < j ? -1 : (i == j ? 0 : 1);
} else {
return ipCompare;
}
}</code></pre>
<p>这是配置的排序策略。先根据host升序,如果相同,再通过priority降序。</p>
<h4>(二)AbsentConfigurator</h4>
<pre><code class="java">public class AbsentConfigurator extends AbstractConfigurator {
public AbsentConfigurator(URL url) {
super(url);
}
@Override
public URL doConfigure(URL currentUrl, URL configUrl) {
// 当不存在时添加
return currentUrl.addParametersIfAbsent(configUrl.getParameters());
}
}</code></pre>
<p>该配置方式就是当配置不存在的时候添加。</p>
<h4>(三)AbsentConfiguratorFactory</h4>
<pre><code class="java">public class AbsentConfiguratorFactory implements ConfiguratorFactory {
@Override
public Configurator getConfigurator(URL url) {
// 创建一个AbsentConfigurator。
return new AbsentConfigurator(url);
}
}</code></pre>
<p>该类是不存在时添加配置的工厂类,用来创建AbsentConfigurator。</p>
<h4>(四)OverrideConfigurator</h4>
<pre><code class="java">public class OverrideConfigurator extends AbstractConfigurator {
public OverrideConfigurator(URL url) {
super(url);
}
@Override
public URL doConfigure(URL currentUrl, URL configUrl) {
// 覆盖添加
return currentUrl.addParameters(configUrl.getParameters());
}
}</code></pre>
<p>这种是覆盖添加。是目前在用的配置方式。</p>
<h4>(五)OverrideConfiguratorFactory</h4>
<pre><code class="java">public class OverrideConfiguratorFactory implements ConfiguratorFactory {
@Override
public Configurator getConfigurator(URL url) {
// 创建OverrideConfigurator
return new OverrideConfigurator(url);
}
}</code></pre>
<p>该类是OverrideConfigurator的工厂类,用来提供OverrideConfigurator实例。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=FeXbpoJ9miSmu9uww1fBIw%3D%3D.CBaRmHb41Ti3IIlZjCNj1UxP4wEn2SA1dsXnS3n2UyfuZDX7FxOSobcsfwK6RgLykG01t05ta3tIcynT%2BkQS14Bb5j76E3j%2BT9NETv8U8Ung8G%2F8ZGKrivEtzYnCeMRoJUWo6UNN77Y0rrvFUlmLKQjGaG1NUHSzFiNmcxaqCQ9Y7UL6qBVteQZZgQ6e2H8j" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了集群中关于configurator实现的部分,讲了两种配置方式,分别是不存在再添加和覆盖添加。接下来我将开始对集群模块关于Directory部分进行讲解。</p>
Dubbo源码解析(三十五)集群——cluster
https://segmentfault.com/a/1190000018099552
2019-02-04T13:30:32+08:00
2019-02-04T13:30:32+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
5
<h2>集群——cluster</h2>
<blockquote>目标:介绍dubbo中集群容错的几种模式,介绍dubbo-cluster下support包的源码。</blockquote>
<h3>前言</h3>
<p>集群容错还是很好理解的,就是当你调用失败的时候所作出的措施。先来看看有哪些模式:</p>
<p><img src="/img/remote/1460000018099555" alt="cluster" title="cluster"></p>
<p>图有点小,见谅,不过可以眯着眼睛看稍微能看出来一点,每一个Cluster实现类都对应着一个invoker,因为这个模式启用的时间点就是在调用的时候,而我在之前的文章里面讲过,invoker贯穿来整个服务的调用。不过这里除了调用失败的一些模式外,还有几个特别的模式,他们应该说成是失败的措施,而已调用的方式。</p>
<ol>
<li>Failsafe Cluster:失败安全,出现异常时,直接忽略。失败安全就是当调用过程中出现异常时,FailsafeClusterInvoker 仅会打印异常,而不会抛出异常。适用于写入审计日志等操作</li>
<li>Failover Cluster:失败自动切换,当调用出现失败的时候,会自动切换集群中其他服务器,来获得invoker重试,通常用于读操作,但重试会带来更长延迟。一般都会设置重试次数。</li>
<li>Failfast Cluster:只会进行一次调用,失败后立即抛出异常。适用于幂等操作,比如新增记录。</li>
<li>Failback Cluster:失败自动恢复,在调用失败后,返回一个空结果给服务提供者。并通过定时任务对失败的调用记录并且重传,适合执行消息通知等操作。</li>
<li>Forking Cluster:会在线程池中运行多个线程,来调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。一般会设置最大并行数。</li>
<li>Available Cluster:调用第一个可用的服务器,仅仅应用于多注册中心。</li>
<li>Broadcast Cluster:广播调用所有提供者,逐个调用,在循环调用结束后,只要任意一台报错就报错。通常用于通知所有提供者更新缓存或日志等本地资源信息</li>
<li>Mergeable Cluster:该部分在分组聚合讲述。</li>
<li>MockClusterWrapper:该部分在本地伪装讲述。</li>
</ol>
<h3>源码分析</h3>
<h4>(一)AbstractClusterInvoker</h4>
<p>该类实现了Invoker接口,是集群Invoker的抽象类。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory
.getLogger(AbstractClusterInvoker.class);
/**
* 目录,包含多个invoker
*/
protected final Directory<T> directory;
/**
* 是否需要核对可用
*/
protected final boolean availablecheck;
/**
* 是否销毁
*/
private AtomicBoolean destroyed = new AtomicBoolean(false);
/**
* 粘滞连接的Invoker
*/
private volatile Invoker<T> stickyInvoker = null;</code></pre>
<h5>2.select</h5>
<pre><code class="java">protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
// 如果invokers为空,则返回null
if (invokers == null || invokers.isEmpty())
return null;
// 获得方法名
String methodName = invocation == null ? "" : invocation.getMethodName();
// 是否启动了粘滞连接
boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY);
{
//ignore overloaded method
// 如果上一次粘滞连接的调用不在可选的提供者列合内,则直接设置为空
if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
stickyInvoker = null;
}
//ignore concurrency problem
// stickyInvoker不为null,并且没在已选列表中,返回上次的服务提供者stickyInvoker,但之前强制校验可达性。
// 由于stickyInvoker不能包含在selected列表中,通过代码看,可以得知forking和failover集群策略,用不了sticky属性
if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
if (availablecheck && stickyInvoker.isAvailable()) {
return stickyInvoker;
}
}
}
// 利用负载均衡选一个提供者
Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
// 如果启动粘滞连接,则记录这一次的调用
if (sticky) {
stickyInvoker = invoker;
}
return invoker;
}</code></pre>
<p>该方法实现了使用负载均衡策略选择一个调用者。首先,使用loadbalance选择一个调用者。如果此调用者位于先前选择的列表中,或者如果此调用者不可用,则重新选择,否则返回第一个选定的调用者。重新选择,重选的验证规则:选择>可用。这条规则可以保证所选的调用者最少有机会成为之前选择的列表中的一个,也是保证这个调用程序可用。</p>
<h5>3.doSelect</h5>
<pre><code class="java">private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (invokers == null || invokers.isEmpty())
return null;
// 如果只有一个 ,就直接返回这个
if (invokers.size() == 1)
return invokers.get(0);
// 如果没有指定用哪个负载均衡策略,则默认用随机负载均衡策略
if (loadbalance == null) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
// 调用负载均衡选择
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
//If the `invoker` is in the `selected` or invoker is unavailable && availablecheck is true, reselect.
// 如果选择的提供者,已在selected中或者不可用则重新选择
if ((selected != null && selected.contains(invoker))
|| (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
// 重新选择
Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
if (rinvoker != null) {
invoker = rinvoker;
} else {
//Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
// 如果重新选择失败,看下第一次选的位置,如果不是最后,选+1位置.
int index = invokers.indexOf(invoker);
try {
//Avoid collision
// 最后再避免选择到同一个invoker
invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0);
} catch (Exception e) {
logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
}
}
} catch (Throwable t) {
logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
}
}
return invoker;
}</code></pre>
<p>该方法是用负载均衡选择一个invoker的主要逻辑。</p>
<h5>4.reselect</h5>
<pre><code class="java">private Invoker<T> reselect(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers, List<Invoker<T>> selected, boolean availablecheck)
throws RpcException {
//Allocating one in advance, this list is certain to be used.
//预先分配一个重选列表,这个列表是一定会用到的.
List<Invoker<T>> reselectInvokers = new ArrayList<Invoker<T>>(invokers.size() > 1 ? (invokers.size() - 1) : invokers.size());
//First, try picking a invoker not in `selected`.
//先从非select中选
//把不包含在selected中的提供者,放入重选列表reselectInvokers,让负载均衡器选择
if (availablecheck) { // invoker.isAvailable() should be checked
for (Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
if (selected == null || !selected.contains(invoker)) {
reselectInvokers.add(invoker);
}
}
}
// 在重选列表中用负载均衡器选择
if (!reselectInvokers.isEmpty()) {
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
} else { // do not check invoker.isAvailable()
// 不核对服务是否可以,把不包含在selected中的提供者,放入重选列表reselectInvokers,让负载均衡器选择
for (Invoker<T> invoker : invokers) {
if (selected == null || !selected.contains(invoker)) {
reselectInvokers.add(invoker);
}
}
if (!reselectInvokers.isEmpty()) {
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
}
// Just pick an available invoker using loadbalance policy
{
// 如果非selected的列表中没有选择到,则从selected中选择
if (selected != null) {
for (Invoker<T> invoker : selected) {
if ((invoker.isAvailable()) // available first
&& !reselectInvokers.contains(invoker)) {
reselectInvokers.add(invoker);
}
}
}
if (!reselectInvokers.isEmpty()) {
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
}
return null;
}</code></pre>
<p>该方法是是重新选择的逻辑实现。</p>
<h5>5.invoke</h5>
<pre><code class="java">@Override
public Result invoke(final Invocation invocation) throws RpcException {
// 核对是否已经销毁
checkWhetherDestroyed();
LoadBalance loadbalance = null;
// binding attachments into invocation.
// 获得上下文的附加值
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
// 把附加值放入到会话域中
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
// 生成服务提供者集合
List<Invoker<T>> invokers = list(invocation);
if (invokers != null && !invokers.isEmpty()) {
// 获得负载均衡器
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}</code></pre>
<p>该方法是invoker接口必备的方法,调用链的逻辑,不过主要的逻辑在doInvoke方法中,该方法是该类的抽象方法,让子类只关注doInvoke方法。</p>
<h5>6.list</h5>
<pre><code class="java">protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
// 把会话域中的invoker加入集合
List<Invoker<T>> invokers = directory.list(invocation);
return invokers;
}</code></pre>
<p>该方法是调用了directory的list方法,从会话域中获得所有的Invoker集合。关于directory我会在后续文章讲解。</p>
<h4>(二)AvailableCluster</h4>
<pre><code class="java">public class AvailableCluster implements Cluster {
public static final String NAME = "available";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建一个AbstractClusterInvoker
return new AbstractClusterInvoker<T>(directory) {
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 遍历所有的involer,只要有一个可用就直接调用。
for (Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
throw new RpcException("No provider available in " + invokers);
}
};
}
}</code></pre>
<p>Available Cluster我在上面已经讲过了,只要找到一个可用的,则直接调用。</p>
<h4>(三)BroadcastCluster</h4>
<pre><code class="java">public class BroadcastCluster implements Cluster {
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建一个BroadcastClusterInvoker
return new BroadcastClusterInvoker<T>(directory);
}
}</code></pre>
<p>关键实现在于BroadcastClusterInvoker。</p>
<h4>(四)BroadcastClusterInvoker</h4>
<pre><code class="java">public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class);
public BroadcastClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 检测invokers是否为空
checkInvokers(invokers, invocation);
// 把invokers放到上下文
RpcContext.getContext().setInvokers((List) invokers);
RpcException exception = null;
Result result = null;
// 遍历invokers,逐个调用,在循环调用结束后,只要任意一台报错就报错
for (Invoker<T> invoker : invokers) {
try {
result = invoker.invoke(invocation);
} catch (RpcException e) {
exception = e;
logger.warn(e.getMessage(), e);
} catch (Throwable e) {
exception = new RpcException(e.getMessage(), e);
logger.warn(e.getMessage(), e);
}
}
if (exception != null) {
throw exception;
}
return result;
}
}
</code></pre>
<h4>(五)ForkingCluster</h4>
<pre><code class="java">public class ForkingCluster implements Cluster {
public final static String NAME = "forking";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建ForkingClusterInvoker
return new ForkingClusterInvoker<T>(directory);
}
}</code></pre>
<h4>(六)ForkingClusterInvoker</h4>
<pre><code class="java">public class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> {
/**
* 线程池
* Use {@link NamedInternalThreadFactory} to produce {@link com.alibaba.dubbo.common.threadlocal.InternalThread}
* which with the use of {@link com.alibaba.dubbo.common.threadlocal.InternalThreadLocal} in {@link RpcContext}.
*/
private final ExecutorService executor = Executors.newCachedThreadPool(
new NamedInternalThreadFactory("forking-cluster-timer", true));
public ForkingClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
// 检测invokers是否为空
checkInvokers(invokers, invocation);
final List<Invoker<T>> selected;
// 获取 forks 配置
final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS);
// 获取超时配置
final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 如果 forks 配置不合理,则直接将 invokers 赋值给 selected
if (forks <= 0 || forks >= invokers.size()) {
selected = invokers;
} else {
selected = new ArrayList<Invoker<T>>();
// 循环选出 forks 个 Invoker,并添加到 selected 中
for (int i = 0; i < forks; i++) {
// TODO. Add some comment here, refer chinese version for more details.
// 选择 Invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
if (!selected.contains(invoker)) {//Avoid add the same invoker several times.
// 加入到selected集合
selected.add(invoker);
}
}
}
// 放入上下文
RpcContext.getContext().setInvokers((List) selected);
final AtomicInteger count = new AtomicInteger();
final BlockingQueue<Object> ref = new LinkedBlockingQueue<Object>();
// 遍历 selected 列表
for (final Invoker<T> invoker : selected) {
// 为每个 Invoker 创建一个执行线程
executor.execute(new Runnable() {
@Override
public void run() {
try {
// 进行远程调用
Result result = invoker.invoke(invocation);
// 将结果存到阻塞队列中
ref.offer(result);
} catch (Throwable e) {
// 仅在 value 大于等于 selected.size() 时,才将异常对象
// 为了防止异常现象覆盖正常的结果
int value = count.incrementAndGet();
if (value >= selected.size()) {
// 将异常对象存入到阻塞队列中
ref.offer(e);
}
}
}
});
}
try {
// 从阻塞队列中取出远程调用结果
Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
// 如果是异常,则抛出
if (ret instanceof Throwable) {
Throwable e = (Throwable) ret;
throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);
}
return (Result) ret;
} catch (InterruptedException e) {
throw new RpcException("Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e);
}
} finally {
// clear attachments which is binding to current thread.
RpcContext.getContext().clearAttachments();
}
}
}</code></pre>
<h4>(七)FailbackCluster</h4>
<pre><code class="java">public class FailbackCluster implements Cluster {
public final static String NAME = "failback";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建一个FailbackClusterInvoker
return new FailbackClusterInvoker<T>(directory);
}
}</code></pre>
<h4>(八)FailbackClusterInvoker</h4>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class);
// 重试间隔
private static final long RETRY_FAILED_PERIOD = 5 * 1000;
/**
* 定时器
* Use {@link NamedInternalThreadFactory} to produce {@link com.alibaba.dubbo.common.threadlocal.InternalThread}
* which with the use of {@link com.alibaba.dubbo.common.threadlocal.InternalThreadLocal} in {@link RpcContext}.
*/
private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2,
new NamedInternalThreadFactory("failback-cluster-timer", true));
/**
* 失败集合
*/
private final ConcurrentMap<Invocation, AbstractClusterInvoker<?>> failed = new ConcurrentHashMap<Invocation, AbstractClusterInvoker<?>>();
/**
* future
*/
private volatile ScheduledFuture<?> retryFuture;</code></pre>
<h5>2.doInvoke</h5>
<pre><code class="java">@Override
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
// 检测invokers是否为空
checkInvokers(invokers, invocation);
// 选择出invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
// 调用
return invoker.invoke(invocation);
} catch (Throwable e) {
logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
+ e.getMessage() + ", ", e);
// 如果失败,则加入到失败队列,等待重试
addFailed(invocation, this);
return new RpcResult(); // ignore
}
}</code></pre>
<p>该方法是选择invoker调用的逻辑,在抛出异常的时候,做了失败重试的机制,主要实现在addFailed。</p>
<h5>3.addFailed</h5>
<pre><code class="java">private void addFailed(Invocation invocation, AbstractClusterInvoker<?> router) {
if (retryFuture == null) {
// 锁住
synchronized (this) {
if (retryFuture == null) {
// 创建定时任务,每隔5秒执行一次
retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
// collect retry statistics
try {
// 对失败的调用进行重试
retryFailed();
} catch (Throwable t) { // Defensive fault tolerance
logger.error("Unexpected error occur at collect statistic", t);
}
}
}, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS);
}
}
}
// 添加 invocation 和 invoker 到 failed 中
failed.put(invocation, router);
}</code></pre>
<p>该方法做的事创建了定时器,然后把失败的调用放入到集合中。</p>
<h5>4.retryFailed</h5>
<pre><code class="java">void retryFailed() {
// 如果失败队列为0,返回
if (failed.size() == 0) {
return;
}
// 遍历失败队列
for (Map.Entry<Invocation, AbstractClusterInvoker<?>> entry : new HashMap<Invocation, AbstractClusterInvoker<?>>(
failed).entrySet()) {
// 获得会话域
Invocation invocation = entry.getKey();
// 获得invoker
Invoker<?> invoker = entry.getValue();
try {
// 重新调用
invoker.invoke(invocation);
// 从失败队列中移除
failed.remove(invocation);
} catch (Throwable e) {
logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
}
}
}</code></pre>
<p>这个方法是调用失败的invoker重新调用的机制。</p>
<h4>(九)FailfastCluster</h4>
<pre><code class="java">public class FailfastCluster implements Cluster {
public final static String NAME = "failfast";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建FailfastClusterInvoker
return new FailfastClusterInvoker<T>(directory);
}
}</code></pre>
<h4>(十)FailfastClusterInvoker</h4>
<pre><code class="java">public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {
public FailfastClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 检测invokers是否为空
checkInvokers(invokers, invocation);
// 选择一个invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
try {
// 调用
return invoker.invoke(invocation);
} catch (Throwable e) {
if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception.
// 抛出异常
throw (RpcException) e;
}
// 抛出异常
throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName() + " select from all providers " + invokers + " for service " + getInterface().getName() + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);
}
}
}</code></pre>
<p>逻辑比较简单,调用抛出异常就直接抛出。</p>
<h4>(十一)FailoverCluster</h4>
<pre><code class="java">public class FailoverCluster implements Cluster {
public final static String NAME = "failover";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建FailoverClusterInvoker
return new FailoverClusterInvoker<T>(directory);
}
}</code></pre>
<h4>(十二)FailoverClusterInvoker</h4>
<pre><code class="java">public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(FailoverClusterInvoker.class);
public FailoverClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 复制一个invoker集合
List<Invoker<T>> copyinvokers = invokers;
// 检测是否为空
checkInvokers(copyinvokers, invocation);
// 获取重试次数
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// retry loop.
// 记录最后一个异常
RpcException le = null; // last exception.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
// 循环调用,失败重试
for (int i = 0; i < len; i++) {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
// 在进行重试前重新列举 Invoker,这样做的好处是,如果某个服务挂了,
// 通过调用 list 可得到最新可用的 Invoker 列表
if (i > 0) {
checkWhetherDestroyed();
copyinvokers = list(invocation);
// check again
// 检测copyinvokers 是否为空
checkInvokers(copyinvokers, invocation);
}
// 通过负载均衡选择invoker
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
// 添加到 invoker 到 invoked 列表中
invoked.add(invoker);
// 设置 invoked 到 RPC 上下文中
RpcContext.getContext().setInvokers((List) invoked);
try {
// 调用目标 Invoker 的 invoke 方法
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + invocation.getMethodName()
+ " in the service " + getInterface().getName()
+ " was successful by the provider " + invoker.getUrl().getAddress()
+ ", but there have been failed providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Last error is: "
+ le.getMessage(), le);
}
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
// 若重试失败,则抛出异常
throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
+ invocation.getMethodName() + " in the service " + getInterface().getName()
+ ". Tried " + len + " times of the providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
+ Version.getVersion() + ". Last error is: "
+ (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
}
}</code></pre>
<p>该类实现了失败重试的容错策略,当调用失败的时候,记录下异常,然后循环调用下一个选择出来的invoker,直到重试次数用完,抛出最后一次的异常。</p>
<h4>(十三)FailsafeCluster</h4>
<pre><code class="java">public class FailsafeCluster implements Cluster {
public final static String NAME = "failsafe";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建FailsafeClusterInvoker
return new FailsafeClusterInvoker<T>(directory);
}
}</code></pre>
<h4>(十四)FailsafeClusterInvoker</h4>
<pre><code class="java">public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class);
public FailsafeClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
// 检测invokers是否为空
checkInvokers(invokers, invocation);
// 选择一个invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
// 调用
return invoker.invoke(invocation);
} catch (Throwable e) {
// 如果失败打印异常,返回一个空结果
logger.error("Failsafe ignore exception: " + e.getMessage(), e);
return new RpcResult(); // ignore
}
}
}</code></pre>
<p>逻辑比较简单,就是不抛出异常,只是打印异常。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=pc0X5z2T%2BI%2BBfNcxUcUC6w%3D%3D.9jbBYQvw9hIAnGG5bjZlFxrWtgCLtp9lhUbRnowXb3WFzGv6qZK0qfH5VwkI9jXUMDxVKPrk9F7vehMGHqDyCNDo6eVz60HwxgtmV0%2BkhKZTF2%2BnSIK9djTHwAdUcFxSVmSwhW8viYnzjnoYU9fn0NPteN6k0%2BBrTz0WJS0XrXQUGTX7Y7HjgRBuPfLpVWHp" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了集群中关于cluster实现的部分,讲了几种调用方式和容错策略。接下来我将开始对集群模块关于配置规则部分进行讲解。</p>
Dubbo源码解析(三十一)远程调用——rmi协议
https://segmentfault.com/a/1190000018098414
2019-02-03T21:51:50+08:00
2019-02-03T21:51:50+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
5
<h2>远程调用——rmi协议</h2>
<blockquote>目标:介绍rmi协议的设计和实现,介绍dubbo-rpc-rmi的源码。</blockquote>
<h3>前言</h3>
<p>dubbo支持rmi协议,主要基于spring封装的org.springframework.remoting.rmi包来实现,当然最原始还是依赖 JDK 标准的java.rmi.*包,采用阻塞式短连接和 JDK 标准序列化方式。关于rmi协议的介绍可以参考dubbo官方文档。</p>
<blockquote>地址:<a href="https://link.segmentfault.com/?enc=1RavHZUzutXWzJgrkW3FFQ%3D%3D.pkFy62Tsakxa%2FqYpO1qZY%2FMsgAK28hA1qSZitCRAb1m3756Anl%2FYONm6CtPdBWtEleq0pR2aznbfkdy1c9KVMmXU8qHQnDpT7dQoiCoZV5c%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<h3>源码分析</h3>
<h4>(一)RmiRemoteInvocation</h4>
<p>该类继承了RemoteInvocation,主要是在RemoteInvocation的基础上新增dubbo自身所需的附加值,避免这些附加值没有被传递,为了做一些验证处理。</p>
<pre><code class="java">public class RmiRemoteInvocation extends RemoteInvocation {
private static final long serialVersionUID = 1L;
private static final String dubboAttachmentsAttrName = "dubbo.attachments";
/**
* executed on consumer side
*/
public RmiRemoteInvocation(MethodInvocation methodInvocation) {
super(methodInvocation);
// 添加dubbo附加值的属性
addAttribute(dubboAttachmentsAttrName, new HashMap<String, String>(RpcContext.getContext().getAttachments()));
}
/**
* Need to restore context on provider side (Though context will be overridden by Invocation's attachment
* when ContextFilter gets executed, we will restore the attachment when Invocation is constructed, check more
* 需要在提供者端恢复上下文(尽管上下文将被Invocation的附件覆盖
* 当ContextFilter执行时,我们将在构造Invocation时恢复附件,检查更多
* from {@link com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler}
*/
@SuppressWarnings("unchecked")
@Override
public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
// 获得上下文
RpcContext context = RpcContext.getContext();
// 设置参数
context.setAttachments((Map<String, String>) getAttribute(dubboAttachmentsAttrName));
try {
return super.invoke(targetObject);
} finally {
// 清空参数
context.setAttachments(null);
}
}
}</code></pre>
<h4>(二)RmiProtocol</h4>
<p>该类继承了AbstractProxyProtocol类,是rmi协议实现的核心,跟其他协议一样,也实现了自己的服务暴露和服务引用方法。</p>
<h5>1.doExport</h5>
<pre><code class="java">@Override
protected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException {
// rmi暴露者
final RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
// 设置端口
rmiServiceExporter.setRegistryPort(url.getPort());
// 设置服务名称
rmiServiceExporter.setServiceName(url.getPath());
// 设置接口
rmiServiceExporter.setServiceInterface(type);
// 设置服务实现
rmiServiceExporter.setService(impl);
try {
// 初始化bean的时候执行
rmiServiceExporter.afterPropertiesSet();
} catch (RemoteException e) {
throw new RpcException(e.getMessage(), e);
}
return new Runnable() {
@Override
public void run() {
try {
// 销毁
rmiServiceExporter.destroy();
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
}
};
}</code></pre>
<p>该方法是服务暴露的逻辑实现。</p>
<h5>2.doRefer</h5>
<pre><code class="java">@Override
@SuppressWarnings("unchecked")
protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException {
// FactoryBean对于RMI代理,支持传统的RMI服务和RMI调用者,创建RmiProxyFactoryBean对象
final RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
// RMI needs extra parameter since it uses customized remote invocation object
// 检测版本
if (url.getParameter(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()).equals(Version.getProtocolVersion())) {
// Check dubbo version on provider, this feature only support
// 设置RemoteInvocationFactory以用于此访问器
rmiProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() {
@Override
public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
// 自定义调用工厂可以向调用添加更多上下文信息
return new RmiRemoteInvocation(methodInvocation);
}
});
}
// 设置此远程访问者的目标服务的URL。URL必须与特定远程处理提供程序的规则兼容。
rmiProxyFactoryBean.setServiceUrl(url.toIdentityString());
// 设置要访问的服务的接口。界面必须适合特定的服务和远程处理策略
rmiProxyFactoryBean.setServiceInterface(serviceType);
// 设置是否在找到RMI存根后缓存它
rmiProxyFactoryBean.setCacheStub(true);
// 设置是否在启动时查找RMI存根
rmiProxyFactoryBean.setLookupStubOnStartup(true);
// 设置是否在连接失败时刷新RMI存根
rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);
// // 初始化bean的时候执行
rmiProxyFactoryBean.afterPropertiesSet();
return (T) rmiProxyFactoryBean.getObject();
}</code></pre>
<p>该方法是服务引用的逻辑实现。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=WSRfnpr%2BB1Vm3c1HdVmg2w%3D%3D.19k3fya3zIZ0dc0PYeupcIFg514RFXSQ%2Byl%2FQJcja5qpPYxcDtc4MCFghYQoDMkZPYR1n06PUH7Z74tdFu%2BupIJvM%2FemOi9omJ%2B2pUoPhjGnj4inBJzIDZG5ms6D4ft%2Bzf8CeMWERcwb5GnJggvGe4j%2BZLIYlWJKKIpBRwcOLmpg%2BCPkgHR4C3KZ2jiut5%2BO" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于rmi协议实现的部分,逻辑比较简单。接下来我将开始对rpc模块关于thrift协议部分进行讲解。</p>
Dubbo源码解析(三十四)集群——开篇
https://segmentfault.com/a/1190000018088905
2019-02-01T17:55:32+08:00
2019-02-01T17:55:32+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
3
<h2>集群——开篇</h2>
<blockquote>目标:介绍接下来集群分哪几部分来描述,介绍dubbo在集群中涉及到的几个功能,介绍dubbo-cluster下跟各个功能相关的接口</blockquote>
<h3>集群是什么?</h3>
<p>如果说分布式是爸爸住在杭州,妈妈住在上海,那么集群就是两个爸爸,一个住在杭州,一个住在上海。对于分布式和集群有高吞吐量、高可用的目标。对于分布式来说每个服务器部署的服务任务是不同的,可能需要这些服务器上的服务共同协作才能完成整个业务流程,各个服务各司其职。而集群不一样,集群是同一个服务,被部署在了多个服务器上,每个服务器的任务都是一样的,是为了减少压力集中的问题,而集群中就会出现负载均衡、容错等问题。</p>
<p>dubbo的集群涉及到以下几部分内容:</p>
<ol>
<li>目录:Directory可以看成是多个Invoker的集合,但是它的值会随着注册中心中服务变化推送而动态变化,那么Invoker以及如何动态变化就是一个重点内容。</li>
<li>集群容错:Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。</li>
<li>路由:dubbo路由规则,路由规则决定了一次dubbo服务调用的目标服务器,路由规则分两种:条件路由规则和脚本路由规则,并且支持可拓展。</li>
<li>负载均衡策略:dubbo支持的所有负载均衡策略算法。</li>
<li>配置:根据url上的配置规则生成配置信息</li>
<li>分组聚合:合并返回结果。</li>
<li>本地伪装:mork通常用于服务降级,mock只在出现非业务异常(比如超时,网络异常等)时执行</li>
</ol>
<p>以上几部分跟<a href="https://segmentfault.com/a/1190000016741532?share_user=1030000009586134">《dubbo源码解析(一)Hello,Dubbo》</a>的"(二)dubbo-cluster——集群模块“介绍有些类似,这里再重新讲一遍是为了明确我接下来介绍集群模块的文章内容分布,也就是除了本文之外,我会用七篇文章来讲解以上的七部分,不管内容多少,只是为了把相应内容区分开来,能让读者有选择性的阅读。</p>
<p>在官方网站上有一段介绍我觉得写的非常的好:</p>
<p>集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。第二个阶段是在服务消费者进行远程调用时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。Directory 的用途是保存 Invoker,可简单类比为 List<invoker>。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Inovker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Inovker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Inovker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoker 方法,进行真正的远程调用。</p>
<p>本文要来讲的无非就是这几部分内容的一个大概,并且介绍一下这几部分内容涉及到的接口。集群的包结构我就不在这里展示了,就是<a href="https://segmentfault.com/a/1190000016741532?share_user=1030000009586134">《dubbo源码解析(一)Hello,Dubbo》</a>的"(二)dubbo-cluster——集群模块“中的图片。下面我们直接对应各个部分来介绍相应的接口源码。</p>
<h3>目录</h3>
<p><img src="/img/remote/1460000018088908" alt="cluster" title="cluster"></p>
<p>关于目录介绍请查看<a href="https://segmentfault.com/a/1190000016741532?share_user=1030000009586134">《dubbo源码解析(一)Hello,Dubbo》</a>的"(二)dubbo-cluster——集群模块“介绍。</p>
<h3>源码分析</h3>
<h4>(一)Cluster</h4>
<pre><code class="java">@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;
}</code></pre>
<p>该接口是集群容错接口,可以看到它是一个可扩展接口,默认实现FailoverCluster,当然它还会有其他的实现,每一种实现都代表了一种集群容错的方式,具体有哪些,可以看下面文章的介绍,他们都在support包下面,在本文只是让读者知道接口的定义。那么它还定义了一个join方法,作用就是把Directory对象变成一个 Invoker 对象用来后续的一系列调用。该Invoker代表了一个集群实现。似懂非懂就够了,后面看具体的实现会比较清晰。</p>
<h4>(二)Configurator</h4>
<pre><code class="java">public interface Configurator extends Comparable<Configurator> {
/**
* get the configurator url.
* 配置规则,生成url
* @return configurator url.
*/
URL getUrl();
/**
* Configure the provider url.
* 把规则配置到URL中
*
* @param url - old rovider url.
* @return new provider url.
*/
URL configure(URL url);
}</code></pre>
<p>该接口是配置规则的接口,定义了两个方法,第一个是配置规则,并且生成url,第二个是把配置配置到旧的url中,其实都是在url上应用规则。</p>
<h4>(三)ConfiguratorFactory</h4>
<pre><code class="java">@SPI
public interface ConfiguratorFactory {
/**
* get the configurator instance.
* 获得configurator实例
* @param url - configurator url.
* @return configurator instance.
*/
@Adaptive("protocol")
Configurator getConfigurator(URL url);
}</code></pre>
<p>该接口是Configurator的工厂接口,定义了一个getConfigurator方法来获得Configurator实例,比较好理解。</p>
<h4>(四)Directory</h4>
<pre><code class="java">public interface Directory<T> extends Node {
/**
* get service type.
* 获得服务类型
* @return service type.
*/
Class<T> getInterface();
/**
* list invokers.
* 获得所有服务Invoker集合
* @return invokers
*/
List<Invoker<T>> list(Invocation invocation) throws RpcException;
}</code></pre>
<p>该接口是目录接口,Directory 代表了多个 Invoker,并且它的值会随着注册中心的服务变更推送而变化 。一个服务类型对应一个Directory。定义的两个方法也比较好理解。</p>
<h4>(五)LoadBalance</h4>
<pre><code class="java">@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* select one invoker in list.
* 选择一个合适的调用,并且返回
* @param invokers invokers.
* @param url refer url
* @param invocation invocation.
* @return selected invoker.
*/
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}</code></pre>
<p>该接口是负载均衡的接口,dubbo也提供了四种负载均衡策略,也会在下面文章讲解。</p>
<h4>(六)Merger</h4>
<pre><code class="java">@SPI
public interface Merger<T> {
/**
* 合并T数组,返回合并后的T对象
* @param items
* @return
*/
T merge(T... items);
}</code></pre>
<p>该接口是分组聚合,将某对象数组合并为一个对象。</p>
<h4>(七)Router</h4>
<pre><code class="java">public interface Router extends Comparable<Router> {
/**
* get the router url.
* 获得路由规则的url
* @return url
*/
URL getUrl();
/**
* route.
* 筛选出跟规则匹配的Invoker集合
* @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;
}</code></pre>
<p>该接口是路由规则的接口,定义的两个方法,第一个方法是获得路由规则的url,第二个方法是筛选出跟规则匹配的Invoker集合。</p>
<h4>(八)RouterFactory</h4>
<pre><code class="java">@SPI
public interface RouterFactory {
/**
* Create router.
* 创建路由
* @param url
* @return router
*/
@Adaptive("protocol")
Router getRouter(URL url);
}</code></pre>
<p>该接口是路由工厂接口,定义了获得路由实例的方法。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=unyDY%2BZc6IUN6kmdInWmwg%3D%3D.GZEIIszLZRD5QL2dOcOGyQzkRzJoAhIqvFf7IOAC9xe8TlzCWk1LzFhpKJ8Eb%2BA1kRWzWBNg9CwVgV4gZkh5pTEXAPYce9JMiEetBmfBIykTQm9lBiCjE%2BCYPRXpDZkLvTsiDXB%2F58aA%2BUlqAiPnwUg3bIpzVyY0BoVUaJB0wyw%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章大致讲解了dubbo中集群模块的内容,并且讲解了相关接口的设计。接下来我将开始对cluster集群模块中的集群容错部分,也就是support中的源码进行讲解。</p>
Dubbo源码解析(三十三)远程调用——webservice协议
https://segmentfault.com/a/1190000018079811
2019-01-31T16:36:18+08:00
2019-01-31T16:36:18+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2>远程调用——webservice协议</h2>
<blockquote>目标:介绍webservice协议的设计和实现,介绍dubbo-rpc-webservice的源码。</blockquote>
<h3>前言</h3>
<p>dubbo集成webservice协议,基于 <a href="https://link.segmentfault.com/?enc=fDZiEjqy%2BukzfzboILgA6w%3D%3D.IldkVD9UqCqNqnCgMT%2FkLGeG6hmYYlFt9s1wEQmztqE%3D" rel="nofollow">Apache CXF</a> 的 <code>frontend-simple</code> 和 <code>transports-http</code> 实现 ,CXF 是 Apache 开源的一个 RPC 框架,由 Xfire 和 Celtix 合并而来。关于webservice协议的优势以及介绍可以查看官方文档,我就不多赘述。</p>
<h3>源码分析</h3>
<h4>(一)WebServiceProtocol</h4>
<p>该类继承了AbstractProxyProtocol,是webservice协议的关键逻辑实现。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 默认端口
*/
public static final int DEFAULT_PORT = 80;
/**
* 服务集合
*/
private final Map<String, HttpServer> serverMap = new ConcurrentHashMap<String, HttpServer>();
/**
* 总线,该总线使用CXF内置的扩展管理器来加载组件(而不是使用Spring总线实现)。虽然加载速度更快,但它不允许像Spring总线那样进行大量配置和定制。
*/
private final ExtensionManagerBus bus = new ExtensionManagerBus();
/**
* http通信工厂对象
*/
private final HTTPTransportFactory transportFactory = new HTTPTransportFactory();
/**
* http绑定者
*/
private HttpBinder httpBinder;</code></pre>
<h5>2.doExport</h5>
<pre><code class="java">@Override
protected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException {
// 获得地址
String addr = getAddr(url);
// 获得http服务
HttpServer httpServer = serverMap.get(addr);
// 如果服务为空,则重新创建服务器。并且加入集合
if (httpServer == null) {
httpServer = httpBinder.bind(url, new WebServiceHandler());
serverMap.put(addr, httpServer);
}
// 服务加载器
final ServerFactoryBean serverFactoryBean = new ServerFactoryBean();
// 设置地址
serverFactoryBean.setAddress(url.getAbsolutePath());
// 设置服务类型
serverFactoryBean.setServiceClass(type);
// 设置实现类
serverFactoryBean.setServiceBean(impl);
// 设置总线
serverFactoryBean.setBus(bus);
// 设置通信工厂
serverFactoryBean.setDestinationFactory(transportFactory);
// 创建
serverFactoryBean.create();
return new Runnable() {
@Override
public void run() {
if(serverFactoryBean.getServer()!= null) {
serverFactoryBean.getServer().destroy();
}
if(serverFactoryBean.getBus()!=null) {
serverFactoryBean.getBus().shutdown(true);
}
}
};
}</code></pre>
<p>该方法是服务暴露的逻辑实现,基于cxf一些类。</p>
<h5>3.doRefer</h5>
<pre><code class="java">@Override
@SuppressWarnings("unchecked")
protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException {
// 创建代理工厂
ClientProxyFactoryBean proxyFactoryBean = new ClientProxyFactoryBean();
// 设置地址
proxyFactoryBean.setAddress(url.setProtocol("http").toIdentityString());
// 设置服务类型
proxyFactoryBean.setServiceClass(serviceType);
// 设置总线
proxyFactoryBean.setBus(bus);
// 创建
T ref = (T) proxyFactoryBean.create();
// 获得代理
Client proxy = ClientProxy.getClient(ref);
// 获得HTTPConduit 处理“http”和“https”传输协议。实例由显式设置或配置的策略控制
HTTPConduit conduit = (HTTPConduit) proxy.getConduit();
// 用于配置客户端HTTP端口的属性
HTTPClientPolicy policy = new HTTPClientPolicy();
// 配置连接超时时间
policy.setConnectionTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT));
// 配置调用超时时间
policy.setReceiveTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
conduit.setClient(policy);
return ref;
}</code></pre>
<p>该方法是服务引用的逻辑实现。</p>
<h5>4.WebServiceHandler</h5>
<pre><code class="java">private class WebServiceHandler implements HttpHandler {
private volatile ServletController servletController;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// 如果servletController为空,则重新加载一个
if (servletController == null) {
HttpServlet httpServlet = DispatcherServlet.getInstance();
if (httpServlet == null) {
response.sendError(500, "No such DispatcherServlet instance.");
return;
}
// 创建servletController
synchronized (this) {
if (servletController == null) {
servletController = new ServletController(transportFactory.getRegistry(), httpServlet.getServletConfig(), httpServlet);
}
}
}
// 设置远程地址
RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
// 调用方法
servletController.invoke(request, response);
}
}</code></pre>
<p>该内部类实现了HttpHandler接口,是WebService协议的请求的处理类。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=Y8qB8%2BAgHYj4%2F8wVwfrpbg%3D%3D.0QzPJ265DkwCYDhK9wXwk7GYsQ9AqKxXbR8WiS%2B264WLU4TdJYhyuPXTnENU57syCrH10fgaEeahhyaqeKyRigkiaGD%2FZc%2FnDtFbjVLblLV38rzZU29oKtUDpVthAmov9kYl%2F6vUEN60OcD6RZBNo7wsroIZnCPuTprZgLbn47FKAJv1%2FoZi%2BuFww2aMt2k%2BgbpIlL18U0GIZjctRO0Gsg%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于webservice协议实现的部分,到这里关于rpc远程调用的部分就结束了,关于远程调用核心的几个内容就是代理、协议,再加上不同功能增强的过滤器等,关键是要把api中关于接口设计方面的内容看清楚,后面各类协议因为很多都是基于第三方的框架去实现,虽然方法逻辑有所区别,但是整体的思路和框架一定顺着api设计的去实现。接下来我将开始对cluster集群模块进行讲解。</p>
Dubbo源码解析(三十二)远程调用——thrift协议
https://segmentfault.com/a/1190000018070746
2019-01-30T17:39:34+08:00
2019-01-30T17:39:34+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
3
<h2>远程调用——thrift协议</h2>
<blockquote>目标:介绍thrift协议的设计和实现,介绍dubbo-rpc-thrift的源码。</blockquote>
<h3>前言</h3>
<p>dubbo集成thrift协议,是基于Thrift来实现的,Thrift是一种轻量级,与语言无关的软件堆栈,具有用于点对点RPC的相关代码生成机制。Thrift为数据传输,数据序列化和应用程序级处理提供了清晰的抽象。代码生成系统采用简单的定义语言作为输入,并跨编程语言生成代码,使用抽象堆栈构建可互操作的RPC客户端和服务器。</p>
<h3>源码分析</h3>
<h4>(一)MultiServiceProcessor</h4>
<p>该类对输入流进行操作并写入某些输出流。它实现了TProcessor接口,关键的方法是process。</p>
<pre><code class="java">@Override
public boolean process(TProtocol in, TProtocol out) throws TException {
// 获得十六进制的魔数
short magic = in.readI16();
// 如果不是规定的魔数,则打印错误日志,返回false
if (magic != ThriftCodec.MAGIC) {
logger.error("Unsupported magic " + magic);
return false;
}
// 获得三十二进制魔数
in.readI32();
// 获得十六进制魔数
in.readI16();
// 获得版本
byte version = in.readByte();
// 获得服务名
String serviceName = in.readString();
// 获得id
long id = in.readI64();
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
// 创建基础运输TIOStreamTransport对象
TIOStreamTransport transport = new TIOStreamTransport(bos);
// 获得协议
TProtocol protocol = protocolFactory.getProtocol(transport);
// 从集合中取出处理器
TProcessor processor = processorMap.get(serviceName);
// 如果处理器为空,则打印错误,返回false
if (processor == null) {
logger.error("Could not find processor for service " + serviceName);
return false;
}
// todo if exception
// 获得结果
boolean result = processor.process(in, protocol);
ByteArrayOutputStream header = new ByteArrayOutputStream(512);
// 协议头的传输器
TIOStreamTransport headerTransport = new TIOStreamTransport(header);
TProtocol headerProtocol = protocolFactory.getProtocol(headerTransport);
// 写入16进制的魔数
headerProtocol.writeI16(magic);
// 写入32进制的Integer最大值
headerProtocol.writeI32(Integer.MAX_VALUE);
// 写入Short最大值的16进制
headerProtocol.writeI16(Short.MAX_VALUE);
// 写入版本号
headerProtocol.writeByte(version);
// 写入服务名
headerProtocol.writeString(serviceName);
// 写入id
headerProtocol.writeI64(id);
// 输出
headerProtocol.getTransport().flush();
out.writeI16(magic);
out.writeI32(bos.size() + header.size());
out.writeI16((short) (0xffff & header.size()));
out.writeByte(version);
out.writeString(serviceName);
out.writeI64(id);
out.getTransport().write(bos.toByteArray());
out.getTransport().flush();
return result;
}</code></pre>
<h4>(二)RandomAccessByteArrayOutputStream</h4>
<p>该类是随机访问数组的输出流,比较简单,我就不多叙述,有兴趣的可以直接看源码,不看影响也不大。</p>
<h4>(三)ClassNameGenerator</h4>
<pre><code class="java">@SPI(DubboClassNameGenerator.NAME)
public interface ClassNameGenerator {
/**
* 生成参数的类名
*/
public String generateArgsClassName(String serviceName, String methodName);
/**
* 生成结果的类名
* @param serviceName
* @param methodName
* @return
*/
public String generateResultClassName(String serviceName, String methodName);
}</code></pre>
<p>该接口是是可扩展接口,定义了两个方法。有两个实现类,下面讲述。</p>
<h4>(四)DubboClassNameGenerator</h4>
<p>该类实现了ClassNameGenerator接口,是dubbo相关的类名生成实现。</p>
<pre><code class="java">public class DubboClassNameGenerator implements ClassNameGenerator {
public static final String NAME = "dubbo";
@Override
public String generateArgsClassName(String serviceName, String methodName) {
return ThriftUtils.generateMethodArgsClassName(serviceName, methodName);
}
@Override
public String generateResultClassName(String serviceName, String methodName) {
return ThriftUtils.generateMethodResultClassName(serviceName, methodName);
}
}</code></pre>
<h4>(五)ThriftClassNameGenerator</h4>
<p>该类实现了ClassNameGenerator接口,是Thrift相关的类名生成实现。</p>
<pre><code class="java">public class ThriftClassNameGenerator implements ClassNameGenerator {
public static final String NAME = "thrift";
@Override
public String generateArgsClassName(String serviceName, String methodName) {
return ThriftUtils.generateMethodArgsClassNameThrift(serviceName, methodName);
}
@Override
public String generateResultClassName(String serviceName, String methodName) {
return ThriftUtils.generateMethodResultClassNameThrift(serviceName, methodName);
}
}</code></pre>
<p>以上两个都调用了ThriftUtils中的方法。</p>
<h4>(六)ThriftUtils</h4>
<p>该类中封装的方法比较简单,就一些字符串的拼接,有兴趣的可以直接查看我下面贴出来的注释连接。</p>
<h4>(七)ThriftCodec</h4>
<p>该类是基于Thrift实现的编解码器。 这里需要大家看一下该类的注释,关于协议的数据:</p>
<pre><code class="java">* |<- message header ->|<- message body ->|
* +----------------+----------------------+------------------+---------------------------+------------------+
* | magic (2 bytes)|message size (4 bytes)|head size(2 bytes)| version (1 byte) | header | message body |
* +----------------+----------------------+------------------+---------------------------+------------------+
* |<- </code></pre>
<h5>1.属性</h5>
<pre><code class="java">/**
* 消息长度索引
*/
public static final int MESSAGE_LENGTH_INDEX = 2;
/**
* 消息头长度索引
*/
public static final int MESSAGE_HEADER_LENGTH_INDEX = 6;
/**
* 消息最短长度
*/
public static final int MESSAGE_SHORTEST_LENGTH = 10;
public static final String NAME = "thrift";
/**
* 类名生成参数
*/
public static final String PARAMETER_CLASS_NAME_GENERATOR = "class.name.generator";
/**
* 版本
*/
public static final byte VERSION = (byte) 1;
/**
* 魔数
*/
public static final short MAGIC = (short) 0xdabc;
/**
* 请求参数集合
*/
static final ConcurrentMap<Long, RequestData> cachedRequest =
new ConcurrentHashMap<Long, RequestData>();
/**
* thrift序列号
*/
private static final AtomicInteger THRIFT_SEQ_ID = new AtomicInteger(0);
/**
* 类缓存
*/
private static final ConcurrentMap<String, Class<?>> cachedClass =
new ConcurrentHashMap<String, Class<?>>();</code></pre>
<h5>2.encode</h5>
<pre><code class="java">@Override
public void encode(Channel channel, ChannelBuffer buffer, Object message)
throws IOException {
// 如果消息是Request类型
if (message instanceof Request) {
// Request类型消息编码
encodeRequest(channel, buffer, (Request) message);
} else if (message instanceof Response) {
// Response类型消息编码
encodeResponse(channel, buffer, (Response) message);
} else {
throw new UnsupportedOperationException("Thrift codec only support encode "
+ Request.class.getName() + " and " + Response.class.getName());
}
}</code></pre>
<p>该方法是编码的逻辑,具体的编码操作根据请求类型不同分别调用不同的方法。</p>
<h5>3.encodeRequest</h5>
<pre><code class="java">private void encodeRequest(Channel channel, ChannelBuffer buffer, Request request)
throws IOException {
// 获得会话域
RpcInvocation inv = (RpcInvocation) request.getData();
// 获得下一个id
int seqId = nextSeqId();
// 获得服务名
String serviceName = inv.getAttachment(Constants.INTERFACE_KEY);
// 如果是空的 则抛出异常
if (StringUtils.isEmpty(serviceName)) {
throw new IllegalArgumentException("Could not find service name in attachment with key "
+ Constants.INTERFACE_KEY);
}
// 创建TMessage对象
TMessage message = new TMessage(
inv.getMethodName(),
TMessageType.CALL,
seqId);
// 获得方法参数
String methodArgs = ExtensionLoader.getExtensionLoader(ClassNameGenerator.class)
.getExtension(channel.getUrl().getParameter(ThriftConstants.CLASS_NAME_GENERATOR_KEY, ThriftClassNameGenerator.NAME))
.generateArgsClassName(serviceName, inv.getMethodName());
// 如果是空,则抛出异常
if (StringUtils.isEmpty(methodArgs)) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION,
"Could not encode request, the specified interface may be incorrect.");
}
// 从缓存中取出类型
Class<?> clazz = cachedClass.get(methodArgs);
if (clazz == null) {
try {
// 重新获得类型
clazz = ClassHelper.forNameWithThreadContextClassLoader(methodArgs);
// 加入缓存
cachedClass.putIfAbsent(methodArgs, clazz);
} catch (ClassNotFoundException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
}
// 生成的Thrift对象的通用基接口
TBase args;
try {
args = (TBase) clazz.newInstance();
} catch (InstantiationException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
// 遍历参数
for (int i = 0; i < inv.getArguments().length; i++) {
Object obj = inv.getArguments()[i];
if (obj == null) {
continue;
}
TFieldIdEnum field = args.fieldForId(i + 1);
// 生成set方法名
String setMethodName = ThriftUtils.generateSetMethodName(field.getFieldName());
Method method;
try {
// 获得方法
method = clazz.getMethod(setMethodName, inv.getParameterTypes()[i]);
} catch (NoSuchMethodException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
try {
// 调用下一个调用链
method.invoke(args, obj);
} catch (IllegalAccessException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
}
// 创建一个随机访问数组输出流
RandomAccessByteArrayOutputStream bos = new RandomAccessByteArrayOutputStream(1024);
// 创建传输器
TIOStreamTransport transport = new TIOStreamTransport(bos);
// 创建协议
TBinaryProtocol protocol = new TBinaryProtocol(transport);
int headerLength, messageLength;
byte[] bytes = new byte[4];
try {
// 开始编码
// magic
protocol.writeI16(MAGIC);
// message length placeholder
protocol.writeI32(Integer.MAX_VALUE);
// message header length placeholder
protocol.writeI16(Short.MAX_VALUE);
// version
protocol.writeByte(VERSION);
// service name
protocol.writeString(serviceName);
// dubbo request id
protocol.writeI64(request.getId());
protocol.getTransport().flush();
// header size
headerLength = bos.size();
// 对body内容进行编码
// message body
protocol.writeMessageBegin(message);
args.write(protocol);
protocol.writeMessageEnd();
protocol.getTransport().flush();
int oldIndex = messageLength = bos.size();
// fill in message length and header length
try {
TFramedTransport.encodeFrameSize(messageLength, bytes);
bos.setWriteIndex(MESSAGE_LENGTH_INDEX);
protocol.writeI32(messageLength);
bos.setWriteIndex(MESSAGE_HEADER_LENGTH_INDEX);
protocol.writeI16((short) (0xffff & headerLength));
} finally {
bos.setWriteIndex(oldIndex);
}
} catch (TException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
buffer.writeBytes(bytes);
buffer.writeBytes(bos.toByteArray());
}</code></pre>
<p>该方法是对request类型的消息进行编码。</p>
<h5>4.encodeResponse</h5>
<pre><code class="java">private void encodeResponse(Channel channel, ChannelBuffer buffer, Response response)
throws IOException {
// 获得结果
RpcResult result = (RpcResult) response.getResult();
// 获得请求
RequestData rd = cachedRequest.get(response.getId());
// 获得结果的类名
String resultClassName = ExtensionLoader.getExtensionLoader(ClassNameGenerator.class).getExtension(
channel.getUrl().getParameter(ThriftConstants.CLASS_NAME_GENERATOR_KEY, ThriftClassNameGenerator.NAME))
.generateResultClassName(rd.serviceName, rd.methodName);
// 如果为空,则序列化失败
if (StringUtils.isEmpty(resultClassName)) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION,
"Could not encode response, the specified interface may be incorrect.");
}
// 获得类型
Class clazz = cachedClass.get(resultClassName);
// 如果为空,则重新获取
if (clazz == null) {
try {
clazz = ClassHelper.forNameWithThreadContextClassLoader(resultClassName);
cachedClass.putIfAbsent(resultClassName, clazz);
} catch (ClassNotFoundException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
}
TBase resultObj;
try {
// 加载该类
resultObj = (TBase) clazz.newInstance();
} catch (InstantiationException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
TApplicationException applicationException = null;
TMessage message;
// 如果结果有异常抛出
if (result.hasException()) {
Throwable throwable = result.getException();
int index = 1;
boolean found = false;
while (true) {
TFieldIdEnum fieldIdEnum = resultObj.fieldForId(index++);
if (fieldIdEnum == null) {
break;
}
String fieldName = fieldIdEnum.getFieldName();
String getMethodName = ThriftUtils.generateGetMethodName(fieldName);
String setMethodName = ThriftUtils.generateSetMethodName(fieldName);
Method getMethod;
Method setMethod;
try {
// 获得get方法
getMethod = clazz.getMethod(getMethodName);
// 如果返回类型和异常类型一样,则创建set方法,并且调用下一个调用链
if (getMethod.getReturnType().equals(throwable.getClass())) {
found = true;
setMethod = clazz.getMethod(setMethodName, throwable.getClass());
setMethod.invoke(resultObj, throwable);
}
} catch (NoSuchMethodException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
}
if (!found) {
// 创建TApplicationException异常
applicationException = new TApplicationException(throwable.getMessage());
}
} else {
// 获得真实的结果
Object realResult = result.getResult();
// result field id is 0
String fieldName = resultObj.fieldForId(0).getFieldName();
String setMethodName = ThriftUtils.generateSetMethodName(fieldName);
String getMethodName = ThriftUtils.generateGetMethodName(fieldName);
Method getMethod;
Method setMethod;
try {
// 创建get和set方法
getMethod = clazz.getMethod(getMethodName);
setMethod = clazz.getMethod(setMethodName, getMethod.getReturnType());
setMethod.invoke(resultObj, realResult);
} catch (NoSuchMethodException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
}
if (applicationException != null) {
message = new TMessage(rd.methodName, TMessageType.EXCEPTION, rd.id);
} else {
message = new TMessage(rd.methodName, TMessageType.REPLY, rd.id);
}
RandomAccessByteArrayOutputStream bos = new RandomAccessByteArrayOutputStream(1024);
TIOStreamTransport transport = new TIOStreamTransport(bos);
TBinaryProtocol protocol = new TBinaryProtocol(transport);
int messageLength;
int headerLength;
//编码
byte[] bytes = new byte[4];
try {
// magic
protocol.writeI16(MAGIC);
// message length
protocol.writeI32(Integer.MAX_VALUE);
// message header length
protocol.writeI16(Short.MAX_VALUE);
// version
protocol.writeByte(VERSION);
// service name
protocol.writeString(rd.serviceName);
// id
protocol.writeI64(response.getId());
protocol.getTransport().flush();
headerLength = bos.size();
// message
protocol.writeMessageBegin(message);
switch (message.type) {
case TMessageType.EXCEPTION:
applicationException.write(protocol);
break;
case TMessageType.REPLY:
resultObj.write(protocol);
break;
}
protocol.writeMessageEnd();
protocol.getTransport().flush();
int oldIndex = messageLength = bos.size();
try {
TFramedTransport.encodeFrameSize(messageLength, bytes);
bos.setWriteIndex(MESSAGE_LENGTH_INDEX);
protocol.writeI32(messageLength);
bos.setWriteIndex(MESSAGE_HEADER_LENGTH_INDEX);
protocol.writeI16((short) (0xffff & headerLength));
} finally {
bos.setWriteIndex(oldIndex);
}
} catch (TException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
buffer.writeBytes(bytes);
buffer.writeBytes(bos.toByteArray());
}
</code></pre>
<p>该方法是对response类型的请求消息进行编码。</p>
<h5>5.decode</h5>
<pre><code class="java"> @Override
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
int available = buffer.readableBytes();
// 如果小于最小的长度,则还需要更多的输入
if (available < MESSAGE_SHORTEST_LENGTH) {
return DecodeResult.NEED_MORE_INPUT;
} else {
TIOStreamTransport transport = new TIOStreamTransport(new ChannelBufferInputStream(buffer));
TBinaryProtocol protocol = new TBinaryProtocol(transport);
short magic;
int messageLength;
// 对协议头中的魔数进行比对
try {
// protocol.readI32(); // skip the first message length
byte[] bytes = new byte[4];
transport.read(bytes, 0, 4);
magic = protocol.readI16();
messageLength = protocol.readI32();
} catch (TException e) {
throw new IOException(e.getMessage(), e);
}
if (MAGIC != magic) {
throw new IOException("Unknown magic code " + magic);
}
if (available < messageLength) {
return DecodeResult.NEED_MORE_INPUT;
}
return decode(protocol);
}
}
/**
* 解码
* @param protocol
* @return
* @throws IOException
*/
private Object decode(TProtocol protocol)
throws IOException {
// version
String serviceName;
long id;
TMessage message;
try {
// 读取协议头中对内容
protocol.readI16();
protocol.readByte();
serviceName = protocol.readString();
id = protocol.readI64();
message = protocol.readMessageBegin();
} catch (TException e) {
throw new IOException(e.getMessage(), e);
}
// 如果是回调
if (message.type == TMessageType.CALL) {
RpcInvocation result = new RpcInvocation();
// 设置服务名和方法名
result.setAttachment(Constants.INTERFACE_KEY, serviceName);
result.setMethodName(message.name);
String argsClassName = ExtensionLoader.getExtensionLoader(ClassNameGenerator.class)
.getExtension(ThriftClassNameGenerator.NAME).generateArgsClassName(serviceName, message.name);
if (StringUtils.isEmpty(argsClassName)) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION,
"The specified interface name incorrect.");
}
// 从缓存中获得class类
Class clazz = cachedClass.get(argsClassName);
if (clazz == null) {
try {
// 重新获得class类型
clazz = ClassHelper.forNameWithThreadContextClassLoader(argsClassName);
// 加入集合
cachedClass.putIfAbsent(argsClassName, clazz);
} catch (ClassNotFoundException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
}
TBase args;
try {
args = (TBase) clazz.newInstance();
} catch (InstantiationException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
try {
args.read(protocol);
protocol.readMessageEnd();
} catch (TException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
// 参数集合
List<Object> parameters = new ArrayList<Object>();
// 参数类型集合
List<Class<?>> parameterTypes = new ArrayList<Class<?>>();
int index = 1;
while (true) {
TFieldIdEnum fieldIdEnum = args.fieldForId(index++);
if (fieldIdEnum == null) {
break;
}
String fieldName = fieldIdEnum.getFieldName();
// 获得方法名
String getMethodName = ThriftUtils.generateGetMethodName(fieldName);
Method getMethod;
try {
getMethod = clazz.getMethod(getMethodName);
} catch (NoSuchMethodException e) {
throw new RpcException(
RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
// 加入参数类型
parameterTypes.add(getMethod.getReturnType());
try {
parameters.add(getMethod.invoke(args));
} catch (IllegalAccessException e) {
throw new RpcException(
RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new RpcException(
RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
}
// 设置参数
result.setArguments(parameters.toArray());
// 设置参数类型
result.setParameterTypes(parameterTypes.toArray(new Class[parameterTypes.size()]));
// 创建一个新的请求
Request request = new Request(id);
// 把结果放入请求中
request.setData(result);
// 放入集合中
cachedRequest.putIfAbsent(id,
RequestData.create(message.seqid, serviceName, message.name));
return request;
// 如果是抛出异常
} else if (message.type == TMessageType.EXCEPTION) {
TApplicationException exception;
try {
// 读取异常
exception = TApplicationException.read(protocol);
protocol.readMessageEnd();
} catch (TException e) {
throw new IOException(e.getMessage(), e);
}
// 创建结果
RpcResult result = new RpcResult();
// 设置异常
result.setException(new RpcException(exception.getMessage()));
// 创建Response响应
Response response = new Response();
// 把结果放入
response.setResult(result);
// 加入唯一id
response.setId(id);
return response;
// 如果类型是回应
} else if (message.type == TMessageType.REPLY) {
// 获得结果的类名
String resultClassName = ExtensionLoader.getExtensionLoader(ClassNameGenerator.class)
.getExtension(ThriftClassNameGenerator.NAME).generateResultClassName(serviceName, message.name);
if (StringUtils.isEmpty(resultClassName)) {
throw new IllegalArgumentException("Could not infer service result class name from service name "
+ serviceName + ", the service name you specified may not generated by thrift idl compiler");
}
// 获得class类型
Class<?> clazz = cachedClass.get(resultClassName);
// 如果为空,则重新获取
if (clazz == null) {
try {
clazz = ClassHelper.forNameWithThreadContextClassLoader(resultClassName);
cachedClass.putIfAbsent(resultClassName, clazz);
} catch (ClassNotFoundException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
}
TBase<?, ? extends TFieldIdEnum> result;
try {
result = (TBase<?, ?>) clazz.newInstance();
} catch (InstantiationException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
try {
result.read(protocol);
protocol.readMessageEnd();
} catch (TException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
Object realResult = null;
int index = 0;
while (true) {
TFieldIdEnum fieldIdEnum = result.fieldForId(index++);
if (fieldIdEnum == null) {
break;
}
Field field;
try {
field = clazz.getDeclaredField(fieldIdEnum.getFieldName());
field.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
try {
// 获得真实的结果
realResult = field.get(result);
} catch (IllegalAccessException e) {
throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, e.getMessage(), e);
}
if (realResult != null) {
break;
}
}
// 创建响应
Response response = new Response();
// 设置唯一id
response.setId(id);
// 创建结果
RpcResult rpcResult = new RpcResult();
// 用RpcResult包裹结果
if (realResult instanceof Throwable) {
rpcResult.setException((Throwable) realResult);
} else {
rpcResult.setValue(realResult);
}
// 设置结果
response.setResult(rpcResult);
return response;
} else {
// Impossible
throw new IOException();
}
}
</code></pre>
<p>该方法是对解码的逻辑。对于消息分为REPLY、EXCEPTION和CALL三种情况来分别进行解码。</p>
<h5>6.RequestData</h5>
<pre><code class="java">static class RequestData {
/**
* 请求id
*/
int id;
/**
* 服务名
*/
String serviceName;
/**
* 方法名
*/
String methodName;
static RequestData create(int id, String sn, String mn) {
RequestData result = new RequestData();
result.id = id;
result.serviceName = sn;
result.methodName = mn;
return result;
}
}
</code></pre>
<p>该内部类是请求参数实体。</p>
<h4>(八)ThriftInvoker</h4>
<p>该类是thrift协议的Invoker实现。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 客户端集合
*/
private final ExchangeClient[] clients;
/**
* 活跃的客户端索引
*/
private final AtomicPositiveInteger index = new AtomicPositiveInteger();
/**
* 销毁锁
*/
private final ReentrantLock destroyLock = new ReentrantLock();
/**
* invoker集合
*/
private final Set<Invoker<?>> invokers;
</code></pre>
<h5>2.doInvoke</h5>
<pre><code class="java">@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName;
// 获得方法名
methodName = invocation.getMethodName();
// 设置附加值 path
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
// for thrift codec
inv.setAttachment(ThriftCodec.PARAMETER_CLASS_NAME_GENERATOR, getUrl().getParameter(
ThriftCodec.PARAMETER_CLASS_NAME_GENERATOR, DubboClassNameGenerator.NAME));
ExchangeClient currentClient;
// 如果只有一个连接的客户端,则直接返回
if (clients.length == 1) {
currentClient = clients[0];
} else {
// 否则,取出下一个客户端,循环数组取
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
// 获得超时时间
int timeout = getUrl().getMethodParameter(
methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
RpcContext.getContext().setFuture(null);
// 发起请求
return (Result) currentClient.request(inv, timeout).get();
} catch (TimeoutException e) {
// 抛出超时异常
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, e.getMessage(), e);
} catch (RemotingException e) {
// 抛出网络异常
throw new RpcException(RpcException.NETWORK_EXCEPTION, e.getMessage(), e);
}
}
</code></pre>
<p>该方法是thrift协议的调用链处理逻辑。</p>
<h4>(九)ThriftProtocol</h4>
<p>该类是thrift协议的主要实现逻辑,分别实现了服务引用和服务调用的逻辑。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 默认端口号
*/
public static final int DEFAULT_PORT = 40880;
/**
* 扩展名
*/
public static final String NAME = "thrift";
// ip:port -> ExchangeServer
/**
* 服务集合,key为ip:port
*/
private final ConcurrentMap<String, ExchangeServer> serverMap =
new ConcurrentHashMap<String, ExchangeServer>();
private ExchangeHandler handler = new ExchangeHandlerAdapter() {
@Override
public Object reply(ExchangeChannel channel, Object msg) throws RemotingException {
// 如果消息是Invocation类型的
if (msg instanceof Invocation) {
Invocation inv = (Invocation) msg;
// 获得服务名
String serviceName = inv.getAttachments().get(Constants.INTERFACE_KEY);
// 获得服务的key
String serviceKey = serviceKey(channel.getLocalAddress().getPort(),
serviceName, null, null);
// 从集合中获得暴露者
DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
// 如果暴露者为空,则抛出异常
if (exporter == null) {
throw new RemotingException(channel,
"Not found exported service: "
+ serviceKey
+ " in "
+ exporterMap.keySet()
+ ", may be version or group mismatch "
+ ", channel: consumer: "
+ channel.getRemoteAddress()
+ " --> provider: "
+ channel.getLocalAddress()
+ ", message:" + msg);
}
// 设置远程地址
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
return exporter.getInvoker().invoke(inv);
}
// 否则抛出异常,不支持的请求消息
throw new RemotingException(channel,
"Unsupported request: "
+ (msg.getClass().getName() + ": " + msg)
+ ", channel: consumer: "
+ channel.getRemoteAddress()
+ " --> provider: "
+ channel.getLocalAddress());
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
// 如果消息是Invocation类型,则调用reply,否则接收消息
if (message instanceof Invocation) {
reply((ExchangeChannel) channel, message);
} else {
super.received(channel, message);
}
}
};
</code></pre>
<h5>2.export</h5>
<pre><code class="java">@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// can use thrift codec only
// 只能使用thrift编解码器
URL url = invoker.getUrl().addParameter(Constants.CODEC_KEY, ThriftCodec.NAME);
// find server.
// 获得服务地址
String key = url.getAddress();
// client can expose a service for server to invoke only.
// 客户端可以为服务器暴露服务以仅调用
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer && !serverMap.containsKey(key)) {
// 加入到集合
serverMap.put(key, getServer(url));
}
// export service.
// 得到服务key
key = serviceKey(url);
// 创建暴露者
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
// 加入集合
exporterMap.put(key, exporter);
return exporter;
}
</code></pre>
<p>该方法是服务暴露的逻辑实现。</p>
<h5>3.refer</h5>
<pre><code class="java">@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 创建ThriftInvoker
ThriftInvoker<T> invoker = new ThriftInvoker<T>(type, url, getClients(url), invokers);
// 加入到集合
invokers.add(invoker);
return invoker;
}
</code></pre>
<p>该方法是服务引用的逻辑实现。</p>
<h5>4.getClients</h5>
<pre><code class="java">private ExchangeClient[] getClients(URL url) {
// 获得连接数
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 1);
// 创建客户端集合
ExchangeClient[] clients = new ExchangeClient[connections];
// 创建客户端
for (int i = 0; i < clients.length; i++) {
clients[i] = initClient(url);
}
return clients;
}
</code></pre>
<p>该方法是获得客户端集合。</p>
<h5>5.initClient</h5>
<pre><code class="java">private ExchangeClient initClient(URL url) {
ExchangeClient client;
// 加上编解码器
url = url.addParameter(Constants.CODEC_KEY, ThriftCodec.NAME);
try {
// 创建客户端
client = Exchangers.connect(url);
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url
+ "): " + e.getMessage(), e);
}
return client;
}
</code></pre>
<p>该方法是创建客户端的逻辑。</p>
<h5>6.getServer</h5>
<pre><code class="java">private ExchangeServer getServer(URL url) {
// enable sending readonly event when server closes by default
// 加入只读事件
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// 获得服务的实现方式
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
// 如果该实现方式不是dubbo支持的方式,则抛出异常
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
ExchangeServer server;
try {
// 获得服务器
server = Exchangers.bind(url, handler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
// 获得实现方式
str = url.getParameter(Constants.CLIENT_KEY);
// 如果客户端实现方式不是dubbo支持的方式,则抛出异常。
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
</code></pre>
<p>该方法是获得server的逻辑实现。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=nsRMt8AGRXIka1AWfDuDEA%3D%3D.DQOBaDyDK2OFO4BMTaOkB2ha6QVDbBJbwiwckluzwWVotIAWKxjhW5538RMuONBLlK8zqkGW%2F%2BzIbgnetGIPNtokB%2F0DteTOwjWHdryznbFJsZNslBB1wsj1Q%2Br2WlTjzBEZxH08Xc7q8FD7onYEpftgNdZlIBPZjUCxnlXHH8jQEQT3i5EmaeIpuB%2BUdzeG" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于thrift协议实现的部分,要对Thrift。接下来我将开始对rpc模块关于webservice协议部分进行讲解。</p>
Dubbo源码解析(三十)远程调用——rest协议
https://segmentfault.com/a/1190000018054361
2019-01-29T12:23:25+08:00
2019-01-29T12:23:25+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2>远程调用——rest协议</h2>
<blockquote>目标:介绍rest协议的设计和实现,介绍dubbo-rpc-rest的源码。</blockquote>
<h3>前言</h3>
<p>REST的英文名是RepresentationalState Transfer,它是一种开发风格,关于REST不清楚的朋友可以了解一下。在dubbo中利用的是红帽子RedHat公司的Resteasy来使dubbo支持REST风格的开发使用。在本文中主要讲解的是基于Resteasy来实现rest协议的实现。</p>
<h3>源码分析</h3>
<h4>(一)RestServer</h4>
<p>该接口是rest协议的服务器接口。定义了服务器相关的方法。</p>
<pre><code class="java">public interface RestServer {
/**
* 服务器启动
* @param url
*/
void start(URL url);
/**
* 部署服务器
* @param resourceDef it could be either resource interface or resource impl
*/
void deploy(Class resourceDef, Object resourceInstance, String contextPath);
/**
* 取消服务器部署
* @param resourceDef
*/
void undeploy(Class resourceDef);
/**
* 停止服务器
*/
void stop();
}</code></pre>
<h4>(二)BaseRestServer</h4>
<p>该类实现了RestServer接口,是rest服务的抽象类,把getDeployment和doStart方法进行抽象,让子类专注于中这两个方法的实现。</p>
<h5>1.start</h5>
<pre><code class="java"> @Override
public void start(URL url) {
// 支持两种 Content-Type
getDeployment().getMediaTypeMappings().put("json", "application/json");
getDeployment().getMediaTypeMappings().put("xml", "text/xml");
// server.getDeployment().getMediaTypeMappings().put("xml", "application/xml");
// 添加拦截器
getDeployment().getProviderClasses().add(RpcContextFilter.class.getName());
// TODO users can override this mapper, but we just rely on the current priority strategy of resteasy
// 异常类映射
getDeployment().getProviderClasses().add(RpcExceptionMapper.class.getName());
// 添加需要加载的类
loadProviders(url.getParameter(Constants.EXTENSION_KEY, ""));
// 开启服务器
doStart(url);
}</code></pre>
<h5>2.deploy</h5>
<pre><code class="java">@Override
public void deploy(Class resourceDef, Object resourceInstance, String contextPath) {
// 如果
if (StringUtils.isEmpty(contextPath)) {
// 添加自定义资源实现端点,部署服务器
getDeployment().getRegistry().addResourceFactory(new DubboResourceFactory(resourceInstance, resourceDef));
} else {
// 添加自定义资源实现端点。指定contextPath
getDeployment().getRegistry().addResourceFactory(new DubboResourceFactory(resourceInstance, resourceDef), contextPath);
}
}</code></pre>
<h5>3.undeploy</h5>
<pre><code class="java">@Override
public void undeploy(Class resourceDef) {
// 取消服务器部署
getDeployment().getRegistry().removeRegistrations(resourceDef);
}</code></pre>
<h5>4.loadProviders</h5>
<pre><code class="java">protected void loadProviders(String value) {
for (String clazz : Constants.COMMA_SPLIT_PATTERN.split(value)) {
if (!StringUtils.isEmpty(clazz)) {
getDeployment().getProviderClasses().add(clazz.trim());
}
}
}</code></pre>
<p>该方法是把类都加入到ResteasyDeployment的providerClasses中,加入各类组件。</p>
<h4>(三)DubboHttpServer</h4>
<p>该类继承了BaseRestServer,实现了doStart和getDeployment方法,当配置选择servlet、jetty或者tomcat作为远程通信的实现时,实现的服务器类</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* HttpServletDispatcher实例
*/
private final HttpServletDispatcher dispatcher = new HttpServletDispatcher();
/**
* Resteasy的服务部署器
*/
private final ResteasyDeployment deployment = new ResteasyDeployment();
/**
* http绑定者
*/
private HttpBinder httpBinder;
/**
* http服务器
*/
private HttpServer httpServer;</code></pre>
<h5>2.doStart</h5>
<pre><code class="java">@Override
protected void doStart(URL url) {
// TODO jetty will by default enable keepAlive so the xml config has no effect now
// 创建http服务器
httpServer = httpBinder.bind(url, new RestHandler());
// 获得ServletContext
ServletContext servletContext = ServletManager.getInstance().getServletContext(url.getPort());
// 如果为空 ,则获得默认端口对应的ServletContext对象
if (servletContext == null) {
servletContext = ServletManager.getInstance().getServletContext(ServletManager.EXTERNAL_SERVER_PORT);
}
// 如果还是为空 ,则抛出异常
if (servletContext == null) {
throw new RpcException("No servlet context found. If you are using server='servlet', " +
"make sure that you've configured " + BootstrapListener.class.getName() + " in web.xml");
}
// 设置属性部署器
servletContext.setAttribute(ResteasyDeployment.class.getName(), deployment);
try {
// 初始化
dispatcher.init(new SimpleServletConfig(servletContext));
} catch (ServletException e) {
throw new RpcException(e);
}
}</code></pre>
<h5>3.RestHandler</h5>
<pre><code class="java">private class RestHandler implements HttpHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// 设置远程地址
RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
// 请求相关的服务
dispatcher.service(request, response);
}
}</code></pre>
<p>该内部类是服务请求的处理器</p>
<h5>4.SimpleServletConfig</h5>
<pre><code class="java">private static class SimpleServletConfig implements ServletConfig {
// ServletContext对象
private final ServletContext servletContext;
public SimpleServletConfig(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public String getServletName() {
return "DispatcherServlet";
}
@Override
public ServletContext getServletContext() {
return servletContext;
}
@Override
public String getInitParameter(String s) {
return null;
}
@Override
public Enumeration getInitParameterNames() {
return new Enumeration() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public Object nextElement() {
return null;
}
};
}
}
</code></pre>
<p>该内部类是配置类。</p>
<h4>(四)NettyServer</h4>
<p>该类继承了BaseRestServer,当配置了netty作为远程通信的实现时,实现的服务器。</p>
<pre><code class="java">public class NettyServer extends BaseRestServer {
/**
* NettyJaxrsServer对象
*/
private final NettyJaxrsServer server = new NettyJaxrsServer();
@Override
protected void doStart(URL url) {
// 获得ip
String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost());
if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) {
// 设置服务的ip
server.setHostname(bindIp);
}
// 设置端口
server.setPort(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()));
// 通道选项集合
Map<ChannelOption, Object> channelOption = new HashMap<ChannelOption, Object>();
// 保持连接检测对方主机是否崩溃
channelOption.put(ChannelOption.SO_KEEPALIVE, url.getParameter(Constants.KEEP_ALIVE_KEY, Constants.DEFAULT_KEEP_ALIVE));
// 设置配置
server.setChildChannelOptions(channelOption);
// 设置线程数,默认为200
server.setExecutorThreadCount(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS));
// 设置核心线程数
server.setIoWorkerCount(url.getParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
// 设置最大的请求数
server.setMaxRequestSize(url.getParameter(Constants.PAYLOAD_KEY, Constants.DEFAULT_PAYLOAD));
// 启动服务
server.start();
}
@Override
public void stop() {
server.stop();
}
@Override
protected ResteasyDeployment getDeployment() {
return server.getDeployment();
}
}
</code></pre>
<h4>(五)DubboResourceFactory</h4>
<p>该类实现了ResourceFactory接口,是资源工程实现类,封装了以下两个属性,实现比较简单。</p>
<pre><code class="java">/**
* 资源类
*/
private Object resourceInstance;
/**
* 扫描的类型
*/
private Class scannableClass;
</code></pre>
<h4>(六)RestConstraintViolation</h4>
<p>该类是当约束违反的实体类,封装了以下三个属性,具体使用可以看下面的介绍。</p>
<pre><code class="java">/**
* 地址
*/
private String path;
/**
* 消息
*/
private String message;
/**
* 值
*/
private String value;
</code></pre>
<h4>(七)RestServerFactory</h4>
<p>该类是服务器工程类,用来提供相应的实例,里面逻辑比较简单。</p>
<pre><code class="java">public class RestServerFactory {
/**
* http绑定者
*/
private HttpBinder httpBinder;
public void setHttpBinder(HttpBinder httpBinder) {
this.httpBinder = httpBinder;
}
/**
* 创建服务器
* @param name
* @return
*/
public RestServer createServer(String name) {
// TODO move names to Constants
// 如果是servlet或者jetty或者tomcat,则创建DubboHttpServer
if ("servlet".equalsIgnoreCase(name) || "jetty".equalsIgnoreCase(name) || "tomcat".equalsIgnoreCase(name)) {
return new DubboHttpServer(httpBinder);
} else if ("netty".equalsIgnoreCase(name)) {
// 如果是netty,那么直接创建netty服务器
return new NettyServer();
} else {
// 否则 抛出异常
throw new IllegalArgumentException("Unrecognized server name: " + name);
}
}
}
</code></pre>
<p>可以看到,根据配置的不同,来创建不同的服务器实现。</p>
<h4>(八)RpcContextFilter</h4>
<p>该类是过滤器。增加了对协议头大小的限制。</p>
<pre><code class="java">public class RpcContextFilter implements ContainerRequestFilter, ClientRequestFilter {
/**
* 附加值key
*/
private static final String DUBBO_ATTACHMENT_HEADER = "Dubbo-Attachments";
// currently we use a single header to hold the attachments so that the total attachment size limit is about 8k
/**
* 目前我们使用单个标头来保存附件,以便总附件大小限制大约为8k
*/
private static final int MAX_HEADER_SIZE = 8 * 1024;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// 获得request
HttpServletRequest request = ResteasyProviderFactory.getContextData(HttpServletRequest.class);
// 把它放到rpc上下文中
RpcContext.getContext().setRequest(request);
// this only works for servlet containers
if (request != null && RpcContext.getContext().getRemoteAddress() == null) {
// 设置远程地址
RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
}
// 设置response
RpcContext.getContext().setResponse(ResteasyProviderFactory.getContextData(HttpServletResponse.class));
// 获得协议头信息
String headers = requestContext.getHeaderString(DUBBO_ATTACHMENT_HEADER);
// 分割协议头信息,把附加值分解开存入上下文中
if (headers != null) {
for (String header : headers.split(",")) {
int index = header.indexOf("=");
if (index > 0) {
String key = header.substring(0, index);
String value = header.substring(index + 1);
if (!StringUtils.isEmpty(key)) {
RpcContext.getContext().setAttachment(key.trim(), value.trim());
}
}
}
}
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
int size = 0;
// 遍历附加值
for (Map.Entry<String, String> entry : RpcContext.getContext().getAttachments().entrySet()) {
// 如果key或者value有出现=或者,则抛出异常
if (entry.getValue().contains(",") || entry.getValue().contains("=")
|| entry.getKey().contains(",") || entry.getKey().contains("=")) {
throw new IllegalArgumentException("The attachments of " + RpcContext.class.getSimpleName() + " must not contain ',' or '=' when using rest protocol");
}
// TODO for now we don't consider the differences of encoding and server limit
// 加入UTF-8配置,计算协议头大小
size += entry.getValue().getBytes("UTF-8").length;
// 如果大于限制,则抛出异常
if (size > MAX_HEADER_SIZE) {
throw new IllegalArgumentException("The attachments of " + RpcContext.class.getSimpleName() + " is too big");
}
// 拼接
String attachments = entry.getKey() + "=" + entry.getValue();
// 加入到请求头上
requestContext.getHeaders().add(DUBBO_ATTACHMENT_HEADER, attachments);
}
}
}
</code></pre>
<p>可以看到有两个filter的方法实现,第一个是解析对于附加值,并且放入上下文中。第二个是对协议头大小的限制。</p>
<h4>(九)RpcExceptionMapper</h4>
<p>该类是异常的处理类。</p>
<pre><code class="java">public class RpcExceptionMapper implements ExceptionMapper<RpcException> {
@Override
public Response toResponse(RpcException e) {
// TODO do more sophisticated exception handling and output
// 如果是约束违反异常
if (e.getCause() instanceof ConstraintViolationException) {
return handleConstraintViolationException((ConstraintViolationException) e.getCause());
}
// we may want to avoid exposing the dubbo exception details to certain clients
// TODO for now just do plain text output
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Internal server error: " + e.getMessage()).type(ContentType.TEXT_PLAIN_UTF_8).build();
}
/**
* 处理参数不合法的异常
* @param cve
* @return
*/
protected Response handleConstraintViolationException(ConstraintViolationException cve) {
// 创建约束违反记录
ViolationReport report = new ViolationReport();
// 遍历约束违反
for (ConstraintViolation cv : cve.getConstraintViolations()) {
// 添加记录
report.addConstraintViolation(new RestConstraintViolation(
cv.getPropertyPath().toString(),
cv.getMessage(),
cv.getInvalidValue() == null ? "null" : cv.getInvalidValue().toString()));
}
// TODO for now just do xml output
// 只支持xml输出
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(report).type(ContentType.TEXT_XML_UTF_8).build();
}
}
</code></pre>
<p>主要是处理参数不合法的异常。</p>
<h4>(十)ViolationReport</h4>
<p>该类是约束违反的记录类,其中就封装了一个约束违反的集合。</p>
<pre><code class="java">public class ViolationReport implements Serializable {
private static final long serialVersionUID = -130498234L;
/**
* 约束违反集合
*/
private List<RestConstraintViolation> constraintViolations;
public List<RestConstraintViolation> getConstraintViolations() {
return constraintViolations;
}
public void setConstraintViolations(List<RestConstraintViolation> constraintViolations) {
this.constraintViolations = constraintViolations;
}
public void addConstraintViolation(RestConstraintViolation constraintViolation) {
if (constraintViolations == null) {
constraintViolations = new LinkedList<RestConstraintViolation>();
}
constraintViolations.add(constraintViolation);
}
}
</code></pre>
<h4>(十一)RestProtocol</h4>
<p>该类继承了AbstractProxyProtocol,是rest协议实现的核心。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 默认端口号
*/
private static final int DEFAULT_PORT = 80;
/**
* 服务器集合
*/
private final Map<String, RestServer> servers = new ConcurrentHashMap<String, RestServer>();
/**
* 服务器工厂
*/
private final RestServerFactory serverFactory = new RestServerFactory();
// TODO in the future maybe we can just use a single rest client and connection manager
/**
* 客户端集合
*/
private final List<ResteasyClient> clients = Collections.synchronizedList(new LinkedList<ResteasyClient>());
/**
* 连接监控
*/
private volatile ConnectionMonitor connectionMonitor;
</code></pre>
<h5>2.doExport</h5>
<pre><code class="java">@Override
protected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException {
// 获得地址
String addr = getAddr(url);
// 获得实现类
Class implClass = (Class) StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).get(url.getServiceKey());
// 获得服务
RestServer server = servers.get(addr);
if (server == null) {
// 创建服务器
server = serverFactory.createServer(url.getParameter(Constants.SERVER_KEY, "jetty"));
// 开启服务器
server.start(url);
// 加入集合
servers.put(addr, server);
}
// 获得contextPath
String contextPath = getContextPath(url);
// 如果以servlet的方式
if ("servlet".equalsIgnoreCase(url.getParameter(Constants.SERVER_KEY, "jetty"))) {
// 获得ServletContext
ServletContext servletContext = ServletManager.getInstance().getServletContext(ServletManager.EXTERNAL_SERVER_PORT);
// 如果为空,则抛出异常
if (servletContext == null) {
throw new RpcException("No servlet context found. Since you are using server='servlet', " +
"make sure that you've configured " + BootstrapListener.class.getName() + " in web.xml");
}
String webappPath = servletContext.getContextPath();
if (StringUtils.isNotEmpty(webappPath)) {
// 检测配置是否正确
webappPath = webappPath.substring(1);
if (!contextPath.startsWith(webappPath)) {
throw new RpcException("Since you are using server='servlet', " +
"make sure that the 'contextpath' property starts with the path of external webapp");
}
contextPath = contextPath.substring(webappPath.length());
if (contextPath.startsWith("/")) {
contextPath = contextPath.substring(1);
}
}
}
// 获得资源
final Class resourceDef = GetRestful.getRootResourceClass(implClass) != null ? implClass : type;
// 部署服务器
server.deploy(resourceDef, impl, contextPath);
final RestServer s = server;
return new Runnable() {
@Override
public void run() {
// TODO due to dubbo's current architecture,
// it will be called from registry protocol in the shutdown process and won't appear in logs
s.undeploy(resourceDef);
}
};
}
</code></pre>
<p>该方法是服务暴露的方法。</p>
<h5>3.doRefer</h5>
<pre><code class="java">protected <T> T doRefer(Class<T> serviceType, URL url) throws RpcException {
// 如果连接监控为空,则创建
if (connectionMonitor == null) {
connectionMonitor = new ConnectionMonitor();
}
// TODO more configs to add
// 创建http连接池
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
// 20 is the default maxTotal of current PoolingClientConnectionManager
// 最大连接数
connectionManager.setMaxTotal(url.getParameter(Constants.CONNECTIONS_KEY, 20));
// 最大的路由数
connectionManager.setDefaultMaxPerRoute(url.getParameter(Constants.CONNECTIONS_KEY, 20));
// 添加监控
connectionMonitor.addConnectionManager(connectionManager);
// 新建请求配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT))
.setSocketTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT))
.build();
// 设置socket配置
SocketConfig socketConfig = SocketConfig.custom()
.setSoKeepAlive(true)
.setTcpNoDelay(true)
.build();
// 创建http客户端
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000;
}
}
// TODO constant
return 30 * 1000;
}
})
.setDefaultRequestConfig(requestConfig)
.setDefaultSocketConfig(socketConfig)
.build();
// 创建ApacheHttpClient4Engine对应,为了使用resteasy
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient/*, localContext*/);
// 创建ResteasyClient对象
ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build();
// 加入集合
clients.add(client);
// 设置过滤器
client.register(RpcContextFilter.class);
// 注册各类组件
for (String clazz : Constants.COMMA_SPLIT_PATTERN.split(url.getParameter(Constants.EXTENSION_KEY, ""))) {
if (!StringUtils.isEmpty(clazz)) {
try {
client.register(Thread.currentThread().getContextClassLoader().loadClass(clazz.trim()));
} catch (ClassNotFoundException e) {
throw new RpcException("Error loading JAX-RS extension class: " + clazz.trim(), e);
}
}
}
// TODO protocol
// 创建 Service Proxy 对象。
ResteasyWebTarget target = client.target("http://" + url.getHost() + ":" + url.getPort() + "/" + getContextPath(url));
return target.proxy(serviceType);
}
</code></pre>
<p>该方法是服务引用的实现。</p>
<h5>4.ConnectionMonitor</h5>
<pre><code class="java">protected class ConnectionMonitor extends Thread {
/**
* 是否关闭
*/
private volatile boolean shutdown;
/**
* 连接池集合
*/
private final List<PoolingHttpClientConnectionManager> connectionManagers = Collections.synchronizedList(new LinkedList<PoolingHttpClientConnectionManager>());
public void addConnectionManager(PoolingHttpClientConnectionManager connectionManager) {
connectionManagers.add(connectionManager);
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(1000);
for (PoolingHttpClientConnectionManager connectionManager : connectionManagers) {
// 关闭池中所有过期的连接
connectionManager.closeExpiredConnections();
// TODO constant
// 关闭池中的空闲连接
connectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
}
} catch (InterruptedException ex) {
// 关闭
shutdown();
}
}
public void shutdown() {
shutdown = true;
connectionManagers.clear();
synchronized (this) {
notifyAll();
}
}
}
</code></pre>
<p>该内部类是处理连接的监控类,当连接过期获取空间的时候,关闭它。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=fRg8yxzzWqORnDAYZ%2BDVMA%3D%3D.QSJnUfnVooku%2BGrO5fUMk%2FZtb4sB1bn7Ch%2F7Mne0qW7DPXK3yI5fsWAqf3ycKbrcerEmsQvxmUYoFDkssqqFI7qrAYA34VKCr6VRT%2BsWAZABc9V8aaJTKipuiG9HuYgVRjKnt20WZWGN5tyPWzV6aPBWe2eAMsIOt0GUDbfP1iJFrEDnZDeJT5bxKWwn8ZWH" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于rest协议实现的部分,关键是要对Resteasy的使用需要有所了解,其他的思路跟其他协议实现差距不大。接下来我将开始对rpc模块关于rmi协议部分进行讲解。</p>
Dubbo源码解析(二十九)远程调用——redis协议
https://segmentfault.com/a/1190000018045851
2019-01-28T16:32:18+08:00
2019-01-28T16:32:18+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2>远程调用——redis协议</h2>
<blockquote>目标:介绍redis协议的设计和实现,介绍dubbo-rpc-redis的源码。</blockquote>
<h3>前言</h3>
<p>dubbo支持的redis协议是基于Redis的,<a href="https://link.segmentfault.com/?enc=6ndnYOme7j1IK8hw7AnWsw%3D%3D.nsIzYc8kKfWo0ZScEPBI1s46tI7b0e5TtlHgAIuNi9o%3D" rel="nofollow">Redis</a> 是一个高效的 KV 存储服务器,跟memcached协议实现差不多,在dubbo中也没有涉及到关于redis协议的服务暴露,只有服务引用,因为在访问服务器时,Redis客户端可以在服务器上存储也可以获取。</p>
<h3>源码分析</h3>
<h4>(一)RedisProtocol</h4>
<p>该类继承了AbstractProtocol类,是redis协议实现的核心。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 默认端口号
*/
public static final int DEFAULT_PORT = 6379;</code></pre>
<h5>2.export</h5>
<pre><code class="java">@Override
public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
// 不支持redis协议的服务暴露,抛出异常
throw new UnsupportedOperationException("Unsupported export redis service. url: " + invoker.getUrl());
}</code></pre>
<p>可以看到不支持服务暴露。</p>
<h5>3.refer</h5>
<pre><code class="java">@Override
public <T> Invoker<T> refer(final Class<T> type, final URL url) throws RpcException {
try {
// 实例化对象池
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
// 如果 testOnBorrow 被设置,pool 会在 borrowObject 返回对象之前使用 PoolableObjectFactory的 validateObject 来验证这个对象是否有效
// 要是对象没通过验证,这个对象会被丢弃,然后重新选择一个新的对象。
config.setTestOnBorrow(url.getParameter("test.on.borrow", true));
// 如果 testOnReturn 被设置, pool 会在 returnObject 的时候通过 PoolableObjectFactory 的validateObject 方法验证对象
// 如果对象没通过验证,对象会被丢弃,不会被放到池中。
config.setTestOnReturn(url.getParameter("test.on.return", false));
// 指定空闲对象是否应该使用 PoolableObjectFactory 的 validateObject 校验,如果校验失败,这个对象会从对象池中被清除。
// 这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0) 的时候才会生效。
config.setTestWhileIdle(url.getParameter("test.while.idle", false));
if (url.getParameter("max.idle", 0) > 0)
// 控制一个pool最多有多少个状态为空闲的jedis实例。
config.setMaxIdle(url.getParameter("max.idle", 0));
if (url.getParameter("min.idle", 0) > 0)
// 控制一个pool最少有多少个状态为空闲的jedis实例。
config.setMinIdle(url.getParameter("min.idle", 0));
if (url.getParameter("max.active", 0) > 0)
// 控制一个pool最多有多少个jedis实例。
config.setMaxTotal(url.getParameter("max.active", 0));
if (url.getParameter("max.total", 0) > 0)
config.setMaxTotal(url.getParameter("max.total", 0));
if (url.getParameter("max.wait", 0) > 0)
//表示当引入一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;
config.setMaxWaitMillis(url.getParameter("max.wait", 0));
if (url.getParameter("num.tests.per.eviction.run", 0) > 0)
// 设置驱逐线程每次检测对象的数量。这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0)的时候才会生效。
config.setNumTestsPerEvictionRun(url.getParameter("num.tests.per.eviction.run", 0));
if (url.getParameter("time.between.eviction.runs.millis", 0) > 0)
// 指定驱逐线程的休眠时间。如果这个值不是正数( >0),不会有驱逐线程运行。
config.setTimeBetweenEvictionRunsMillis(url.getParameter("time.between.eviction.runs.millis", 0));
if (url.getParameter("min.evictable.idle.time.millis", 0) > 0)
// 指定最小的空闲驱逐的时间间隔(空闲超过指定的时间的对象,会被清除掉)。
// 这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0)的时候才会生效。
config.setMinEvictableIdleTimeMillis(url.getParameter("min.evictable.idle.time.millis", 0));
// 创建redis连接池
final JedisPool jedisPool = new JedisPool(config, url.getHost(), url.getPort(DEFAULT_PORT),
url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT),
StringUtils.isBlank(url.getPassword()) ? null : url.getPassword(),
url.getParameter("db.index", 0));
// 获得值的过期时间
final int expiry = url.getParameter("expiry", 0);
// 获得get命令
final String get = url.getParameter("get", "get");
// 获得set命令
final String set = url.getParameter("set", Map.class.equals(type) ? "put" : "set");
// 获得delete命令
final String delete = url.getParameter("delete", Map.class.equals(type) ? "remove" : "delete");
return new AbstractInvoker<T>(type, url) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
Jedis resource = null;
try {
resource = jedisPool.getResource();
// 如果是get命令
if (get.equals(invocation.getMethodName())) {
// get 命令必须只有一个参数
if (invocation.getArguments().length != 1) {
throw new IllegalArgumentException("The redis get method arguments mismatch, must only one arguments. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url);
}
// 获得值
byte[] value = resource.get(String.valueOf(invocation.getArguments()[0]).getBytes());
if (value == null) {
return new RpcResult();
}
// 反序列化
ObjectInput oin = getSerialization(url).deserialize(url, new ByteArrayInputStream(value));
return new RpcResult(oin.readObject());
} else if (set.equals(invocation.getMethodName())) {
// 如果是set命令,参数长度必须是2
if (invocation.getArguments().length != 2) {
throw new IllegalArgumentException("The redis set method arguments mismatch, must be two arguments. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url);
}
//
byte[] key = String.valueOf(invocation.getArguments()[0]).getBytes();
ByteArrayOutputStream output = new ByteArrayOutputStream();
// 对需要存入对值进行序列化
ObjectOutput value = getSerialization(url).serialize(url, output);
value.writeObject(invocation.getArguments()[1]);
// 存入值
resource.set(key, output.toByteArray());
// 设置该key过期时间,不能大于1000s
if (expiry > 1000) {
resource.expire(key, expiry / 1000);
}
return new RpcResult();
} else if (delete.equals(invocation.getMethodName())) {
// 如果是删除命令,则参数长度必须是1
if (invocation.getArguments().length != 1) {
throw new IllegalArgumentException("The redis delete method arguments mismatch, must only one arguments. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url);
}
// 删除该值
resource.del(String.valueOf(invocation.getArguments()[0]).getBytes());
return new RpcResult();
} else {
// 否则抛出该操作不支持的异常
throw new UnsupportedOperationException("Unsupported method " + invocation.getMethodName() + " in redis service.");
}
} catch (Throwable t) {
RpcException re = new RpcException("Failed to invoke redis service method. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url + ", cause: " + t.getMessage(), t);
if (t instanceof TimeoutException || t instanceof SocketTimeoutException) {
// 抛出超时异常
re.setCode(RpcException.TIMEOUT_EXCEPTION);
} else if (t instanceof JedisConnectionException || t instanceof IOException) {
// 抛出网络异常
re.setCode(RpcException.NETWORK_EXCEPTION);
} else if (t instanceof JedisDataException) {
// 抛出序列化异常
re.setCode(RpcException.SERIALIZATION_EXCEPTION);
}
throw re;
} finally {
if (resource != null) {
try {
jedisPool.returnResource(resource);
} catch (Throwable t) {
logger.warn("returnResource error: " + t.getMessage(), t);
}
}
}
}
@Override
public void destroy() {
super.destroy();
try {
// 关闭连接池
jedisPool.destroy();
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
}
};
} catch (Throwable t) {
throw new RpcException("Failed to refer redis service. interface: " + type.getName() + ", url: " + url + ", cause: " + t.getMessage(), t);
}
}</code></pre>
<p>可以看到首先是对连接池的配置赋值,然后创建连接池后,根据redis的get、set、delete命令来进行相关操作。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=3vbUsh3M32TSYSAteImBcA%3D%3D.OWa06jux6GHKhv5AixvLvpm%2B7VlreauW86BClNfoBxnDuPHreA8FqQjDuwq75%2FIhauXSEPr3dZQ9Eo1oL9Z3d7irohdAwgy15ZRlzRXS5DMB30cef7LlUbdG%2BVhM%2Bz%2FtY7sEopcgSy2x%2FbJKq%2B6cIvwRuVQENLtyInGcGlEUJud2z6UBLZdKJ2z%2FN%2F5ZciOV" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于redis协议实现的部分,逻辑比较简单。接下来我将开始对rpc模块关于rest协议部分进行讲解。</p>
Dubbo源码解析(二十八)远程调用——memcached协议
https://segmentfault.com/a/1190000018034217
2019-01-27T09:01:25+08:00
2019-01-27T09:01:25+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2>远程调用——memcached协议</h2>
<blockquote>目标:介绍memcached协议的设计和实现,介绍dubbo-rpc-memcached的源码。</blockquote>
<h3>前言</h3>
<p>dubbo实现memcached协议是基于Memcached,<a href="https://link.segmentfault.com/?enc=cX3D0E5o63dIyQUVxaGB5Q%3D%3D.n9RDZBinzjL5DcLjFJ87NO9ObO9DnUefjN0wNgGxw8s%3D" rel="nofollow">Memcached</a> 是一个高效的 KV 缓存服务器,在dubbo中没有涉及到关于memcached协议的服务暴露,只有服务引用,因为在访问Memcached服务器时,Memcached客户端可以在服务器上存储也可以获取。</p>
<h3>源码分析</h3>
<h4>(一)MemcachedProtocol</h4>
<p>该类继承AbstractProtocol,是memcached协议实现的核心。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 默认端口号
*/
public static final int DEFAULT_PORT = 11211;</code></pre>
<h5>2.export</h5>
<pre><code class="java">@Override
public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
// 不支持memcached服务暴露
throw new UnsupportedOperationException("Unsupported export memcached service. url: " + invoker.getUrl());
}</code></pre>
<p>可以看到,服务暴露方法直接抛出异常。</p>
<h5>3.refer</h5>
<pre><code class="java">@Override
public <T> Invoker<T> refer(final Class<T> type, final URL url) throws RpcException {
try {
// 获得地址
String address = url.getAddress();
// 获得备用地址
String backup = url.getParameter(Constants.BACKUP_KEY);
// 把备用地址拼接上
if (backup != null && backup.length() > 0) {
address += "," + backup;
}
// 创建Memcached客户端构造器
MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(address));
// 创建客户端
final MemcachedClient memcachedClient = builder.build();
// 到期时间参数配置
final int expiry = url.getParameter("expiry", 0);
// 获得值命令
final String get = url.getParameter("get", "get");
// 添加值命令根据类型来取决是put还是set
final String set = url.getParameter("set", Map.class.equals(type) ? "put" : "set");
// 删除值命令
final String delete = url.getParameter("delete", Map.class.equals(type) ? "remove" : "delete");
return new AbstractInvoker<T>(type, url) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
try {
// 如果是获取方法名的值
if (get.equals(invocation.getMethodName())) {
// 如果参数长度不等于1,则抛出异常
if (invocation.getArguments().length != 1) {
throw new IllegalArgumentException("The memcached get method arguments mismatch, must only one arguments. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url);
}
// 否则调用get方法来获取
return new RpcResult(memcachedClient.get(String.valueOf(invocation.getArguments()[0])));
} else if (set.equals(invocation.getMethodName())) {
// 如果参数长度不为2,则抛出异常
if (invocation.getArguments().length != 2) {
throw new IllegalArgumentException("The memcached set method arguments mismatch, must be two arguments. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url);
}
// 无论任何现有值如何,都在缓存中设置一个对象
memcachedClient.set(String.valueOf(invocation.getArguments()[0]), expiry, invocation.getArguments()[1]);
return new RpcResult();
} else if (delete.equals(invocation.getMethodName())) {
// 删除操作只有一个参数,如果参数长度不等于1,则抛出异常
if (invocation.getArguments().length != 1) {
throw new IllegalArgumentException("The memcached delete method arguments mismatch, must only one arguments. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url);
}
// 删除某个值
memcachedClient.delete(String.valueOf(invocation.getArguments()[0]));
return new RpcResult();
} else {
// 不支持的操作
throw new UnsupportedOperationException("Unsupported method " + invocation.getMethodName() + " in memcached service.");
}
} catch (Throwable t) {
RpcException re = new RpcException("Failed to invoke memcached service method. interface: " + type.getName() + ", method: " + invocation.getMethodName() + ", url: " + url + ", cause: " + t.getMessage(), t);
if (t instanceof TimeoutException || t instanceof SocketTimeoutException) {
re.setCode(RpcException.TIMEOUT_EXCEPTION);
} else if (t instanceof MemcachedException || t instanceof IOException) {
re.setCode(RpcException.NETWORK_EXCEPTION);
}
throw re;
}
}
@Override
public void destroy() {
super.destroy();
try {
// 关闭客户端
memcachedClient.shutdown();
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
}
};
} catch (Throwable t) {
throw new RpcException("Failed to refer memcached service. interface: " + type.getName() + ", url: " + url + ", cause: " + t.getMessage(), t);
}
}</code></pre>
<p>该方法是服务引用方法,基于MemcachedClient的get、set、delete方法来对应Memcached的get、set、delete命令进行对值的操作。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=i52HjTVMh6AAFScvpW3m2w%3D%3D.i%2Fl3Lwc9gKlDfkMAwTEBqwRACI3fPJKHxSyGZS%2FRud0ZT0xMxMiLHdHttaevns3FdHYVcLIMHNA5RrbfvbHKSuZrdKCssiN9WK1q%2BAmVvvQytRRGU%2FH19cdTiecJZ6%2FCBtruHT8dcYSeWiZnr1hwTFlk2waHwSCQ%2FoUGYDHnV8t6sg0eAUUlsNHuJaIkwoVxFdv8GJI7l6MksSrKdMmiTA%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于memcached协议实现的部分,逻辑比较简单。接下来我将开始对rpc模块关于redis协议部分进行讲解。</p>
Dubbo源码解析(二十七)远程调用——injvm本地调用
https://segmentfault.com/a/1190000018016406
2019-01-25T06:54:36+08:00
2019-01-25T06:54:36+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
4
<h2>远程调用——injvm本地调用</h2>
<blockquote>目标:介绍injvm本地调用的设计和实现,介绍dubbo-rpc-injvm的源码。</blockquote>
<h3>前言</h3>
<p>dubbo是一个远程调用的框架,但是它没有理由不支持本地调用,本文就要讲解dubbo关于本地调用的实现。本地调用要比远程调用简单的多。</p>
<h3>源码分析</h3>
<h4>(一)InjvmExporter</h4>
<p>该类继承了AbstractExporter,是本地服务的暴露者封装,其中实现比较简单。只是实现了unexport方法,并且维护了一份保存暴露者的集合。</p>
<pre><code class="java">class InjvmExporter<T> extends AbstractExporter<T> {
/**
* 服务key
*/
private final String key;
/**
* 暴露者集合
*/
private final Map<String, Exporter<?>> exporterMap;
InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
exporterMap.put(key, this);
}
/**
* 取消暴露
*/
@Override
public void unexport() {
// 调用父类的取消暴露方法
super.unexport();
// 从集合中移除
exporterMap.remove(key);
}
}</code></pre>
<h4>(二)InjvmInvoker</h4>
<p>该类继承了AbstractInvoker类,是本地调用的invoker实现。</p>
<pre><code class="java">class InjvmInvoker<T> extends AbstractInvoker<T> {
/**
* 服务key
*/
private final String key;
/**
* 暴露者集合
*/
private final Map<String, Exporter<?>> exporterMap;
InjvmInvoker(Class<T> type, URL url, String key, Map<String, Exporter<?>> exporterMap) {
super(type, url);
this.key = key;
this.exporterMap = exporterMap;
}
/**
* 服务是否活跃
* @return
*/
@Override
public boolean isAvailable() {
InjvmExporter<?> exporter = (InjvmExporter<?>) exporterMap.get(key);
if (exporter == null) {
return false;
} else {
return super.isAvailable();
}
}
/**
* invoke方法
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Result doInvoke(Invocation invocation) throws Throwable {
// 获得暴露者
Exporter<?> exporter = InjvmProtocol.getExporter(exporterMap, getUrl());
// 如果为空,则抛出异常
if (exporter == null) {
throw new RpcException("Service [" + key + "] not found.");
}
// 设置远程地址为127.0.0.1
RpcContext.getContext().setRemoteAddress(NetUtils.LOCALHOST, 0);
// 调用下一个调用链
return exporter.getInvoker().invoke(invocation);
}
}</code></pre>
<p>其中重写了isAvailable和doInvoke方法。</p>
<h4>(三)InjvmProtocol</h4>
<p>该类是本地调用的协议实现,其中实现了服务调用和服务暴露方法,并且封装了一个判断是否是本地调用的方法。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 本地调用 Protocol的实现类key
*/
public static final String NAME = Constants.LOCAL_PROTOCOL;
/**
* 默认端口
*/
public static final int DEFAULT_PORT = 0;
/**
* 单例
*/
private static InjvmProtocol INSTANCE;</code></pre>
<h5>2.getExporter</h5>
<pre><code class="java">static Exporter<?> getExporter(Map<String, Exporter<?>> map, URL key) {
Exporter<?> result = null;
// 如果服务key不是*
if (!key.getServiceKey().contains("*")) {
// 直接从集合中取出
result = map.get(key.getServiceKey());
} else {
// 如果 map不为空,则遍历暴露者,来找到对应的exporter
if (map != null && !map.isEmpty()) {
for (Exporter<?> exporter : map.values()) {
// 如果是服务key
if (UrlUtils.isServiceKeyMatch(key, exporter.getInvoker().getUrl())) {
// 赋值
result = exporter;
break;
}
}
}
}
// 如果没有找到exporter
if (result == null) {
// 则返回null
return null;
} else if (ProtocolUtils.isGeneric(
result.getInvoker().getUrl().getParameter(Constants.GENERIC_KEY))) {
// 如果是泛化调用,则返回null
return null;
} else {
return result;
}
}</code></pre>
<p>该方法是获得相关的暴露者。</p>
<h5>3.export</h5>
<pre><code class="java">@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 创建InjvmExporter 并且返回
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}</code></pre>
<h5>4.refer</h5>
<pre><code class="java">@Override
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
// 创建InjvmInvoker 并且返回
return new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap);
}</code></pre>
<h5>5.isInjvmRefer</h5>
<pre><code class="java">public boolean isInjvmRefer(URL url) {
final boolean isJvmRefer;
// 获得scope配置
String scope = url.getParameter(Constants.SCOPE_KEY);
// Since injvm protocol is configured explicitly, we don't need to set any extra flag, use normal refer process.
if (Constants.LOCAL_PROTOCOL.toString().equals(url.getProtocol())) {
// 如果是injvm,则不是本地调用
isJvmRefer = false;
} else if (Constants.SCOPE_LOCAL.equals(scope) || (url.getParameter("injvm", false))) {
// if it's declared as local reference
// 'scope=local' is equivalent to 'injvm=true', injvm will be deprecated in the future release
// 如果它被声明为本地引用 scope = local'相当于'injvm = true',将在以后的版本中弃用injvm
isJvmRefer = true;
} else if (Constants.SCOPE_REMOTE.equals(scope)) {
// it's declared as remote reference
// 如果被声明为远程调用
isJvmRefer = false;
} else if (url.getParameter(Constants.GENERIC_KEY, false)) {
// generic invocation is not local reference
// 泛化的调用不是本地调用
isJvmRefer = false;
} else if (getExporter(exporterMap, url) != null) {
// by default, go through local reference if there's the service exposed locally
// 默认情况下,如果本地暴露服务,请通过本地引用
isJvmRefer = true;
} else {
isJvmRefer = false;
}
return isJvmRefer;
}</code></pre>
<p>该方法是判断是否为本地调用。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=R3Z%2BlB61vi%2FvytJ04tcY7A%3D%3D.qlypI2UihQ0ArIv9O9dmQONKLGhe3%2Ff7bQW9Wzyho%2BEipW1ajN9YNXBHCOSe%2Bk5ekVssETB7WOFJ9%2BS1eRljL1HMDnDqDHL7%2FD0n%2BO6yjgw2GCxBAg842tVV6vyzeyE28v3WYKbnK8KywkBJ0niJJ%2FnD8BUQx2IFQZ6J0Ej2blwgMZAsn%2F8xXwhJ0KcMWhQr" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于injvm本地调用的部分,三种抽象的角色还是比较鲜明的,服务暴露相关的exporter、服务引用相关的invoker、以及协议相关的protocol,关键还是弄清楚再设计上的意图,以及他们分别代表的是什么。那么看这些不同的协议实现会很容易看懂。接下来我将开始对rpc模块关于memcached协议部分进行讲解。</p>
Dubbo源码解析(二十六)远程调用——http协议
https://segmentfault.com/a/1190000018002784
2019-01-24T06:55:22+08:00
2019-01-24T06:55:22+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
4
<h2>远程调用——http协议</h2>
<blockquote>目标:介绍远程调用中跟http协议相关的设计和实现,介绍dubbo-rpc-http的源码。</blockquote>
<h3>前言</h3>
<p>基于HTTP表单的远程调用协议,采用 Spring 的HttpInvoker实现,关于http协议就不用多说了吧。</p>
<h3>源码分析</h3>
<h4>(一)HttpRemoteInvocation</h4>
<p>该类继承了RemoteInvocation类,是在RemoteInvocation上增加了泛化调用的参数设置,以及增加了dubbo本身需要的附加值设置。</p>
<pre><code class="java">public class HttpRemoteInvocation extends RemoteInvocation {
private static final long serialVersionUID = 1L;
/**
* dubbo的附加值名称
*/
private static final String dubboAttachmentsAttrName = "dubbo.attachments";
public HttpRemoteInvocation(MethodInvocation methodInvocation) {
super(methodInvocation);
// 把附加值加入到会话域的属性里面
addAttribute(dubboAttachmentsAttrName, new HashMap<String, String>(RpcContext.getContext().getAttachments()));
}
@Override
public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
// 获得上下文
RpcContext context = RpcContext.getContext();
// 获得附加值
context.setAttachments((Map<String, String>) getAttribute(dubboAttachmentsAttrName));
// 泛化标志
String generic = (String) getAttribute(Constants.GENERIC_KEY);
// 如果不为空,则设置泛化标志
if (StringUtils.isNotEmpty(generic)) {
context.setAttachment(Constants.GENERIC_KEY, generic);
}
try {
// 调用下一个调用链
return super.invoke(targetObject);
} finally {
context.setAttachments(null);
}
}
}</code></pre>
<h4>(二)HttpProtocol</h4>
<p>该类是http实现的核心,跟我在《dubbo源码解析(二十五)远程调用——hessian协议》中讲到的HessianProtocol实现有很多地方相似。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 默认的端口号
*/
public static final int DEFAULT_PORT = 80;
/**
* http服务器集合
*/
private final Map<String, HttpServer> serverMap = new ConcurrentHashMap<String, HttpServer>();
/**
* Spring HttpInvokerServiceExporter 集合
*/
private final Map<String, HttpInvokerServiceExporter> skeletonMap = new ConcurrentHashMap<String, HttpInvokerServiceExporter>();
/**
* HttpBinder对象
*/
private HttpBinder httpBinder;</code></pre>
<h5>2.doExport</h5>
<pre><code class="java">@Override
protected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException {
// 获得ip地址
String addr = getAddr(url);
// 获得http服务器
HttpServer server = serverMap.get(addr);
// 如果服务器为空,则重新创建服务器,并且加入到集合
if (server == null) {
server = httpBinder.bind(url, new InternalHandler());
serverMap.put(addr, server);
}
// 获得服务path
final String path = url.getAbsolutePath();
// 加入集合
skeletonMap.put(path, createExporter(impl, type));
// 通用path
final String genericPath = path + "/" + Constants.GENERIC_KEY;
// 添加泛化的服务调用
skeletonMap.put(genericPath, createExporter(impl, GenericService.class));
return new Runnable() {
@Override
public void run() {
skeletonMap.remove(path);
skeletonMap.remove(genericPath);
}
};
}</code></pre>
<p>该方法是暴露服务等逻辑,因为dubbo实现http协议采用了Spring 的HttpInvoker实现,所以调用了createExporter方法来创建创建HttpInvokerServiceExporter。</p>
<h5>3.createExporter</h5>
<pre><code class="java">private <T> HttpInvokerServiceExporter createExporter(T impl, Class<?> type) {
// 创建HttpInvokerServiceExporter
final HttpInvokerServiceExporter httpServiceExporter = new HttpInvokerServiceExporter();
// 设置要访问的服务的接口
httpServiceExporter.setServiceInterface(type);
// 设置服务实现
httpServiceExporter.setService(impl);
try {
// 在BeanFactory设置了所有提供的bean属性,初始化bean的时候执行,可以针对某个具体的bean进行配
httpServiceExporter.afterPropertiesSet();
} catch (Exception e) {
throw new RpcException(e.getMessage(), e);
}
return httpServiceExporter;
}</code></pre>
<p>该方法是创建一个spring 的HttpInvokerServiceExporter。</p>
<h5>4.doRefer</h5>
<pre><code class="java">@Override
@SuppressWarnings("unchecked")
protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException {
// 获得泛化配置
final String generic = url.getParameter(Constants.GENERIC_KEY);
// 是否为泛化调用
final boolean isGeneric = ProtocolUtils.isGeneric(generic) || serviceType.equals(GenericService.class);
// 创建HttpInvokerProxyFactoryBean
final HttpInvokerProxyFactoryBean httpProxyFactoryBean = new HttpInvokerProxyFactoryBean();
// 设置RemoteInvocation的工厂类
httpProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() {
/**
* 为给定的AOP方法调用创建一个新的RemoteInvocation对象。
* @param methodInvocation
* @return
*/
@Override
public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
// 新建一个HttpRemoteInvocation
RemoteInvocation invocation = new HttpRemoteInvocation(methodInvocation);
// 如果是泛化调用
if (isGeneric) {
// 设置标志
invocation.addAttribute(Constants.GENERIC_KEY, generic);
}
return invocation;
}
});
// 获得identity message
String key = url.toIdentityString();
// 如果是泛化调用
if (isGeneric) {
key = key + "/" + Constants.GENERIC_KEY;
}
// 设置服务url
httpProxyFactoryBean.setServiceUrl(key);
// 设置服务接口
httpProxyFactoryBean.setServiceInterface(serviceType);
// 获得客户端参数
String client = url.getParameter(Constants.CLIENT_KEY);
if (client == null || client.length() == 0 || "simple".equals(client)) {
// 创建SimpleHttpInvokerRequestExecutor连接池 使用的是JDK HttpClient
SimpleHttpInvokerRequestExecutor httpInvokerRequestExecutor = new SimpleHttpInvokerRequestExecutor() {
@Override
protected void prepareConnection(HttpURLConnection con,
int contentLength) throws IOException {
super.prepareConnection(con, contentLength);
// 设置读取超时时间
con.setReadTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
// 设置连接超时时间
con.setConnectTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT));
}
};
httpProxyFactoryBean.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor);
} else if ("commons".equals(client)) {
// 创建 HttpComponentsHttpInvokerRequestExecutor连接池 使用的是Apache HttpClient
HttpComponentsHttpInvokerRequestExecutor httpInvokerRequestExecutor = new HttpComponentsHttpInvokerRequestExecutor();
// 设置读取超时时间
httpInvokerRequestExecutor.setReadTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
// 设置连接超时时间
httpInvokerRequestExecutor.setConnectTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT));
httpProxyFactoryBean.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor);
} else {
throw new IllegalStateException("Unsupported http protocol client " + client + ", only supported: simple, commons");
}
httpProxyFactoryBean.afterPropertiesSet();
// 返回HttpInvokerProxyFactoryBean对象
return (T) httpProxyFactoryBean.getObject();
}</code></pre>
<p>该方法是服务引用的方法,其中根据url配置simple还是commons来选择创建连接池的方式。其中的区别就是SimpleHttpInvokerRequestExecutor使用的是JDK HttpClient,HttpComponentsHttpInvokerRequestExecutor 使用的是Apache HttpClient。</p>
<h5>5.getErrorCode</h5>
<pre><code class="java">@Override
protected int getErrorCode(Throwable e) {
if (e instanceof RemoteAccessException) {
e = e.getCause();
}
if (e != null) {
Class<?> cls = e.getClass();
if (SocketTimeoutException.class.equals(cls)) {
// 返回超时异常
return RpcException.TIMEOUT_EXCEPTION;
} else if (IOException.class.isAssignableFrom(cls)) {
// 返回网络异常
return RpcException.NETWORK_EXCEPTION;
} else if (ClassNotFoundException.class.isAssignableFrom(cls)) {
// 返回序列化异常
return RpcException.SERIALIZATION_EXCEPTION;
}
}
return super.getErrorCode(e);
}</code></pre>
<p>该方法是处理异常情况,设置错误码。</p>
<h5>6.InternalHandler</h5>
<pre><code class="java">private class InternalHandler implements HttpHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// 获得请求uri
String uri = request.getRequestURI();
// 获得服务暴露者HttpInvokerServiceExporter对象
HttpInvokerServiceExporter skeleton = skeletonMap.get(uri);
// 如果不是post,则返回码设置500
if (!request.getMethod().equalsIgnoreCase("POST")) {
response.setStatus(500);
} else {
// 远程地址放到上下文
RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
try {
// 调用下一个调用
skeleton.handleRequest(request, response);
} catch (Throwable e) {
throw new ServletException(e);
}
}
}
}</code></pre>
<p>该内部类实现了HttpHandler,做了设置远程地址的逻辑。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=qp1mgPEsAFIBo3fLeOuwyA%3D%3D.rQzlHL2yaHgT%2Fbd9TEVLt0odaZzXPdzW4Laf5RG0n8tsmawAaae8ZYiv6ZUtywuD4WoYG1bbuP6vtM6tVSXDKbIIpD4mJ%2B84QzTIBrro%2B2%2BIrnu2zzsjbe9up0n9IX1hUiwREodM4TgPm7Lw4I1oorf1sC8TVfOusKvHpLVqaGN%2FIhDXZ%2F32SOhPUc%2B5m4SF" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于http协议的部分,内容比较简单,可以参考着官方文档了解一下。接下来我将开始对rpc模块关于injvm本地调用部分进行讲解。</p>
Dubbo源码解析(二十五)远程调用——hessian协议
https://segmentfault.com/a/1190000017998711
2019-01-23T17:21:10+08:00
2019-01-23T17:21:10+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
3
<h2>远程调用——hessian协议</h2>
<blockquote>目标:介绍远程调用中跟hessian协议相关的设计和实现,介绍dubbo-rpc-hessian的源码。</blockquote>
<h3>前言</h3>
<p>本文讲解多是dubbo集成的第二种协议,hessian协议,<a href="https://link.segmentfault.com/?enc=vyZO4wCtQNxytvFSy9yMPw%3D%3D.gxIlapbcjdmftRR6Nt8MXL6bugnQeTqrqeaLcUzfqCg%3D" rel="nofollow">Hessian</a> 是 Caucho 开源的一个 RPC 框架,其通讯效率高于 WebService 和 Java 自带的序列化。dubbo集成hessian所提供的hessian协议相关介绍可以参考官方文档,我就不再赘述。</p>
<blockquote>文档地址:<a href="https://link.segmentfault.com/?enc=lkSbjDxuAFZJC216ypBxRQ%3D%3D.QSWhEghGvx%2FfqIQQPdf2J8yxjc%2B%2BWNACHfmSd8COrljn%2FnaMc5TzdgNsBHr0JxE%2BPXxnvkzsE%2FxfWeiIM8DJZojHRxOcR7fp%2BEtCS%2FGIVmg%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<h3>源码分析</h3>
<h4>(一)DubboHessianURLConnectionFactory</h4>
<p>该类继承了HessianURLConnectionFactory类,是dubbo,用于创建与服务器的连接的内部工厂,重写了父类中open方法。</p>
<pre><code class="java">public class DubboHessianURLConnectionFactory extends HessianURLConnectionFactory {
/**
* 打开与HTTP服务器的新连接或循环连接
* @param url
* @return
* @throws IOException
*/
@Override
public HessianConnection open(URL url) throws IOException {
// 获得一个连接
HessianConnection connection = super.open(url);
// 获得上下文
RpcContext context = RpcContext.getContext();
for (String key : context.getAttachments().keySet()) {
// 在http协议头里面加入dubbo中附加值,key为 header+key value为附加值的value
connection.addHeader(Constants.DEFAULT_EXCHANGER + key, context.getAttachment(key));
}
return connection;
}
}</code></pre>
<p>在hessian上加入dubbo自己所需要的附加值,放到协议头里面进行发送。</p>
<h4>(二)HttpClientConnection</h4>
<p>该类是基于HttpClient封装来实现HessianConnection接口,其中逻辑比较简单。</p>
<pre><code class="java">public class HttpClientConnection implements HessianConnection {
/**
* http客户端对象
*/
private final HttpClient httpClient;
/**
* 字节输出流
*/
private final ByteArrayOutputStream output;
/**
* http post请求对象
*/
private final HttpPost request;
/**
* http 响应对象
*/
private volatile HttpResponse response;
public HttpClientConnection(HttpClient httpClient, URL url) {
this.httpClient = httpClient;
this.output = new ByteArrayOutputStream();
this.request = new HttpPost(url.toString());
}
/**
* 增加协议头
* @param key
* @param value
*/
@Override
public void addHeader(String key, String value) {
request.addHeader(new BasicHeader(key, value));
}
@Override
public OutputStream getOutputStream() throws IOException {
return output;
}
/**
* 发送请求
* @throws IOException
*/
@Override
public void sendRequest() throws IOException {
request.setEntity(new ByteArrayEntity(output.toByteArray()));
this.response = httpClient.execute(request);
}
/**
* 获得请求后的状态码
* @return
*/
@Override
public int getStatusCode() {
return response == null || response.getStatusLine() == null ? 0 : response.getStatusLine().getStatusCode();
}
@Override
public String getStatusMessage() {
return response == null || response.getStatusLine() == null ? null : response.getStatusLine().getReasonPhrase();
}
@Override
public String getContentEncoding() {
return (response == null || response.getEntity() == null || response.getEntity().getContentEncoding() == null) ? null : response.getEntity().getContentEncoding().getValue();
}
@Override
public InputStream getInputStream() throws IOException {
return response == null || response.getEntity() == null ? null : response.getEntity().getContent();
}
@Override
public void close() throws IOException {
HttpPost request = this.request;
if (request != null) {
request.abort();
}
}
@Override
public void destroy() throws IOException {
}</code></pre>
<h4>(三)HttpClientConnectionFactory</h4>
<p>该类实现了HessianConnectionFactory接口,是创建HttpClientConnection的工厂类。该类的实现跟DubboHessianURLConnectionFactory类类似,但是DubboHessianURLConnectionFactory是标准的Hessian接口调用会采用的工厂类,而HttpClientConnectionFactory是Dubbo 的 Hessian 协议调用。当然Dubbo 的 Hessian 协议也是基于http的。</p>
<pre><code class="java">public class HttpClientConnectionFactory implements HessianConnectionFactory {
/**
* httpClient对象
*/
private final HttpClient httpClient = new DefaultHttpClient();
@Override
public void setHessianProxyFactory(HessianProxyFactory factory) {
// 设置连接超时时间
HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), (int) factory.getConnectTimeout());
// 设置读取数据时阻塞链路的超时时间
HttpConnectionParams.setSoTimeout(httpClient.getParams(), (int) factory.getReadTimeout());
}
@Override
public HessianConnection open(URL url) throws IOException {
// 创建一个HttpClientConnection实例
HttpClientConnection httpClientConnection = new HttpClientConnection(httpClient, url);
// 获得上下文,用来获得附加值
RpcContext context = RpcContext.getContext();
// 遍历附加值,放入到协议头里面
for (String key : context.getAttachments().keySet()) {
httpClientConnection.addHeader(Constants.DEFAULT_EXCHANGER + key, context.getAttachment(key));
}
return httpClientConnection;
}
}</code></pre>
<p>实现了两个方法,第一个方法是给http连接设置两个参数配置,第二个方法是创建一个连接。</p>
<h4>(四)HessianProtocol</h4>
<p>该类继承了AbstractProxyProtocol类,是hessian协议的实现类。其中实现类基于hessian协议的服务引用、服务暴露等方法。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* http服务器集合
* key为ip:port
*/
private final Map<String, HttpServer> serverMap = new ConcurrentHashMap<String, HttpServer>();
/**
* HessianSkeleto 集合
* key为服务名
*/
private final Map<String, HessianSkeleton> skeletonMap = new ConcurrentHashMap<String, HessianSkeleton>();
/**
* HttpBinder对象,默认是jetty实现
*/
private HttpBinder httpBinder;</code></pre>
<h5>2.doExport</h5>
<pre><code class="java">@Override
protected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException {
// 获得ip地址
String addr = getAddr(url);
// 获得http服务器对象
HttpServer server = serverMap.get(addr);
// 如果为空,则重新创建一个server,然后放入集合
if (server == null) {
server = httpBinder.bind(url, new HessianHandler());
serverMap.put(addr, server);
}
// 获得服务path
final String path = url.getAbsolutePath();
// 创建Hessian服务端对象
final HessianSkeleton skeleton = new HessianSkeleton(impl, type);
// 加入集合
skeletonMap.put(path, skeleton);
// 获得通用的path
final String genericPath = path + "/" + Constants.GENERIC_KEY;
// 加入集合
skeletonMap.put(genericPath, new HessianSkeleton(impl, GenericService.class));
// 返回一个线程
return new Runnable() {
@Override
public void run() {
skeletonMap.remove(path);
skeletonMap.remove(genericPath);
}
};
}</code></pre>
<p>该方法是服务暴露的主要逻辑实现。</p>
<h5>3.doRefer</h5>
<pre><code class="java">@Override
@SuppressWarnings("unchecked")
protected <T> T doRefer(Class<T> serviceType, URL url) throws RpcException {
// 获得泛化的参数
String generic = url.getParameter(Constants.GENERIC_KEY);
// 是否是泛化调用
boolean isGeneric = ProtocolUtils.isGeneric(generic) || serviceType.equals(GenericService.class);
// 如果是泛化调用。则设置泛化的path和附加值
if (isGeneric) {
RpcContext.getContext().setAttachment(Constants.GENERIC_KEY, generic);
url = url.setPath(url.getPath() + "/" + Constants.GENERIC_KEY);
}
// 创建代理工厂
HessianProxyFactory hessianProxyFactory = new HessianProxyFactory();
// 是否是Hessian2的请求 默认为否
boolean isHessian2Request = url.getParameter(Constants.HESSIAN2_REQUEST_KEY, Constants.DEFAULT_HESSIAN2_REQUEST);
// 设置是否应使用Hessian协议的版本2来解析请求
hessianProxyFactory.setHessian2Request(isHessian2Request);
// 是否应为远程调用启用重载方法,默认为否
boolean isOverloadEnabled = url.getParameter(Constants.HESSIAN_OVERLOAD_METHOD_KEY, Constants.DEFAULT_HESSIAN_OVERLOAD_METHOD);
// 设置是否应为远程调用启用重载方法。
hessianProxyFactory.setOverloadEnabled(isOverloadEnabled);
// 获得client实现方式,默认为jdk
String client = url.getParameter(Constants.CLIENT_KEY, Constants.DEFAULT_HTTP_CLIENT);
if ("httpclient".equals(client)) {
// 用http来创建
hessianProxyFactory.setConnectionFactory(new HttpClientConnectionFactory());
} else if (client != null && client.length() > 0 && !Constants.DEFAULT_HTTP_CLIENT.equals(client)) {
// 抛出不支持的协议异常
throw new IllegalStateException("Unsupported http protocol client=\"" + client + "\"!");
} else {
// 创建一个HessianConnectionFactory对象
HessianConnectionFactory factory = new DubboHessianURLConnectionFactory();
// 设置代理工厂
factory.setHessianProxyFactory(hessianProxyFactory);
// 设置工厂
hessianProxyFactory.setConnectionFactory(factory);
}
// 获得超时时间
int timeout = url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 设置超时时间
hessianProxyFactory.setConnectTimeout(timeout);
hessianProxyFactory.setReadTimeout(timeout);
// 创建代理
return (T) hessianProxyFactory.create(serviceType, url.setProtocol("http").toJavaURL(), Thread.currentThread().getContextClassLoader());
}</code></pre>
<p>该方法是服务引用的主要逻辑实现,根据客户端配置,来选择标准 Hessian 接口调用还是Dubbo 的 Hessian 协议调用。</p>
<h5>4.getErrorCode</h5>
<pre><code class="java">@Override
protected int getErrorCode(Throwable e) {
// 如果属于HessianConnectionException异常
if (e instanceof HessianConnectionException) {
if (e.getCause() != null) {
Class<?> cls = e.getCause().getClass();
// 如果属于超时异常,则返回超时异常
if (SocketTimeoutException.class.equals(cls)) {
return RpcException.TIMEOUT_EXCEPTION;
}
}
// 否则返回网络异常
return RpcException.NETWORK_EXCEPTION;
} else if (e instanceof HessianMethodSerializationException) {
// 序列化异常
return RpcException.SERIALIZATION_EXCEPTION;
}
return super.getErrorCode(e);
}</code></pre>
<p>该方法是针对异常的处理。</p>
<h5>5.HessianHandler</h5>
<pre><code class="java">private class HessianHandler implements HttpHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// 获得请求的uri
String uri = request.getRequestURI();
// 获得对应的HessianSkeleton对象
HessianSkeleton skeleton = skeletonMap.get(uri);
// 如果如果不是post方法
if (!request.getMethod().equalsIgnoreCase("POST")) {
// 返回状态设置为500
response.setStatus(500);
} else {
// 设置远程地址
RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
// 获得请求头内容
Enumeration<String> enumeration = request.getHeaderNames();
// 遍历请求头内容
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
// 如果key开头是deader,则把附加值取出来放入上下文
if (key.startsWith(Constants.DEFAULT_EXCHANGER)) {
RpcContext.getContext().setAttachment(key.substring(Constants.DEFAULT_EXCHANGER.length()),
request.getHeader(key));
}
}
try {
// 执行下一个
skeleton.invoke(request.getInputStream(), response.getOutputStream());
} catch (Throwable e) {
throw new ServletException(e);
}
}
}
}</code></pre>
<p>该内部类是Hessian的处理器,用来处理请求中的协议头内容。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=GothSpcYbZ3cD3r14HMRnQ%3D%3D.lss5QNN%2BX4V%2Fs4lgiEjUwdYp4%2BJPWlpO2Telb10fy6KK2rNItPH3%2Fa6dKW8Bp42mISjgASvJegykAHFDFpcwyVlkYvpPOKHcudOxAyZ6PXUxuOTj5NrS9muSSR2UB8KVWs2ZUL%2BB2RYWT1pfSCmEMvxEsfqDiVaAeqCrjzPMo84iN1kPyzRxA8O92GuO0jBq" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于hessian协议的部分,内容比较简单,可以参考着官方文档了解一下。接下来我将开始对rpc模块关于hessian协议部分进行讲解。</p>
Dubbo源码解析(二十四)远程调用——dubbo协议
https://segmentfault.com/a/1190000017973639
2019-01-22T07:09:08+08:00
2019-01-22T07:09:08+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
9
<h2>远程调用——dubbo协议</h2>
<blockquote>目标:介绍远程调用中跟dubbo协议相关的设计和实现,介绍dubbo-rpc-dubbo的源码。</blockquote>
<h3>前言</h3>
<p>Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。这是官方文档的原话,并且官方文档还介绍了为什么使用单一长连接和 NIO 异步通讯以及为什么不适合传输大数据的服务。我就不赘述了。</p>
<p>我们先来看看dubbo-rpc-dubbo下的包结构:</p>
<p><img src="/img/remote/1460000017973642" alt="dubbo-rpc-dubbo" title="dubbo-rpc-dubbo"></p>
<ol>
<li>filter:该包下面是对于dubbo协议独有的两个过滤器</li>
<li>status:该包下是做了对于服务和线程池状态的检测</li>
<li>telnet:该包下是对于telnet命令的支持</li>
<li>最外层:最外层是dubbo协议的核心</li>
</ol>
<h3>源码分析</h3>
<h4>(一)DubboInvoker</h4>
<p>该类是dubbo协议独自实现的的invoker,其中实现了调用方法的三种模式,分别是异步发送、单向发送和同步发送,具体在下面介绍。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 信息交换客户端数组
*/
private final ExchangeClient[] clients;
/**
* 客户端数组位置
*/
private final AtomicPositiveInteger index = new AtomicPositiveInteger();
/**
* 版本号
*/
private final String version;
/**
* 销毁锁
*/
private final ReentrantLock destroyLock = new ReentrantLock();
/**
* Invoker对象集合
*/
private final Set<Invoker<?>> invokers;</code></pre>
<h5>2.doInvoke</h5>
<pre><code class="java">@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
// rpc会话域
RpcInvocation inv = (RpcInvocation) invocation;
// 获得方法名
final String methodName = RpcUtils.getMethodName(invocation);
// 把path放入到附加值中
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
// 把版本号放入到附加值
inv.setAttachment(Constants.VERSION_KEY, version);
// 当前的客户端
ExchangeClient currentClient;
// 如果数组内就一个客户端,则直接取出
if (clients.length == 1) {
currentClient = clients[0];
} else {
// 取模轮询 从数组中取,当取到最后一个时,从头开始
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
// 是否启用异步
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
// 是否是单向发送
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
// 获得超时时间
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 如果是单项发送
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
// 单向发送只负责发送消息,不等待服务端应答,所以没有返回值
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
// 异步调用
ResponseFuture future = currentClient.request(inv, timeout);
// 保存future,方便后期处理
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
// 同步调用,等待返回结果
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}</code></pre>
<p>在调用invoker的时候,通过远程通信将Invocation信息传递给服务端,服务端在接收到该invocation信息后,要找到对应的本地方法,然后通过反射执行该方法,将方法的执行结果返回给客户端,在这里,客户端发送有三种模式:</p>
<ol>
<li>异步发送,也就是当我发送调用后,我不阻塞等待结果,直接返回,将返回的future保存到上下文,方便后期使用。</li>
<li>单向发送,执行方法不需要返回结果。</li>
<li>同步发送,执行方法后,等待结果返回,否则一直阻塞。</li>
</ol>
<h5>3.isAvailable</h5>
<pre><code class="java">@Override
public boolean isAvailable() {
if (!super.isAvailable())
return false;
for (ExchangeClient client : clients) {
// 只要有一个客户端连接并且不是只读,则表示存活
if (client.isConnected() && !client.hasAttribute(Constants.CHANNEL_ATTRIBUTE_READONLY_KEY)) {
//cannot write == not Available ?
return true;
}
}
return false;
}</code></pre>
<p>该方法是检查服务端是否存活。</p>
<h5>4.destroy</h5>
<pre><code class="java">@Override
public void destroy() {
// in order to avoid closing a client multiple times, a counter is used in case of connection per jvm, every
// time when client.close() is called, counter counts down once, and when counter reaches zero, client will be
// closed.
if (super.isDestroyed()) {
return;
} else {
// double check to avoid dup close
// 获得销毁锁
destroyLock.lock();
try {
if (super.isDestroyed()) {
return;
}
// 销毁
super.destroy();
// 从集合中移除
if (invokers != null) {
invokers.remove(this);
}
for (ExchangeClient client : clients) {
try {
// 关闭每一个客户端
client.close(ConfigUtils.getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
} finally {
// 释放锁
destroyLock.unlock();
}
}
}</code></pre>
<p>该方法是销毁服务端,关闭所有连接到远程通信客户端。</p>
<h4>(二)DubboExporter</h4>
<p>该类继承了AbstractExporter,是dubbo协议中独有的服务暴露者。</p>
<pre><code class="java">/**
* 服务key
*/
private final String key;
/**
* 服务暴露者集合
*/
private final Map<String, Exporter<?>> exporterMap;
public DubboExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
}
@Override
public void unexport() {
super.unexport();
// 从集合中移除该key
exporterMap.remove(key);
}</code></pre>
<p>其中对于服务暴露者用集合做了缓存,并且只重写了了unexport。</p>
<h4>(三)DubboProtocol</h4>
<p>该类是dubbo协议的核心实现,其中增加了比如延迟加载等处理。 并且其中还包括了对服务暴露和服务引用的逻辑处理。</p>
<h5>1.属性</h5>
<pre><code class="java">public static final String NAME = "dubbo";
/**
* 默认端口号
*/
public static final int DEFAULT_PORT = 20880;
/**
* 回调名称
*/
private static final String IS_CALLBACK_SERVICE_INVOKE = "_isCallBackServiceInvoke";
/**
* dubbo协议的单例
*/
private static DubboProtocol INSTANCE;
/**
* 信息交换服务器集合 key:host:port value:ExchangeServer
*/
private final Map<String, ExchangeServer> serverMap = new ConcurrentHashMap<String, ExchangeServer>(); // <host:port,Exchanger>
/**
* 信息交换客户端集合
*/
private final Map<String, ReferenceCountExchangeClient> referenceClientMap = new ConcurrentHashMap<String, ReferenceCountExchangeClient>(); // <host:port,Exchanger>
/**
* 懒加载的客户端集合
*/
private final ConcurrentMap<String, LazyConnectExchangeClient> ghostClientMap = new ConcurrentHashMap<String, LazyConnectExchangeClient>();
/**
* 锁集合
*/
private final ConcurrentMap<String, Object> locks = new ConcurrentHashMap<String, Object>();
/**
* 序列化类名集合
*/
private final Set<String> optimizers = new ConcurrentHashSet<String>();
//consumer side export a stub service for dispatching event
//servicekey-stubmethods
/**
* 本地存根服务方法集合
*/
private final ConcurrentMap<String, String> stubServiceMethodsMap = new ConcurrentHashMap<String, String>();
/**
* 新建一个请求处理器
*/
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
/**
* 回复请求结果,返回的是请求结果
* @param channel
* @param message
* @return
* @throws RemotingException
*/
@Override
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
// 如果请求消息属于会话域
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
// 获得暴露的invoker
Invoker<?> invoker = getInvoker(channel, inv);
// need to consider backward-compatibility if it's a callback
// 如果是回调服务
if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
// 获得 方法定义
String methodsStr = invoker.getUrl().getParameters().get("methods");
boolean hasMethod = false;
// 判断看是否有会话域中的方法
if (methodsStr == null || methodsStr.indexOf(",") == -1) {
hasMethod = inv.getMethodName().equals(methodsStr);
} else {
// 如果方法不止一个,则分割后遍历查询,找到了则设置为true
String[] methods = methodsStr.split(",");
for (String method : methods) {
if (inv.getMethodName().equals(method)) {
hasMethod = true;
break;
}
}
}
// 如果没有该方法,则打印告警日志
if (!hasMethod) {
logger.warn(new IllegalStateException("The methodName " + inv.getMethodName()
+ " not found in callback service interface ,invoke will be ignored."
+ " please update the api interface. url is:"
+ invoker.getUrl()) + " ,invocation is :" + inv);
return null;
}
}
// 设置远程地址
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
// 调用下一个调用链
return invoker.invoke(inv);
}
// 否则抛出异常
throw new RemotingException(channel, "Unsupported request: "
+ (message == null ? null : (message.getClass().getName() + ": " + message))
+ ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}
/**
* 接收消息
* @param channel
* @param message
* @throws RemotingException
*/
@Override
public void received(Channel channel, Object message) throws RemotingException {
// 如果消息是会话域中的消息,则调用reply方法。
if (message instanceof Invocation) {
reply((ExchangeChannel) channel, message);
} else {
super.received(channel, message);
}
}
@Override
public void connected(Channel channel) throws RemotingException {
// 接收连接事件
invoke(channel, Constants.ON_CONNECT_KEY);
}
@Override
public void disconnected(Channel channel) throws RemotingException {
if (logger.isInfoEnabled()) {
logger.info("disconnected from " + channel.getRemoteAddress() + ",url:" + channel.getUrl());
}
// 接收断开连接事件
invoke(channel, Constants.ON_DISCONNECT_KEY);
}
/**
* 接收事件
* @param channel
* @param methodKey
*/
private void invoke(Channel channel, String methodKey) {
// 创建会话域
Invocation invocation = createInvocation(channel, channel.getUrl(), methodKey);
if (invocation != null) {
try {
// 接收事件
received(channel, invocation);
} catch (Throwable t) {
logger.warn("Failed to invoke event method " + invocation.getMethodName() + "(), cause: " + t.getMessage(), t);
}
}
}
/**
* 创建会话域, 把url内的值加入到会话域的附加值中
* @param channel
* @param url
* @param methodKey
* @return
*/
private Invocation createInvocation(Channel channel, URL url, String methodKey) {
// 获得方法,methodKey是onconnect或者ondisconnect
String method = url.getParameter(methodKey);
if (method == null || method.length() == 0) {
return null;
}
// 创建一个rpc会话域
RpcInvocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
// 加入附加值path
invocation.setAttachment(Constants.PATH_KEY, url.getPath());
// 加入附加值group
invocation.setAttachment(Constants.GROUP_KEY, url.getParameter(Constants.GROUP_KEY));
// 加入附加值interface
invocation.setAttachment(Constants.INTERFACE_KEY, url.getParameter(Constants.INTERFACE_KEY));
// 加入附加值version
invocation.setAttachment(Constants.VERSION_KEY, url.getParameter(Constants.VERSION_KEY));
// 如果是本地存根服务,则加入附加值dubbo.stub.event为true
if (url.getParameter(Constants.STUB_EVENT_KEY, false)) {
invocation.setAttachment(Constants.STUB_EVENT_KEY, Boolean.TRUE.toString());
}
return invocation;
}
};</code></pre>
<p>该属性中关键的是实例化了一个请求处理器,其中实现了基于dubbo协议等连接、取消连接、回复请求结果等方法。</p>
<h5>2.export</h5>
<pre><code class="java">@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
// 得到服务key group+"/"+serviceName+":"+serviceVersion+":"+port
String key = serviceKey(url);
// 创建exporter
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
// 加入到集合
exporterMap.put(key, exporter);
//export an stub service for dispatching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
// 如果是本地存根事件而不是回调服务
if (isStubSupportEvent && !isCallbackservice) {
// 获得本地存根的方法
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
// 如果为空,则抛出异常
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
// 加入集合
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
// 打开服务
openServer(url);
// 序列化
optimizeSerialization(url);
return exporter;
}</code></pre>
<p>该方法是基于dubbo协议的服务暴露,除了对于存根服务和本地服务进行标记以外,打开服务和序列化分别在openServer和optimizeSerialization中实现。</p>
<h5>3.openServer</h5>
<pre><code class="java">private void openServer(URL url) {
// find server.
String key = url.getAddress();
//client can export a service which's only for server to invoke
// 客户端是否可以暴露仅供服务器调用的服务
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
// 如果是的话
if (isServer) {
// 获得信息交换服务器
ExchangeServer server = serverMap.get(key);
if (server == null) {
// 重新创建服务器对象,然后放入集合
serverMap.put(key, createServer(url));
} else {
// server supports reset, use together with override
// 重置
server.reset(url);
}
}
}</code></pre>
<p>该方法就是打开服务。其中的逻辑其实是把服务对象放入集合中进行缓存,如果该地址对应的服务器不存在,则调用createServer创建一个服务器对象。</p>
<h5>4.createServer</h5>
<pre><code class="java">private ExchangeServer createServer(URL url) {
// send readonly event when server closes, it's enabled by default
// 服务器关闭时发送readonly事件,默认情况下启用
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// enable heartbeat by default
// 心跳默认间隔一分钟
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// 获得远程通讯服务端实现方式,默认用netty3
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
/**
* 如果没有该配置,则抛出异常
*/
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
/**
* 添加编解码器DubboCodec实现
*/
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
// 启动服务器
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
// 获得客户端侧设置的远程通信方式
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
// 获得远程通信的实现集合
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
// 如果客户端侧设置的远程通信方式不在支持的方式中,则抛出异常
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
</code></pre>
<p>该方法就是根据url携带的远程通信实现方法来创建一个服务器对象。</p>
<h5>5.optimizeSerialization</h5>
<pre><code class="java">private void optimizeSerialization(URL url) throws RpcException {
// 获得类名
String className = url.getParameter(Constants.OPTIMIZER_KEY, "");
if (StringUtils.isEmpty(className) || optimizers.contains(className)) {
return;
}
logger.info("Optimizing the serialization process for Kryo, FST, etc...");
try {
// 加载类
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
if (!SerializationOptimizer.class.isAssignableFrom(clazz)) {
throw new RpcException("The serialization optimizer " + className + " isn't an instance of " + SerializationOptimizer.class.getName());
}
// 强制类型转化为SerializationOptimizer
SerializationOptimizer optimizer = (SerializationOptimizer) clazz.newInstance();
if (optimizer.getSerializableClasses() == null) {
return;
}
// 遍历序列化的类,把该类放入到集合进行缓存
for (Class c : optimizer.getSerializableClasses()) {
SerializableClassRegistry.registerClass(c);
}
// 加入到集合
optimizers.add(className);
} catch (ClassNotFoundException e) {
throw new RpcException("Cannot find the serialization optimizer class: " + className, e);
} catch (InstantiationException e) {
throw new RpcException("Cannot instantiate the serialization optimizer class: " + className, e);
} catch (IllegalAccessException e) {
throw new RpcException("Cannot instantiate the serialization optimizer class: " + className, e);
}
}
</code></pre>
<p>该方法是把序列化的类放入到集合,以便进行序列化</p>
<h5>6.refer</h5>
<pre><code class="java">@Override
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
// 序列化
optimizeSerialization(url);
// create rpc invoker. 创建一个DubboInvoker对象
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
// 把该invoker放入集合
invokers.add(invoker);
return invoker;
}
</code></pre>
<p>该方法是服务引用,其中就是新建一个DubboInvoker对象后把它放入到集合。</p>
<h5>7.getClients</h5>
<pre><code class="java">private ExchangeClient[] getClients(URL url) {
// whether to share connection
// 一个连接是否对于一个服务
boolean service_share_connect = false;
// 获得url中欢愉连接共享的配置 默认为0
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
// if not configured, connection is shared, otherwise, one connection for one service
// 如果为0,则是共享类,并且连接数为1
if (connections == 0) {
service_share_connect = true;
connections = 1;
}
// 创建数组
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
// 如果共享,则获得共享客户端对象,否则新建客户端
if (service_share_connect) {
clients[i] = getSharedClient(url);
} else {
clients[i] = initClient(url);
}
}
return clients;
}
</code></pre>
<p>该方法是获得客户端集合的方法,分为共享客户端和非共享客户端。共享客户端是共用同一个连接,非共享客户端是每个客户端都有自己的一个连接。</p>
<h5>8.getSharedClient</h5>
<pre><code class="java">private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
// 从集合中取出客户端对象
ReferenceCountExchangeClient client = referenceClientMap.get(key);
// 如果不为空并且没关闭连接,则计数器加1,返回
if (client != null) {
if (!client.isClosed()) {
client.incrementAndGetCount();
return client;
} else {
// 如果连接断开,则从集合中移除
referenceClientMap.remove(key);
}
}
locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {
// 如果集合中有该key
if (referenceClientMap.containsKey(key)) {
// 则直接返回client
return referenceClientMap.get(key);
}
// 否则新建一个连接
ExchangeClient exchangeClient = initClient(url);
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
// 存入集合
referenceClientMap.put(key, client);
// 从ghostClientMap中移除
ghostClientMap.remove(key);
// 从对象锁中移除
locks.remove(key);
return client;
}
}
</code></pre>
<p>该方法是获得分享的客户端连接。</p>
<h5>9.initClient</h5>
<pre><code class="java">private ExchangeClient initClient(URL url) {
// client type setting.
// 获得客户端的实现方法 默认netty3
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
// 添加编码器
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
// enable heartbeat by default
// 默认开启心跳
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// BIO is not allowed since it has severe performance issue.
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: " + str + "," +
" supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
}
ExchangeClient client;
try {
// connection should be lazy
// 是否需要延迟连接,,默认不开启
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
// 创建延迟连接的客户端
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
// 否则就直接连接
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
return client;
}
</code></pre>
<p>该方法是新建一个客户端连接</p>
<h5>10.destroy</h5>
<pre><code class="java">@Override
public void destroy() {
// 遍历服务器逐个关闭
for (String key : new ArrayList<String>(serverMap.keySet())) {
ExchangeServer server = serverMap.remove(key);
if (server != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo server: " + server.getLocalAddress());
}
server.close(ConfigUtils.getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
// 遍历客户端集合逐个关闭
for (String key : new ArrayList<String>(referenceClientMap.keySet())) {
ExchangeClient client = referenceClientMap.remove(key);
if (client != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo connect: " + client.getLocalAddress() + "-->" + client.getRemoteAddress());
}
client.close(ConfigUtils.getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
// 遍历懒加载的集合,逐个关闭客户端
for (String key : new ArrayList<String>(ghostClientMap.keySet())) {
ExchangeClient client = ghostClientMap.remove(key);
if (client != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close dubbo connect: " + client.getLocalAddress() + "-->" + client.getRemoteAddress());
}
client.close(ConfigUtils.getServerShutdownTimeout());
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
stubServiceMethodsMap.clear();
super.destroy();
}
</code></pre>
<p>该方法是销毁的方法重写。</p>
<h4>(四)ChannelWrappedInvoker</h4>
<p>该类是对当前通道内的客户端调用消息进行包装</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 通道
*/
private final Channel channel;
/**
* 服务key
*/
private final String serviceKey;
/**
* 当前的客户端
*/
private final ExchangeClient currentClient;
</code></pre>
<h5>2.doInvoke</h5>
<pre><code class="java">@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
// use interface's name as service path to export if it's not found on client side
// 设置服务path,默认用接口名称
inv.setAttachment(Constants.PATH_KEY, getInterface().getName());
// 设置回调的服务key
inv.setAttachment(Constants.CALLBACK_SERVICE_KEY, serviceKey);
try {
// 如果是异步的
if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) { // may have concurrency issue
// 直接发送请求消息
currentClient.send(inv, getUrl().getMethodParameter(invocation.getMethodName(), Constants.SENT_KEY, false));
return new RpcResult();
}
// 获得超时时间
int timeout = getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (timeout > 0) {
return (Result) currentClient.request(inv, timeout).get();
} else {
return (Result) currentClient.request(inv).get();
}
} catch (RpcException e) {
throw e;
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, e.getMessage(), e);
} catch (Throwable e) { // here is non-biz exception, wrap it.
throw new RpcException(e.getMessage(), e);
}
}
</code></pre>
<p>该方法是在invoker调用的时候对发送请求消息进行了包装。</p>
<h5>3.ChannelWrapper</h5>
<p>该类是个内部没,继承了ClientDelegate,其中将编码器变成了dubbo的编码器,其他方法比较简单。</p>
<h4>(五)DecodeableRpcInvocation</h4>
<p>该类主要做了对于会话域内的数据进行序列化和解码。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger log = LoggerFactory.getLogger(DecodeableRpcInvocation.class);
/**
* 通道
*/
private Channel channel;
/**
* 序列化类型
*/
private byte serializationType;
/**
* 输入流
*/
private InputStream inputStream;
/**
* 请求
*/
private Request request;
/**
* 是否解码
*/
private volatile boolean hasDecoded;
</code></pre>
<h5>2.decode</h5>
<pre><code class="java">@Override
public void decode() throws Exception {
// 如果没有解码,则进行解码
if (!hasDecoded && channel != null && inputStream != null) {
try {
decode(channel, inputStream);
} catch (Throwable e) {
if (log.isWarnEnabled()) {
log.warn("Decode rpc invocation failed: " + e.getMessage(), e);
}
request.setBroken(true);
request.setData(e);
} finally {
// 设置已经解码
hasDecoded = true;
}
}
}
@Override
public Object decode(Channel channel, InputStream input) throws IOException {
// 对数据进行反序列化
ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
.deserialize(channel.getUrl(), input);
// dubbo版本
String dubboVersion = in.readUTF();
// 请求中放入dubbo版本
request.setVersion(dubboVersion);
// 附加值内加入dubbo版本,path以及版本号
setAttachment(Constants.DUBBO_VERSION_KEY, dubboVersion);
setAttachment(Constants.PATH_KEY, in.readUTF());
setAttachment(Constants.VERSION_KEY, in.readUTF());
// 设置方法名称
setMethodName(in.readUTF());
try {
// 方法参数数组
Object[] args;
// 方法参数类型数组
Class<?>[] pts;
// 描述
String desc = in.readUTF();
// 如果为空,则方法参数数组和对方法参数类型数组都设置为空
if (desc.length() == 0) {
pts = DubboCodec.EMPTY_CLASS_ARRAY;
args = DubboCodec.EMPTY_OBJECT_ARRAY;
} else {
// 分割成类,获得类数组
pts = ReflectUtils.desc2classArray(desc);
// 创建等长等数组
args = new Object[pts.length];
for (int i = 0; i < args.length; i++) {
try {
// 读取对象放入数组中
args[i] = in.readObject(pts[i]);
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("Decode argument failed: " + e.getMessage(), e);
}
}
}
}
// 设置参数类型
setParameterTypes(pts);
Map<String, String> map = (Map<String, String>) in.readObject(Map.class);
if (map != null && map.size() > 0) {
// 获得所有附加值
Map<String, String> attachment = getAttachments();
if (attachment == null) {
attachment = new HashMap<String, String>();
}
// 把流中读到的配置放入附加值
attachment.putAll(map);
// 放回去
setAttachments(attachment);
}
//decode argument ,may be callback
for (int i = 0; i < args.length; i++) {
// 如果是回调,则再一次解码
args[i] = decodeInvocationArgument(channel, this, pts, i, args[i]);
}
setArguments(args);
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read invocation data failed.", e));
} finally {
if (in instanceof Cleanable) {
((Cleanable) in).cleanup();
}
}
return this;
}
</code></pre>
<p>该方法就是处理Invocation内数据的逻辑,其中主要是做了序列化和解码。把读取出来的设置放入对对应位置传递给后面的调用。</p>
<h4>(六)DecodeableRpcResult</h4>
<p>该类是做了基于dubbo协议对prc结果的解码</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger log = LoggerFactory.getLogger(DecodeableRpcResult.class);
/**
* 通道
*/
private Channel channel;
/**
* 序列化类型
*/
private byte serializationType;
/**
* 输入流
*/
private InputStream inputStream;
/**
* 响应
*/
private Response response;
/**
* 会话域
*/
private Invocation invocation;
/**
* 是否解码
*/
private volatile boolean hasDecoded;
</code></pre>
<h5>2.decode</h5>
<pre><code class="java">@Override
public Object decode(Channel channel, InputStream input) throws IOException {
// 反序列化
ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
.deserialize(channel.getUrl(), input);
byte flag = in.readByte();
// 根据返回的不同结果来进行处理
switch (flag) {
case DubboCodec.RESPONSE_NULL_VALUE:
// 返回结果为空
break;
case DubboCodec.RESPONSE_VALUE:
//
try {
// 获得返回类型数组
Type[] returnType = RpcUtils.getReturnTypes(invocation);
// 根据返回类型读取返回结果并且放入RpcResult
setValue(returnType == null || returnType.length == 0 ? in.readObject() :
(returnType.length == 1 ? in.readObject((Class<?>) returnType[0])
: in.readObject((Class<?>) returnType[0], returnType[1])));
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
case DubboCodec.RESPONSE_WITH_EXCEPTION:
// 返回结果有异常
try {
Object obj = in.readObject();
// 把异常放入RpcResult
if (obj instanceof Throwable == false)
throw new IOException("Response data error, expect Throwable, but get " + obj);
setException((Throwable) obj);
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS:
// 返回值为空,但是有附加值
try {
// 把附加值加入到RpcResult
setAttachments((Map<String, String>) in.readObject(Map.class));
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS:
// 返回值
try {
// 设置返回结果
Type[] returnType = RpcUtils.getReturnTypes(invocation);
setValue(returnType == null || returnType.length == 0 ? in.readObject() :
(returnType.length == 1 ? in.readObject((Class<?>) returnType[0])
: in.readObject((Class<?>) returnType[0], returnType[1])));
// 设置附加值
setAttachments((Map<String, String>) in.readObject(Map.class));
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS:
// 返回结果有异常并且有附加值
try {
// 设置异常
Object obj = in.readObject();
if (obj instanceof Throwable == false)
throw new IOException("Response data error, expect Throwable, but get " + obj);
setException((Throwable) obj);
// 设置附加值
setAttachments((Map<String, String>) in.readObject(Map.class));
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
default:
throw new IOException("Unknown result flag, expect '0' '1' '2', get " + flag);
}
if (in instanceof Cleanable) {
((Cleanable) in).cleanup();
}
return this;
}
@Override
public void decode() throws Exception {
// 如果没有解码
if (!hasDecoded && channel != null && inputStream != null) {
try {
// 进行解码
decode(channel, inputStream);
} catch (Throwable e) {
if (log.isWarnEnabled()) {
log.warn("Decode rpc result failed: " + e.getMessage(), e);
}
response.setStatus(Response.CLIENT_ERROR);
response.setErrorMessage(StringUtils.toString(e));
} finally {
hasDecoded = true;
}
}
}
</code></pre>
<p>该方法是对响应结果的解码,其中根据不同的返回结果来对RpcResult设置不同的值。</p>
<h4>(七)LazyConnectExchangeClient</h4>
<p>该类实现了ExchangeClient接口,是ExchangeClient的装饰器,用到了装饰模式,是延迟连接的客户端实现类。</p>
<h5>1.属性</h5>
<pre><code class="java">// when this warning rises from invocation, program probably have bug.
/**
* 延迟连接请求错误key
*/
static final String REQUEST_WITH_WARNING_KEY = "lazyclient_request_with_warning";
private final static Logger logger = LoggerFactory.getLogger(LazyConnectExchangeClient.class);
/**
* 是否在延迟连接请求时错误
*/
protected final boolean requestWithWarning;
/**
* url对象
*/
private final URL url;
/**
* 请求处理器
*/
private final ExchangeHandler requestHandler;
/**
* 连接锁
*/
private final Lock connectLock = new ReentrantLock();
// lazy connect, initial state for connection
/**
* 初始化状态
*/
private final boolean initialState;
/**
* 客户端对象
*/
private volatile ExchangeClient client;
/**
* 错误次数
*/
private AtomicLong warningcount = new AtomicLong(0);
</code></pre>
<p>可以看到有属性ExchangeClient client,该类中很多方法就直接调用了client的方法。</p>
<h5>2.构造方法</h5>
<pre><code class="java">public LazyConnectExchangeClient(URL url, ExchangeHandler requestHandler) {
// lazy connect, need set send.reconnect = true, to avoid channel bad status.
// 默认有重连
this.url = url.addParameter(Constants.SEND_RECONNECT_KEY, Boolean.TRUE.toString());
this.requestHandler = requestHandler;
// 默认延迟连接初始化成功
this.initialState = url.getParameter(Constants.LAZY_CONNECT_INITIAL_STATE_KEY, Constants.DEFAULT_LAZY_CONNECT_INITIAL_STATE);
// 默认没有错误
this.requestWithWarning = url.getParameter(REQUEST_WITH_WARNING_KEY, false);
}
</code></pre>
<h5>3.initClient</h5>
<pre><code class="java">private void initClient() throws RemotingException {
// 如果客户端已经初始化,则直接返回
if (client != null)
return;
if (logger.isInfoEnabled()) {
logger.info("Lazy connect to " + url);
}
// 获得连接锁
connectLock.lock();
try {
// 二次判空
if (client != null)
return;
// 新建一个客户端
this.client = Exchangers.connect(url, requestHandler);
} finally {
// 释放锁
connectLock.unlock();
}
}
</code></pre>
<p>该方法是初始化客户端的方法。</p>
<h5>4.request</h5>
<pre><code class="java">@Override
public ResponseFuture request(Object request) throws RemotingException {
warning(request);
initClient();
return client.request(request);
}
</code></pre>
<p>该方法在调用client.request前调用了前面两个方法,initClient我在上面讲到了,就是用来初始化客户端的。而warning是用来报错的。</p>
<h5>5.warning</h5>
<pre><code class="java">private void warning(Object request) {
if (requestWithWarning) {
// 每5000次报错一次
if (warningcount.get() % 5000 == 0) {
logger.warn(new IllegalStateException("safe guard client , should not be called ,must have a bug."));
}
warningcount.incrementAndGet();
}
}
</code></pre>
<p>每5000次记录报错一次。</p>
<h4>(八)ReferenceCountExchangeClient</h4>
<p>该类也是对ExchangeClient的装饰,其中增强了调用次数多功能。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* url对象
*/
private final URL url;
/**
* 计数
*/
private final AtomicInteger refenceCount = new AtomicInteger(0);
// private final ExchangeHandler handler;
/**
* 延迟连接客户端集合
*/
private final ConcurrentMap<String, LazyConnectExchangeClient> ghostClientMap;
/**
* 客户端对象
*/
private ExchangeClient client;
</code></pre>
<h5>2.replaceWithLazyClient</h5>
<pre><code class="java">// ghost client
private LazyConnectExchangeClient replaceWithLazyClient() {
// this is a defensive operation to avoid client is closed by accident, the initial state of the client is false
// 设置延迟连接初始化状态、是否重连、是否已经重连等配置
URL lazyUrl = url.addParameter(Constants.LAZY_CONNECT_INITIAL_STATE_KEY, Boolean.FALSE)
.addParameter(Constants.RECONNECT_KEY, Boolean.FALSE)
.addParameter(Constants.SEND_RECONNECT_KEY, Boolean.TRUE.toString())
.addParameter("warning", Boolean.TRUE.toString())
.addParameter(LazyConnectExchangeClient.REQUEST_WITH_WARNING_KEY, true)
.addParameter("_client_memo", "referencecounthandler.replacewithlazyclient");
// 获得服务地址
String key = url.getAddress();
// in worst case there's only one ghost connection.
// 从集合中获取客户端
LazyConnectExchangeClient gclient = ghostClientMap.get(key);
// 如果对应等客户端不存在或者已经关闭连接,则重新创建一个延迟连接等客户端,并且放入集合
if (gclient == null || gclient.isClosed()) {
gclient = new LazyConnectExchangeClient(lazyUrl, client.getExchangeHandler());
ghostClientMap.put(key, gclient);
}
return gclient;
}
</code></pre>
<p>该方法是用延迟连接替代,该方法在close方法中被调用。</p>
<h5>3.close</h5>
<pre><code class="java">@Override
public void close(int timeout) {
if (refenceCount.decrementAndGet() <= 0) {
if (timeout == 0) {
client.close();
} else {
client.close(timeout);
}
client = replaceWithLazyClient();
}
}
</code></pre>
<h4>(九)FutureAdapter</h4>
<p>该类实现了Future接口,是响应的Future适配器。其中是基于ResponseFuture做适配。其中比较简单,我就不多讲解了。</p>
<h4>(十)CallbackServiceCodec</h4>
<p>该类是针对回调服务的编解码器。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 代理工厂
*/
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
/**
* dubbo协议
*/
private static final DubboProtocol protocol = DubboProtocol.getDubboProtocol();
/**
* 回调的标志
*/
private static final byte CALLBACK_NONE = 0x0;
/**
* 回调的创建标志
*/
private static final byte CALLBACK_CREATE = 0x1;
/**
* 回调的销毁标志
*/
private static final byte CALLBACK_DESTROY = 0x2;
/**
* 回调参数key
*/
private static final String INV_ATT_CALLBACK_KEY = "sys_callback_arg-";
</code></pre>
<h5>2.encodeInvocationArgument</h5>
<pre><code class="java">public static Object encodeInvocationArgument(Channel channel, RpcInvocation inv, int paraIndex) throws IOException {
// get URL directly
// 直接获得url
URL url = inv.getInvoker() == null ? null : inv.getInvoker().getUrl();
// 设置回调标志
byte callbackstatus = isCallBack(url, inv.getMethodName(), paraIndex);
// 获得参数集合
Object[] args = inv.getArguments();
// 获得参数类型集合
Class<?>[] pts = inv.getParameterTypes();
// 根据不同的回调状态来设置附加值和返回参数
switch (callbackstatus) {
case CallbackServiceCodec.CALLBACK_NONE:
return args[paraIndex];
case CallbackServiceCodec.CALLBACK_CREATE:
inv.setAttachment(INV_ATT_CALLBACK_KEY + paraIndex, exportOrunexportCallbackService(channel, url, pts[paraIndex], args[paraIndex], true));
return null;
case CallbackServiceCodec.CALLBACK_DESTROY:
inv.setAttachment(INV_ATT_CALLBACK_KEY + paraIndex, exportOrunexportCallbackService(channel, url, pts[paraIndex], args[paraIndex], false));
return null;
default:
return args[paraIndex];
}
}
</code></pre>
<p>该方法是对会话域的信息进行编码。</p>
<h5>3.decodeInvocationArgument</h5>
<pre><code class="java">public static Object decodeInvocationArgument(Channel channel, RpcInvocation inv, Class<?>[] pts, int paraIndex, Object inObject) throws IOException {
// if it's a callback, create proxy on client side, callback interface on client side can be invoked through channel
// need get URL from channel and env when decode
URL url = null;
try {
// 获得url
url = DubboProtocol.getDubboProtocol().getInvoker(channel, inv).getUrl();
} catch (RemotingException e) {
if (logger.isInfoEnabled()) {
logger.info(e.getMessage(), e);
}
return inObject;
}
// 获得回调状态
byte callbackstatus = isCallBack(url, inv.getMethodName(), paraIndex);
// 根据回调状态来返回结果
switch (callbackstatus) {
case CallbackServiceCodec.CALLBACK_NONE:
return inObject;
case CallbackServiceCodec.CALLBACK_CREATE:
try {
return referOrdestroyCallbackService(channel, url, pts[paraIndex], inv, Integer.parseInt(inv.getAttachment(INV_ATT_CALLBACK_KEY + paraIndex)), true);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new IOException(StringUtils.toString(e));
}
case CallbackServiceCodec.CALLBACK_DESTROY:
try {
return referOrdestroyCallbackService(channel, url, pts[paraIndex], inv, Integer.parseInt(inv.getAttachment(INV_ATT_CALLBACK_KEY + paraIndex)), false);
} catch (Exception e) {
throw new IOException(StringUtils.toString(e));
}
default:
return inObject;
}
}
</code></pre>
<p>该方法是对会话域内的信息进行解码。</p>
<h5>4.isCallBack</h5>
<pre><code class="java">private static byte isCallBack(URL url, String methodName, int argIndex) {
// parameter callback rule: method-name.parameter-index(starting from 0).callback
// 参数的规则:ethod-name.parameter-index(starting from 0).callback
byte isCallback = CALLBACK_NONE;
if (url != null) {
// 获得回调的值
String callback = url.getParameter(methodName + "." + argIndex + ".callback");
if (callback != null) {
// 如果为true,则设置为创建标志
if (callback.equalsIgnoreCase("true")) {
isCallback = CALLBACK_CREATE;
// 如果为false,则设置为销毁标志
} else if (callback.equalsIgnoreCase("false")) {
isCallback = CALLBACK_DESTROY;
}
}
}
return isCallback;
}
</code></pre>
<p>该方法是根据url携带的参数设置回调的标志,以供执行不同的编解码逻辑。</p>
<h5>5.exportOrunexportCallbackService</h5>
<pre><code class="java">private static String exportOrunexportCallbackService(Channel channel, URL url, Class clazz, Object inst, Boolean export) throws IOException {
// 返回对象的hashCode
int instid = System.identityHashCode(inst);
Map<String, String> params = new HashMap<String, String>(3);
// no need to new client again
// 设置不是服务端标志为否
params.put(Constants.IS_SERVER_KEY, Boolean.FALSE.toString());
// mark it's a callback, for troubleshooting
// 设置是回调服务标志为true
params.put(Constants.IS_CALLBACK_SERVICE, Boolean.TRUE.toString());
String group = url.getParameter(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
// 设置是消费侧还是提供侧
params.put(Constants.GROUP_KEY, group);
}
// add method, for verifying against method, automatic fallback (see dubbo protocol)
// 添加方法,在dubbo的协议里面用到
params.put(Constants.METHODS_KEY, StringUtils.join(Wrapper.getWrapper(clazz).getDeclaredMethodNames(), ","));
Map<String, String> tmpmap = new HashMap<String, String>(url.getParameters());
tmpmap.putAll(params);
// 移除版本信息
tmpmap.remove(Constants.VERSION_KEY);// doesn't need to distinguish version for callback
// 设置接口名
tmpmap.put(Constants.INTERFACE_KEY, clazz.getName());
// 创建服务暴露的url
URL exporturl = new URL(DubboProtocol.NAME, channel.getLocalAddress().getAddress().getHostAddress(), channel.getLocalAddress().getPort(), clazz.getName() + "." + instid, tmpmap);
// no need to generate multiple exporters for different channel in the same JVM, cache key cannot collide.
// 获得缓存的key
String cacheKey = getClientSideCallbackServiceCacheKey(instid);
// 获得计数的key
String countkey = getClientSideCountKey(clazz.getName());
// 如果是暴露服务
if (export) {
// one channel can have multiple callback instances, no need to re-export for different instance.
if (!channel.hasAttribute(cacheKey)) {
if (!isInstancesOverLimit(channel, url, clazz.getName(), instid, false)) {
// 获得代理对象
Invoker<?> invoker = proxyFactory.getInvoker(inst, clazz, exporturl);
// should destroy resource?
// 暴露服务
Exporter<?> exporter = protocol.export(invoker);
// this is used for tracing if instid has published service or not.
// 放到通道
channel.setAttribute(cacheKey, exporter);
logger.info("export a callback service :" + exporturl + ", on " + channel + ", url is: " + url);
// 计数器加1
increaseInstanceCount(channel, countkey);
}
}
} else {
// 如果通道内已经有该服务的缓存
if (channel.hasAttribute(cacheKey)) {
// 则获得该暴露者
Exporter<?> exporter = (Exporter<?>) channel.getAttribute(cacheKey);
// 取消暴露
exporter.unexport();
// 移除该缓存
channel.removeAttribute(cacheKey);
// 计数器减1
decreaseInstanceCount(channel, countkey);
}
}
return String.valueOf(instid);
}
</code></pre>
<p>该方法是在客户端侧暴露服务和取消暴露服务。</p>
<h5>6.referOrdestroyCallbackService</h5>
<pre><code class="java">private static Object referOrdestroyCallbackService(Channel channel, URL url, Class<?> clazz, Invocation inv, int instid, boolean isRefer) {
Object proxy = null;
// 获得服务调用的缓存key
String invokerCacheKey = getServerSideCallbackInvokerCacheKey(channel, clazz.getName(), instid);
// 获得代理缓存key
String proxyCacheKey = getServerSideCallbackServiceCacheKey(channel, clazz.getName(), instid);
// 从通道内获得代理对象
proxy = channel.getAttribute(proxyCacheKey);
// 获得计数器key
String countkey = getServerSideCountKey(channel, clazz.getName());
// 如果是服务引用
if (isRefer) {
// 如果代理对象为空
if (proxy == null) {
// 获得服务引用的url
URL referurl = URL.valueOf("callback://" + url.getAddress() + "/" + clazz.getName() + "?" + Constants.INTERFACE_KEY + "=" + clazz.getName());
referurl = referurl.addParametersIfAbsent(url.getParameters()).removeParameter(Constants.METHODS_KEY);
if (!isInstancesOverLimit(channel, referurl, clazz.getName(), instid, true)) {
@SuppressWarnings("rawtypes")
Invoker<?> invoker = new ChannelWrappedInvoker(clazz, channel, referurl, String.valueOf(instid));
// 获得代理类
proxy = proxyFactory.getProxy(invoker);
// 设置代理类
channel.setAttribute(proxyCacheKey, proxy);
// 设置实体域
channel.setAttribute(invokerCacheKey, invoker);
// 计数器加1
increaseInstanceCount(channel, countkey);
//convert error fail fast .
//ignore concurrent problem.
Set<Invoker<?>> callbackInvokers = (Set<Invoker<?>>) channel.getAttribute(Constants.CHANNEL_CALLBACK_KEY);
if (callbackInvokers == null) {
// 创建回调的服务实体域集合
callbackInvokers = new ConcurrentHashSet<Invoker<?>>(1);
// 把该实体域加入集合中
callbackInvokers.add(invoker);
channel.setAttribute(Constants.CHANNEL_CALLBACK_KEY, callbackInvokers);
}
logger.info("method " + inv.getMethodName() + " include a callback service :" + invoker.getUrl() + ", a proxy :" + invoker + " has been created.");
}
}
} else {
// 销毁
if (proxy != null) {
Invoker<?> invoker = (Invoker<?>) channel.getAttribute(invokerCacheKey);
try {
Set<Invoker<?>> callbackInvokers = (Set<Invoker<?>>) channel.getAttribute(Constants.CHANNEL_CALLBACK_KEY);
if (callbackInvokers != null) {
// 从集合中移除
callbackInvokers.remove(invoker);
}
// 销毁该调用
invoker.destroy();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
// cancel refer, directly remove from the map
// 取消引用,直接从集合中移除
channel.removeAttribute(proxyCacheKey);
channel.removeAttribute(invokerCacheKey);
// 计数器减1
decreaseInstanceCount(channel, countkey);
}
}
return proxy;
}
</code></pre>
<p>该方法是在服务端侧进行服务引用或者销毁回调服务。</p>
<h4>(十一)DubboCodec</h4>
<p>该类是dubbo的编解码器,分别针对dubbo协议的request和response进行编码和解码。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* dubbo名称
*/
public static final String NAME = "dubbo";
/**
* 协议版本号
*/
public static final String DUBBO_VERSION = Version.getProtocolVersion();
/**
* 响应携带着异常
*/
public static final byte RESPONSE_WITH_EXCEPTION = 0;
/**
* 响应
*/
public static final byte RESPONSE_VALUE = 1;
/**
* 响应结果为空
*/
public static final byte RESPONSE_NULL_VALUE = 2;
/**
* 响应结果有异常并且带有附加值
*/
public static final byte RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS = 3;
/**
* 响应结果有附加值
*/
public static final byte RESPONSE_VALUE_WITH_ATTACHMENTS = 4;
/**
* 响应结果为空并带有附加值
*/
public static final byte RESPONSE_NULL_VALUE_WITH_ATTACHMENTS = 5;
/**
* 对象空集合
*/
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
/**
* 空的类集合
*/
public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
private static final Logger log = LoggerFactory.getLogger(DubboCodec.class);
</code></pre>
<h5>2.decodeBody</h5>
<pre><code class="java">@Override
protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
// get request id.
long id = Bytes.bytes2long(header, 4);
// 如果是response
if ((flag & FLAG_REQUEST) == 0) {
// decode response.
// 创建一个response
Response res = new Response(id);
// 如果是事件,则设置事件,这里有个问题,我提交了pr在新版本已经修复
if ((flag & FLAG_EVENT) != 0) {
res.setEvent(Response.HEARTBEAT_EVENT);
}
// get status.
// 设置状态
byte status = header[3];
res.setStatus(status);
try {
// 反序列化
ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
// 如果状态是响应成功
if (status == Response.OK) {
Object data;
// 如果是心跳事件,则按照心跳事件解码
if (res.isHeartbeat()) {
data = decodeHeartbeatData(channel, in);
} else if (res.isEvent()) {
// 如果是事件,则
data = decodeEventData(channel, in);
} else {
// 否则对结果进行解码
DecodeableRpcResult result;
if (channel.getUrl().getParameter(
Constants.DECODE_IN_IO_THREAD_KEY,
Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
result = new DecodeableRpcResult(channel, res, is,
(Invocation) getRequestData(id), proto);
result.decode();
} else {
result = new DecodeableRpcResult(channel, res,
new UnsafeByteArrayInputStream(readMessageData(is)),
(Invocation) getRequestData(id), proto);
}
data = result;
}
// 把结果重新放入response中
res.setResult(data);
} else {
// 否则设置错误信息
res.setErrorMessage(in.readUTF());
}
} catch (Throwable t) {
if (log.isWarnEnabled()) {
log.warn("Decode response failed: " + t.getMessage(), t);
}
res.setStatus(Response.CLIENT_ERROR);
res.setErrorMessage(StringUtils.toString(t));
}
return res;
} else {
// decode request.
// 如果该消息是request
Request req = new Request(id);
// 设置版本
req.setVersion(Version.getProtocolVersion());
// 设置是否是双向请求
req.setTwoWay((flag & FLAG_TWOWAY) != 0);
// 设置是否是事件,该地方问题也在新版本修复
if ((flag & FLAG_EVENT) != 0) {
req.setEvent(Request.HEARTBEAT_EVENT);
}
try {
Object data;
// 反序列化
ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
// 进行解码
if (req.isHeartbeat()) {
data = decodeHeartbeatData(channel, in);
} else if (req.isEvent()) {
data = decodeEventData(channel, in);
} else {
DecodeableRpcInvocation inv;
if (channel.getUrl().getParameter(
Constants.DECODE_IN_IO_THREAD_KEY,
Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
inv = new DecodeableRpcInvocation(channel, req, is, proto);
inv.decode();
} else {
inv = new DecodeableRpcInvocation(channel, req,
new UnsafeByteArrayInputStream(readMessageData(is)), proto);
}
data = inv;
}
// 把body数据设置到response
req.setData(data);
} catch (Throwable t) {
if (log.isWarnEnabled()) {
log.warn("Decode request failed: " + t.getMessage(), t);
}
// bad request
req.setBroken(true);
req.setData(t);
}
return req;
}
}
</code></pre>
<p>该方法是对request和response进行解码,用位运算来进行解码,其中的逻辑跟我在<a href="https://segmentfault.com/a/1190000017467343"> 《dubbo源码解析(十)远程通信——Exchange层》</a>中讲到的编解码器逻辑差不多。</p>
<h5>3.encodeRequestData</h5>
<pre><code class="java">@Override
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
// 输出版本
out.writeUTF(version);
// 输出path
out.writeUTF(inv.getAttachment(Constants.PATH_KEY));
// 输出版本号
out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));
// 输出方法名称
out.writeUTF(inv.getMethodName());
// 输出参数类型
out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));
// 输出参数
Object[] args = inv.getArguments();
if (args != null)
for (int i = 0; i < args.length; i++) {
out.writeObject(encodeInvocationArgument(channel, inv, i));
}
// 输出附加值
out.writeObject(inv.getAttachments());
}
</code></pre>
<p>该方法是对请求数据的编码。</p>
<h5>4.encodeResponseData</h5>
<pre><code class="java">@Override
protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
Result result = (Result) data;
// currently, the version value in Response records the version of Request
boolean attach = Version.isSupportResponseAttatchment(version);
// 获得异常
Throwable th = result.getException();
if (th == null) {
Object ret = result.getValue();
// 根据结果的不同输出不同的值
if (ret == null) {
out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);
} else {
out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);
out.writeObject(ret);
}
} else {
// 如果有异常,则输出异常
out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);
out.writeObject(th);
}
if (attach) {
// returns current version of Response to consumer side.
// 在附加值中加入版本号
result.getAttachments().put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
// 输出版本号
out.writeObject(result.getAttachments());
}
}
</code></pre>
<p>该方法是对响应数据的编码。</p>
<h4>(十二)DubboCountCodec</h4>
<p>该类是对DubboCodec的功能增强,增加了消息长度的限制。</p>
<pre><code class="java">public final class DubboCountCodec implements Codec2 {
private DubboCodec codec = new DubboCodec();
@Override
public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
codec.encode(channel, buffer, msg);
}
@Override
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
// 保存读取的标志
int save = buffer.readerIndex();
MultiMessage result = MultiMessage.create();
do {
Object obj = codec.decode(channel, buffer);
// 粘包拆包
if (Codec2.DecodeResult.NEED_MORE_INPUT == obj) {
buffer.readerIndex(save);
break;
} else {
// 增加消息
result.addMessage(obj);
// 记录消息长度
logMessageLength(obj, buffer.readerIndex() - save);
save = buffer.readerIndex();
}
} while (true);
// 如果结果为空,则返回需要更多的输入
if (result.isEmpty()) {
return Codec2.DecodeResult.NEED_MORE_INPUT;
}
if (result.size() == 1) {
return result.get(0);
}
return result;
}
private void logMessageLength(Object result, int bytes) {
if (bytes <= 0) {
return;
}
// 如果是request类型
if (result instanceof Request) {
try {
// 设置附加值
((RpcInvocation) ((Request) result).getData()).setAttachment(
Constants.INPUT_KEY, String.valueOf(bytes));
} catch (Throwable e) {
/* ignore */
}
} else if (result instanceof Response) {
try {
// 设置附加值 输出的长度
((RpcResult) ((Response) result).getResult()).setAttachment(
Constants.OUTPUT_KEY, String.valueOf(bytes));
} catch (Throwable e) {
/* ignore */
}
}
}
}
</code></pre>
<h4>(十三)TraceFilter</h4>
<p>该过滤器是增强的功能是通道的跟踪,会在通道内把最大的调用次数和现在的调用数量放进去。方便使用telnet来跟踪服务的调用次数等。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 跟踪数量的最大值key
*/
private static final String TRACE_MAX = "trace.max";
/**
* 跟踪的数量
*/
private static final String TRACE_COUNT = "trace.count";
/**
* 通道集合
*/
private static final ConcurrentMap<String, Set<Channel>> tracers = new ConcurrentHashMap<String, Set<Channel>>();
</code></pre>
<h5>2.addTracer</h5>
<pre><code class="java">public static void addTracer(Class<?> type, String method, Channel channel, int max) {
// 设置最大的数量
channel.setAttribute(TRACE_MAX, max);
// 设置当前的数量
channel.setAttribute(TRACE_COUNT, new AtomicInteger());
// 获得key
String key = method != null && method.length() > 0 ? type.getName() + "." + method : type.getName();
// 获得通道集合
Set<Channel> channels = tracers.get(key);
// 如果为空,则新建
if (channels == null) {
tracers.putIfAbsent(key, new ConcurrentHashSet<Channel>());
channels = tracers.get(key);
}
channels.add(channel);
}
</code></pre>
<p>该方法是对某一个通道进行跟踪,把现在的调用数量放到属性里面</p>
<h5>3.removeTracer</h5>
<pre><code class="java">public static void removeTracer(Class<?> type, String method, Channel channel) {
// 移除最大值属性
channel.removeAttribute(TRACE_MAX);
// 移除数量属性
channel.removeAttribute(TRACE_COUNT);
String key = method != null && method.length() > 0 ? type.getName() + "." + method : type.getName();
Set<Channel> channels = tracers.get(key);
if (channels != null) {
// 集合中移除该通道
channels.remove(channel);
}
}
</code></pre>
<p>该方法是移除通道的跟踪。</p>
<h5>4.invoke</h5>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 开始时间
long start = System.currentTimeMillis();
// 调用下一个调用链 获得结果
Result result = invoker.invoke(invocation);
// 调用结束时间
long end = System.currentTimeMillis();
// 如果通道跟踪大小大于0
if (tracers.size() > 0) {
// 服务key
String key = invoker.getInterface().getName() + "." + invocation.getMethodName();
// 获得通道集合
Set<Channel> channels = tracers.get(key);
if (channels == null || channels.isEmpty()) {
key = invoker.getInterface().getName();
channels = tracers.get(key);
}
if (channels != null && !channels.isEmpty()) {
// 遍历通道集合
for (Channel channel : new ArrayList<Channel>(channels)) {
// 如果通道是连接的
if (channel.isConnected()) {
try {
// 获得跟踪的最大数
int max = 1;
Integer m = (Integer) channel.getAttribute(TRACE_MAX);
if (m != null) {
max = (int) m;
}
// 获得跟踪数量
int count = 0;
AtomicInteger c = (AtomicInteger) channel.getAttribute(TRACE_COUNT);
if (c == null) {
c = new AtomicInteger();
channel.setAttribute(TRACE_COUNT, c);
}
count = c.getAndIncrement();
// 如果数量小于最大数量则发送
if (count < max) {
String prompt = channel.getUrl().getParameter(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);
channel.send("\r\n" + RpcContext.getContext().getRemoteAddress() + " -> "
+ invoker.getInterface().getName()
+ "." + invocation.getMethodName()
+ "(" + JSON.toJSONString(invocation.getArguments()) + ")" + " -> " + JSON.toJSONString(result.getValue())
+ "\r\nelapsed: " + (end - start) + " ms."
+ "\r\n\r\n" + prompt);
}
// 如果数量大于等于max - 1,则移除该通道
if (count >= max - 1) {
channels.remove(channel);
}
} catch (Throwable e) {
channels.remove(channel);
logger.warn(e.getMessage(), e);
}
} else {
// 如果未连接,也移除该通道
channels.remove(channel);
}
}
}
}
return result;
}
</code></pre>
<p>该方法是当服务被调用时,进行跟踪或者取消跟踪的处理逻辑,是核心的功能增强逻辑。</p>
<h4>(十四)FutureFilter</h4>
<p>该类是处理异步和同步调用结果的过滤器。</p>
<h5>1.invoke</h5>
<pre><code class="java">@Override
public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
// 是否是异步的调用
final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);
fireInvokeCallback(invoker, invocation);
// need to configure if there's return value before the invocation in order to help invoker to judge if it's
// necessary to return future.
Result result = invoker.invoke(invocation);
if (isAsync) {
// 调用异步处理
asyncCallback(invoker, invocation);
} else {
// 调用同步结果处理
syncCallback(invoker, invocation, result);
}
return result;
}
</code></pre>
<p>该方法中根据是否为异步调用来分别执行asyncCallback和syncCallback方法。</p>
<h5>2.syncCallback</h5>
<pre><code class="java">private void syncCallback(final Invoker<?> invoker, final Invocation invocation, final Result result) {
// 如果有异常
if (result.hasException()) {
// 则调用异常的结果处理
fireThrowCallback(invoker, invocation, result.getException());
} else {
// 调用正常的结果处理
fireReturnCallback(invoker, invocation, result.getValue());
}
}
</code></pre>
<p>该方法是同步调用的返回结果处理,比较简单。</p>
<h5>3.asyncCallback</h5>
<pre><code class="java">private void asyncCallback(final Invoker<?> invoker, final Invocation invocation) {
Future<?> f = RpcContext.getContext().getFuture();
if (f instanceof FutureAdapter) {
ResponseFuture future = ((FutureAdapter<?>) f).getFuture();
// 设置回调
future.setCallback(new ResponseCallback() {
@Override
public void done(Object rpcResult) {
// 如果结果为空,则打印错误日志
if (rpcResult == null) {
logger.error(new IllegalStateException("invalid result value : null, expected " + Result.class.getName()));
return;
}
///must be rpcResult
// 如果不是Result则打印错误日志
if (!(rpcResult instanceof Result)) {
logger.error(new IllegalStateException("invalid result type :" + rpcResult.getClass() + ", expected " + Result.class.getName()));
return;
}
Result result = (Result) rpcResult;
if (result.hasException()) {
// 如果有异常,则调用异常处理方法
fireThrowCallback(invoker, invocation, result.getException());
} else {
// 如果正常的返回结果,则调用正常的处理方法
fireReturnCallback(invoker, invocation, result.getValue());
}
}
@Override
public void caught(Throwable exception) {
fireThrowCallback(invoker, invocation, exception);
}
});
}
}
</code></pre>
<p>该方法是异步调用的结果处理,把异步返回结果的逻辑写在回调函数里面。</p>
<h5>4.fireInvokeCallback</h5>
<pre><code class="java">private void fireInvokeCallback(final Invoker<?> invoker, final Invocation invocation) {
// 获得调用的方法
final Method onInvokeMethod = (Method) StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_INVOKE_METHOD_KEY));
// 获得调用的服务
final Object onInvokeInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_INVOKE_INSTANCE_KEY));
if (onInvokeMethod == null && onInvokeInst == null) {
return;
}
if (onInvokeMethod == null || onInvokeInst == null) {
throw new IllegalStateException("service:" + invoker.getUrl().getServiceKey() + " has a onreturn callback config , but no such " + (onInvokeMethod == null ? "method" : "instance") + " found. url:" + invoker.getUrl());
}
// 如果不可以访问,则设置为可访问
if (!onInvokeMethod.isAccessible()) {
onInvokeMethod.setAccessible(true);
}
// 获得参数数组
Object[] params = invocation.getArguments();
try {
// 调用方法
onInvokeMethod.invoke(onInvokeInst, params);
} catch (InvocationTargetException e) {
fireThrowCallback(invoker, invocation, e.getTargetException());
} catch (Throwable e) {
fireThrowCallback(invoker, invocation, e);
}
}
</code></pre>
<p>该方法是调用方法的执行。</p>
<h5>5.fireReturnCallback</h5>
<pre><code class="java">private void fireReturnCallback(final Invoker<?> invoker, final Invocation invocation, final Object result) {
final Method onReturnMethod = (Method) StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_RETURN_METHOD_KEY));
final Object onReturnInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_RETURN_INSTANCE_KEY));
//not set onreturn callback
if (onReturnMethod == null && onReturnInst == null) {
return;
}
if (onReturnMethod == null || onReturnInst == null) {
throw new IllegalStateException("service:" + invoker.getUrl().getServiceKey() + " has a onreturn callback config , but no such " + (onReturnMethod == null ? "method" : "instance") + " found. url:" + invoker.getUrl());
}
if (!onReturnMethod.isAccessible()) {
onReturnMethod.setAccessible(true);
}
Object[] args = invocation.getArguments();
Object[] params;
// 获得返回结果类型
Class<?>[] rParaTypes = onReturnMethod.getParameterTypes();
// 设置参数和返回结果
if (rParaTypes.length > 1) {
if (rParaTypes.length == 2 && rParaTypes[1].isAssignableFrom(Object[].class)) {
params = new Object[2];
params[0] = result;
params[1] = args;
} else {
params = new Object[args.length + 1];
params[0] = result;
System.arraycopy(args, 0, params, 1, args.length);
}
} else {
params = new Object[]{result};
}
try {
// 调用方法
onReturnMethod.invoke(onReturnInst, params);
} catch (InvocationTargetException e) {
fireThrowCallback(invoker, invocation, e.getTargetException());
} catch (Throwable e) {
fireThrowCallback(invoker, invocation, e);
}
}
</code></pre>
<p>该方法是正常的返回结果的处理。</p>
<h5>6.fireThrowCallback</h5>
<pre><code class="java">private void fireThrowCallback(final Invoker<?> invoker, final Invocation invocation, final Throwable exception) {
final Method onthrowMethod = (Method) StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_THROW_METHOD_KEY));
final Object onthrowInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_THROW_INSTANCE_KEY));
//onthrow callback not configured
if (onthrowMethod == null && onthrowInst == null) {
return;
}
if (onthrowMethod == null || onthrowInst == null) {
throw new IllegalStateException("service:" + invoker.getUrl().getServiceKey() + " has a onthrow callback config , but no such " + (onthrowMethod == null ? "method" : "instance") + " found. url:" + invoker.getUrl());
}
if (!onthrowMethod.isAccessible()) {
onthrowMethod.setAccessible(true);
}
// 获得抛出异常的类型
Class<?>[] rParaTypes = onthrowMethod.getParameterTypes();
if (rParaTypes[0].isAssignableFrom(exception.getClass())) {
try {
Object[] args = invocation.getArguments();
Object[] params;
// 把类型和抛出的异常值放入返回结果
if (rParaTypes.length > 1) {
if (rParaTypes.length == 2 && rParaTypes[1].isAssignableFrom(Object[].class)) {
params = new Object[2];
params[0] = exception;
params[1] = args;
} else {
params = new Object[args.length + 1];
params[0] = exception;
System.arraycopy(args, 0, params, 1, args.length);
}
} else {
params = new Object[]{exception};
}
// 调用下一个调用连
onthrowMethod.invoke(onthrowInst, params);
} catch (Throwable e) {
logger.error(invocation.getMethodName() + ".call back method invoke error . callback method :" + onthrowMethod + ", url:" + invoker.getUrl(), e);
}
} else {
logger.error(invocation.getMethodName() + ".call back method invoke error . callback method :" + onthrowMethod + ", url:" + invoker.getUrl(), exception);
}
}
</code></pre>
<p>该方法是异常抛出时的结果处理。</p>
<h4>(十五)ServerStatusChecker</h4>
<p>该类是对于服务状态的监控设置。</p>
<pre><code class="java">public class ServerStatusChecker implements StatusChecker {
@Override
public Status check() {
// 获得服务集合
Collection<ExchangeServer> servers = DubboProtocol.getDubboProtocol().getServers();
// 如果为空则返回UNKNOWN的状态
if (servers == null || servers.isEmpty()) {
return new Status(Status.Level.UNKNOWN);
}
// 设置状态为ok
Status.Level level = Status.Level.OK;
StringBuilder buf = new StringBuilder();
// 遍历集合
for (ExchangeServer server : servers) {
// 如果服务没有绑定到本地端口
if (!server.isBound()) {
// 状态改为error
level = Status.Level.ERROR;
// 加入服务本地地址
buf.setLength(0);
buf.append(server.getLocalAddress());
break;
}
if (buf.length() > 0) {
buf.append(",");
}
// 如果服务绑定了本地端口,拼接clients数量
buf.append(server.getLocalAddress());
buf.append("(clients:");
buf.append(server.getChannels().size());
buf.append(")");
}
return new Status(level, buf.toString());
}
}
</code></pre>
<h4>(十六)ThreadPoolStatusChecker</h4>
<p>该类是对于线程池的状态进行监控。</p>
<pre><code class="java">@Activate
public class ThreadPoolStatusChecker implements StatusChecker {
@Override
public Status check() {
// 获得数据中心
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
// 获得线程池集合
Map<String, Object> executors = dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY);
StringBuilder msg = new StringBuilder();
// 设置为ok
Status.Level level = Status.Level.OK;
// 遍历线程池集合
for (Map.Entry<String, Object> entry : executors.entrySet()) {
String port = entry.getKey();
ExecutorService executor = (ExecutorService) entry.getValue();
if (executor != null && executor instanceof ThreadPoolExecutor) {
ThreadPoolExecutor tp = (ThreadPoolExecutor) executor;
boolean ok = tp.getActiveCount() < tp.getMaximumPoolSize() - 1;
Status.Level lvl = Status.Level.OK;
// 如果活跃数量超过了最大的线程数量,则设置warn
if (!ok) {
level = Status.Level.WARN;
lvl = Status.Level.WARN;
}
if (msg.length() > 0) {
msg.append(";");
}
// 输出线程池相关信息
msg.append("Pool status:" + lvl
+ ", max:" + tp.getMaximumPoolSize()
+ ", core:" + tp.getCorePoolSize()
+ ", largest:" + tp.getLargestPoolSize()
+ ", active:" + tp.getActiveCount()
+ ", task:" + tp.getTaskCount()
+ ", service port: " + port);
}
}
return msg.length() == 0 ? new Status(Status.Level.UNKNOWN) : new Status(level, msg.toString());
}
}
</code></pre>
<p>逻辑比较简单,我就不赘述了。</p>
<p>关于telnet下的相关实现请感兴趣的朋友直接查看,里面都是对于telnet命令的实现,内容比较独立。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=4NtmIiSG1PMWX9c6he%2FDQA%3D%3D.UhtMZH6orWzSbuJJystlid4F24T6IbYw1yiwXgqVD68pfXXyUMw1MQ%2B6Ob8r3v957sClVokUGZgVqpv9zvpP8gyfgwoH5H%2BwjEqlyoxKixRqj6mgJXoldvBw7hoCERHprMhSssZQmA5xUmoWj03F%2BsMYan8Q2F8qCnjawNyQ8IIMqCVfSZWfOm5%2BmMBBTORn" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于dubbo协议的部分,dubbo协议是官方推荐使用的协议,并且对于telnet命令也做了很好的支持,要看懂这部分的逻辑,必须先对于之前的一些接口设计了解的很清楚。接下来我将开始对rpc模块关于hessian协议部分进行讲解。</p>
Dubbo源码解析(二十三)远程调用——Proxy
https://segmentfault.com/a/1190000017892690
2019-01-15T07:04:40+08:00
2019-01-15T07:04:40+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
8
<h2>远程调用——Proxy</h2>
<blockquote>目标:介绍远程调用代理的设计和实现,介绍dubbo-rpc-api中的各种proxy包的源码。</blockquote>
<h3>前言</h3>
<p>首先声明叫做代理,代理在很多领域都存在,最形象的就是现在朋友圈的微商代理,厂家委托代理帮他们卖东西。这样做厂家对于消费者来说就是透明的,并且代理可以自己加上一些活动或者销售措施,但这并不影响到厂家。这里的厂家就是委托类,而代理就可以抽象为代理类。这样做有两个优点,第一是可以隐藏代理类的实现,第二就是委托类和调用方的解耦,并且能够在不修改委托类原本的逻辑情况下新增一些额外的处理。</p>
<p>代理分为两种,静态代理和动态代理。</p>
<ol>
<li>静态代理:如果代理类在程序运行前就已经存在,那么这种代理就是静态代理。</li>
<li>动态代理:代理类在程序运行时创建的代理方式。动态代理关系由两组静态代理关系组成,这就是动态代理的原理。</li>
</ol>
<p>上述稍微回顾了一下静态代理和动态代理,那么dubbo对于动态代理有两种方法实现,分别是javassist和jdk。Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。我们来看看下面的图:</p>
<p><img src="/img/remote/1460000017892693" alt="dubbo-framework" title="dubbo-framework"></p>
<p>我们能看到左边是消费者的调用链,只有当消费者调用的时候,ProxyFactory才会通过Proxy把接口实现转化为invoker,并且在其他层的调用都使用的是invoker,同样的道理,在服务提供者暴露服务的时候,也只有在最后暴露给消费者的时候才会通过Proxy 将 Invoker 转成接口。</p>
<p>动态代理的底层原理就是字节码技术,dubbo提供了两种方式来实现代理:</p>
<ol>
<li>第一种jdk,jdk动态代理比较简单,它内置在JDK中,因此不依赖第三方jar包,但是功能相对较弱,当调用Proxy 的静态方法创建动态代理类时,类名格式是“$ProxyN”,N代表第 N 次生成的动态代理类,如果重复创建动态代理类会直接返回原先创建的代理类。但是这个以“\$ProxyN”命名的类是继承Proxy类的,并且实现了其所代理的一组接口,这里就出现了它的一个局限性,由于java的类只能单继承,所以JDK动态代理仅支持接口代理。</li>
<li>第二种是Javassist,Javassist是一款Java字节码引擎工具,能够在运行时编译生成class。该方法也是代理的默认方法。</li>
</ol>
<h3>源码分析</h3>
<h4>(一)AbstractProxyFactory</h4>
<p>该类是代理工厂的抽象类,主要处理了一下需要代理的接口,然后把代理getProxy方法抽象出来。</p>
<pre><code class="java">public abstract class AbstractProxyFactory implements ProxyFactory {
@Override
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
return getProxy(invoker, false);
}
@Override
public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException {
Class<?>[] interfaces = null;
// 获得需要代理的接口
String config = invoker.getUrl().getParameter("interfaces");
if (config != null && config.length() > 0) {
// 根据逗号把每个接口分割开
String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
if (types != null && types.length > 0) {
// 创建接口类型数组
interfaces = new Class<?>[types.length + 2];
// 第一个放invoker的服务接口
interfaces[0] = invoker.getInterface();
// 第二个位置放回声测试服务的接口类
interfaces[1] = EchoService.class;
// 其他接口循环放入
for (int i = 0; i < types.length; i++) {
interfaces[i + 1] = ReflectUtils.forName(types[i]);
}
}
}
// 如果接口为空,就是config为空,则是回声测试
if (interfaces == null) {
interfaces = new Class<?>[]{invoker.getInterface(), EchoService.class};
}
// 如果是泛化服务,那么在代理的接口集合中加入泛化服务类型
if (!invoker.getInterface().equals(GenericService.class) && generic) {
int len = interfaces.length;
Class<?>[] temp = interfaces;
interfaces = new Class<?>[len + 1];
System.arraycopy(temp, 0, interfaces, 0, len);
interfaces[len] = GenericService.class;
}
// 获得代理
return getProxy(invoker, interfaces);
}
public abstract <T> T getProxy(Invoker<T> invoker, Class<?>[] types);
}</code></pre>
<p>逻辑比较简单,就是处理了url中携带的interfaces的值。</p>
<h4>(二)AbstractProxyInvoker</h4>
<p>该类实现了Invoker接口,是代理invoker对象的抽象类。</p>
<pre><code class="java">@Override
public Result invoke(Invocation invocation) throws RpcException {
try {
// 调用了抽象方法doInvoke
return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
} catch (InvocationTargetException e) {
return new RpcResult(e.getTargetException());
} catch (Throwable e) {
throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable;</code></pre>
<p>该类最关键的就是这两个方法,一个是invoke方法,调用了抽象方法doInvoke,另一个则是抽象方法。该方法被子类实现。</p>
<h4>(三)InvokerInvocationHandler</h4>
<p>该类实现了InvocationHandler接口,动态代理类都必须要实现InvocationHandler接口,而该类实现的是对于基础方法不适用rpc调用,其他方法使用rpc调用。</p>
<pre><code class="java">public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker<?> invoker;
public InvokerInvocationHandler(Invoker<?> handler) {
this.invoker = handler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获得方法名
String methodName = method.getName();
// 获得参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
// 如果方法参数类型是object类型,则直接反射调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
// 基础方法,不使用 RPC 调用
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
// rpc调用
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}</code></pre>
<h4>(四)StubProxyFactoryWrapper</h4>
<p>该类实现了本地存根的逻辑,关于本地存根的概念和使用在官方文档中都有详细说明。</p>
<blockquote>地址:<a href="https://link.segmentfault.com/?enc=BJ77nSWE0LVKL22kAQ%2F6tw%3D%3D.5JIXnS3aCek%2B%2BjH%2BK5DX8DRGTFM%2BHvSPefo5VZIBwwLyMMO84dR0e%2FAiVADTRywXMTSH%2Fj9kb7FJZF4u5pfERA%3D%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<pre><code class="java">public class StubProxyFactoryWrapper implements ProxyFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(StubProxyFactoryWrapper.class);
/**
* 代理工厂
*/
private final ProxyFactory proxyFactory;
/**
* 协议
*/
private Protocol protocol;
public StubProxyFactoryWrapper(ProxyFactory proxyFactory) {
this.proxyFactory = proxyFactory;
}
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
@Override
public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException {
return proxyFactory.getProxy(invoker, generic);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
// 获得代理类对象
T proxy = proxyFactory.getProxy(invoker);
// 如果不是返回服务调用
if (GenericService.class != invoker.getInterface()) {
// 获得stub的配置
String stub = invoker.getUrl().getParameter(Constants.STUB_KEY, invoker.getUrl().getParameter(Constants.LOCAL_KEY));
// 如果配置不为空
if (ConfigUtils.isNotEmpty(stub)) {
Class<?> serviceType = invoker.getInterface();
if (ConfigUtils.isDefault(stub)) {
// 根据local和stub来生成stub
if (invoker.getUrl().hasParameter(Constants.STUB_KEY)) {
stub = serviceType.getName() + "Stub";
} else {
stub = serviceType.getName() + "Local";
}
}
try {
// 生成stub类
Class<?> stubClass = ReflectUtils.forName(stub);
if (!serviceType.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + serviceType.getName());
}
try {
// 获得构造方法,该构造方法必须是带有代理的对象的参数
Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
// 使用指定的初始化参数创建和初始化构造函数声明类的新实例
proxy = (T) constructor.newInstance(new Object[]{proxy});
//export stub service
URL url = invoker.getUrl();
if (url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT)) {
url = url.addParameter(Constants.STUB_EVENT_METHODS_KEY, StringUtils.join(Wrapper.getWrapper(proxy.getClass()).getDeclaredMethodNames(), ","));
url = url.addParameter(Constants.IS_SERVER_KEY, Boolean.FALSE.toString());
try {
// 暴露stub服务
export(proxy, (Class) invoker.getInterface(), url);
} catch (Exception e) {
LOGGER.error("export a stub service error.", e);
}
}
} catch (NoSuchMethodException e) {
throw new IllegalStateException("No such constructor \"public " + stubClass.getSimpleName() + "(" + serviceType.getName() + ")\" in stub implementation class " + stubClass.getName(), e);
}
} catch (Throwable t) {
LOGGER.error("Failed to create stub implementation class " + stub + " in consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", cause: " + t.getMessage(), t);
// ignore
}
}
}
return proxy;
}
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
return proxyFactory.getInvoker(proxy, type, url);
}
private <T> Exporter<T> export(T instance, Class<T> type, URL url) {
return protocol.export(proxyFactory.getInvoker(instance, type, url));
}</code></pre>
<p>该类里面最重要的就是getProxy方法的实现,在该方法中先根据配置生成加载stub服务类,然后通过构造方法将代理的对象进行包装,最后暴露该服务,然后返回代理类对象。</p>
<h4>(五)JdkProxyFactory</h4>
<p>该类继承了AbstractProxyFactory,是jdk的代理工厂的主要逻辑。</p>
<pre><code class="java">public class JdkProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
// 调用了 Proxy.newProxyInstance直接获得代理类
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
}
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 创建AbstractProxyInvoker对象
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// 反射获得方法
Method method = proxy.getClass().getMethod(methodName, parameterTypes);
// 执行方法
return method.invoke(proxy, arguments);
}
};
}
}</code></pre>
<p>不过逻辑实现比较简单,因为jdk中都封装好了,直接调用Proxy.newProxyInstance方法就可以获得代理类。</p>
<h4>(六)JavassistProxyFactory</h4>
<p>该类是基于Javassist实现的动态代理工厂类。</p>
<pre><code class="java">public class JavassistProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
// 创建代理
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
// 创建Wrapper对象
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// 调用方法
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}</code></pre>
<p>在这里看不出什么具体的实现,感觉看起来跟JdkProxyFactory差不多,下面我将讲解com.alibaba.dubbo.common.bytecode.Proxy类的getProxy方法和com.alibaba.dubbo.common.bytecode.Wrapper类的getWrapper方法。</p>
<h4>(七)Proxy#getProxy()</h4>
<pre><code class="java">public static Proxy getProxy(Class<?>... ics) {
// 获得代理类
return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);
}
/**
* Get proxy.
*
* @param cl class loader.
* @param ics interface class array.
* @return Proxy instance.
*/
public static Proxy getProxy(ClassLoader cl, Class<?>... ics) {
// 最大的代理接口数限制是65535
if (ics.length > 65535)
throw new IllegalArgumentException("interface limit exceeded");
StringBuilder sb = new StringBuilder();
// 遍历代理接口,获取接口的全限定名并以分号分隔连接成字符串
for (int i = 0; i < ics.length; i++) {
// 获得类名
String itf = ics[i].getName();
// 判断是否为接口
if (!ics[i].isInterface())
throw new RuntimeException(itf + " is not a interface.");
Class<?> tmp = null;
try {
// 获得与itf对应的Class对象
tmp = Class.forName(itf, false, cl);
} catch (ClassNotFoundException e) {
}
// 如果通过类名获得的类型跟ics中的类型不一样,则抛出异常
if (tmp != ics[i])
throw new IllegalArgumentException(ics[i] + " is not visible from class loader");
// 拼接类
sb.append(itf).append(';');
}
// use interface class name list as key.
String key = sb.toString();
// get cache by class loader.
Map<String, Object> cache;
synchronized (ProxyCacheMap) {
// 通过类加载器获得缓存
cache = ProxyCacheMap.get(cl);
if (cache == null) {
cache = new HashMap<String, Object>();
ProxyCacheMap.put(cl, cache);
}
}
Proxy proxy = null;
synchronized (cache) {
do {
Object value = cache.get(key);
// 如果缓存中存在,则直接返回代理对象
if (value instanceof Reference<?>) {
proxy = (Proxy) ((Reference<?>) value).get();
if (proxy != null)
return proxy;
}
// 是等待生成的类型,则等待
if (value == PendingGenerationMarker) {
try {
cache.wait();
} catch (InterruptedException e) {
}
} else {
// 否则放入缓存中
cache.put(key, PendingGenerationMarker);
break;
}
}
while (true);
}
// AtomicLong自增生成代理类类名后缀id,防止冲突
long id = PROXY_CLASS_COUNTER.getAndIncrement();
String pkg = null;
ClassGenerator ccp = null, ccm = null;
try {
ccp = ClassGenerator.newInstance(cl);
Set<String> worked = new HashSet<String>();
List<Method> methods = new ArrayList<Method>();
for (int i = 0; i < ics.length; i++) {
// 判断是否为public
if (!Modifier.isPublic(ics[i].getModifiers())) {
// 获得该类的包名
String npkg = ics[i].getPackage().getName();
if (pkg == null) {
pkg = npkg;
} else {
if (!pkg.equals(npkg))
throw new IllegalArgumentException("non-public interfaces from different packages");
}
}
// 把接口加入到ccp的mInterfaces中
ccp.addInterface(ics[i]);
// 遍历每个类的方法
for (Method method : ics[i].getMethods()) {
// 获得方法描述 这个方法描述是自定义:
// 例如:int do(int arg1) => "do(I)I"
// 例如:void do(String arg1,boolean arg2) => "do(Ljava/lang/String;Z)V"
String desc = ReflectUtils.getDesc(method);
if (worked.contains(desc))
continue;
// 如果集合中不存在,则加入该描述
worked.add(desc);
int ix = methods.size();
// 获得方法返回类型
Class<?> rt = method.getReturnType();
// 获得方法参数类型
Class<?>[] pts = method.getParameterTypes();
// 新建一句代码
// 例如Object[] args = new Object[参数数量】
StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
// 每一个参数都生成一句代码
// 例如args[0] = ($w)$1;
// 例如 Object ret = handler.invoke(this, methods[3], args);
for (int j = 0; j < pts.length; j++)
code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");
// 如果方法不是void类型
// 则拼接 return ret;
if (!Void.TYPE.equals(rt))
code.append(" return ").append(asArgument(rt, "ret")).append(";");
methods.add(method);
ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
}
}
if (pkg == null)
pkg = PACKAGE_NAME;
// create ProxyInstance class.
String pcn = pkg + ".proxy" + id;
ccp.setClassName(pcn);
// 添加静态字段Method[] methods
ccp.addField("public static java.lang.reflect.Method[] methods;");
ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
// 添加实例对象InvokerInvocationHandler hanler,添加参数为InvokerInvocationHandler的构造器
ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");
// 添加默认无参构造器
ccp.addDefaultConstructor();
// 使用toClass方法生成对应的字节码
Class<?> clazz = ccp.toClass();
clazz.getField("methods").set(null, methods.toArray(new Method[0]));
// create Proxy class.
// 生成的字节码对象为服务接口的代理对象
String fcn = Proxy.class.getName() + id;
ccm = ClassGenerator.newInstance(cl);
ccm.setClassName(fcn);
ccm.addDefaultConstructor();
ccm.setSuperClass(Proxy.class);
ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
Class<?> pc = ccm.toClass();
proxy = (Proxy) pc.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
// release ClassGenerator
// 重置类构造器
if (ccp != null)
ccp.release();
if (ccm != null)
ccm.release();
synchronized (cache) {
if (proxy == null)
cache.remove(key);
else
cache.put(key, new WeakReference<Proxy>(proxy));
cache.notifyAll();
}
}
return proxy;
}</code></pre>
<p>Proxy是是生成代理对象的工具类,跟JdkProxyFactory中用到的Proxy不是同一个,JdkProxyFactory中的是jdk自带的java.lang.reflect.Proxy。而该Proxy是dubbo基于javassit实现的com.alibaba.dubbo.common.bytecode.Proxy。该方法比较长,可以分开五个步骤来看:</p>
<ol>
<li>遍历代理接口,获取接口的全限定名,并以分号分隔连接成字符串,以此字符串为key,查找缓存map,如果缓存存在,则获取代理对象直接返回。</li>
<li>由一个AtomicLong自增生成代理类类名后缀id,防止冲突</li>
<li>遍历接口中的方法,获取返回类型和参数类型,构建的方法体见注释</li>
<li>创建工具类ClassGenerator实例,添加静态字段Method[] methods,添加实例对象InvokerInvocationHandler hanler,添加参数为InvokerInvocationHandler的构造器,添加无参构造器,然后使用toClass方法生成对应的字节码。</li>
<li>4中生成的字节码对象为服务接口的代理对象,而Proxy类本身是抽象类,需要实现newInstance(InvocationHandler handler)方法,生成Proxy的实现类,其中proxy0即上面生成的服务接口的代理对象。</li>
</ol>
<h4>(八)Wrapper#getWrapper</h4>
<pre><code class="java">public static Wrapper getWrapper(Class<?> c) {
// 判断c是否继承 ClassGenerator.DC.class ,如果是,则拿到父类,避免重复包装
while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.
c = c.getSuperclass();
// 如果类为object类型
if (c == Object.class)
return OBJECT_WRAPPER;
// 如果缓存里面没有该对象,则新建一个wrapper
Wrapper ret = WRAPPER_MAP.get(c);
if (ret == null) {
ret = makeWrapper(c);
WRAPPER_MAP.put(c, ret);
}
return ret;
}
private static Wrapper makeWrapper(Class<?> c) {
// 如果c不是似有类,则抛出异常
if (c.isPrimitive())
throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);
// 获得类名
String name = c.getName();
// 获得类加载器
ClassLoader cl = ClassHelper.getClassLoader(c);
// 设置属性的方法第一行public void setPropertyValue(Object o, String n, Object v){
StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
// 获得属性的方法第一行 public Object getPropertyValue(Object o, String n){
StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
// 执行方法的第一行
StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");
// 添加每个方法中被调用对象的类型转换的代码
c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
Map<String, Class<?>> pts = new HashMap<String, Class<?>>(); // <property name, property types>
Map<String, Method> ms = new LinkedHashMap<String, Method>(); // <method desc, Method instance>
List<String> mns = new ArrayList<String>(); // method names.
List<String> dmns = new ArrayList<String>(); // declaring method names.
// get all public field.
// 遍历每个public的属性,放入setPropertyValue和getPropertyValue方法中
for (Field f : c.getFields()) {
String fn = f.getName();
Class<?> ft = f.getType();
// // 排除有static 和 transient修饰的属性
if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers()))
continue;
c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");
c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");
pts.put(fn, ft);
}
Method[] methods = c.getMethods();
// get all public method.
boolean hasMethod = hasMethods(methods);
// 在invokeMethod方法中添加try的代码
if (hasMethod) {
c3.append(" try{");
}
// 遍历方法
for (Method m : methods) {
// 忽律Object的方法
if (m.getDeclaringClass() == Object.class) //ignore Object's method.
continue;
// 判断方法名和方法参数长度
String mn = m.getName();
c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
// 方法参数长度
int len = m.getParameterTypes().length;
// 判断方法参数长度代码
c3.append(" && ").append(" $3.length == ").append(len);
// 若相同方法名存在多个,增加参数类型数组的比较判断
boolean override = false;
for (Method m2 : methods) {
if (m != m2 && m.getName().equals(m2.getName())) {
override = true;
break;
}
}
if (override) {
if (len > 0) {
for (int l = 0; l < len; l++) {
c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")
.append(m.getParameterTypes()[l].getName()).append("\")");
}
}
}
c3.append(" ) { ");
// 如果返回类型是void,则return null,如果不是,则返回对应参数类型
if (m.getReturnType() == Void.TYPE)
c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
else
c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");
c3.append(" }");
mns.add(mn);
if (m.getDeclaringClass() == c)
dmns.add(mn);
ms.put(ReflectUtils.getDesc(m), m);
}
if (hasMethod) {
c3.append(" } catch(Throwable e) { ");
c3.append(" throw new java.lang.reflect.InvocationTargetException(e); ");
c3.append(" }");
}
c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }");
// 处理get set方法
// deal with get/set method.
Matcher matcher;
for (Map.Entry<String, Method> entry : ms.entrySet()) {
String md = entry.getKey();
Method method = (Method) entry.getValue();
if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
String pn = propertyName(matcher.group(1));
c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
pts.put(pn, method.getReturnType());
} else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
String pn = propertyName(matcher.group(1));
c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
pts.put(pn, method.getReturnType());
} else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
Class<?> pt = method.getParameterTypes()[0];
String pn = propertyName(matcher.group(1));
c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
pts.put(pn, pt);
}
}
c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");
c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");
// make class
long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
ClassGenerator cc = ClassGenerator.newInstance(cl);
cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
cc.setSuperClass(Wrapper.class);
// 增加无参构造器
cc.addDefaultConstructor();
// 添加属性
cc.addField("public static String[] pns;"); // property name array.
cc.addField("public static " + Map.class.getName() + " pts;"); // property type map.
cc.addField("public static String[] mns;"); // all method name array.
cc.addField("public static String[] dmns;"); // declared method name array.
for (int i = 0, len = ms.size(); i < len; i++)
cc.addField("public static Class[] mts" + i + ";");
// 添加属性相关的方法
cc.addMethod("public String[] getPropertyNames(){ return pns; }");
cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
cc.addMethod("public String[] getMethodNames(){ return mns; }");
cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
cc.addMethod(c1.toString());
cc.addMethod(c2.toString());
cc.addMethod(c3.toString());
try {
// 生成字节码
Class<?> wc = cc.toClass();
// setup static field.
// 反射,设置静态变量的值
wc.getField("pts").set(null, pts);
wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
wc.getField("mns").set(null, mns.toArray(new String[0]));
wc.getField("dmns").set(null, dmns.toArray(new String[0]));
int ix = 0;
for (Method m : ms.values())
wc.getField("mts" + ix++).set(null, m.getParameterTypes());
// // 创建对象并且返回
return (Wrapper) wc.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
cc.release();
ms.clear();
mns.clear();
dmns.clear();
}
}</code></pre>
<p>Wrapper是用于创建某个对象的方法调用的包装器,利用字节码技术在调用方法时进行编译相关方法。其中getWrapper就是获得Wrapper 对象,其中关键的是makeWrapper方法,所以我在上面加上了makeWrapper方法的解释,其中就是相关方法的字节码生成过程。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=h8vL61oTrZNfiZansH0Btw%3D%3D.4otvkY9lNQTLj4B%2FUPPTeVUjf6Ae8H8zeBYMiGbUR%2FIBjl9IygAjoGUWXl5F6ABbqJrYd3si8UHqtTRXUeZ7htkN3dIMElrO2Hdgmm35a%2BHm40OQSmZPmNqLFkp%2FD%2FrcVzn6qFuL9ukkbzI8MklMFyVae%2BLMSH2ybrb34L%2B3zCXhly4mJZckPvCB3w1kW%2FwH" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于代理的部分,关键部分在于基于javassist实现的字节码技术来支撑动态代理。接下来我将开始对rpc模块的dubbo-rpc-dubbo关于dubbo协议部分进行讲解。</p>
Dubbo源码解析(二十二)远程调用——Protocol
https://segmentfault.com/a/1190000017854954
2019-01-11T13:08:07+08:00
2019-01-11T13:08:07+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
5
<h2>远程调用——Protocol</h2>
<blockquote>目标:介绍远程调用中协议的设计和实现,介绍dubbo-rpc-api中的各种protocol包的源码,是重点内容。</blockquote>
<h3>前言</h3>
<p>在远程调用中协议是非常重要的一层,看下面这张图:</p>
<p><img src="/img/remote/1460000017854957?w=900&h=674" alt="dubbo-framework" title="dubbo-framework"></p>
<p>该层是在信息交换层之上,分为了并且夹杂在服务暴露和服务引用中间,为了有一个约定的方式进行调用。</p>
<p>dubbo支持不同协议的扩展,比如http、thrift等等,具体的可以参照官方文档。本文讲解的源码大部分是对于公共方法的实现,而具体的服务暴露和服务引用会在各个协议实现中讲到。</p>
<p>下面是该包下面的类图:</p>
<p><img src="/img/remote/1460000017854958?w=1896&h=688" alt="protocol包类图" title="protocol包类图"></p>
<h3>源码分析</h3>
<h4>(一)AbstractProtocol</h4>
<p>该类是协议的抽象类,实现了Protocol接口,其中实现了一些公共的方法,抽象方法在它的子类AbstractProxyProtocol中定义。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 服务暴露者集合
*/
protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<String, Exporter<?>>();
/**
* 服务引用者集合
*/
//TODO SOFEREFENCE
protected final Set<Invoker<?>> invokers = new ConcurrentHashSet<Invoker<?>>();</code></pre>
<h5>2.serviceKey</h5>
<pre><code class="java">protected static String serviceKey(URL url) {
// 获得绑定的端口号
int port = url.getParameter(Constants.BIND_PORT_KEY, url.getPort());
return serviceKey(port, url.getPath(), url.getParameter(Constants.VERSION_KEY),
url.getParameter(Constants.GROUP_KEY));
}
protected static String serviceKey(int port, String serviceName, String serviceVersion, String serviceGroup) {
return ProtocolUtils.serviceKey(port, serviceName, serviceVersion, serviceGroup);
}</code></pre>
<p>该方法是为了得到服务key group+"/"+serviceName+":"+serviceVersion+":"+port</p>
<h5>3.destroy</h5>
<pre><code class="java">@Override
public void destroy() {
// 遍历服务引用实体
for (Invoker<?> invoker : invokers) {
if (invoker != null) {
// 从集合中移除
invokers.remove(invoker);
try {
if (logger.isInfoEnabled()) {
logger.info("Destroy reference: " + invoker.getUrl());
}
// 销毁
invoker.destroy();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
// 遍历服务暴露者
for (String key : new ArrayList<String>(exporterMap.keySet())) {
// 从集合中移除
Exporter<?> exporter = exporterMap.remove(key);
if (exporter != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Unexport service: " + exporter.getInvoker().getUrl());
}
// 取消暴露
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
}</code></pre>
<p>该方法是对invoker和exporter的销毁。</p>
<h4>(二)AbstractProxyProtocol</h4>
<p>该类继承了AbstractProtocol类,其中利用了代理工厂对AbstractProtocol中的两个集合进行了填充,并且对异常做了处理。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* rpc的异常类集合
*/
private final List<Class<?>> rpcExceptions = new CopyOnWriteArrayList<Class<?>>();
/**
* 代理工厂
*/
private ProxyFactory proxyFactory;</code></pre>
<h5>2.export</h5>
<pre><code class="java">@Override
@SuppressWarnings("unchecked")
public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
// 获得uri
final String uri = serviceKey(invoker.getUrl());
// 获得服务暴露者
Exporter<T> exporter = (Exporter<T>) exporterMap.get(uri);
if (exporter != null) {
return exporter;
}
// 新建一个线程
final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
exporter = new AbstractExporter<T>(invoker) {
/**
* 取消暴露
*/
@Override
public void unexport() {
super.unexport();
// 移除该key对应的服务暴露者
exporterMap.remove(uri);
if (runnable != null) {
try {
// 启动线程
runnable.run();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
};
// 加入集合
exporterMap.put(uri, exporter);
return exporter;
}</code></pre>
<p>其中分为两个步骤,创建一个exporter,放入到集合汇中。在创建exporter时对unexport方法进行了重写。</p>
<h5>3.refer</h5>
<pre><code class="java">@Override
public <T> Invoker<T> refer(final Class<T> type, final URL url) throws RpcException {
// 通过代理获得实体域
final Invoker<T> target = proxyFactory.getInvoker(doRefer(type, url), type, url);
Invoker<T> invoker = new AbstractInvoker<T>(type, url) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
try {
// 获得调用结果
Result result = target.invoke(invocation);
Throwable e = result.getException();
// 如果抛出异常,则抛出相应异常
if (e != null) {
for (Class<?> rpcException : rpcExceptions) {
if (rpcException.isAssignableFrom(e.getClass())) {
throw getRpcException(type, url, invocation, e);
}
}
}
return result;
} catch (RpcException e) {
// 抛出未知异常
if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
e.setCode(getErrorCode(e.getCause()));
}
throw e;
} catch (Throwable e) {
throw getRpcException(type, url, invocation, e);
}
}
};
// 加入集合
invokers.add(invoker);
return invoker;
}</code></pre>
<p>该方法是服务引用,先从代理工厂中获得Invoker对象target,然后创建了真实的invoker在重写方法中调用代理的方法,最后加入到集合。</p>
<pre><code class="java">protected abstract <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException;
protected abstract <T> T doRefer(Class<T> type, URL url) throws RpcException;</code></pre>
<p>可以看到其中抽象了服务引用和暴露的方法,让各类协议各自实现。</p>
<h4>(三)AbstractInvoker</h4>
<p>该类是invoker的抽象方法,因为协议被夹在服务引用和服务暴露中间,无论什么协议都有一些通用的Invoker和exporter的方法实现,而该类就是实现了Invoker的公共方法,而把doInvoke抽象出来,让子类只关注这个方法。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 服务类型
*/
private final Class<T> type;
/**
* url对象
*/
private final URL url;
/**
* 附加值
*/
private final Map<String, String> attachment;
/**
* 是否可用
*/
private volatile boolean available = true;
/**
* 是否销毁
*/
private AtomicBoolean destroyed = new AtomicBoolean(false);</code></pre>
<h5>2.convertAttachment</h5>
<pre><code class="java">private static Map<String, String> convertAttachment(URL url, String[] keys) {
if (keys == null || keys.length == 0) {
return null;
}
Map<String, String> attachment = new HashMap<String, String>();
// 遍历key,把值放入附加值集合中
for (String key : keys) {
String value = url.getParameter(key);
if (value != null && value.length() > 0) {
attachment.put(key, value);
}
}
return attachment;
}
</code></pre>
<p>该方法是转化为附加值,把url中的值转化为服务调用invoker的附加值。</p>
<h5>3.invoke</h5>
<pre><code class="java">@Override
public Result invoke(Invocation inv) throws RpcException {
// if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceed
// 如果服务引用销毁,则打印告警日志,但是通过
if (destroyed.get()) {
logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
+ ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
}
RpcInvocation invocation = (RpcInvocation) inv;
// 会话域中加入该调用链
invocation.setInvoker(this);
// 把附加值放入会话域
if (attachment != null && attachment.size() > 0) {
invocation.addAttachmentsIfAbsent(attachment);
}
// 把上下文的附加值放入会话域
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
/**
* invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
* because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
* by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
* a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
*/
invocation.addAttachments(contextAttachments);
}
// 如果开启的是异步调用,则把该设置也放入附加值
if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) {
invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
}
// 加入编号
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
try {
// 执行调用链
return doInvoke(invocation);
} catch (InvocationTargetException e) { // biz exception
Throwable te = e.getTargetException();
if (te == null) {
return new RpcResult(e);
} else {
if (te instanceof RpcException) {
((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
}
return new RpcResult(te);
}
} catch (RpcException e) {
if (e.isBiz()) {
return new RpcResult(e);
} else {
throw e;
}
} catch (Throwable e) {
return new RpcResult(e);
}
}
</code></pre>
<p>该方法做了一些公共的操作,比如服务引用销毁的检测,加入附加值,加入调用链实体域到会话域中等。然后执行了doInvoke抽象方法。各协议自己去实现。</p>
<h4>(四)AbstractExporter</h4>
<p>该类和AbstractInvoker类似,也是在服务暴露中实现了一些公共方法。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 实体域
*/
private final Invoker<T> invoker;
/**
* 是否取消暴露服务
*/
private volatile boolean unexported = false;
</code></pre>
<h5>2.unexport</h5>
<pre><code class="java">@Override
public void unexport() {
// 如果已经消取消暴露,则之间返回
if (unexported) {
return;
}
// 设置为true
unexported = true;
// 销毁该实体域
getInvoker().destroy();
}
</code></pre>
<h4>(五)InvokerWrapper</h4>
<p>该类是Invoker的包装类,其中用到类装饰模式,不过并没有实现实际的功能增强。</p>
<pre><code class="java">public class InvokerWrapper<T> implements Invoker<T> {
/**
* invoker对象
*/
private final Invoker<T> invoker;
private final URL url;
public InvokerWrapper(Invoker<T> invoker, URL url) {
this.invoker = invoker;
this.url = url;
}
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return url;
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
@Override
public void destroy() {
invoker.destroy();
}
}
</code></pre>
<h4>(六)ProtocolFilterWrapper</h4>
<p>该类实现了Protocol接口,其中也用到了装饰模式,是对Protocol的装饰,是在服务引用和暴露的方法上加上了过滤器功能。</p>
<h5>1.buildInvokerChain</h5>
<pre><code class="java">private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
// 获得过滤器的所有扩展实现类实例集合
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
// 从最后一个过滤器开始循环,创建一个带有过滤器链的invoker对象
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
// 记录last的invoker
final Invoker<T> next = last;
// 新建last
last = new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
/**
* 关键在这里,调用下一个filter代表的invoker,把每一个过滤器串起来
* @param invocation
* @return
* @throws RpcException
*/
@Override
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
</code></pre>
<p>该方法就是创建带 Filter 链的 Invoker 对象。倒序的把每一个过滤器串连起来,形成一个invoker。</p>
<h5>2.export</h5>
<pre><code class="java">@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 如果是注册中心,则直接暴露服务
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
// 服务提供侧暴露服务
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
</code></pre>
<p>该方法是在服务暴露上做了过滤器链的增强,也就是加上了过滤器。</p>
<h5>3.refer</h5>
<pre><code class="java">@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 如果是注册中心,则直接引用
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
// 消费者侧引用服务
return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
</code></pre>
<p>该方法是在服务引用上做了过滤器链的增强,也就是加上了过滤器。</p>
<h4>(七)ProtocolListenerWrapper</h4>
<p>该类也实现了Protocol,也是装饰了Protocol接口,但是它是在服务引用和暴露过程中加上了监听器的功能。</p>
<h5>1.export</h5>
<pre><code class="java">@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 如果是注册中心,则暴露该invoker
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
// 创建一个暴露者监听器包装类对象
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}
</code></pre>
<p>该方法是在服务暴露上做了监听器功能的增强,也就是加上了监听器。</p>
<h5>2.refer</h5>
<pre><code class="java">@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 如果是注册中心。则直接引用服务
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
// 创建引用服务监听器包装类对象
return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
Collections.unmodifiableList(
ExtensionLoader.getExtensionLoader(InvokerListener.class)
.getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
}
</code></pre>
<p>该方法是在服务引用上做了监听器功能的增强,也就是加上了监听器。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=8CMBdNLYN4a92IKB7i7v4w%3D%3D.3%2Bb4eTjYroLfYOS%2FLPxR9COwBIUOYFBqoY0tzhLAZYKF%2BCwXsIvf0NZcgh8JuDBQNORqdlHskG268zF4saZYBowkkQ5qeuDo9CxV4yDrygqIVbYOzmb03p8eIyF20K%2BCsrneEeMALSzsjZKZv0xLh3uUi2vn7lwyRLZh028YZ7YrrafHSkly1iGuwkrvKn4y" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用中关于协议的部分,其实就是讲了一些公共的方法,并且把关键方法抽象出来让子类实现,具体的方法实现都在各个协议中自己实现。接下来我将开始对rpc模块的代理进行讲解。</p>
Dubbo源码解析(二十一)远程调用——Listener
https://segmentfault.com/a/1190000017827073
2019-01-09T13:17:20+08:00
2019-01-09T13:17:20+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
7
<h2>远程调用——Listener</h2>
<blockquote>目标:介绍dubbo-rpc-api中的各种listener监听器的实现逻辑,内容略少,随便撇两眼,不是重点。</blockquote>
<h3>前言</h3>
<p>本文介绍监听器的相关逻辑。在服务引用和服务发现中监听器处于的位置请看下面的图:</p>
<ol><li>服务暴露:</li></ol>
<p><img src="/img/remote/1460000017827076" alt="dubbo-export" title="dubbo-export"></p>
<ol><li>服务引用:</li></ol>
<p><img src="/img/remote/1460000017827077" alt="dubbo-refer" title="dubbo-refer"></p>
<p>这两个监听器所做的工作不是很多,来看看源码理解一下。</p>
<h3>源码分析</h3>
<h4>(一)ListenerInvokerWrapper</h4>
<p>该类实现了Invoker,是服务引用监听器的包装类。</p>
<h4>1.属性</h4>
<pre><code class="java">/**
* invoker对象
*/
private final Invoker<T> invoker;
/**
* 监听器集合
*/
private final List<InvokerListener> listeners;</code></pre>
<p>用到了装饰模式,其中很多实现方法直接调用了invoker的方法。</p>
<h4>2.构造方法</h4>
<pre><code class="java">public ListenerInvokerWrapper(Invoker<T> invoker, List<InvokerListener> listeners) {
// 如果invoker为空则抛出异常
if (invoker == null) {
throw new IllegalArgumentException("invoker == null");
}
this.invoker = invoker;
this.listeners = listeners;
if (listeners != null && !listeners.isEmpty()) {
// 遍历监听器
for (InvokerListener listener : listeners) {
if (listener != null) {
try {
// 调用在服务引用的时候进行监听
listener.referred(invoker);
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
}
}
}</code></pre>
<p>构造方法中直接调用了监听器的服务引用。</p>
<h5>3.destroy</h5>
<pre><code class="java">@Override
public void destroy() {
try {
// 销毁invoker
invoker.destroy();
} finally {
// 销毁所有监听的实体域
if (listeners != null && !listeners.isEmpty()) {
for (InvokerListener listener : listeners) {
if (listener != null) {
try {
listener.destroyed(invoker);
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
}
}
}
}</code></pre>
<p>该方法是把服务引用的监听器销毁。</p>
<h4>(二)InvokerListenerAdapter</h4>
<pre><code class="java">public abstract class InvokerListenerAdapter implements InvokerListener {
/**
* 引用服务
* @param invoker
* @throws RpcException
*/
@Override
public void referred(Invoker<?> invoker) throws RpcException {
}
/**
* 销毁
* @param invoker
*/
@Override
public void destroyed(Invoker<?> invoker) {
}
}</code></pre>
<p>该类是服务引用监听器的适配类,没有做实际的操作。</p>
<h4>(三)DeprecatedInvokerListener</h4>
<pre><code class="java">@Activate(Constants.DEPRECATED_KEY)
public class DeprecatedInvokerListener extends InvokerListenerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(DeprecatedInvokerListener.class);
@Override
public void referred(Invoker<?> invoker) throws RpcException {
// 当该引用的服务被废弃时,打印错误日志
if (invoker.getUrl().getParameter(Constants.DEPRECATED_KEY, false)) {
LOGGER.error("The service " + invoker.getInterface().getName() + " is DEPRECATED! Declare from " + invoker.getUrl());
}
}
}</code></pre>
<p>该类是当调用废弃的服务时候打印错误日志。</p>
<h4>(四)ListenerExporterWrapper</h4>
<p>该类是服务暴露监听器包装类。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 服务暴露者
*/
private final Exporter<T> exporter;
/**
* 服务暴露监听者集合
*/
private final List<ExporterListener> listeners;</code></pre>
<p>用到了装饰模式,其中很多实现方法直接调用了exporter的方法。</p>
<h5>2.构造方法</h5>
<pre><code class="java">public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {
if (exporter == null) {
throw new IllegalArgumentException("exporter == null");
}
this.exporter = exporter;
this.listeners = listeners;
if (listeners != null && !listeners.isEmpty()) {
RuntimeException exception = null;
// 遍历服务暴露监听集合
for (ExporterListener listener : listeners) {
if (listener != null) {
try {
// 暴露服务监听
listener.exported(this);
} catch (RuntimeException t) {
logger.error(t.getMessage(), t);
exception = t;
}
}
}
if (exception != null) {
throw exception;
}
}
}</code></pre>
<p>该方法中对于每个服务暴露进行监听。</p>
<h5>3.unexport</h5>
<pre><code class="java">@Override
public void unexport() {
try {
// 取消暴露
exporter.unexport();
} finally {
if (listeners != null && !listeners.isEmpty()) {
RuntimeException exception = null;
// 遍历监听集合
for (ExporterListener listener : listeners) {
if (listener != null) {
try {
// 监听取消暴露
listener.unexported(this);
} catch (RuntimeException t) {
logger.error(t.getMessage(), t);
exception = t;
}
}
}
if (exception != null) {
throw exception;
}
}
}
}</code></pre>
<p>该方法是对每个取消服务暴露的监听。</p>
<h4>(五)ExporterListenerAdapter</h4>
<pre><code class="java">public abstract class ExporterListenerAdapter implements ExporterListener {
/**
* 暴露服务
* @param exporter
* @throws RpcException
*/
@Override
public void exported(Exporter<?> exporter) throws RpcException {
}
/**
* 取消暴露服务
* @param exporter
* @throws RpcException
*/
@Override
public void unexported(Exporter<?> exporter) throws RpcException {
}
}</code></pre>
<p>该类是服务暴露监听器的适配类,没有做实际的操作。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=xOVWURGbmI6ebBFgSekDbA%3D%3D.unnvdzYptdGnrgSJmJwAp7hSPt9R%2BCN1RCqiCg3zdVLI8m2HEiaV5cB%2BvV0uI2OR8nz29sYSINcxNgj7oQ3q%2BCEtuDTjCdX37fPIz8PrC1%2FA6Pbs0IbLtvy%2FDn7sRER%2BWTyCa%2F%2B%2FyuAcFqup9aZfIGPm0YQKdAsX6OLB5DDpEXDSp6V0pjJw0DkLkmt5LWn3" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了在服务引用和服务暴露中的各种listener监听器,其中内容很少。接下来我将开始对rpc模块的协议protocol进行讲解。</p>
Dubbo源码解析(二十)远程调用——Filter
https://segmentfault.com/a/1190000017815616
2019-01-08T21:36:52+08:00
2019-01-08T21:36:52+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
9
<h2>远程调用——Filter</h2>
<blockquote>目标:介绍dubbo-rpc-api中的各种filter过滤器的实现逻辑。</blockquote>
<h3>前言</h3>
<p>本文会介绍在dubbo中的过滤器,先来看看下面的图:</p>
<p><img src="/img/remote/1460000017815619" alt="dubbo-framework" title="dubbo-framework"></p>
<p>可以看到红色圈圈不服,在服务发现和服务引用中都会进行一些过滤器过滤。具体有哪些过滤器,就看下面的介绍。</p>
<h3>源码分析</h3>
<h4>(一)AccessLogFilter</h4>
<p>该过滤器是对记录日志的过滤器,它所做的工作就是把引用服务或者暴露服务的调用链信息写入到文件中。日志消息先被放入日志集合,然后加入到日志队列,然后被放入到写入文件到任务中,最后进入文件。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(AccessLogFilter.class);
/**
* 日志访问名称,默认的日志访问名称
*/
private static final String ACCESS_LOG_KEY = "dubbo.accesslog";
/**
* 日期格式
*/
private static final String FILE_DATE_FORMAT = "yyyyMMdd";
private static final String MESSAGE_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* 日志队列大小
*/
private static final int LOG_MAX_BUFFER = 5000;
/**
* 日志输出的频率
*/
private static final long LOG_OUTPUT_INTERVAL = 5000;
/**
* 日志队列 key为访问日志的名称,value为该日志名称对应的日志集合
*/
private final ConcurrentMap<String, Set<String>> logQueue = new ConcurrentHashMap<String, Set<String>>();
/**
* 日志线程池
*/
private final ScheduledExecutorService logScheduled = Executors.newScheduledThreadPool(2, new NamedThreadFactory("Dubbo-Access-Log", true));
/**
* 日志记录任务
*/
private volatile ScheduledFuture<?> logFuture = null;</code></pre>
<p>按照我上面讲到日志流向,日志先进入到是日志队列中的日志集合,再进入logQueue,在进入logFuture,最后落地到文件。</p>
<h5>2.init</h5>
<pre><code class="java">private void init() {
// synchronized是一个重操作消耗性能,所有加上判空
if (logFuture == null) {
synchronized (logScheduled) {
// 为了不重复初始化
if (logFuture == null) {
// 创建日志记录任务
logFuture = logScheduled.scheduleWithFixedDelay(new LogTask(), LOG_OUTPUT_INTERVAL, LOG_OUTPUT_INTERVAL, TimeUnit.MILLISECONDS);
}
}
}
}</code></pre>
<p>该方法是初始化方法,就创建了日志记录任务。</p>
<h5>3.log</h5>
<pre><code class="java">private void log(String accesslog, String logmessage) {
init();
Set<String> logSet = logQueue.get(accesslog);
if (logSet == null) {
logQueue.putIfAbsent(accesslog, new ConcurrentHashSet<String>());
logSet = logQueue.get(accesslog);
}
if (logSet.size() < LOG_MAX_BUFFER) {
logSet.add(logmessage);
}
}</code></pre>
<p>该方法是增加日志信息到日志集合中。</p>
<h5>4.invoke</h5>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
try {
// 获得日志名称
String accesslog = invoker.getUrl().getParameter(Constants.ACCESS_LOG_KEY);
if (ConfigUtils.isNotEmpty(accesslog)) {
// 获得rpc上下文
RpcContext context = RpcContext.getContext();
// 获得调用的接口名称
String serviceName = invoker.getInterface().getName();
// 获得版本号
String version = invoker.getUrl().getParameter(Constants.VERSION_KEY);
// 获得组,是消费者侧还是生产者侧
String group = invoker.getUrl().getParameter(Constants.GROUP_KEY);
StringBuilder sn = new StringBuilder();
sn.append("[").append(new SimpleDateFormat(MESSAGE_DATE_FORMAT).format(new Date())).append("] ").append(context.getRemoteHost()).append(":").append(context.getRemotePort())
.append(" -> ").append(context.getLocalHost()).append(":").append(context.getLocalPort())
.append(" - ");
// 拼接组
if (null != group && group.length() > 0) {
sn.append(group).append("/");
}
// 拼接服务名称
sn.append(serviceName);
// 拼接版本号
if (null != version && version.length() > 0) {
sn.append(":").append(version);
}
sn.append(" ");
// 拼接方法名
sn.append(inv.getMethodName());
sn.append("(");
// 拼接参数类型
Class<?>[] types = inv.getParameterTypes();
// 拼接参数类型
if (types != null && types.length > 0) {
boolean first = true;
for (Class<?> type : types) {
if (first) {
first = false;
} else {
sn.append(",");
}
sn.append(type.getName());
}
}
sn.append(") ");
// 拼接参数
Object[] args = inv.getArguments();
if (args != null && args.length > 0) {
sn.append(JSON.toJSONString(args));
}
String msg = sn.toString();
// 如果用默认的日志访问名称
if (ConfigUtils.isDefault(accesslog)) {
LoggerFactory.getLogger(ACCESS_LOG_KEY + "." + invoker.getInterface().getName()).info(msg);
} else {
// 把日志加入集合
log(accesslog, msg);
}
}
} catch (Throwable t) {
logger.warn("Exception in AcessLogFilter of service(" + invoker + " -> " + inv + ")", t);
}
// 调用下一个调用链
return invoker.invoke(inv);
}</code></pre>
<p>该方法是最重要的方法,从拼接了日志信息,把日志加入到集合,并且调用下一个调用链。</p>
<h5>4.LogTask</h5>
<pre><code class="java">private class LogTask implements Runnable {
@Override
public void run() {
try {
if (logQueue != null && logQueue.size() > 0) {
// 遍历日志队列
for (Map.Entry<String, Set<String>> entry : logQueue.entrySet()) {
try {
// 获得日志名称
String accesslog = entry.getKey();
// 获得日志集合
Set<String> logSet = entry.getValue();
// 如果文件不存在则创建文件
File file = new File(accesslog);
File dir = file.getParentFile();
if (null != dir && !dir.exists()) {
dir.mkdirs();
}
if (logger.isDebugEnabled()) {
logger.debug("Append log to " + accesslog);
}
if (file.exists()) {
// 获得现在的时间
String now = new SimpleDateFormat(FILE_DATE_FORMAT).format(new Date());
// 获得文件最后一次修改的时间
String last = new SimpleDateFormat(FILE_DATE_FORMAT).format(new Date(file.lastModified()));
// 如果文件最后一次修改的时间不等于现在的时间
if (!now.equals(last)) {
// 获得重新生成文件名称
File archive = new File(file.getAbsolutePath() + "." + last);
// 因为都是file的绝对路径,所以没有进行移动文件,而是修改文件名
file.renameTo(archive);
}
}
// 把日志集合中的日志写入到文件
FileWriter writer = new FileWriter(file, true);
try {
for (Iterator<String> iterator = logSet.iterator();
iterator.hasNext();
iterator.remove()) {
writer.write(iterator.next());
writer.write("\r\n");
}
writer.flush();
} finally {
writer.close();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}</code></pre>
<p>该内部类实现了Runnable,是把日志消息落地到文件到线程。</p>
<h4>(二)ActiveLimitFilter</h4>
<p>该类时对于每个服务的每个方法的最大可并行调用数量限制的过滤器,它是在服务消费者侧的过滤。</p>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获得url对象
URL url = invoker.getUrl();
// 获得方法名称
String methodName = invocation.getMethodName();
// 获得并发调用数(单个服务的单个方法),默认为0
int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
// 通过方法名来获得对应的状态
RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
if (max > 0) {
// 获得该方法调用的超时次数
long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
// 获得系统时间
long start = System.currentTimeMillis();
long remain = timeout;
// 获得该方法的调用数量
int active = count.getActive();
// 如果活跃数量大于等于最大的并发调用数量
if (active >= max) {
synchronized (count) {
// 当活跃数量大于等于最大的并发调用数量时一直循环
while ((active = count.getActive()) >= max) {
try {
// 等待超时时间
count.wait(remain);
} catch (InterruptedException e) {
}
// 获得累计时间
long elapsed = System.currentTimeMillis() - start;
remain = timeout - elapsed;
// 如果累计时间大于超时时间,则抛出异常
if (remain <= 0) {
throw new RpcException("Waiting concurrent invoke timeout in client-side for service: "
+ invoker.getInterface().getName() + ", method: "
+ invocation.getMethodName() + ", elapsed: " + elapsed
+ ", timeout: " + timeout + ". concurrent invokes: " + active
+ ". max concurrent invoke limit: " + max);
}
}
}
}
}
try {
// 获得系统时间作为开始时间
long begin = System.currentTimeMillis();
// 开始计数
RpcStatus.beginCount(url, methodName);
try {
// 调用后面的调用链,如果没有抛出异常,则算成功
Result result = invoker.invoke(invocation);
// 结束计数,记录时间
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
return result;
} catch (RuntimeException t) {
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
throw t;
}
} finally {
if (max > 0) {
synchronized (count) {
// 唤醒count
count.notify();
}
}
}
}</code></pre>
<p>该类只有这一个方法。该过滤器是用来限制调用数量,先进行调用数量的检测,如果没有到达最大的调用数量,则先调用后面的调用链,如果在后面的调用链失败,则记录相关时间,如果成功也记录相关时间和调用次数。</p>
<h4>(三)ClassLoaderFilter</h4>
<p>该过滤器是做类加载器切换的。</p>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获得当前的类加载器
ClassLoader ocl = Thread.currentThread().getContextClassLoader();
// 设置invoker携带的服务的类加载器
Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader());
try {
// 调用下面的调用链
return invoker.invoke(invocation);
} finally {
// 最后切换回原来的类加载器
Thread.currentThread().setContextClassLoader(ocl);
}
}</code></pre>
<p>可以看到先切换成当前的线程锁携带的类加载器,然后调用结束后,再切换回原先的类加载器。</p>
<h4>(四)CompatibleFilter</h4>
<p>该过滤器是做兼容性的过滤器。</p>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 调用下一个调用链
Result result = invoker.invoke(invocation);
// 如果方法前面没有$或者结果没有异常
if (!invocation.getMethodName().startsWith("$") && !result.hasException()) {
Object value = result.getValue();
if (value != null) {
try {
// 获得方法
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
// 获得返回的数据类型
Class<?> type = method.getReturnType();
Object newValue;
// 序列化方法
String serialization = invoker.getUrl().getParameter(Constants.SERIALIZATION_KEY);
// 如果是json或者fastjson形式
if ("json".equals(serialization)
|| "fastjson".equals(serialization)) {
// 获得方法的泛型返回值类型
Type gtype = method.getGenericReturnType();
// 把数据结果进行类型转化
newValue = PojoUtils.realize(value, type, gtype);
// 如果value不是type类型
} else if (!type.isInstance(value)) {
// 如果是pojo,则,转化为type类型,如果不是,则进行兼容类型转化。
newValue = PojoUtils.isPojo(type)
? PojoUtils.realize(value, type)
: CompatibleTypeUtils.compatibleTypeConvert(value, type);
} else {
newValue = value;
}
// 重新设置RpcResult的result
if (newValue != value) {
result = new RpcResult(newValue);
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
return result;
}</code></pre>
<p>可以看到对于调用链的返回结果,如果返回值类型和返回值不一样的时候,就需要做兼容类型的转化。重新把结果放入RpcResult,返回。</p>
<h4>(五)ConsumerContextFilter</h4>
<p>该过滤器做的是在当前的RpcContext中记录本地调用的一次状态信息。</p>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 设置rpc上下文
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
.setLocalAddress(NetUtils.getLocalHost(), 0)
.setRemoteAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
// 如果该会话域是rpc会话域
if (invocation instanceof RpcInvocation) {
// 设置实体域
((RpcInvocation) invocation).setInvoker(invoker);
}
try {
// 调用下个调用链
RpcResult result = (RpcResult) invoker.invoke(invocation);
// 设置附加值
RpcContext.getServerContext().setAttachments(result.getAttachments());
return result;
} finally {
// 情况附加值
RpcContext.getContext().clearAttachments();
}
}</code></pre>
<p>可以看到RpcContext记录了一次调用状态信息,然后先调用后面的调用链,再回来把附加值设置到RpcContext中。然后返回RpcContext,再清空,这样是因为后面的调用链中的附加值对前面的调用链是不可见的。</p>
<h4>(六)ContextFilter</h4>
<p>该过滤器做的是初始化rpc上下文。</p>
<pre><code class="java"> @Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获得会话域的附加值
Map<String, String> attachments = invocation.getAttachments();
// 删除异步属性以避免传递给以下调用链
if (attachments != null) {
attachments = new HashMap<String, String>(attachments);
attachments.remove(Constants.PATH_KEY);
attachments.remove(Constants.GROUP_KEY);
attachments.remove(Constants.VERSION_KEY);
attachments.remove(Constants.DUBBO_VERSION_KEY);
attachments.remove(Constants.TOKEN_KEY);
attachments.remove(Constants.TIMEOUT_KEY);
attachments.remove(Constants.ASYNC_KEY);// Remove async property to avoid being passed to the following invoke chain.
}
// 在rpc上下文添加上一个调用链的信息
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
// .setAttachments(attachments) // merged from dubbox
.setLocalAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
// mreged from dubbox
// we may already added some attachments into RpcContext before this filter (e.g. in rest protocol)
if (attachments != null) {
// 把会话域中的附加值全部加入RpcContext中
if (RpcContext.getContext().getAttachments() != null) {
RpcContext.getContext().getAttachments().putAll(attachments);
} else {
RpcContext.getContext().setAttachments(attachments);
}
}
// 如果会话域属于rpc的会话域,则设置实体域
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(invoker);
}
try {
// 调用下一个调用链
RpcResult result = (RpcResult) invoker.invoke(invocation);
// pass attachments to result 把附加值加入到RpcResult
result.addAttachments(RpcContext.getServerContext().getAttachments());
return result;
} finally {
// 移除本地的上下文
RpcContext.removeContext();
// 清空附加值
RpcContext.getServerContext().clearAttachments();
}
}</code></pre>
<p>在<a href="https://segmentfault.com/a/1190000017787521">《 dubbo源码解析(十九)远程调用——开篇》</a>中我已经介绍了RpcContext的作用,角色。该过滤器就是做了初始化RpcContext的作用。</p>
<h4>(七)DeprecatedFilter</h4>
<p>该过滤器的作用是调用了废弃的方法时打印错误日志。</p>
<pre><code class="java">private static final Logger LOGGER = LoggerFactory.getLogger(DeprecatedFilter.class);
/**
* 日志集合
*/
private static final Set<String> logged = new ConcurrentHashSet<String>();
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获得key 服务+方法
String key = invoker.getInterface().getName() + "." + invocation.getMethodName();
// 如果集合中没有该key
if (!logged.contains(key)) {
// 则加入集合
logged.add(key);
// 如果该服务方法是废弃的,则打印错误日志
if (invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.DEPRECATED_KEY, false)) {
LOGGER.error("The service method " + invoker.getInterface().getName() + "." + getMethodSignature(invocation) + " is DEPRECATED! Declare from " + invoker.getUrl());
}
}
// 调用下一个调用链
return invoker.invoke(invocation);
}
/**
* 获得方法定义
* @param invocation
* @return
*/
private String getMethodSignature(Invocation invocation) {
// 方法名
StringBuilder buf = new StringBuilder(invocation.getMethodName());
buf.append("(");
// 参数类型
Class<?>[] types = invocation.getParameterTypes();
// 拼接参数
if (types != null && types.length > 0) {
boolean first = true;
for (Class<?> type : types) {
if (first) {
first = false;
} else {
buf.append(", ");
}
buf.append(type.getSimpleName());
}
}
buf.append(")");
return buf.toString();
}</code></pre>
<p>该过滤器比较简单。</p>
<h4>(八)EchoFilter</h4>
<p>该过滤器是处理回声测试的方法。</p>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
// 如果调用的方法是回声测试的方法 则直接返回结果,否则 调用下一个调用链
if (inv.getMethodName().equals(Constants.$ECHO) && inv.getArguments() != null && inv.getArguments().length == 1)
return new RpcResult(inv.getArguments()[0]);
return invoker.invoke(inv);
}</code></pre>
<p>如果调用的方法是回声测试的方法 则直接返回结果,否则 调用下一个调用链。</p>
<h4>(九)ExceptionFilter</h4>
<p>该过滤器是作用是对异常的处理。</p>
<pre><code class="java">private final Logger logger;
public ExceptionFilter() {
this(LoggerFactory.getLogger(ExceptionFilter.class));
}
public ExceptionFilter(Logger logger) {
this.logger = logger;
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
// 调用下一个调用链,返回结果
Result result = invoker.invoke(invocation);
// 如果结果有异常,并且该服务不是一个泛化调用
if (result.hasException() && GenericService.class != invoker.getInterface()) {
try {
// 获得异常
Throwable exception = result.getException();
// directly throw if it's checked exception
// 如果这是一个checked的异常,则直接返回异常,也就是接口上声明的Unchecked的异常
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}
// directly throw if the exception appears in the signature
// 如果已经在接口方法上声明了该异常,则直接返回
try {
// 获得方法
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
// 获得异常类型
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return result;
}
}
} catch (NoSuchMethodException e) {
return result;
}
// for the exception not found in method's signature, print ERROR message in server's log.
// 打印错误 该异常没有在方法上申明
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
// directly throw if exception class and interface class are in the same jar file.
// 如果异常类和接口类在同一个jar包里面,则抛出异常
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return result;
}
// directly throw if it's JDK exception
// 如果是jdk中定义的异常,则直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// directly throw if it's dubbo exception
// 如果 是dubbo的异常,则直接抛出
if (exception instanceof RpcException) {
return result;
}
// otherwise, wrap with RuntimeException and throw back to the client
// 如果不是以上的异常,则包装成为RuntimeException并且抛出
return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
return result;
}
}
return result;
} catch (RuntimeException e) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
throw e;
}
}</code></pre>
<p>可以看到除了接口上声明的Unchecked的异常和有定义的异常外,都会包装成RuntimeException来返回,为了防止客户端反序列化失败。</p>
<h4>(十)ExecuteLimitFilter</h4>
<p>该过滤器是限制最大可并行执行请求数,该过滤器是服务提供者侧,而上述讲到的ActiveLimitFilter是在消费者侧的限制。</p>
<pre><code class="java"> @Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获得url对象
URL url = invoker.getUrl();
// 方法名称
String methodName = invocation.getMethodName();
Semaphore executesLimit = null;
boolean acquireResult = false;
int max = url.getMethodParameter(methodName, Constants.EXECUTES_KEY, 0);
// 如果该方法设置了executes并且值大于0
if (max > 0) {
// 获得该方法对应的RpcStatus
RpcStatus count = RpcStatus.getStatus(url, invocation.getMethodName());
// if (count.getActive() >= max) {
/**
* http://manzhizhen.iteye.com/blog/2386408
* use semaphore for concurrency control (to limit thread number)
*/
// 获得信号量
executesLimit = count.getSemaphore(max);
// 如果不能获得许可,则抛出异常
if(executesLimit != null && !(acquireResult = executesLimit.tryAcquire())) {
throw new RpcException("Failed to invoke method " + invocation.getMethodName() + " in provider " + url + ", cause: The service using threads greater than <dubbo:service executes=\"" + max + "\" /> limited.");
}
}
long begin = System.currentTimeMillis();
boolean isSuccess = true;
// 计数加1
RpcStatus.beginCount(url, methodName);
try {
// 调用下一个调用链
Result result = invoker.invoke(invocation);
return result;
} catch (Throwable t) {
isSuccess = false;
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new RpcException("unexpected exception when ExecuteLimitFilter", t);
}
} finally {
// 计数减1
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, isSuccess);
if(acquireResult) {
executesLimit.release();
}
}
}</code></pre>
<p>为什么这里需要用到信号量来控制,可以看一下以下链接的介绍:<a href="https://link.segmentfault.com/?enc=kvO1A25fCfbOrHPTtsgRSg%3D%3D.ShQLENmCf7qUSpB9NsDPHbUpofQV4Hvyhf7Yncdgv5MqLqQLa%2FVN06N9metAtGQ%2B" rel="nofollow">http://manzhizhen.iteye.com/b...</a></p>
<h4>(十一)GenericFilter</h4>
<p>该过滤器就是对于泛化调用的请求和结果进行反序列化和序列化的操作,它是服务提供者侧的。</p>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
// 如果是泛化调用
if (inv.getMethodName().equals(Constants.$INVOKE)
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !invoker.getInterface().equals(GenericService.class)) {
// 获得请求名字
String name = ((String) inv.getArguments()[0]).trim();
// 获得请求参数类型
String[] types = (String[]) inv.getArguments()[1];
// 获得请求参数
Object[] args = (Object[]) inv.getArguments()[2];
try {
// 获得方法
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
// 获得该方法的参数类型
Class<?>[] params = method.getParameterTypes();
if (args == null) {
args = new Object[params.length];
}
// 获得附加值
String generic = inv.getAttachment(Constants.GENERIC_KEY);
// 如果附加值为空,在用上下文携带的附加值
if (StringUtils.isBlank(generic)) {
generic = RpcContext.getContext().getAttachment(Constants.GENERIC_KEY);
}
// 如果附加值还是为空或者是默认的泛化序列化类型
if (StringUtils.isEmpty(generic)
|| ProtocolUtils.isDefaultGenericSerialization(generic)) {
// 直接进行类型转化
args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
for (int i = 0; i < args.length; i++) {
if (byte[].class == args[i].getClass()) {
try {
UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]);
// 使用nativejava方式反序列化
args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
.deserialize(null, is).readObject();
} catch (Exception e) {
throw new RpcException("Deserialize argument [" + (i + 1) + "] failed.", e);
}
} else {
throw new RpcException(
"Generic serialization [" +
Constants.GENERIC_SERIALIZATION_NATIVE_JAVA +
"] only support message type " +
byte[].class +
" and your message type is " +
args[i].getClass());
}
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof JavaBeanDescriptor) {
// 用JavaBean方式反序列化
args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);
} else {
throw new RpcException(
"Generic serialization [" +
Constants.GENERIC_SERIALIZATION_BEAN +
"] only support message type " +
JavaBeanDescriptor.class.getName() +
" and your message type is " +
args[i].getClass().getName());
}
}
}
// 调用下一个调用链
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
if (result.hasException()
&& !(result.getException() instanceof GenericException)) {
return new RpcResult(new GenericException(result.getException()));
}
if (ProtocolUtils.isJavaGenericSerialization(generic)) {
try {
UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
// 用nativejava方式序列化
ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
.serialize(null, os).writeObject(result.getValue());
return new RpcResult(os.toByteArray());
} catch (IOException e) {
throw new RpcException("Serialize result failed.", e);
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
// 使用JavaBean方式序列化返回结果
return new RpcResult(JavaBeanSerializeUtil.serialize(result.getValue(), JavaBeanAccessor.METHOD));
} else {
// 直接转化为pojo类型然后返回
return new RpcResult(PojoUtils.generalize(result.getValue()));
}
} catch (NoSuchMethodException e) {
throw new RpcException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new RpcException(e.getMessage(), e);
}
}
// 调用下一个调用链
return invoker.invoke(inv);
}</code></pre>
<h4>(十二)GenericImplFilter</h4>
<p>该过滤器也是对于泛化调用的序列化检查和处理,它是消费者侧的过滤器。</p>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(GenericImplFilter.class);
/**
* 参数集合
*/
private static final Class<?>[] GENERIC_PARAMETER_TYPES = new Class<?>[]{String.class, String[].class, Object[].class};
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获得泛化的值
String generic = invoker.getUrl().getParameter(Constants.GENERIC_KEY);
// 如果该值是nativejava或者bean或者true,并且不是一个返回调用
if (ProtocolUtils.isGeneric(generic)
&& !Constants.$INVOKE.equals(invocation.getMethodName())
&& invocation instanceof RpcInvocation) {
RpcInvocation invocation2 = (RpcInvocation) invocation;
// 获得方法名称
String methodName = invocation2.getMethodName();
// 获得参数类型集合
Class<?>[] parameterTypes = invocation2.getParameterTypes();
// 获得参数集合
Object[] arguments = invocation2.getArguments();
// 把参数类型的名称放入集合
String[] types = new String[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
types[i] = ReflectUtils.getName(parameterTypes[i]);
}
Object[] args;
// 对参数集合进行序列化
if (ProtocolUtils.isBeanGenericSerialization(generic)) {
args = new Object[arguments.length];
for (int i = 0; i < arguments.length; i++) {
args[i] = JavaBeanSerializeUtil.serialize(arguments[i], JavaBeanAccessor.METHOD);
}
} else {
args = PojoUtils.generalize(arguments);
}
// 重新把序列化的参数放入
invocation2.setMethodName(Constants.$INVOKE);
invocation2.setParameterTypes(GENERIC_PARAMETER_TYPES);
invocation2.setArguments(new Object[]{methodName, types, args});
// 调用下一个调用链
Result result = invoker.invoke(invocation2);
if (!result.hasException()) {
Object value = result.getValue();
try {
Method method = invoker.getInterface().getMethod(methodName, parameterTypes);
if (ProtocolUtils.isBeanGenericSerialization(generic)) {
if (value == null) {
return new RpcResult(value);
} else if (value instanceof JavaBeanDescriptor) {
// 用javabean方式反序列化
return new RpcResult(JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) value));
} else {
throw new RpcException(
"The type of result value is " +
value.getClass().getName() +
" other than " +
JavaBeanDescriptor.class.getName() +
", and the result is " +
value);
}
} else {
// 直接转化为pojo类型
return new RpcResult(PojoUtils.realize(value, method.getReturnType(), method.getGenericReturnType()));
}
} catch (NoSuchMethodException e) {
throw new RpcException(e.getMessage(), e);
}
// 如果调用链中有异常抛出,并且是GenericException类型的异常
} else if (result.getException() instanceof GenericException) {
GenericException exception = (GenericException) result.getException();
try {
// 获得异常类名
String className = exception.getExceptionClass();
Class<?> clazz = ReflectUtils.forName(className);
Throwable targetException = null;
Throwable lastException = null;
try {
targetException = (Throwable) clazz.newInstance();
} catch (Throwable e) {
lastException = e;
for (Constructor<?> constructor : clazz.getConstructors()) {
try {
targetException = (Throwable) constructor.newInstance(new Object[constructor.getParameterTypes().length]);
break;
} catch (Throwable e1) {
lastException = e1;
}
}
}
if (targetException != null) {
try {
Field field = Throwable.class.getDeclaredField("detailMessage");
if (!field.isAccessible()) {
field.setAccessible(true);
}
field.set(targetException, exception.getExceptionMessage());
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
result = new RpcResult(targetException);
} else if (lastException != null) {
throw lastException;
}
} catch (Throwable e) {
throw new RpcException("Can not deserialize exception " + exception.getExceptionClass() + ", message: " + exception.getExceptionMessage(), e);
}
}
return result;
}
// 如果是泛化调用
if (invocation.getMethodName().equals(Constants.$INVOKE)
&& invocation.getArguments() != null
&& invocation.getArguments().length == 3
&& ProtocolUtils.isGeneric(generic)) {
Object[] args = (Object[]) invocation.getArguments()[2];
if (ProtocolUtils.isJavaGenericSerialization(generic)) {
for (Object arg : args) {
// 如果调用消息不是字节数组类型,则抛出异常
if (!(byte[].class == arg.getClass())) {
error(generic, byte[].class.getName(), arg.getClass().getName());
}
}
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
for (Object arg : args) {
if (!(arg instanceof JavaBeanDescriptor)) {
error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName());
}
}
}
// 设置附加值
((RpcInvocation) invocation).setAttachment(
Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
}
return invoker.invoke(invocation);
}
/**
* 抛出错误异常
* @param generic
* @param expected
* @param actual
* @throws RpcException
*/
private void error(String generic, String expected, String actual) throws RpcException {
throw new RpcException(
"Generic serialization [" +
generic +
"] only support message type " +
expected +
" and your message type is " +
actual);
}</code></pre>
<h4>(十三)TimeoutFilter</h4>
<p>该过滤器是当服务调用超时的时候,记录告警日志。</p>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获得开始时间
long start = System.currentTimeMillis();
// 调用下一个调用链
Result result = invoker.invoke(invocation);
// 获得调用使用的时间
long elapsed = System.currentTimeMillis() - start;
// 如果服务调用超时,则打印告警日志
if (invoker.getUrl() != null
&& elapsed > invoker.getUrl().getMethodParameter(invocation.getMethodName(),
"timeout", Integer.MAX_VALUE)) {
if (logger.isWarnEnabled()) {
logger.warn("invoke time out. method: " + invocation.getMethodName()
+ " arguments: " + Arrays.toString(invocation.getArguments()) + " , url is "
+ invoker.getUrl() + ", invoke elapsed " + elapsed + " ms.");
}
}
return result;
}</code></pre>
<h4>(十四)TokenFilter</h4>
<p>该过滤器提供了token的验证功能,关于token的介绍可以查看官方文档。</p>
<pre><code class="java">@Override
public Result invoke(Invoker<?> invoker, Invocation inv)
throws RpcException {
// 获得token值
String token = invoker.getUrl().getParameter(Constants.TOKEN_KEY);
if (ConfigUtils.isNotEmpty(token)) {
// 获得服务类型
Class<?> serviceType = invoker.getInterface();
// 获得附加值
Map<String, String> attachments = inv.getAttachments();
String remoteToken = attachments == null ? null : attachments.get(Constants.TOKEN_KEY);
// 如果令牌不一样,则抛出异常
if (!token.equals(remoteToken)) {
throw new RpcException("Invalid token! Forbid invoke remote service " + serviceType + " method " + inv.getMethodName() + "() from consumer " + RpcContext.getContext().getRemoteHost() + " to provider " + RpcContext.getContext().getLocalHost());
}
}
// 调用下一个调用链
return invoker.invoke(inv);
}</code></pre>
<h4>(十五)TpsLimitFilter</h4>
<p>该过滤器的作用是对TPS限流。</p>
<pre><code class="java">/**
* TPS 限制器对象
*/
private final TPSLimiter tpsLimiter = new DefaultTPSLimiter();
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 如果限流器不允许,则抛出异常
if (!tpsLimiter.isAllowable(invoker.getUrl(), invocation)) {
throw new RpcException(
"Failed to invoke service " +
invoker.getInterface().getName() +
"." +
invocation.getMethodName() +
" because exceed max service tps.");
}
// 调用下一个调用链
return invoker.invoke(invocation);
}</code></pre>
<p>其中关键是TPS 限制器对象,请看下面的分析。</p>
<h4>(十六)TPSLimiter</h4>
<pre><code class="java">public interface TPSLimiter {
/**
* judge if the current invocation is allowed by TPS rule
* 是否允许通过
* @param url url
* @param invocation invocation
* @return true allow the current invocation, otherwise, return false
*/
boolean isAllowable(URL url, Invocation invocation);
}</code></pre>
<p>该接口是tps限流器的接口,只定义了一个是否允许通过的方法。</p>
<h4>(十七)StatItem</h4>
<p>该类是统计的数据结构。</p>
<pre><code class="java">class StatItem {
/**
* 服务名
*/
private String name;
/**
* 最后一次重置的时间
*/
private long lastResetTime;
/**
* 周期
*/
private long interval;
/**
* 剩余多少流量
*/
private AtomicInteger token;
/**
* 限制大小
*/
private int rate;
StatItem(String name, int rate, long interval) {
this.name = name;
this.rate = rate;
this.interval = interval;
this.lastResetTime = System.currentTimeMillis();
this.token = new AtomicInteger(rate);
}
public boolean isAllowable() {
long now = System.currentTimeMillis();
// 如果限制的时间大于最后一次时间加上周期,则重置
if (now > lastResetTime + interval) {
token.set(rate);
lastResetTime = now;
}
int value = token.get();
boolean flag = false;
// 直到有流量
while (value > 0 && !flag) {
flag = token.compareAndSet(value, value - 1);
value = token.get();
}
// 返回flag
return flag;
}
long getLastResetTime() {
return lastResetTime;
}
int getToken() {
return token.get();
}
@Override
public String toString() {
return new StringBuilder(32).append("StatItem ")
.append("[name=").append(name).append(", ")
.append("rate = ").append(rate).append(", ")
.append("interval = ").append(interval).append("]")
.toString();
}
}</code></pre>
<p>可以看到该类中记录了一些访问的流量,并且设置了周期重置机制。</p>
<h4>(十八)DefaultTPSLimiter</h4>
<p>该类实现了TPSLimiter,是默认的tps限流器实现。</p>
<pre><code class="java">public class DefaultTPSLimiter implements TPSLimiter {
/**
* 统计项集合
*/
private final ConcurrentMap<String, StatItem> stats
= new ConcurrentHashMap<String, StatItem>();
@Override
public boolean isAllowable(URL url, Invocation invocation) {
// 获得tps限制大小,默认-1,不限制
int rate = url.getParameter(Constants.TPS_LIMIT_RATE_KEY, -1);
// 获得限流周期
long interval = url.getParameter(Constants.TPS_LIMIT_INTERVAL_KEY,
Constants.DEFAULT_TPS_LIMIT_INTERVAL);
String serviceKey = url.getServiceKey();
// 如果限制
if (rate > 0) {
// 从集合中获得统计项
StatItem statItem = stats.get(serviceKey);
// 如果为空,则新建
if (statItem == null) {
stats.putIfAbsent(serviceKey,
new StatItem(serviceKey, rate, interval));
statItem = stats.get(serviceKey);
}
// 返回是否允许
return statItem.isAllowable();
} else {
StatItem statItem = stats.get(serviceKey);
if (statItem != null) {
// 移除该服务的统计项
stats.remove(serviceKey);
}
}
return true;
}
}</code></pre>
<p>是否允许的逻辑还是调用了统计项中的isAllowable方法。</p>
<p>本文介绍了很多的过滤器,哪些过滤器是在服务引用的,哪些服务器是服务暴露的,可以查看相应源码过滤器的实现上的注解,</p>
<p>例如ActiveLimitFilter上:</p>
<pre><code class="java">@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)</code></pre>
<p>可以看到group为consumer组的,也就是服务消费者侧的,则是服务引用过程中的的过滤器。 </p>
<p>例如ExecuteLimitFilter上:</p>
<pre><code class="java">@Activate(group = Constants.PROVIDER, value = Constants.EXECUTES_KEY)</code></pre>
<p>可以看到group为provider组的,也就是服务提供者侧的,则是服务暴露过程中的的过滤器。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=ZrRfSYmwpY7zvONR80hz0w%3D%3D.c18PuufgTqqy3MFngN8U4LtVeF5ZtI1%2BsmUCQ9cOlz7ama6LOdVvYzvGgyDT9nzktNTj%2FlIGXyOi%2Bk7WpNlLkivDYyeKOiSPwPun0scrZESzplqaREbMxGFm%2BZjqJYww8MxzvKyj3r2aLuODoD6iEASReeItbYoAEUpq%2BtmaSTsIlyuwj0N4%2Fb0nXRM43DSf" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了在服务引用和服务暴露中的各种filter过滤器。接下来我将开始对rpc模块的监听器进行讲解。</p>
Dubbo源码解析(十九)远程调用——开篇
https://segmentfault.com/a/1190000017787521
2019-01-07T09:33:08+08:00
2019-01-07T09:33:08+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
9
<h2>远程调用——开篇</h2>
<blockquote>目标:介绍之后解读远程调用模块的内容如何编排、介绍dubbo-rpc-api中的包结构设计以及最外层的的源码解析。</blockquote>
<h3>前言</h3>
<p>最近我面临着一个选择,因为dubbo 2.7.0-release出现在了仓库里,最近一直在进行2.7.0版本的code review,那我之前说这一系列的文章都是讲述2.6.x版本的源代码,我现在要不要选择直接开始讲解2.7.0的版本的源码呢?我最后还是决定继续讲解2.6.x,因为我觉得还是有很多公司在用着2.6.x的版本,并且对于升级2.7.0的计划应该还没那么快,并且在了解2.6.x版本的原理后,再去了解2.7.0新增的特性会更加容易,也能够品位到设计者的意图。当然在结束2.6.x的重要模块讲解后,我也会对2.7.0的新特性以及实现原理做一个全面的分析,2.7.0作为dubbo社区的毕业版,更加强大,敬请期待。</p>
<p>前面讲了很多的内容,现在开始将远程调用RPC,好像又回到我第一篇文章<a href="https://segmentfault.com/a/1190000016741532"> 《dubbo源码解析(一)Hello,Dubbo》</a>,在这篇文章开头我讲到了什么叫做RPC,再通俗一点讲,就是我把一个项目的两部分代码分开来,分别放到两台机器上,当我部署在A服务器上的应用想要调用部署在B服务器上的应用等方法,由于不存在同一个内存空间,不能直接调用。而其实整个dubbo都在做远程调用的事情,它涉及到很多内容,比如配置、代理、集群、监控等等,那么这次讲的内容是只关心一对一的调用,dubbo-rpc远程调用模块抽象各种协议,以及动态代理,Proxy层和Protocol层rpc的核心,我将会在本系列中讲到。下面我们来看两张官方文档的图:</p>
<ol><li>暴露服务的时序图:</li></ol>
<p><img src="/img/remote/1460000017787524" alt="dubbo-export" title="dubbo-export"></p>
<p>你会发现其中有我们以前讲到的Transporter、Server、Registry,而这次的系列将会讲到的就是红色框框内的部分。</p>
<ol><li>引用服务时序图</li></ol>
<p><img src="/img/remote/1460000017787525" alt="dubbo-refer" title="dubbo-refer"></p>
<p>在引用服务时序图中,对应的也是红色框框的部分。</p>
<p>当阅读完该系列后,希望能对这个调用链有所感悟。接下来看看dubbo-rpc的包结构:</p>
<p><img src="/img/remote/1460000017787526" alt="rpc目录" title="rpc目录"></p>
<p>可以看到有很多包,很规整,其中dubbo-rpc-api是对协议、暴露、引用、代理等的抽象和实现,是rpc整个设计的核心内容。其他的包则是dubbo支持的9种协议,在官方文档也能查看介绍,并且包括一种本地调用injvm。那么我们再来看看dubbo-rpc-api中包结构:</p>
<p><img src="/img/remote/1460000017787527" alt="dubbo-rpc-api包结构" title="dubbo-rpc-api包结构"></p>
<ol>
<li>filter包:在进行服务引用时会进行一系列的过滤。其中包括了很多过滤器。</li>
<li>listener包:看上面两张服务引用和服务暴露的时序图,发现有两个listener,其中的逻辑实现就在这个包内</li>
<li>protocol包:这个包实现了协议的一些公共逻辑</li>
<li>proxy包:实现了代理的逻辑。</li>
<li>service包:其中包含了一个需要调用的方法等封装抽象。</li>
<li>support包:包括了工具类</li>
<li>最外层的实现。</li>
</ol>
<p>下面的篇幅设计,本文会讲解最外层的源码和service下的源码,support包下的源码我会穿插在其他用到的地方一并讲解,filter、listener、protocol、proxy以及各类协议的实现各自用一篇来讲。</p>
<h3>源码分析</h3>
<h4>(一)Invoker</h4>
<pre><code class="java">public interface Invoker<T> extends Node {
/**
* get service interface.
* 获得服务接口
* @return service interface.
*/
Class<T> getInterface();
/**
* invoke.
* 调用下一个会话域
* @param invocation
* @return result
* @throws RpcException
*/
Result invoke(Invocation invocation) throws RpcException;
}</code></pre>
<p>该接口是实体域,它是dubbo的核心模型,其他模型都向它靠拢,或者转化成它,它代表了一个可执行体,可以向它发起invoke调用,这个有可能是一个本地的实现,也可能是一个远程的实现,也可能是一个集群的实现。它代表了一次调用</p>
<h4>(二)Invocation</h4>
<pre><code class="java">public interface Invocation {
/**
* get method name.
* 获得方法名称
* @return method name.
* @serial
*/
String getMethodName();
/**
* get parameter types.
* 获得参数类型
* @return parameter types.
* @serial
*/
Class<?>[] getParameterTypes();
/**
* get arguments.
* 获得参数
* @return arguments.
* @serial
*/
Object[] getArguments();
/**
* get attachments.
* 获得附加值集合
* @return attachments.
* @serial
*/
Map<String, String> getAttachments();
/**
* get attachment by key.
* 获得附加值
* @return attachment value.
* @serial
*/
String getAttachment(String key);
/**
* get attachment by key with default value.
* 获得附加值
* @return attachment value.
* @serial
*/
String getAttachment(String key, String defaultValue);
/**
* get the invoker in current context.
* 获得当前上下文的invoker
* @return invoker.
* @transient
*/
Invoker<?> getInvoker();
}</code></pre>
<p>Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。</p>
<h4>(三)Exporter</h4>
<pre><code class="java">public interface Exporter<T> {
/**
* get invoker.
* 获得对应的实体域invoker
* @return invoker
*/
Invoker<T> getInvoker();
/**
* unexport.
* 取消暴露
* <p>
* <code>
* getInvoker().destroy();
* </code>
*/
void unexport();
}</code></pre>
<p>该接口是暴露服务的接口,定义了两个方法分别是获得invoker和取消暴露服务。</p>
<h4>(四)ExporterListener</h4>
<pre><code class="java">@SPI
public interface ExporterListener {
/**
* The exporter exported.
* 暴露服务
* @param exporter
* @throws RpcException
* @see com.alibaba.dubbo.rpc.Protocol#export(Invoker)
*/
void exported(Exporter<?> exporter) throws RpcException;
/**
* The exporter unexported.
* 取消暴露
* @param exporter
* @throws RpcException
* @see com.alibaba.dubbo.rpc.Exporter#unexport()
*/
void unexported(Exporter<?> exporter);
}</code></pre>
<p>该接口是服务暴露的监听器接口,定义了两个方法是暴露和取消暴露,参数都是Exporter类型的。</p>
<h4>(五)Protocol</h4>
<pre><code class="java">@SPI("dubbo")
public interface Protocol {
/**
* Get default port when user doesn't config the port.
* 获得默认的端口
* @return default port
*/
int getDefaultPort();
/**
* Export service for remote invocation: <br>
* 1. Protocol should record request source address after receive a request:
* RpcContext.getContext().setRemoteAddress();<br>
* 2. export() must be idempotent, that is, there's no difference between invoking once and invoking twice when
* export the same URL<br>
* 3. Invoker instance is passed in by the framework, protocol needs not to care <br>
* 暴露服务方法,
* @param <T> Service type 服务类型
* @param invoker Service invoker 服务的实体域
* @return exporter reference for exported service, useful for unexport the service later
* @throws RpcException thrown when error occurs during export the service, for example: port is occupied
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
/**
* Refer a remote service: <br>
* 1. When user calls `invoke()` method of `Invoker` object which's returned from `refer()` call, the protocol
* needs to correspondingly execute `invoke()` method of `Invoker` object <br>
* 2. It's protocol's responsibility to implement `Invoker` which's returned from `refer()`. Generally speaking,
* protocol sends remote request in the `Invoker` implementation. <br>
* 3. When there's check=false set in URL, the implementation must not throw exception but try to recover when
* connection fails.
* 引用服务方法
* @param <T> Service type 服务类型
* @param type Service class 服务类名
* @param url URL address for the remote service
* @return invoker service's local proxy
* @throws RpcException when there's any error while connecting to the service provider
*/
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
/**
* Destroy protocol: <br>
* 1. Cancel all services this protocol exports and refers <br>
* 2. Release all occupied resources, for example: connection, port, etc. <br>
* 3. Protocol can continue to export and refer new service even after it's destroyed.
*/
void destroy();
}</code></pre>
<p>该接口是服务域接口,也是协议接口,它是一个可扩展的接口,默认实现的是dubbo协议。定义了四个方法,关键的是服务暴露和引用两个方法。</p>
<h4>(六)Filter</h4>
<pre><code class="java">@SPI
public interface Filter {
/**
* do invoke filter.
* <p>
* <code>
* // before filter
* Result result = invoker.invoke(invocation);
* // after filter
* return result;
* </code>
*
* @param invoker service
* @param invocation invocation.
* @return invoke result.
* @throws RpcException
* @see com.alibaba.dubbo.rpc.Invoker#invoke(Invocation)
*/
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}</code></pre>
<p>该接口是invoker调用时过滤器接口,其中就只有一个invoke方法。在该方法中对调用进行过滤</p>
<h4>(七)InvokerListener</h4>
<pre><code class="java">@SPI
public interface InvokerListener {
/**
* The invoker referred
* 在服务引用的时候进行监听
* @param invoker
* @throws RpcException
* @see com.alibaba.dubbo.rpc.Protocol#refer(Class, com.alibaba.dubbo.common.URL)
*/
void referred(Invoker<?> invoker) throws RpcException;
/**
* The invoker destroyed.
* 销毁实体域
* @param invoker
* @see com.alibaba.dubbo.rpc.Invoker#destroy()
*/
void destroyed(Invoker<?> invoker);
}</code></pre>
<p>该接口是实体域的监听器,定义了两个方法,分别是服务引用和销毁的时候执行的方法。</p>
<h4>(八)Result</h4>
<p>该接口是实体域执行invoke的结果接口,里面定义了获得结果异常以及附加值等方法。比较好理解我就不贴代码了。</p>
<h4>(九)ProxyFactory</h4>
<pre><code class="java">@SPI("javassist")
public interface ProxyFactory {
/**
* create proxy.
* 创建一个代理
* @param invoker
* @return proxy
*/
@Adaptive({Constants.PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException;
/**
* create proxy.
* 创建一个代理
* @param invoker
* @return proxy
*/
@Adaptive({Constants.PROXY_KEY})
<T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;
/**
* create invoker.
* 创建一个实体域
* @param <T>
* @param proxy
* @param type
* @param url
* @return invoker
*/
@Adaptive({Constants.PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}</code></pre>
<p>该接口是代理工厂接口,它也是个可扩展接口,默认实现javassist,dubbo提供两种动态代理方法分别是javassist/jdk,该接口定义了三个方法,前两个方法是通过invoker创建代理,最后一个是通过代理来获得invoker。</p>
<h4>(十)RpcContext</h4>
<p>该类就是远程调用的上下文,贯穿着整个调用,例如A调用B,然后B调用C。在服务B上,RpcContext在B之前将调用信息从A保存到B。开始调用C,并在B调用C后将调用信息从B保存到C。RpcContext保存了调用信息。</p>
<pre><code class="java">public class RpcContext {
/**
* use internal thread local to improve performance
* 本地上下文
*/
private static final InternalThreadLocal<RpcContext> LOCAL = new InternalThreadLocal<RpcContext>() {
@Override
protected RpcContext initialValue() {
return new RpcContext();
}
};
/**
* 服务上下文
*/
private static final InternalThreadLocal<RpcContext> SERVER_LOCAL = new InternalThreadLocal<RpcContext>() {
@Override
protected RpcContext initialValue() {
return new RpcContext();
}
};
/**
* 附加值集合
*/
private final Map<String, String> attachments = new HashMap<String, String>();
/**
* 上下文值
*/
private final Map<String, Object> values = new HashMap<String, Object>();
/**
* 线程结果
*/
private Future<?> future;
/**
* url集合
*/
private List<URL> urls;
/**
* 当前的url
*/
private URL url;
/**
* 方法名称
*/
private String methodName;
/**
* 参数类型集合
*/
private Class<?>[] parameterTypes;
/**
* 参数集合
*/
private Object[] arguments;
/**
* 本地地址
*/
private InetSocketAddress localAddress;
/**
* 远程地址
*/
private InetSocketAddress remoteAddress;
/**
* 实体域集合
*/
@Deprecated
private List<Invoker<?>> invokers;
/**
* 实体域
*/
@Deprecated
private Invoker<?> invoker;
/**
* 会话域
*/
@Deprecated
private Invocation invocation;
// now we don't use the 'values' map to hold these objects
// we want these objects to be as generic as possible
/**
* 请求
*/
private Object request;
/**
* 响应
*/
private Object response;</code></pre>
<p>该类中最重要的是它的一些属性,因为该上下文就是用来保存信息的。方法我就不介绍了,因为比较简单。</p>
<h4>(十一)RpcException</h4>
<pre><code class="java">/**
* 不知道异常
*/
public static final int UNKNOWN_EXCEPTION = 0;
/**
* 网络异常
*/
public static final int NETWORK_EXCEPTION = 1;
/**
* 超时异常
*/
public static final int TIMEOUT_EXCEPTION = 2;
/**
* 基础异常
*/
public static final int BIZ_EXCEPTION = 3;
/**
* 禁止访问异常
*/
public static final int FORBIDDEN_EXCEPTION = 4;
/**
* 序列化异常
*/
public static final int SERIALIZATION_EXCEPTION = 5;</code></pre>
<p>该类是rpc调用抛出的异常类,其中封装了五种通用的错误码。</p>
<h4>(十二)RpcInvocation</h4>
<pre><code class="java">/**
* 方法名称
*/
private String methodName;
/**
* 参数类型集合
*/
private Class<?>[] parameterTypes;
/**
* 参数集合
*/
private Object[] arguments;
/**
* 附加值
*/
private Map<String, String> attachments;
/**
* 实体域
*/
private transient Invoker<?> invoker;</code></pre>
<p>该类实现了Invocation接口,是rpc的会话域,其中的方法比较简单,主要是封装了上述的属性。</p>
<h4>(十三)RpcResult</h4>
<pre><code class="java">/**
* 结果
*/
private Object result;
/**
* 异常
*/
private Throwable exception;
/**
* 附加值
*/
private Map<String, String> attachments = new HashMap<String, String>();</code></pre>
<p>该类实现了Result接口,是rpc的结果实现类,其中关键是封装了以上三个属性。</p>
<h4>(十四)RpcStatus</h4>
<p>该类是rpc的一些状态监控,其中封装了许多的计数器,用来记录rpc调用的状态。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* uri对应的状态集合,key为uri,value为RpcStatus对象
*/
private static final ConcurrentMap<String, RpcStatus> SERVICE_STATISTICS = new ConcurrentHashMap<String, RpcStatus>();
/**
* method对应的状态集合,key是uri,第二个key是方法名methodName
*/
private static final ConcurrentMap<String, ConcurrentMap<String, RpcStatus>> METHOD_STATISTICS = new ConcurrentHashMap<String, ConcurrentMap<String, RpcStatus>>();
/**
* 已经没用了
*/
private final ConcurrentMap<String, Object> values = new ConcurrentHashMap<String, Object>();
/**
* 活跃状态
*/
private final AtomicInteger active = new AtomicInteger();
/**
* 总的数量
*/
private final AtomicLong total = new AtomicLong();
/**
* 失败的个数
*/
private final AtomicInteger failed = new AtomicInteger();
/**
* 总调用时长
*/
private final AtomicLong totalElapsed = new AtomicLong();
/**
* 总调用失败时长
*/
private final AtomicLong failedElapsed = new AtomicLong();
/**
* 最大调用时长
*/
private final AtomicLong maxElapsed = new AtomicLong();
/**
* 最大调用失败时长
*/
private final AtomicLong failedMaxElapsed = new AtomicLong();
/**
* 最大调用成功时长
*/
private final AtomicLong succeededMaxElapsed = new AtomicLong();
/**
* Semaphore used to control concurrency limit set by `executes`
* 信号量用来控制`execution`设置的并发限制
*/
private volatile Semaphore executesLimit;
/**
* 用来控制`execution`设置的许可证
*/
private volatile int executesPermits;</code></pre>
<p>以上是该类的属性,可以看到保存了很多的计数器,分别用来记录了失败调用成功调用等累计数。</p>
<h5>2.beginCount</h5>
<pre><code class="java">/**
* 开始计数
* @param url
*/
public static void beginCount(URL url, String methodName) {
// 对该url对应对活跃计数器加一
beginCount(getStatus(url));
// 对该方法对活跃计数器加一
beginCount(getStatus(url, methodName));
}
/**
* 以原子方式加1
* @param status
*/
private static void beginCount(RpcStatus status) {
status.active.incrementAndGet();
}</code></pre>
<p>该方法是增加计数。</p>
<h5>3.endCount</h5>
<pre><code class="java">public static void endCount(URL url, String methodName, long elapsed, boolean succeeded) {
// url对应的状态中计数器减一
endCount(getStatus(url), elapsed, succeeded);
// 方法对应的状态中计数器减一
endCount(getStatus(url, methodName), elapsed, succeeded);
}
private static void endCount(RpcStatus status, long elapsed, boolean succeeded) {
// 活跃计数器减一
status.active.decrementAndGet();
// 总计数器加1
status.total.incrementAndGet();
// 总调用时长加上调用时长
status.totalElapsed.addAndGet(elapsed);
// 如果最大调用时长小于elapsed,则设置最大调用时长
if (status.maxElapsed.get() < elapsed) {
status.maxElapsed.set(elapsed);
}
// 如果rpc调用成功
if (succeeded) {
// 如果成最大调用成功时长小于elapsed,则设置最大调用成功时长
if (status.succeededMaxElapsed.get() < elapsed) {
status.succeededMaxElapsed.set(elapsed);
}
} else {
// 失败计数器加一
status.failed.incrementAndGet();
// 失败的过期数加上elapsed
status.failedElapsed.addAndGet(elapsed);
// 总调用失败时长小于elapsed,则设置总调用失败时长
if (status.failedMaxElapsed.get() < elapsed) {
status.failedMaxElapsed.set(elapsed);
}
}
}</code></pre>
<p>该方法是计数器减少。</p>
<h4>(十五)StaticContext</h4>
<p>该类是系统上下文,仅供内部使用。</p>
<pre><code class="java">/**
* 系统名称
*/
private static final String SYSTEMNAME = "system";
/**
* 系统上下文集合,仅供内部使用
*/
private static final ConcurrentMap<String, StaticContext> context_map = new ConcurrentHashMap<String, StaticContext>();
/**
* 系统上下文名称
*/
private String name;</code></pre>
<p>上面是该类的属性,它还记录了所有的系统上下文集合。</p>
<h4>(十六)EchoService</h4>
<pre><code class="java">public interface EchoService {
/**
* echo test.
* 回声测试
* @param message message.
* @return message.
*/
Object $echo(Object message);
}</code></pre>
<p>该接口是回声服务接口,定义了一个一个回声测试的方法,回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控,所有服务自动实现该接口,只需将任意服务强制转化为EchoService,就可以用了。</p>
<h4>(十七)GenericException</h4>
<p>该方法是通用的异常类。</p>
<pre><code class="java">/**
* 异常类名
*/
private String exceptionClass;
/**
* 异常信息
*/
private String exceptionMessage;</code></pre>
<p>比较简单,就封装了两个属性。</p>
<h4>(十八)GenericService</h4>
<pre><code class="java">public interface GenericService {
/**
* Generic invocation
* 通用的会话域
* @param method Method name, e.g. findPerson. If there are overridden methods, parameter info is
* required, e.g. findPerson(java.lang.String)
* @param parameterTypes Parameter types
* @param args Arguments
* @return invocation return value
* @throws Throwable potential exception thrown from the invocation
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
}</code></pre>
<p>该接口是通用的服务接口,同样定义了一个类似invoke的方法</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=mt4a8IKQuv6XnmJIIKpSRQ%3D%3D.Fy2b6zoiwa8vT64T%2Fo2Mn6WLTflja10Q9YbvwyMOt%2FRXg5vGJTtpmvCCGatGHVQFwmK7WJB5XheFxqFUU%2FhOBdEx6xzwhMhcH38pi7suoQIx%2Brd75pqlQyHIitX5PfDH%2BLFiCwTsTLAi1N78tKn8HYz7PRv5rpnOssGhhp4FBuI%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了远程调用的开篇,介绍之后解读远程调用模块的内容如何编排、介绍dubbo-rpc-api中的包结构设计以及最外层的的源码解析,其中的逻辑不负责,要关注的是其中的一些概念和dubbo如何去做暴露服务和引用服务,其中很多的接口定义需要弄清楚。接下来我将开始对rpc模块的过滤器进行讲解。</p>
Dubbo源码解析(十八)远程通信——Zookeeper
https://segmentfault.com/a/1190000017565522
2018-12-29T17:31:10+08:00
2018-12-29T17:31:10+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
4
<h2>远程通讯——Zookeeper</h2>
<blockquote>目标:介绍基于zookeeper的来实现的远程通信、介绍dubbo-remoting-zookeeper内的源码解析。</blockquote>
<h3>前言</h3>
<p>对于zookeeper我相信肯定不陌生,在之前的文章里面也有讲到zookeeper来作为注册中心。在这里,基于zookeeper来实现远程通讯,duubo封装了zookeeper client,来和zookeeper server通讯。</p>
<p>下面是类图:</p>
<p><img src="/img/remote/1460000017565525" alt="zookeeper-类图" title="zookeeper-类图"></p>
<h3>源码分析</h3>
<h4>(一)ZookeeperClient</h4>
<pre><code class="java">public interface ZookeeperClient {
/**
* 创建client
* @param path
* @param ephemeral
*/
void create(String path, boolean ephemeral);
/**
* 删除client
* @param path
*/
void delete(String path);
/**
* 获得子节点集合
* @param path
* @return
*/
List<String> getChildren(String path);
/**
* 向zookeeper的该节点发起订阅,获得该节点所有
* @param path
* @param listener
* @return
*/
List<String> addChildListener(String path, ChildListener listener);
/**
* 移除该节点的子节点监听器
* @param path
* @param listener
*/
void removeChildListener(String path, ChildListener listener);
/**
* 新增状态监听器
* @param listener
*/
void addStateListener(StateListener listener);
/**
* 移除状态监听
* @param listener
*/
void removeStateListener(StateListener listener);
/**
* 判断是否连接
* @return
*/
boolean isConnected();
/**
* 关闭客户端
*/
void close();
/**
* 获得url
* @return
*/
URL getUrl();
}</code></pre>
<p>该接口是基于zookeeper的客户端接口,其中封装了客户端的一些方法。</p>
<h4>(二)AbstractZookeeperClient</h4>
<p>该类实现了ZookeeperClient接口,是客户端的抽象类,它实现了一些公共逻辑,把具体的doClose、createPersistent等方法抽象出来,留给子类来实现。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* url对象
*/
private final URL url;
/**
* 状态监听器集合
*/
private final Set<StateListener> stateListeners = new CopyOnWriteArraySet<StateListener>();
/**
* 客户端监听器集合
*/
private final ConcurrentMap<String, ConcurrentMap<ChildListener, TargetChildListener>> childListeners = new ConcurrentHashMap<String, ConcurrentMap<ChildListener, TargetChildListener>>();
/**
* 是否关闭
*/
private volatile boolean closed = false;</code></pre>
<h5>2.create</h5>
<pre><code class="java">@Override
public void create(String path, boolean ephemeral) {
// 如果不是临时节点
if (!ephemeral) {
// 判断该客户端是否存在
if (checkExists(path)) {
return;
}
}
// 获得/的位置
int i = path.lastIndexOf('/');
if (i > 0) {
// 创建客户端
create(path.substring(0, i), false);
}
// 如果是临时节点
if (ephemeral) {
// 创建临时节点
createEphemeral(path);
} else {
// 递归创建节点
createPersistent(path);
}
}</code></pre>
<p>该方法是创建客户端的方法,其中createEphemeral和createPersistent方法都被抽象出来。具体看下面的类的介绍。</p>
<h5>3.addStateListener</h5>
<pre><code class="java">@Override
public void addStateListener(StateListener listener) {
// 状态监听器加入集合
stateListeners.add(listener);
}</code></pre>
<p>该方法就是增加状态监听器。</p>
<h5>4.close</h5>
<pre><code class="java">@Override
public void close() {
if (closed) {
return;
}
closed = true;
try {
// 关闭
doClose();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}</code></pre>
<p>该方法是关闭客户端,其中doClose方法也被抽象出。</p>
<pre><code class="java">/**
* 关闭客户端
*/
protected abstract void doClose();
/**
* 递归创建节点
* @param path
*/
protected abstract void createPersistent(String path);
/**
* 创建临时节点
* @param path
*/
protected abstract void createEphemeral(String path);
/**
* 检测该节点是否存在
* @param path
* @return
*/
protected abstract boolean checkExists(String path);
/**
* 创建子节点监听器
* @param path
* @param listener
* @return
*/
protected abstract TargetChildListener createTargetChildListener(String path, ChildListener listener);
/**
* 为子节点添加监听器
* @param path
* @param listener
* @return
*/
protected abstract List<String> addTargetChildListener(String path, TargetChildListener listener);
/**
* 移除子节点监听器
* @param path
* @param listener
*/
protected abstract void removeTargetChildListener(String path, TargetChildListener listener);</code></pre>
<p>上述的方法都是被抽象的,又它的两个子类来实现。</p>
<h4>(三)ZkclientZookeeperClient</h4>
<p>该类继承了AbstractZookeeperClient,是zk客户端的实现类。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* zk客户端包装类
*/
private final ZkClientWrapper client;
/**
* 连接状态
*/
private volatile KeeperState state = KeeperState.SyncConnected;</code></pre>
<p>该类有两个属性,其中client就是核心所在,几乎所有方法都调用了client的方法。</p>
<h5>2.构造函数</h5>
<pre><code class="java">public ZkclientZookeeperClient(URL url) {
super(url);
// 新建一个zkclient包装类
client = new ZkClientWrapper(url.getBackupAddress(), 30000);
// 增加状态监听
client.addListener(new IZkStateListener() {
/**
* 如果状态改变
* @param state
* @throws Exception
*/
@Override
public void handleStateChanged(KeeperState state) throws Exception {
ZkclientZookeeperClient.this.state = state;
// 如果状态变为了断开连接
if (state == KeeperState.Disconnected) {
// 则修改状态
stateChanged(StateListener.DISCONNECTED);
} else if (state == KeeperState.SyncConnected) {
stateChanged(StateListener.CONNECTED);
}
}
@Override
public void handleNewSession() throws Exception {
// 状态变为重连
stateChanged(StateListener.RECONNECTED);
}
});
// 启动客户端
client.start();
}</code></pre>
<p>该方法是构造方法,同时在里面也做了创建客户端和启动客户端的操作。其他方法都是实现了父类抽象的方法,并且调用的是client方法,为举个例子:</p>
<pre><code class="java">@Override
public void createPersistent(String path) {
try {
// 递归创建节点
client.createPersistent(path);
} catch (ZkNodeExistsException e) {
}
}</code></pre>
<p>该方法是递归场景节点,调用的就是client.createPersistent(path)。</p>
<h4>(四)CuratorZookeeperClient</h4>
<p>该类是Curator框架提供的一套高级API,简化了ZooKeeper的操作,从而对客户端的实现。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 框架式客户端
*/
private final CuratorFramework client;</code></pre>
<h5>2.构造方法</h5>
<pre><code class="java">public CuratorZookeeperClient(URL url) {
super(url);
try {
// 工厂创建者
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(url.getBackupAddress())
.retryPolicy(new RetryNTimes(1, 1000))
.connectionTimeoutMs(5000);
String authority = url.getAuthority();
if (authority != null && authority.length() > 0) {
builder = builder.authorization("digest", authority.getBytes());
}
// 创建客户端
client = builder.build();
// 添加监听器
client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState state) {
// 如果为状态为lost,则改变为未连接
if (state == ConnectionState.LOST) {
CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
} else if (state == ConnectionState.CONNECTED) {
// 改变状态为连接
CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
} else if (state == ConnectionState.RECONNECTED) {
// 改变状态为未连接
CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
}
}
});
// 启动客户端
client.start();
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}</code></pre>
<p>该方法是构造方法,同样里面也包含了客户端创建和启动的逻辑。</p>
<p>其他的方法也一样是实现了父类的抽象方法,举个列子:</p>
<pre><code class="java">@Override
public void createPersistent(String path) {
try {
client.create().forPath(path);
} catch (NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}</code></pre>
<h4>(五)ZookeeperTransporter</h4>
<pre><code class="java">@SPI("curator")
public interface ZookeeperTransporter {
/**
* 连接服务器
* @param url
* @return
*/
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
ZookeeperClient connect(URL url);
}</code></pre>
<p>该方法是zookeeper的信息交换接口。同样也是一个可扩展接口,默认实现CuratorZookeeperTransporter类。</p>
<h4>(六)ZkclientZookeeperTransporter</h4>
<pre><code class="java">public class ZkclientZookeeperTransporter implements ZookeeperTransporter {
@Override
public ZookeeperClient connect(URL url) {
// 新建ZkclientZookeeperClient实例
return new ZkclientZookeeperClient(url);
}
}</code></pre>
<p>该类实现了ZookeeperTransporter,其中就是创建了ZkclientZookeeperClient实例。</p>
<h4>(七)CuratorZookeeperTransporter</h4>
<pre><code class="java">public class CuratorZookeeperTransporter implements ZookeeperTransporter {
@Override
public ZookeeperClient connect(URL url) {
// 创建CuratorZookeeperClient实例
return new CuratorZookeeperClient(url);
}
}</code></pre>
<p>该接口实现了ZookeeperTransporter,是ZookeeperTransporter默认的实现类,同样也是创建了;对应的CuratorZookeeperClient实例。</p>
<h4>(八)ZkClientWrapper</h4>
<p>该类是zk客户端的包装类。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 超时事件
*/
private long timeout;
/**
* zk客户端
*/
private ZkClient client;
/**
* 客户端状态
*/
private volatile KeeperState state;
/**
* 客户端线程
*/
private ListenableFutureTask<ZkClient> listenableFutureTask;
/**
* 是否开始
*/
private volatile boolean started = false;</code></pre>
<h5>2.构造方法</h5>
<pre><code class="java">public ZkClientWrapper(final String serverAddr, long timeout) {
this.timeout = timeout;
listenableFutureTask = ListenableFutureTask.create(new Callable<ZkClient>() {
@Override
public ZkClient call() throws Exception {
// 创建zk客户端
return new ZkClient(serverAddr, Integer.MAX_VALUE);
}
});
}</code></pre>
<p>设置了超时时间和客户端线程。</p>
<h5>3.start</h5>
<pre><code class="java">public void start() {
// 如果客户端没有开启
if (!started) {
// 创建连接线程
Thread connectThread = new Thread(listenableFutureTask);
connectThread.setName("DubboZkclientConnector");
connectThread.setDaemon(true);
// 开启线程
connectThread.start();
try {
// 获得zk客户端
client = listenableFutureTask.get(timeout, TimeUnit.MILLISECONDS);
} catch (Throwable t) {
logger.error("Timeout! zookeeper server can not be connected in : " + timeout + "ms!", t);
}
started = true;
} else {
logger.warn("Zkclient has already been started!");
}
}</code></pre>
<p>该方法是客户端启动方法。</p>
<h5>4.addListener</h5>
<pre><code class="java">public void addListener(final IZkStateListener listener) {
// 增加监听器
listenableFutureTask.addListener(new Runnable() {
@Override
public void run() {
try {
client = listenableFutureTask.get();
// 增加监听器
client.subscribeStateChanges(listener);
} catch (InterruptedException e) {
logger.warn(Thread.currentThread().getName() + " was interrupted unexpectedly, which may cause unpredictable exception!");
} catch (ExecutionException e) {
logger.error("Got an exception when trying to create zkclient instance, can not connect to zookeeper server, please check!", e);
}
}
});
}</code></pre>
<p>该方法是为客户端添加监听器。</p>
<p>其他方法都是对于 客户端是否还连接的检测,可自行查看代码。</p>
<h4>(九)ChildListener</h4>
<pre><code class="java">public interface ChildListener {
/**
* 子节点修改
* @param path
* @param children
*/
void childChanged(String path, List<String> children);
}</code></pre>
<p>该接口是子节点的监听器,当子节点变化的时候会用到。</p>
<h4>(十)StateListener</h4>
<pre><code class="java">public interface StateListener {
int DISCONNECTED = 0;
int CONNECTED = 1;
int RECONNECTED = 2;
/**
* 状态修改
* @param connected
*/
void stateChanged(int connected);
}</code></pre>
<p>该接口是状态监听器,其中定义了一个状态更改的方法以及三种状态。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=sWVYuGbBoExpcQ5tH5TfIQ%3D%3D.194AAbAgW7ECyF%2FLMxtc6s0y74u%2BmrYShbtNF0oO0VnI%2BCFA%2FB9wAo1%2FMpc4BXbZuQ2b80Af2krLUs9mTRe7fFeBYRXFEOUjgFIUR4nvLS3EInIFE5W%2B6%2FQ4MsPHMKo37LkygZekL%2F3rsGP7VvKGN2nZL1fPi5RwyWy0b2GScP%2FFU7WE4fu2jEu%2BlO4qYyzSyn3oiyjbusPL46xLEL5zIw%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了基于zookeeper的来实现的远程通信、介绍dubbo-remoting-zookeeper内的源码解析,关键需要对zookeeper有所了解。该篇之后,远程通讯的源码解析就先到这里了,其实大家会发现,如果能够对讲解api系列的文章了解透了,那么后面的文章九很简单,就好像轨道铺好,可以直接顺着轨道往后,根本没有阻碍。接下来我将开始对rpc模块进行讲解。</p>
Dubbo源码解析(十七)远程通信——Netty4
https://segmentfault.com/a/1190000017553202
2018-12-28T17:15:24+08:00
2018-12-28T17:15:24+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
5
<h2>远程通讯——Netty4</h2>
<blockquote>目标:介绍基于netty4的来实现的远程通信、介绍dubbo-remoting-netty4内的源码解析。</blockquote>
<h3>前言</h3>
<p>netty4对netty3兼容性不是很好,并且netty4在很多的术语和api也发生了改变,导致升级netty4会很艰辛,网上应该有很多相关文章,高版本的总有高版本的优势所在,所以dubbo也需要与时俱进,又新增了基于netty4来实现远程通讯模块。下面讲解的,如果跟上一篇文章有重复的地方我就略过去了。关键还是要把远程通讯的api那几篇看懂,看这几篇实现才会很简单。</p>
<p>下面是包的结构:</p>
<p><img src="/img/remote/1460000017553205" alt="dubbo-remoting-netty4包结构" title="dubbo-remoting-netty4包结构"></p>
<h3>源码分析</h3>
<h4>(一)NettyChannel</h4>
<p>该类继承了AbstractChannel,是基于netty4的通道实现类</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 通道集合
*/
private static final ConcurrentMap<Channel, NettyChannel> channelMap = new ConcurrentHashMap<Channel, NettyChannel>();
/**
* 通道
*/
private final Channel channel;
/**
* 属性集合
*/
private final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();</code></pre>
<p>属性跟netty3实现的通道类属性几乎一样,我就不讲解了。</p>
<h5>2.getOrAddChannel</h5>
<pre><code class="java">static NettyChannel getOrAddChannel(Channel ch, URL url, ChannelHandler handler) {
if (ch == null) {
return null;
}
// 首先从集合中取通道
NettyChannel ret = channelMap.get(ch);
// 如果为空,则新建
if (ret == null) {
NettyChannel nettyChannel = new NettyChannel(ch, url, handler);
// 如果通道还活跃着
if (ch.isActive()) {
// 加入集合
ret = channelMap.putIfAbsent(ch, nettyChannel);
}
if (ret == null) {
ret = nettyChannel;
}
}
return ret;
}</code></pre>
<p>该方法是获得通道,如果集合中没有找到对应通道,则创建一个,然后加入集合。</p>
<h5>3.send</h5>
<pre><code class="java">@Override
public void send(Object message, boolean sent) throws RemotingException {
super.send(message, sent);
boolean success = true;
int timeout = 0;
try {
// 写入数据,发送消息
ChannelFuture future = channel.writeAndFlush(message);
// 如果已经发送过
if (sent) {
// 获得超时时间
timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 等待timeout的连接时间后查看是否发送成功
success = future.await(timeout);
}
// 获得异常
Throwable cause = future.cause();
// 如果异常不为空,则抛出异常
if (cause != null) {
throw cause;
}
} catch (Throwable e) {
throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);
}
if (!success) {
throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress()
+ "in timeout(" + timeout + "ms) limit");
}
}</code></pre>
<p>该方法是发送消息,调用了channel.writeAndFlush方法,与netty3的实现只是调用的api不同。</p>
<h5>4.close</h5>
<pre><code class="java">@Override
public void close() {
try {
super.close();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
try {
// 移除通道
removeChannelIfDisconnected(channel);
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
try {
// 清理属性集合
attributes.clear();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
try {
if (logger.isInfoEnabled()) {
logger.info("Close netty channel " + channel);
}
// 关闭通道
channel.close();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}</code></pre>
<p>该方法就是操作了四个步骤,比较清晰。</p>
<h4>(二)NettyClientHandler</h4>
<p>该类继承了ChannelDuplexHandler,是基于netty4实现的客户端通道处理实现类。这里的设计与netty3实现的通道处理器有所不同,netty3实现的通道处理器是被客户端和服务端统一使用的,而在这里服务端和客户端使用了两个不同的Handler来处理。并且netty3的NettyHandler是基于netty3的SimpleChannelHandler设计的,而这里是基于netty4的ChannelDuplexHandler。</p>
<pre><code class="java">/**
* url对象
*/
private final URL url;
/**
* 通道
*/
private final ChannelHandler handler;</code></pre>
<p>该类的属性只有两个,下面实现的方法也都是调用了handler的方法,我就举一个例子:</p>
<pre><code class="java">@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise future)
throws Exception {
// 获得通道
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
try {
// 断开连接
handler.disconnected(channel);
} finally {
// 从集合中移除
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}</code></pre>
<p>可以看到分了三部,获得通道对象,调用handler方法,最后检测一下通道是否活跃。其他方法也是差不多。</p>
<h4>(三)NettyServerHandler</h4>
<p>该类继承了ChannelDuplexHandler,是基于netty4实现的服务端通道处理实现类。</p>
<pre><code class="java">/**
* 连接该服务器的通道数 key为ip:port
*/
private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>(); // <ip:port, channel>
/**
* url对象
*/
private final URL url;
/**
* 通道处理器
*/
private final ChannelHandler handler;</code></pre>
<p>该类有三个属性,比NettyClientHandler多了一个属性channels,下面的实现方法也是一样的,都是调用了handler方法,来看一个例子:</p>
<pre><code class="java">@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 激活事件
ctx.fireChannelActive();
// 获得通道
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
try {
// 如果通道不为空,则加入集合中
if (channel != null) {
channels.put(NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress()), channel);
}
// 连接该通道
handler.connected(channel);
} finally {
// 如果通道不活跃,则移除通道
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}</code></pre>
<p>该方法是通道活跃的时候调用了handler.connected,差不多也是常规套路,就多了激活事件和加入到通道中。其他方法也差不多。</p>
<h4>(四)NettyClient</h4>
<p>该类继承了AbstractClient,是基于netty4实现的客户端实现类。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* NioEventLoopGroup对象
*/
private static final NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(Constants.DEFAULT_IO_THREADS, new DefaultThreadFactory("NettyClientWorker", true));
/**
* 客户端引导类
*/
private Bootstrap bootstrap;
/**
* 通道
*/
private volatile Channel channel; // volatile, please copy reference to use</code></pre>
<p>属性里的NioEventLoopGroup对象是netty4中的对象,什么用处请看netty的解析。</p>
<h5>2.doOpen</h5>
<pre><code class="java">@Override
protected void doOpen() throws Throwable {
// 创建一个客户端的通道处理器
final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
// 创建一个引导类
bootstrap = new Bootstrap();
// 设置可选项
bootstrap.group(nioEventLoopGroup)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
//.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout())
.channel(NioSocketChannel.class);
// 如果连接超时时间小于3s,则设置为3s,也就是说最低的超时时间为3s
if (getConnectTimeout() < 3000) {
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
} else {
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout());
}
// 创建一个客户端
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
// 编解码器
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
// 加入责任链
ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("handler", nettyClientHandler);
}
});
}</code></pre>
<p>该方法还是做了创建客户端,并且打开的操作,其中很多的参数设置操作。</p>
<p>其他方法跟<a href="https://segmentfault.com/a/1190000017530167"> dubbo源码解析(十六)远程通信——Netty3</a>中写到的NettyClient实现一样。</p>
<h4>(五)NettyServer</h4>
<p>该类继承了AbstractServer,实现了Server。是基于netty4实现的服务器类</p>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
/**
* 连接该服务器的通道集合 key为ip:port
*/
private Map<String, Channel> channels; // <ip:port, channel>
/**
* 服务器引导类
*/
private ServerBootstrap bootstrap;
/**
* 通道
*/
private io.netty.channel.Channel channel;
/**
* boss线程组
*/
private EventLoopGroup bossGroup;
/**
* worker线程组
*/
private EventLoopGroup workerGroup;</code></pre>
<p>属性相较netty3而言,新增了两个线程组,同样也是因为netty3和netty4的设计不同。</p>
<h5>2.doOpen</h5>
<pre><code class="java">@Override
protected void doOpen() throws Throwable {
// 创建服务引导类
bootstrap = new ServerBootstrap();
// 创建boss线程组
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
// 创建worker线程组
workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
new DefaultThreadFactory("NettyServerWorker", true));
// 创建服务器处理器
final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
// 获得通道集合
channels = nettyServerHandler.getChannels();
// 设置ventLoopGroup还有可选项
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 编解码器
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
// 增加责任链
ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("handler", nettyServerHandler);
}
});
// bind 绑定
ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
// 等待绑定完成
channelFuture.syncUninterruptibly();
// 设置通道
channel = channelFuture.channel();
}</code></pre>
<p>该方法是创建服务器,并且开启。如果熟悉netty4点朋友应该觉得还是很好理解的。其他方法跟<a href="https://segmentfault.com/a/1190000017530167">《 dubbo源码解析(十六)远程通信——Netty3》</a>中写到的NettyClient实现一样,处理close中要多关闭两个线程组</p>
<h4>(六)NettyTransporter</h4>
<p>该类跟<a href="https://segmentfault.com/a/1190000017530167"> 《dubbo源码解析(十六)远程通信——Netty3》</a>中的NettyTransporter一样的实现。</p>
<h4>(七)NettyCodecAdapter</h4>
<p>该类是基于netty4的编解码器。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 编码器
*/
private final ChannelHandler encoder = new InternalEncoder();
/**
* 解码器
*/
private final ChannelHandler decoder = new InternalDecoder();
/**
* 编解码器
*/
private final Codec2 codec;
/**
* url对象
*/
private final URL url;
/**
* 通道处理器
*/
private final com.alibaba.dubbo.remoting.ChannelHandler handler;</code></pre>
<p>属性跟基于netty3实现的编解码一样。</p>
<h5>2.InternalEncoder</h5>
<pre><code class="java">private class InternalEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
// 创建缓冲区
com.alibaba.dubbo.remoting.buffer.ChannelBuffer buffer = new NettyBackedChannelBuffer(out);
// 获得通道
Channel ch = ctx.channel();
// 获得netty通道
NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);
try {
// 编码
codec.encode(channel, buffer, msg);
} finally {
// 检测通道是否活跃
NettyChannel.removeChannelIfDisconnected(ch);
}
}
}</code></pre>
<p>该内部类是编码器的抽象,主要的编码还是调用了codec.encode。</p>
<h5>3.InternalDecoder</h5>
<pre><code class="java">private class InternalDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception {
// 创建缓冲区
ChannelBuffer message = new NettyBackedChannelBuffer(input);
// 获得通道
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
Object msg;
int saveReaderIndex;
try {
// decode object.
do {
// 记录读索引
saveReaderIndex = message.readerIndex();
try {
// 解码
msg = codec.decode(channel, message);
} catch (IOException e) {
throw e;
}
// 拆包
if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
message.readerIndex(saveReaderIndex);
break;
} else {
//is it possible to go here ?
if (saveReaderIndex == message.readerIndex()) {
throw new IOException("Decode without read data.");
}
// 读取数据
if (msg != null) {
out.add(msg);
}
}
} while (message.readable());
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}
}</code></pre>
<p>该内部类是解码器的抽象类,其中关键的是调用了codec.decode。</p>
<h4>(八)NettyBackedChannelBuffer</h4>
<p>该类是缓冲区类。</p>
<pre><code class="java">/**
* 缓冲区
*/
private ByteBuf buffer;</code></pre>
<p>其中的方法几乎都调用了该属性的方法。而ByteBuf是netty4中的字节数据的容器。</p>
<h4>(九)FormattingTuple和MessageFormatter</h4>
<p>这两个类是用于用于格式化的,是从netty4中复制出来的,其中并且稍微做了一下改动。我就不再讲解了。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=ZUB%2F6Ko6pez%2BB1Aur9U5Dg%3D%3D.JzoRJWGVqhBLo2HbKZVZ4cqIEwH%2FI4B1itrHW6QZzI7KUyAaG1kHwr6Yb6%2BHsV7ios5e8XsB111QGtQdKaEfxpU%2Ftoj9Hvkf4LWAFJ%2BZXzVu4SOWJYkFDN%2BYHt%2Fc0yLDfEGb2CKInPqRjwgldH274fPae%2BuOFfZF2PnI22925yXZgCY%2F2916kN%2F%2FP8c2YKqMeUc99wQvWOLfwv9FLdfh%2FQ%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了基于netty4的来实现的远程通信、介绍dubbo-remoting-netty4内的源码解析,关键需要对netty4有所了解。下一篇我会讲解基于zookeeper实现远程通信部分。</p>
Dubbo源码解析(十六)远程通信——Netty3
https://segmentfault.com/a/1190000017530167
2018-12-27T07:48:07+08:00
2018-12-27T07:48:07+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
4
<h2>远程通讯——Netty3</h2>
<blockquote>目标:介绍基于netty3的来实现的远程通信、介绍dubbo-remoting-netty内的源码解析。</blockquote>
<h3>前言</h3>
<p>现在dubbo默认的网络传输Transport接口默认实现的还是基于netty3实现的网络传输,不过马上后面默认实现就要改为netty4了。由于netty4对netty3对兼容性不是很好,所以保留了两个版本的实现。</p>
<p>下面是包结构:</p>
<p><img src="/img/remote/1460000017530170" alt="netty3包结构" title="netty3包结构"></p>
<h3>源码分析</h3>
<h4>(一)NettyChannel</h4>
<p>该类继承了AbstractChannel类,是基于netty3实现的通道。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 通道集合
*/
private static final ConcurrentMap<org.jboss.netty.channel.Channel, NettyChannel> channelMap = new ConcurrentHashMap<org.jboss.netty.channel.Channel, NettyChannel>();
/**
* 通道
*/
private final org.jboss.netty.channel.Channel channel;
/**
* 属性集合
*/
private final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();</code></pre>
<h5>2.getOrAddChannel</h5>
<pre><code class="java">static NettyChannel getOrAddChannel(org.jboss.netty.channel.Channel ch, URL url, ChannelHandler handler) {
if (ch == null) {
return null;
}
// 首先从集合中取通道
NettyChannel ret = channelMap.get(ch);
// 如果为空,则新建
if (ret == null) {
NettyChannel nc = new NettyChannel(ch, url, handler);
// 如果通道连接着
if (ch.isConnected()) {
// 加入集合
ret = channelMap.putIfAbsent(ch, nc);
}
if (ret == null) {
ret = nc;
}
}
return ret;
}</code></pre>
<p>该方法是获得通道,当通道在集合中没有的时候,新建一个通道。</p>
<h5>3.removeChannelIfDisconnected</h5>
<pre><code class="java">static void removeChannelIfDisconnected(org.jboss.netty.channel.Channel ch) {
if (ch != null && !ch.isConnected()) {
channelMap.remove(ch);
}
}</code></pre>
<p>该方法是当通道没有连接的时候,从集合中移除它。</p>
<h5>4.send</h5>
<pre><code class="java">@Override
public void send(Object message, boolean sent) throws RemotingException {
super.send(message, sent);
boolean success = true;
int timeout = 0;
try {
// 写入数据,发送消息
ChannelFuture future = channel.write(message);
// 如果已经发送过
if (sent) {
// 获得超时时间
timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 等待timeout的连接时间后查看是否发送成功
success = future.await(timeout);
}
// 看是否有异常
Throwable cause = future.getCause();
if (cause != null) {
throw cause;
}
} catch (Throwable e) {
throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);
}
if (!success) {
throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress()
+ "in timeout(" + timeout + "ms) limit");
}
}</code></pre>
<p>该方法是发送消息,其中用到了channe.write方法传输消息,并且通过返回的future来判断是否发送成功。</p>
<h5>5.close</h5>
<pre><code class="java">@Override
public void close() {
try {
super.close();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
try {
// 如果通道断开,则移除该通道
removeChannelIfDisconnected(channel);
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
try {
// 清空属性
attributes.clear();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
try {
if (logger.isInfoEnabled()) {
logger.info("Close netty channel " + channel);
}
// 关闭通道
channel.close();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}</code></pre>
<p>该方法是关闭通道,做了三个操作,分别是从集合中移除、清除属性、关闭通道。</p>
<p>其他实现方法比较简单,我就讲解了。</p>
<h4>(二)NettyHandler</h4>
<p>该类继承了SimpleChannelHandler类,是基于netty3的通道处理器,而该类被加上了@Sharable注解,也就是说该处理器可以从属于多个ChannelPipeline</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 通道集合,key是主机地址 ip:port
*/
private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>(); // <ip:port, channel>
/**
* url对象
*/
private final URL url;
/**
* 通道
*/
private final ChannelHandler handler;</code></pre>
<p>该类的属性比较简单,并且该类中实现的方法都是调用了属性handler的方法,我举一个例子来讲,其他的可以自己查看源码,比较简单。</p>
<pre><code class="java">@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
// 获得通道实例
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
try {
if (channel != null) {
// 保存该通道,加入到集合中
channels.put(NetUtils.toAddressString((InetSocketAddress) ctx.getChannel().getRemoteAddress()), channel);
}
// 连接
handler.connected(channel);
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
}
}</code></pre>
<p>该方法是通道连接的方法,其中先获取了通道实例,然后吧该实例加入到集合中,最好带哦用handler.connected来进行连接。</p>
<h4>(三)NettyClient</h4>
<p>该类继承了AbstractClient,是基于netty3实现的客户端类。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
// ChannelFactory's closure has a DirectMemory leak, using static to avoid
// https://issues.jboss.org/browse/NETTY-424
/**
* 通道工厂,用static来避免直接缓存区的一个OOM问题
*/
private static final ChannelFactory channelFactory = new NioClientSocketChannelFactory(Executors.newCachedThreadPool(new NamedThreadFactory("NettyClientBoss", true)),
Executors.newCachedThreadPool(new NamedThreadFactory("NettyClientWorker", true)),
Constants.DEFAULT_IO_THREADS);
/**
* 客户端引导对象
*/
private ClientBootstrap bootstrap;
/**
* 通道
*/
private volatile Channel channel; // volatile, please copy reference to use</code></pre>
<p>上述属性中ChannelFactory用了static修饰,为了避免netty3中会有直接缓冲内存泄漏的现象,具体的讨论可以访问注释中的讨论。</p>
<h5>2.doOpen</h5>
<pre><code class="java">@Override
protected void doOpen() throws Throwable {
// 设置日志工厂
NettyHelper.setNettyLoggerFactory();
// 实例化客户端引导类
bootstrap = new ClientBootstrap(channelFactory);
// config
// @see org.jboss.netty.channel.socket.SocketChannelConfig
// 配置选择项
bootstrap.setOption("keepAlive", true);
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("connectTimeoutMillis", getConnectTimeout());
// 创建通道处理器
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
// 设置责任链路
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
/**
* 获得通道
* @return
*/
@Override
public ChannelPipeline getPipeline() {
// 新建编解码
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
// 获得管道
ChannelPipeline pipeline = Channels.pipeline();
// 设置解码器
pipeline.addLast("decoder", adapter.getDecoder());
// 设置编码器
pipeline.addLast("encoder", adapter.getEncoder());
// 设置通道处理器
pipeline.addLast("handler", nettyHandler);
// 返回通道
return pipeline;
}
});
}</code></pre>
<p>该方法是创建客户端,并且打开,其中的逻辑就是用netty3的客户端引导类来创建一个客户端,如果对netty不熟悉的朋友可以先补补netty知识。</p>
<h5>3.doConnect</h5>
<pre><code class="java">@Override
protected void doConnect() throws Throwable {
long start = System.currentTimeMillis();
// 用引导类连接
ChannelFuture future = bootstrap.connect(getConnectAddress());
try {
// 在超时时间内是否连接完成
boolean ret = future.awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS);
if (ret && future.isSuccess()) {
// 获得通道
Channel newChannel = future.getChannel();
// 异步修改此通道
newChannel.setInterestOps(Channel.OP_READ_WRITE);
try {
// Close old channel 关闭旧的通道
Channel oldChannel = NettyClient.this.channel; // copy reference
if (oldChannel != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close old netty channel " + oldChannel + " on create new netty channel " + newChannel);
}
// 关闭
oldChannel.close();
} finally {
// 移除通道
NettyChannel.removeChannelIfDisconnected(oldChannel);
}
}
} finally {
// 如果客户端关闭
if (NettyClient.this.isClosed()) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close new netty channel " + newChannel + ", because the client closed.");
}
// 关闭通道
newChannel.close();
} finally {
NettyClient.this.channel = null;
NettyChannel.removeChannelIfDisconnected(newChannel);
}
} else {
NettyClient.this.channel = newChannel;
}
}
} else if (future.getCause() != null) {
throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+ getRemoteAddress() + ", error message is:" + future.getCause().getMessage(), future.getCause());
} else {
throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server "
+ getRemoteAddress() + " client-side timeout "
+ getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client "
+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion());
}
} finally {
// 如果客户端没有连接
if (!isConnected()) {
// 取消future
future.cancel();
}
}
}</code></pre>
<p>该方法是客户端连接服务器的方法。其中调用了bootstrap.connect。后面的逻辑是用来检测是否连接,最后如果未连接,则会取消该连接任务。</p>
<h5>4.doClose</h5>
<pre><code class="java">@Override
protected void doClose() throws Throwable {
/*try {
bootstrap.releaseExternalResources();
} catch (Throwable t) {
logger.warn(t.getMessage());
}*/
}</code></pre>
<p>在这里不能关闭是因为channelFactory 是静态属性,被多个 NettyClient 共用。所以不能释放资源。</p>
<h4>(四)NettyServer</h4>
<p>该类继承了AbstractServer,实现了Server,是基于netty3实现的服务器类。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 连接该服务器的通道集合
*/
private Map<String, Channel> channels; // <ip:port, channel>
/**
* 服务器引导类对象
*/
private ServerBootstrap bootstrap;
/**
* 通道
*/
private org.jboss.netty.channel.Channel channel;</code></pre>
<h5>2.doOpen</h5>
<pre><code class="java">@Override
protected void doOpen() throws Throwable {
// 设置日志工厂
NettyHelper.setNettyLoggerFactory();
// 创建线程池
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
// 新建通道工厂
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
// 新建服务引导类对象
bootstrap = new ServerBootstrap(channelFactory);
// 新建通道处理器
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
// 获得通道集合
channels = nettyHandler.getChannels();
// https://issues.jboss.org/browse/NETTY-365
// https://issues.jboss.org/browse/NETTY-379
// final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
// 禁用nagle算法,将数据立即发送出去。纳格算法是以减少封包传送量来增进TCP/IP网络的效能
bootstrap.setOption("child.tcpNoDelay", true);
// 设置管道工厂
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
/**
* 获得通道
* @return
*/
@Override
public ChannelPipeline getPipeline() {
// 新建编解码器
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
// 获得通道
ChannelPipeline pipeline = Channels.pipeline();
/*int idleTimeout = getIdleTimeout();
if (idleTimeout > 10000) {
pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
}*/
// 设置解码器
pipeline.addLast("decoder", adapter.getDecoder());
// 设置编码器
pipeline.addLast("encoder", adapter.getEncoder());
// 设置通道处理器
pipeline.addLast("handler", nettyHandler);
// 返回通道
return pipeline;
}
});
// bind 绑定地址,也就是启用服务器
channel = bootstrap.bind(getBindAddress());
}</code></pre>
<p>该方法是创建服务器,并且打开服务器。同样创建服务器的方式跟正常的用netty创建服务器方式一样,只是新加了编码器和解码器。还有一个注意点就是这里ServerBootstrap 的可选项。</p>
<h5>3.doClose</h5>
<pre><code class="java">@Override
protected void doClose() throws Throwable {
try {
if (channel != null) {
// unbind.关闭通道
channel.close();
}
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
try {
// 获得所有连接该服务器的通道集合
Collection<com.alibaba.dubbo.remoting.Channel> channels = getChannels();
if (channels != null && !channels.isEmpty()) {
// 遍历通道集合
for (com.alibaba.dubbo.remoting.Channel channel : channels) {
try {
// 关闭通道连接
channel.close();
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
}
}
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
try {
if (bootstrap != null) {
// release external resource. 回收资源
bootstrap.releaseExternalResources();
}
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
try {
if (channels != null) {
// 清空集合
channels.clear();
}
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
}</code></pre>
<p>该方法是关闭服务器,一系列的操作很清晰,我就不多说了。</p>
<h5>4.getChannels</h5>
<pre><code class="java">@Override
public Collection<Channel> getChannels() {
Collection<Channel> chs = new HashSet<Channel>();
for (Channel channel : this.channels.values()) {
// 如果通道连接,则加入集合,返回
if (channel.isConnected()) {
chs.add(channel);
} else {
channels.remove(NetUtils.toAddressString(channel.getRemoteAddress()));
}
}
return chs;
}</code></pre>
<p>该方法是返回连接该服务器的通道集合,并且用了HashSet保存,不会重复。</p>
<h4>(五)NettyTransporter</h4>
<pre><code class="java">public class NettyTransporter implements Transporter {
public static final String NAME = "netty";
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
// 创建一个NettyServer
return new NettyServer(url, listener);
}
@Override
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
// 创建一个NettyClient
return new NettyClient(url, listener);
}
}</code></pre>
<p>该类就是基于netty3的Transporter实现类,同样两个方法也是分别创建了NettyServer和NettyClient。</p>
<h4>(六)NettyHelper</h4>
<p>该类是设置日志的工具类,其中基于netty3的InternalLoggerFactory实现类一个DubboLoggerFactory。这个我就不讲解了,比较好理解,不理解也无伤大雅。</p>
<h4>(七)NettyCodecAdapter</h4>
<p>该类是基于netty3实现的编解码类。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 编码者
*/
private final ChannelHandler encoder = new InternalEncoder();
/**
* 解码者
*/
private final ChannelHandler decoder = new InternalDecoder();
/**
* 编解码器
*/
private final Codec2 codec;
/**
* url对象
*/
private final URL url;
/**
* 缓冲区大小
*/
private final int bufferSize;
/**
* 通道对象
*/
private final com.alibaba.dubbo.remoting.ChannelHandler handler;</code></pre>
<p>InternalEncoder和InternalDecoder属性是该类的内部类,分别掌管着编码和解码</p>
<h5>2.构造方法</h5>
<pre><code class="java">public NettyCodecAdapter(Codec2 codec, URL url, com.alibaba.dubbo.remoting.ChannelHandler handler) {
this.codec = codec;
this.url = url;
this.handler = handler;
int b = url.getPositiveParameter(Constants.BUFFER_KEY, Constants.DEFAULT_BUFFER_SIZE);
// 如果缓存区大小在16字节以内,则设置配置大小,如果不是,则设置8字节的缓冲区大小
this.bufferSize = b >= Constants.MIN_BUFFER_SIZE && b <= Constants.MAX_BUFFER_SIZE ? b : Constants.DEFAULT_BUFFER_SIZE;
}</code></pre>
<p>你会发现对于缓存区大小的规则都是一样的。</p>
<h5>3.InternalEncoder</h5>
<pre><code class="java">@Sharable
private class InternalEncoder extends OneToOneEncoder {
@Override
protected Object encode(ChannelHandlerContext ctx, Channel ch, Object msg) throws Exception {
// 动态分配一个1k的缓冲区
com.alibaba.dubbo.remoting.buffer.ChannelBuffer buffer =
com.alibaba.dubbo.remoting.buffer.ChannelBuffers.dynamicBuffer(1024);
// 获得通道对象
NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);
try {
// 编码
codec.encode(channel, buffer, msg);
} finally {
NettyChannel.removeChannelIfDisconnected(ch);
}
// 基于buteBuffer创建一个缓冲区,并且写入数据
return ChannelBuffers.wrappedBuffer(buffer.toByteBuffer());
}
}</code></pre>
<p>该内部类实现类编码的逻辑,主要调用了codec.encode。</p>
<h5>4.InternalDecoder</h5>
<pre><code class="java">private class InternalDecoder extends SimpleChannelUpstreamHandler {
private com.alibaba.dubbo.remoting.buffer.ChannelBuffer buffer =
com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
Object o = event.getMessage();
// 如果消息不是一个ChannelBuffer类型
if (!(o instanceof ChannelBuffer)) {
// 转发事件到与此上下文关联的处理程序最近的上游
ctx.sendUpstream(event);
return;
}
ChannelBuffer input = (ChannelBuffer) o;
// 如果可读数据不大于0,直接返回
int readable = input.readableBytes();
if (readable <= 0) {
return;
}
com.alibaba.dubbo.remoting.buffer.ChannelBuffer message;
if (buffer.readable()) {
// 判断buffer是否是动态分配的缓冲区
if (buffer instanceof DynamicChannelBuffer) {
// 写入数据
buffer.writeBytes(input.toByteBuffer());
message = buffer;
} else {
// 需要的缓冲区大小
int size = buffer.readableBytes() + input.readableBytes();
// 动态生成缓冲区
message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.dynamicBuffer(
size > bufferSize ? size : bufferSize);
// 把buffer数据写入message
message.writeBytes(buffer, buffer.readableBytes());
// 把input数据写入message
message.writeBytes(input.toByteBuffer());
}
} else {
// 否则 基于ByteBuffer通过buffer来创建一个新的缓冲区
message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer(
input.toByteBuffer());
}
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
Object msg;
int saveReaderIndex;
try {
// decode object.
do {
saveReaderIndex = message.readerIndex();
try {
// 解码
msg = codec.decode(channel, message);
} catch (IOException e) {
buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
throw e;
}
// 拆包
if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
message.readerIndex(saveReaderIndex);
break;
} else {
// 如果已经到达读索引,则没有数据可解码
if (saveReaderIndex == message.readerIndex()) {
buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
throw new IOException("Decode without read data.");
}
//
if (msg != null) {
// 将消息发送到指定关联的处理程序最近的上游
Channels.fireMessageReceived(ctx, msg, event.getRemoteAddress());
}
}
} while (message.readable());
} finally {
// 如果消息还有可读数据,则丢弃
if (message.readable()) {
message.discardReadBytes();
buffer = message;
} else {
buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER;
}
NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
ctx.sendUpstream(e);
}
}</code></pre>
<p>该内部类实现了解码的逻辑,其中大部分逻辑都在对数据做读写,关键的解码调用了codec.decode。</p>
<h4>(八)NettyBackedChannelBufferFactory</h4>
<p>该类是创建缓冲区的工厂类。它实现了ChannelBufferFactory接口,也就是实现类它的三种获得缓冲区的方法。</p>
<pre><code class="java">public class NettyBackedChannelBufferFactory implements ChannelBufferFactory {
/**
* 单例
*/
private static final NettyBackedChannelBufferFactory INSTANCE = new NettyBackedChannelBufferFactory();
public static ChannelBufferFactory getInstance() {
return INSTANCE;
}
@Override
public ChannelBuffer getBuffer(int capacity) {
return new NettyBackedChannelBuffer(ChannelBuffers.dynamicBuffer(capacity));
}
@Override
public ChannelBuffer getBuffer(byte[] array, int offset, int length) {
org.jboss.netty.buffer.ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(length);
buffer.writeBytes(array, offset, length);
return new NettyBackedChannelBuffer(buffer);
}
@Override
public ChannelBuffer getBuffer(ByteBuffer nioBuffer) {
return new NettyBackedChannelBuffer(ChannelBuffers.wrappedBuffer(nioBuffer));
}
}</code></pre>
<p>可以看到,都是创建了一个NettyBackedChannelBuffer,下面讲解NettyBackedChannelBuffer。</p>
<h4>(九)NettyBackedChannelBuffer</h4>
<p>该类是基于netty3的buffer重新实现的缓冲区,它实现了ChannelBuffer接口,并且有一个属性:</p>
<pre><code class="java">private org.jboss.netty.buffer.ChannelBuffer buffer;</code></pre>
<p>那么其中的几乎所有方法都是调用了这个buffer的方法,因为我在<a href="https://segmentfault.com/a/1190000017483889">dubbo源码解析(十一)远程通信——Buffer</a>中写到ChannelBuffer接口方法定义跟netty中的缓冲区定义几乎一样,连注释都几乎一样。所有知识单纯的调用了buffer的方法。具体的代码可以查看我的GitHub</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=nrPVRAP582GAU1xLK7LiNA%3D%3D.i0EEi%2Bfy9TbDcCZLGPhl5rqJDEqKwbA1DTVs4CaUwB0%2FFsSvyQg4mv24M53T7f59IusBEKHILzwuFWK5LvS%2BTGCtLq1CLoBFV5zb16krqJlpGlixjQGbchLwb7nCriNakNXIaSye2fWKOXojfNyxNoh1X0wsPD%2FFFSnUlDC%2FG0sMjzXr3Iqv3GnzrW%2FwoXk1hWfA1D5%2Bka%2FL%2FxnCwkHq8Q%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了基于netty3的来实现的远程通信、介绍dubbo-remoting-netty内的源码解析,关键需要对netty有所了解。下一篇我会讲解基于netty4实现远程通信部分。</p>
Dubbo源码解析(十五)远程通信——Mina
https://segmentfault.com/a/1190000017519378
2018-12-26T13:12:12+08:00
2018-12-26T13:12:12+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
3
<h2>远程通讯——Mina</h2>
<blockquote>目标:介绍基于Mina的来实现的远程通信、介绍dubbo-remoting-mina内的源码解析。</blockquote>
<h3>前言</h3>
<p>Apache MINA是一个网络应用程序框架,可帮助用户轻松开发高性能和高可扩展性的网络应用程序。它通过Java NIO在各种传输(如TCP / IP和UDP / IP)上提供抽象的事件驱动异步API。它通常被称为NIO框架库、客户端服务器框架库或者网络套接字库。那么本问就要讲解在dubbo项目中,基于mina的API实现服务端和客户端来完成远程通讯这件事情。</p>
<p>下面是mina实现的包结构:</p>
<p><img src="/img/remote/1460000017519381" alt="mina包结构" title="mina包结构"></p>
<h3>源码分析</h3>
<h4>(一)MinaChannel</h4>
<p>该类继承了AbstractChannel,是基于mina实现的通道。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(MinaChannel.class);
/**
* 通道的key
*/
private static final String CHANNEL_KEY = MinaChannel.class.getName() + ".CHANNEL";
/**
* mina中的一个句柄,表示两个端点之间的连接,与传输类型无关
*/
private final IoSession session;</code></pre>
<p>该类的属性除了封装了一个CHANNEL_KEY以外,还用到了mina中的IoSession,它封装着一个连接所需要的方法,比如获得远程地址等。</p>
<h5>2.getOrAddChannel</h5>
<pre><code class="java">static MinaChannel getOrAddChannel(IoSession session, URL url, ChannelHandler handler) {
// 如果连接session为空,则返回空
if (session == null) {
return null;
}
// 获得MinaChannel实例
MinaChannel ret = (MinaChannel) session.getAttribute(CHANNEL_KEY);
// 如果不存在,则创建
if (ret == null) {
// 创建一个MinaChannel实例
ret = new MinaChannel(session, url, handler);
// 如果两个端点连接
if (session.isConnected()) {
// 把新创建的MinaChannel添加到session 中
MinaChannel old = (MinaChannel) session.setAttribute(CHANNEL_KEY, ret);
// 如果属性的旧值不为空,则重新设置旧值
if (old != null) {
session.setAttribute(CHANNEL_KEY, old);
ret = old;
}
}
}
return ret;
}</code></pre>
<p>该方法是一个获得MinaChannel对象的方法,其中每一个MinaChannel都会被放在session的属性值中。</p>
<h5>3.removeChannelIfDisconnected</h5>
<pre><code class="java">static void removeChannelIfDisconnected(IoSession session) {
if (session != null && !session.isConnected()) {
session.removeAttribute(CHANNEL_KEY);
}
}</code></pre>
<p>该方法是当没有连接时移除该通道,比较简单。</p>
<h5>4.send</h5>
<pre><code class="java">@Override
public void send(Object message, boolean sent) throws RemotingException {
super.send(message, sent);
boolean success = true;
int timeout = 0;
try {
// 发送消息,返回future
WriteFuture future = session.write(message);
// 如果已经发送过了
if (sent) {
// 获得延迟时间
timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 等待timeout的连接时间后查看是否发送成功
success = future.join(timeout);
}
} catch (Throwable e) {
throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);
}
if (!success) {
throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress()
+ "in timeout(" + timeout + "ms) limit");
}
}</code></pre>
<p>该方法是最关键的发送消息,其中调用到了session的write方法,就是mina封装的发送消息。并且根据返回的WriteFuture对象来判断是否发送成功。</p>
<h4>(二)MinaHandler</h4>
<p>该类继承了IoHandlerAdapter,是通道处理器实现类,其中就是mina项目中IoHandler接口的几个 方法。</p>
<pre><code class="java">/**
* url对象
*/
private final URL url;
/**
* 通道处理器对象
*/
private final ChannelHandler handler;</code></pre>
<p>该类有两个属性,上述提到的实现IoHandler接口方法都是调用了handler来实现的,我就举例讲一个,其他的都一样的写法:</p>
<pre><code class="java">@Override
public void sessionOpened(IoSession session) throws Exception {
// 获得MinaChannel对象
MinaChannel channel = MinaChannel.getOrAddChannel(session, url, handler);
try {
// 调用接连该通道
handler.connected(channel);
} finally {
// 如果没有连接则移除通道
MinaChannel.removeChannelIfDisconnected(session);
}
}</code></pre>
<p>该方法在IoHandler中叫做sessionOpened,其实就是连接方法,所以调用的是handler.connected。其他方法也一样,请自行查看。</p>
<h4>(三)MinaClient</h4>
<p>该类继承了AbstractClient类,是基于mina实现的客户端类。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 套接字连接集合
*/
private static final Map<String, SocketConnector> connectors = new ConcurrentHashMap<String, SocketConnector>();
/**
* 连接的key
*/
private String connectorKey;
/**
* 套接字连接者
*/
private SocketConnector connector;
/**
* 一个句柄
*/
private volatile IoSession session; // volatile, please copy reference to use</code></pre>
<p>该类中的属性都跟mina项目中封装类有关系。</p>
<h5>2.doOpen</h5>
<pre><code class="java">@Override
protected void doOpen() throws Throwable {
// 用url来作为key
connectorKey = getUrl().toFullString();
// 先从集合中取套接字连接
SocketConnector c = connectors.get(connectorKey);
if (c != null) {
connector = c;
// 如果为空
} else {
// set thread pool. 设置线程池
connector = new SocketConnector(Constants.DEFAULT_IO_THREADS,
Executors.newCachedThreadPool(new NamedThreadFactory("MinaClientWorker", true)));
// config 获得套接字连接配置
SocketConnectorConfig cfg = (SocketConnectorConfig) connector.getDefaultConfig();
cfg.setThreadModel(ThreadModel.MANUAL);
// 启用TCP_NODELAY
cfg.getSessionConfig().setTcpNoDelay(true);
// 启用SO_KEEPALIVE
cfg.getSessionConfig().setKeepAlive(true);
int timeout = getConnectTimeout();
// 设置连接超时时间
cfg.setConnectTimeout(timeout < 1000 ? 1 : timeout / 1000);
// set codec.
// 设置编解码器
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MinaCodecAdapter(getCodec(), getUrl(), this)));
// 加入集合
connectors.put(connectorKey, connector);
}
}</code></pre>
<p>该方法是打开客户端,在mina中用套接字连接者connector来表示。其中的操作就是新建一个connector,并且设置相应的属性,然后加入集合。</p>
<h5>3.doConnect</h5>
<pre><code class="java">@Override
protected void doConnect() throws Throwable {
// 连接服务器
ConnectFuture future = connector.connect(getConnectAddress(), new MinaHandler(getUrl(), this));
long start = System.currentTimeMillis();
final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
// 用于对线程的阻塞和唤醒
final CountDownLatch finish = new CountDownLatch(1); // resolve future.awaitUninterruptibly() dead lock
// 加入监听器
future.addListener(new IoFutureListener() {
@Override
public void operationComplete(IoFuture future) {
try {
// 如果已经读完了
if (future.isReady()) {
// 创建获得该连接的IoSession实例
IoSession newSession = future.getSession();
try {
// Close old channel 关闭旧的session
IoSession oldSession = MinaClient.this.session; // copy reference
if (oldSession != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close old mina channel " + oldSession + " on create new mina channel " + newSession);
}
// 关闭连接
oldSession.close();
} finally {
// 移除通道
MinaChannel.removeChannelIfDisconnected(oldSession);
}
}
} finally {
// 如果MinaClient关闭了
if (MinaClient.this.isClosed()) {
try {
if (logger.isInfoEnabled()) {
logger.info("Close new mina channel " + newSession + ", because the client closed.");
}
// 关闭session
newSession.close();
} finally {
MinaClient.this.session = null;
MinaChannel.removeChannelIfDisconnected(newSession);
}
} else {
// 设置新的session
MinaClient.this.session = newSession;
}
}
}
} catch (Exception e) {
exception.set(e);
} finally {
// 减少数量,释放所有等待的线程
finish.countDown();
}
}
});
try {
// 当前线程等待,直到锁存器倒计数到零,除非线程被中断,或者指定的等待时间过去
finish.await(getConnectTimeout(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new RemotingException(this, "client(url: " + getUrl() + ") failed to connect to server " + getRemoteAddress() + " client-side timeout "
+ getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start)
+ "ms) from netty client " + NetUtils.getLocalHost() + " using dubbo version "
+ Version.getVersion() + ", cause: " + e.getMessage(), e);
}
Throwable e = exception.get();
if (e != null) {
throw e;
}
}</code></pre>
<p>该方法是客户端连接服务器的实现方法。其中用到了CountDownLatch来代表完成完成事件,它来做一个线程等待,直到1个线程完成上述的动作,也就是连接完成结束,才释放等待的线程。保证每次只有一条线程去连接,解决future.awaitUninterruptibly()死锁问题。</p>
<p>其他方法请自行查看我写的注释。</p>
<h4>(四)MinaServer</h4>
<p>该类继承了AbstractServer,是基于mina实现的服务端实现类。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(MinaServer.class);
/**
* 套接字接收者对象
*/
private SocketAcceptor acceptor;</code></pre>
<h5>2.doOpen</h5>
<pre><code class="java">@Override
protected void doOpen() throws Throwable {
// set thread pool.
// 创建套接字接收者对象
acceptor = new SocketAcceptor(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
Executors.newCachedThreadPool(new NamedThreadFactory("MinaServerWorker",
true)));
// config
// 设置配置
SocketAcceptorConfig cfg = (SocketAcceptorConfig) acceptor.getDefaultConfig();
cfg.setThreadModel(ThreadModel.MANUAL);
// set codec. 设置编解码器
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MinaCodecAdapter(getCodec(), getUrl(), this)));
// 开启服务器
acceptor.bind(getBindAddress(), new MinaHandler(getUrl(), this));
}</code></pre>
<p>该方法是创建服务器,并且打开服务器。关键就是调用了acceptor的方法。</p>
<h5>3.doClose</h5>
<pre><code class="java">@Override
protected void doClose() throws Throwable {
try {
if (acceptor != null) {
// 取消绑定,也就是关闭服务器
acceptor.unbind(getBindAddress());
}
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
}</code></pre>
<p>该方法是关闭服务器,就是调用了acceptor.unbind方法。</p>
<h5>4.getChannels</h5>
<pre><code class="java">@Override
public Collection<Channel> getChannels() {
// 获得连接到该服务器到所有连接句柄
Set<IoSession> sessions = acceptor.getManagedSessions(getBindAddress());
Collection<Channel> channels = new HashSet<Channel>();
for (IoSession session : sessions) {
if (session.isConnected()) {
// 每次都用一个连接句柄创建一个通道
channels.add(MinaChannel.getOrAddChannel(session, getUrl(), this));
}
}
return channels;
}</code></pre>
<p>该方法是获得所有连接该服务器的通道。</p>
<h5>5.getChannel</h5>
<pre><code class="java">@Override
public Channel getChannel(InetSocketAddress remoteAddress) {
// 获得连接到该服务器到所有连接句柄
Set<IoSession> sessions = acceptor.getManagedSessions(getBindAddress());
// 遍历所有句柄,找到要找的通道
for (IoSession session : sessions) {
if (session.getRemoteAddress().equals(remoteAddress)) {
return MinaChannel.getOrAddChannel(session, getUrl(), this);
}
}
return null;
}</code></pre>
<p>该方法是获得地址对应的单个通道。</p>
<h4>(五)MinaTransporter</h4>
<pre><code class="java">public class MinaTransporter implements Transporter {
public static final String NAME = "mina";
@Override
public Server bind(URL url, ChannelHandler handler) throws RemotingException {
// 创建MinaServer实例
return new MinaServer(url, handler);
}
@Override
public Client connect(URL url, ChannelHandler handler) throws RemotingException {
// 创建MinaClient实例
return new MinaClient(url, handler);
}
}</code></pre>
<p>该类实现了Transporter接口,是基于mina的传输层实现。可以看到,bind和connect方法分别就是创建了MinaServer和MinaClient实例。这里我建议查看一下《<a href="https://segmentfault.com/a/1190000017390253">dubbo源码解析(九)远程通信——Transport层</a>》。</p>
<h4>(六)MinaCodecAdapter</h4>
<p>该类是基于mina实现的编解码类,实现了ProtocolCodecFactory。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 编码对象
*/
private final ProtocolEncoder encoder = new InternalEncoder();
/**
* 解码对象
*/
private final ProtocolDecoder decoder = new InternalDecoder();
/**
* 编解码器
*/
private final Codec2 codec;
/**
* url对象
*/
private final URL url;
/**
* 通道处理器对象
*/
private final ChannelHandler handler;
/**
* 缓冲区大小
*/
private final int bufferSize;</code></pre>
<p>属性比较好理解,该编解码器用到了ProtocolEncoder和ProtocolDecoder,而InternalEncoder和InternalDecoder两个类是该类的内部类,它们实现了ProtocolEncoder和ProtocolDecoder,关键的编解码逻辑在这两个类中实现。</p>
<h5>2.构造方法</h5>
<pre><code class="java">public MinaCodecAdapter(Codec2 codec, URL url, ChannelHandler handler) {
this.codec = codec;
this.url = url;
this.handler = handler;
int b = url.getPositiveParameter(Constants.BUFFER_KEY, Constants.DEFAULT_BUFFER_SIZE);
// 如果缓存区大小在16字节以内,则设置配置大小,如果不是,则设置8字节的缓冲区大小
this.bufferSize = b >= Constants.MIN_BUFFER_SIZE && b <= Constants.MAX_BUFFER_SIZE ? b : Constants.DEFAULT_BUFFER_SIZE;
}</code></pre>
<h5>3.InternalEncoder</h5>
<pre><code class="java">private class InternalEncoder implements ProtocolEncoder {
@Override
public void dispose(IoSession session) throws Exception {
}
@Override
public void encode(IoSession session, Object msg, ProtocolEncoderOutput out) throws Exception {
// 动态分配一个1k的缓冲区
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(1024);
// 获得通道
MinaChannel channel = MinaChannel.getOrAddChannel(session, url, handler);
try {
// 编码
codec.encode(channel, buffer, msg);
} finally {
// 检测是否断开连接,如果断开,则移除
MinaChannel.removeChannelIfDisconnected(session);
}
// 写数据到out中
out.write(ByteBuffer.wrap(buffer.toByteBuffer()));
out.flush();
}
}</code></pre>
<p>该内部类是编码类,其中的encode方法中写到了编码核心调用的是codec.encode。</p>
<h5>4.InternalDecoder</h5>
<pre><code class="java">private class InternalDecoder implements ProtocolDecoder {
private ChannelBuffer buffer = ChannelBuffers.EMPTY_BUFFER;
@Override
public void decode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception {
int readable = in.limit();
if (readable <= 0) return;
ChannelBuffer frame;
// 如果缓冲区还有可读字节数
if (buffer.readable()) {
// 如果缓冲区是DynamicChannelBuffer类型的
if (buffer instanceof DynamicChannelBuffer) {
// 往buffer中写入数据
buffer.writeBytes(in.buf());
frame = buffer;
} else {
// 缓冲区大小
int size = buffer.readableBytes() + in.remaining();
// 动态分配一个缓冲区
frame = ChannelBuffers.dynamicBuffer(size > bufferSize ? size : bufferSize);
// buffer的数据把写到frame
frame.writeBytes(buffer, buffer.readableBytes());
// 把流中的数据写到frame
frame.writeBytes(in.buf());
}
} else {
// 否则是基于Java NIO的ByteBuffer生成的缓冲区
frame = ChannelBuffers.wrappedBuffer(in.buf());
}
// 获得通道
Channel channel = MinaChannel.getOrAddChannel(session, url, handler);
Object msg;
int savedReadIndex;
try {
do {
// 获得读索引
savedReadIndex = frame.readerIndex();
try {
// 解码
msg = codec.decode(channel, frame);
} catch (Exception e) {
buffer = ChannelBuffers.EMPTY_BUFFER;
throw e;
}
// 拆包
if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
frame.readerIndex(savedReadIndex);
break;
} else {
if (savedReadIndex == frame.readerIndex()) {
buffer = ChannelBuffers.EMPTY_BUFFER;
throw new Exception("Decode without read data.");
}
if (msg != null) {
// 把数据写到输出流里面
out.write(msg);
}
}
} while (frame.readable());
} finally {
// 如果frame还有可读数据
if (frame.readable()) {
//丢弃可读数据
frame.discardReadBytes();
buffer = frame;
} else {
buffer = ChannelBuffers.EMPTY_BUFFER;
}
MinaChannel.removeChannelIfDisconnected(session);
}
}
@Override
public void dispose(IoSession session) throws Exception {
}
@Override
public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {
}
}</code></pre>
<p>该内部类是解码类,其中decode方法中关键的是调用了codec.decode,其余的操作是利用缓冲区对数据的冲刷流转。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=E5uCdHK6I2BRvpt0KS5SoA%3D%3D.MiX2lvLGDkCE8%2FuM2qye9jZLoG8r%2BMd6bKegcHnwK8xM5KyKcGhR%2B9dnGF6mb4XVXP5fAjCh8750Qqcc4SIDdpopiPfSRGF99Mc1Ax0hm8%2Fh0MakH4yx4gAETcAszoch2XUgl90injmaVGNkxaPtlYwjW9prkDJTw5SZhegPXnmxlr7SpmdF6SXV1EGfnNIHcPffgROgnTX35WIzt8IaFw%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了基于mina的来实现的远程通信、介绍dubbo-remoting-mina内的源码解析,关键需要对mina有所了解。下一篇我会讲解基于netty3实现远程通信部分。</p>
Dubbo源码解析(十四)远程通信——Http
https://segmentfault.com/a/1190000017508549
2018-12-25T16:09:53+08:00
2018-12-25T16:09:53+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
4
<h2>远程通讯——Http</h2>
<blockquote>目标:介绍基于Http的来实现的远程通信、介绍dubbo-remoting-http内的源码解析。</blockquote>
<h3>前言</h3>
<p>本文我们讲解的是如何基于Tomcat或者Jetty实现HTTP服务器。Tomcat和Jetty都是一种servlet引擎,Jetty要比Tomcat的架构更简单一些。关于它们之间的比较,我觉得google一些更加方便,我就不多废话了 。</p>
<p>下面是dubbo-remoting-http包下的类图:</p>
<p><img src="/img/remote/1460000017508552" alt="dubbo-remoting-http类图" title="dubbo-remoting-http类图"></p>
<p>可以看到这个包下只提供了服务端实现,并没有客户端实现。</p>
<h3>源码分析</h3>
<h4>(一)HttpServer</h4>
<pre><code class="java">public interface HttpServer extends Resetable {
/**
* get http handler.
* 获得http的处理类
* @return http handler.
*/
HttpHandler getHttpHandler();
/**
* get url.
* 获得url
* @return url
*/
URL getUrl();
/**
* get local address.
* 获得本地服务器地址
* @return local address.
*/
InetSocketAddress getLocalAddress();
/**
* close the channel.
* 关闭通道
*/
void close();
/**
* Graceful close the channel.
* 优雅的关闭通道
*/
void close(int timeout);
/**
* is bound.
* 是否绑定
* @return bound
*/
boolean isBound();
/**
* is closed.
* 服务器是否关闭
* @return closed
*/
boolean isClosed();
}</code></pre>
<p>该接口是http服务器的接口,定义了服务器相关的方法,都比较好理解。</p>
<h4>(二)AbstractHttpServer</h4>
<p>该类实现接口HttpServer,是http服务器接口的抽象类。</p>
<pre><code class="java">/**
* url
*/
private final URL url;
/**
* http服务器处理器
*/
private final HttpHandler handler;
/**
* 该服务器是否关闭
*/
private volatile boolean closed;
public AbstractHttpServer(URL url, HttpHandler handler) {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
this.url = url;
this.handler = handler;
}</code></pre>
<p>该类中有三个属性,关键是看在后面怎么用到,因为该类是抽象类,所以该类中的方法并没有实现具体的逻辑,我们继续往下看它的三个子类。</p>
<h4>(三)TomcatHttpServer</h4>
<p>该类是基于Tomcat来实现服务器的实现类,它继承了AbstractHttpServer。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 内嵌的tomcat对象
*/
private final Tomcat tomcat;
/**
* url对象
*/
private final URL url;</code></pre>
<p>该类的两个属性,关键是内嵌的tomcat。</p>
<h5>2.构造方法</h5>
<pre><code class="java"> public TomcatHttpServer(URL url, final HttpHandler handler) {
super(url, handler);
this.url = url;
// 添加处理器
DispatcherServlet.addHttpHandler(url.getPort(), handler);
// 获得java.io.tmpdir的绝对路径目录
String baseDir = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
// 创建内嵌的tomcat对象
tomcat = new Tomcat();
// 设置根目录
tomcat.setBaseDir(baseDir);
// 设置端口号
tomcat.setPort(url.getPort());
// 给默认的http连接器。设置最大线程数
tomcat.getConnector().setProperty(
"maxThreads", String.valueOf(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS)));
// tomcat.getConnector().setProperty(
// "minSpareThreads", String.valueOf(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS)));
// 设置最大的连接数
tomcat.getConnector().setProperty(
"maxConnections", String.valueOf(url.getParameter(Constants.ACCEPTS_KEY, -1)));
// 设置URL编码格式
tomcat.getConnector().setProperty("URIEncoding", "UTF-8");
// 设置连接超时事件为60s
tomcat.getConnector().setProperty("connectionTimeout", "60000");
// 设置最大长连接个数为不限制个数
tomcat.getConnector().setProperty("maxKeepAliveRequests", "-1");
// 设置将由连接器使用的Coyote协议。
tomcat.getConnector().setProtocol("org.apache.coyote.http11.Http11NioProtocol");
// 添加上下文
Context context = tomcat.addContext("/", baseDir);
// 添加servlet,把servlet添加到context
Tomcat.addServlet(context, "dispatcher", new DispatcherServlet());
// 添加servlet映射
context.addServletMapping("/*", "dispatcher");
// 添加servlet上下文
ServletManager.getInstance().addServletContext(url.getPort(), context.getServletContext());
try {
// 开启tomcat
tomcat.start();
} catch (LifecycleException e) {
throw new IllegalStateException("Failed to start tomcat server at " + url.getAddress(), e);
}
}</code></pre>
<p>该方法的构造函数中就启动了tomcat,前面很多都是对tomcat的启动参数以及配置设置。如果有过tomcat配置经验的朋友应该看起来很简单。</p>
<h5>3.close</h5>
<pre><code class="java">@Override
public void close() {
super.close();
// 移除相关的servlet上下文
ServletManager.getInstance().removeServletContext(url.getPort());
try {
// 停止tomcat
tomcat.stop();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}</code></pre>
<p>该方法是关闭服务器的方法。调用了tomcat.stop</p>
<h4>(四)JettyHttpServer</h4>
<p>该类是基于Jetty来实现服务器的实现类,它继承了AbstractHttpServer。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 内嵌的Jetty服务器对象
*/
private Server server;
/**
* url对象
*/
private URL url;</code></pre>
<p>该类的两个属性,关键是内嵌的sever,它是内嵌的etty服务器对象。</p>
<h5>2.构造方法</h5>
<pre><code class="java"> public JettyHttpServer(URL url, final HttpHandler handler) {
super(url, handler);
this.url = url;
// TODO we should leave this setting to slf4j
// we must disable the debug logging for production use
// 设置日志
Log.setLog(new StdErrLog());
// 禁用调试用的日志
Log.getLog().setDebugEnabled(false);
// 添加http服务器处理器
DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), handler);
// 获得线程数
int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
// 创建线程池
QueuedThreadPool threadPool = new QueuedThreadPool();
// 设置线程池配置
threadPool.setDaemon(true);
threadPool.setMaxThreads(threads);
threadPool.setMinThreads(threads);
// 创建选择NIO连接器
SelectChannelConnector connector = new SelectChannelConnector();
// 获得绑定的ip
String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost());
if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) {
// 设置主机地址
connector.setHost(bindIp);
}
// 设置端口号
connector.setPort(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()));
// 创建Jetty服务器对象
server = new Server();
// 设置线程池
server.setThreadPool(threadPool);
// 设置连接器
server.addConnector(connector);
// 添加DispatcherServlet到jetty
ServletHandler servletHandler = new ServletHandler();
ServletHolder servletHolder = servletHandler.addServletWithMapping(DispatcherServlet.class, "/*");
servletHolder.setInitOrder(2);
// dubbo's original impl can't support the use of ServletContext
// server.addHandler(servletHandler);
// TODO Context.SESSIONS is the best option here?
Context context = new Context(server, "/", Context.SESSIONS);
context.setServletHandler(servletHandler);
// 添加 ServletContext 对象,到 ServletManager 中
ServletManager.getInstance().addServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), context.getServletContext());
try {
// 启动jetty服务器
server.start();
} catch (Exception e) {
throw new IllegalStateException("Failed to start jetty server on " + url.getParameter(Constants.BIND_IP_KEY) + ":" + url.getParameter(Constants.BIND_PORT_KEY) + ", cause: "
+ e.getMessage(), e);
}
}</code></pre>
<p>可以看到它跟TomcatHttpServer中构造函数不同的是API的不同,不过思路差不多,先设置启动参数和配置,然后启动jetty服务器。</p>
<h5>3.close</h5>
<pre><code class="java">@Override
public void close() {
super.close();
// 移除 ServletContext 对象
ServletManager.getInstance().removeServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()));
if (server != null) {
try {
// 停止服务器
server.stop();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}</code></pre>
<p>该方法是关闭服务器的方法,调用的是server的stop方法。</p>
<h4>(五)ServletHttpServer</h4>
<p>该类继承了AbstractHttpServer,是基于 Servlet 的服务器实现类。</p>
<pre><code class="java">public class ServletHttpServer extends AbstractHttpServer {
public ServletHttpServer(URL url, HttpHandler handler) {
super(url, handler);
// /把 HttpHandler 到 DispatcherServlet 中,默认端口为8080
DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, 8080), handler);
}
}</code></pre>
<p>该类就一个构造方法。就是把服务器处理器注册到DispatcherServlet上。</p>
<h4>(六)HttpBinder</h4>
<pre><code class="java">@SPI("jetty")
public interface HttpBinder {
/**
* bind the server.
* 绑定到服务器
* @param url server url.
* @return server.
*/
@Adaptive({Constants.SERVER_KEY})
HttpServer bind(URL url, HttpHandler handler);
}</code></pre>
<p>该接口是http绑定器接口,其中就定义了一个方法就是绑定方法,并且返回服务器对象。该接口是一个可扩展接口,默认实现JettyHttpBinder。它有三个实现类,请往下看。</p>
<h4>(七)TomcatHttpBinder</h4>
<pre><code class="java">public class TomcatHttpBinder implements HttpBinder {
@Override
public HttpServer bind(URL url, HttpHandler handler) {
// 创建一个TomcatHttpServer
return new TomcatHttpServer(url, handler);
}
}</code></pre>
<p>一眼就看出来,就是创建了个基于Tomcat实现的服务器TomcatHttpServer对象。具体的就往上看TomcatHttpServer里面的实现。</p>
<h4>(八)JettyHttpBinder</h4>
<pre><code class="java">public class JettyHttpBinder implements HttpBinder {
@Override
public HttpServer bind(URL url, HttpHandler handler) {
// 创建JettyHttpServer实例
return new JettyHttpServer(url, handler);
}
}</code></pre>
<p>一眼就看出来,就是创建了个基于Jetty实现的服务器JettyHttpServer对象。具体的就往上看JettyHttpServer里面的实现。</p>
<h4>(九)ServletHttpBinder</h4>
<pre><code class="java">public class ServletHttpBinder implements HttpBinder {
@Override
@Adaptive()
public HttpServer bind(URL url, HttpHandler handler) {
// 创建ServletHttpServer对象
return new ServletHttpServer(url, handler);
}
}</code></pre>
<p>创建了个基于servlet实现的服务器ServletHttpServer对象。并且方法上加入了Adaptive,用到了dubbo SPI机制。</p>
<h4>(十)BootstrapListener</h4>
<pre><code class="java">public class BootstrapListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// context创建的时候,把ServletContext添加到ServletManager
ServletManager.getInstance().addServletContext(ServletManager.EXTERNAL_SERVER_PORT, servletContextEvent.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
// context销毁到时候,把servletContextEvent移除
ServletManager.getInstance().removeServletContext(ServletManager.EXTERNAL_SERVER_PORT);
}
}</code></pre>
<p>该类实现了ServletContextListener,是 启动监听器,当context创建和销毁的时候对ServletContext做处理。不过需要配置BootstrapListener到web.xml,通过这样的方式,让外部的 ServletContext 对象,添加到 ServletManager 中。</p>
<h4>(十一)DispatcherServlet</h4>
<pre><code class="java">public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 5766349180380479888L;
/**
* http服务器处理器
*/
private static final Map<Integer, HttpHandler> handlers = new ConcurrentHashMap<Integer, HttpHandler>();
/**
* 单例
*/
private static DispatcherServlet INSTANCE;
public DispatcherServlet() {
DispatcherServlet.INSTANCE = this;
}
/**
* 添加处理器
* @param port
* @param processor
*/
public static void addHttpHandler(int port, HttpHandler processor) {
handlers.put(port, processor);
}
public static void removeHttpHandler(int port) {
handlers.remove(port);
}
public static DispatcherServlet getInstance() {
return INSTANCE;
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得处理器
HttpHandler handler = handlers.get(request.getLocalPort());
// 如果处理器不存在
if (handler == null) {// service not found.
// 返回404
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Service not found.");
} else {
// 处理请求
handler.handle(request, response);
}
}
}</code></pre>
<p>该类继承了HttpServlet,是服务请求调度servlet类,主要是service方法,根据请求来调度不同的处理器去处理请求,如果没有该处理器,则报错404</p>
<h4>(十二)ServletManager</h4>
<pre><code class="java">public class ServletManager {
/**
* 外部服务器端口,用于 `servlet` 的服务器端口
*/
public static final int EXTERNAL_SERVER_PORT = -1234;
/**
* 单例
*/
private static final ServletManager instance = new ServletManager();
/**
* ServletContext 集合
*/
private final Map<Integer, ServletContext> contextMap = new ConcurrentHashMap<Integer, ServletContext>();
public static ServletManager getInstance() {
return instance;
}
/**
* 添加ServletContext
* @param port
* @param servletContext
*/
public void addServletContext(int port, ServletContext servletContext) {
contextMap.put(port, servletContext);
}
/**
* 移除ServletContext
* @param port
*/
public void removeServletContext(int port) {
contextMap.remove(port);
}
/**
* 获得ServletContext对象
* @param port
* @return
*/
public ServletContext getServletContext(int port) {
return contextMap.get(port);
}
}</code></pre>
<p>该类是servlet的管理器,管理着ServletContext。</p>
<h4>(十三)HttpHandler</h4>
<pre><code class="java">public interface HttpHandler {
/**
* invoke.
* HTTP请求处理
* @param request request.
* @param response response.
* @throws IOException
* @throws ServletException
*/
void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
}</code></pre>
<p>该接口是HTTP 处理器接口,就定义了一个处理请求的方法。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=eeh03e0ub3hAnqSwRYaeNw%3D%3D.qwz8FLcorzPsBG6mAo%2FsB2F6Em0DduiZKlNDsZX1IK5oQAIv9BtzQdsK%2BagKP5uhiJWiGPfeGmi9D97aPzCJO%2FnqL0NusAkbfu1p5v30Xfa0EmxN%2BsqsPAtWAI3GGQ%2BpbvvIHpQ6hy863kMxhmNWpToB0qxi65Dm%2Bw2XILZFfaq70ST9uVqgAIvijVwpXM3O" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了内嵌tomcat和jetty的来实现的http服务器,关键需要对tomcat和jetty的配置有所了解。下一篇我会讲解基于mina实现远程通信部分。</p>
Dubbo源码解析(十三)远程通信——Grizzly
https://segmentfault.com/a/1190000017496988
2018-12-24T16:46:21+08:00
2018-12-24T16:46:21+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
2
<h2>远程通讯——Grizzly</h2>
<blockquote>目标:介绍基于Grizzly的来实现的远程通信、介绍dubbo-remoting-grizzly内的源码解析。</blockquote>
<h3>前言</h3>
<p>Grizzly NIO框架的设计初衷是帮助开发者更好地利用Java NIO API,构建强大的可扩展的服务器应用。关于Grizzly我也没有很熟悉,所以只能根据grizzly在dubbo的远程通讯中应用稍微讲解一下。</p>
<p>下面是dubbo-remoting-grizzly下的包结构:</p>
<p><img src="/img/remote/1460000017496991" alt="dubbo-remoting-grizzly包结构" title="dubbo-remoting-grizzly包结构"></p>
<h3>源码分析</h3>
<h4>(一)GrizzlyChannel</h4>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(GrizzlyChannel.class);
/**
* 通道key
*/
private static final String CHANNEL_KEY = GrizzlyChannel.class.getName() + ".CHANNEL";
/**
* 通道属性
*/
private static final Attribute<GrizzlyChannel> ATTRIBUTE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(CHANNEL_KEY);
/**
* Grizzly的连接实例
*/
private final Connection<?> connection;</code></pre>
<p>可以看到,该类中的ATTRIBUTE和connection都是Grizzly涉及到的属性,ATTRIBUTE中封装了GrizzlyChannel的实例还有Connection实例。Grizzly把连接的一些连接的方法定义在了Connection接口中,包括获得远程地址、检测通道是否连接等方法。</p>
<h5>2.send</h5>
<pre><code class="java">@Override
@SuppressWarnings("rawtypes")
public void send(Object message, boolean sent) throws RemotingException {
super.send(message, sent);
int timeout = 0;
try {
// 发送消息,获得GrizzlyFuture实例
GrizzlyFuture future = connection.write(message);
if (sent) {
// 获得延迟多少时间获得响应
timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 获得请求的值
future.get(timeout, TimeUnit.MILLISECONDS);
}
} catch (TimeoutException e) {
throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress()
+ "in timeout(" + timeout + "ms) limit", e);
} catch (Throwable e) {
throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);
}
}</code></pre>
<p>该方法是发送消息的方法,调用了connection的write方法,它会返回一个GrizzlyFuture实例,GrizzlyFuture继承了java.util.concurrent.Future。</p>
<p>其他方法比较简单,实现的方法都基本调用connection的方法或者Grizzly.DEFAULT_ATTRIBUTE_BUILDER的方法。</p>
<h4>(二)GrizzlyHandler</h4>
<p>该类是Grizzly的通道处理器,它继承了BaseFilter。</p>
<pre><code class="java">/**
* url
*/
private final URL url;
/**
* 通道处理器
*/
private final ChannelHandler handler;</code></pre>
<p>该类有两个属性,这两个属性应该很熟悉了。而该类有handleConnect、handleClose、handleRead、handleWrite、exceptionOccurred的实现方法,分别调用了ChannelHandler中封装的五个方法。举个例子:</p>
<pre><code class="java">@Override
public NextAction handleConnect(FilterChainContext ctx) throws IOException {
// 获得Connection连接实例
Connection<?> connection = ctx.getConnection();
// 获得GrizzlyChannel通道
GrizzlyChannel channel = GrizzlyChannel.getOrAddChannel(connection, url, handler);
try {
// 连接
handler.connected(channel);
} catch (RemotingException e) {
throw new IOException(StringUtils.toString(e));
} finally {
GrizzlyChannel.removeChannelIfDisconnected(connection);
}
return ctx.getInvokeAction();
}</code></pre>
<p>可以看到获得GrizzlyChannel通道就调用了handler.connected进行连接。而其他四个方法也差不多,感兴趣的可以自行查看代码。</p>
<h4>(三)GrizzlyClient</h4>
<p>该类是Grizzly的客户端实现类,继承了AbstractClient类</p>
<pre><code class="java">/**
* Grizzly中的传输对象
*/
private TCPNIOTransport transport;
/**
* 连接实例
*/
private volatile Connection<?> connection; // volatile, please copy reference to use</code></pre>
<p>可以看到属性中有TCPNIOTransport的实例,在该类中实现的客户端方法都调用了transport中的方法,TCPNIOTransport中封装了创建,连接,断开连接等方法。</p>
<p>我们来看创建客户端的逻辑:</p>
<pre><code class="java">@Override
protected void doOpen() throws Throwable {
// 做一些过滤,用于处理消息
FilterChainBuilder filterChainBuilder = FilterChainBuilder.stateless();
filterChainBuilder.add(new TransportFilter());
filterChainBuilder.add(new GrizzlyCodecAdapter(getCodec(), getUrl(), this));
filterChainBuilder.add(new GrizzlyHandler(getUrl(), this));
// 传输构建者
TCPNIOTransportBuilder builder = TCPNIOTransportBuilder.newInstance();
// 获得线程池配置实例
ThreadPoolConfig config = builder.getWorkerThreadPoolConfig();
// 设置线程池的配置,包括核心线程数等
config.setPoolName(CLIENT_THREAD_POOL_NAME)
.setQueueLimit(-1)
.setCorePoolSize(0)
.setMaxPoolSize(Integer.MAX_VALUE)
.setKeepAliveTime(60L, TimeUnit.SECONDS);
// 设置创建属性
builder.setTcpNoDelay(true).setKeepAlive(true)
.setConnectionTimeout(getConnectTimeout())
.setIOStrategy(SameThreadIOStrategy.getInstance());
// 创建一个transport
transport = builder.build();
transport.setProcessor(filterChainBuilder.build());
// 创建客户端
transport.start();
}</code></pre>
<p>可以看到首先是设置及一些过滤,在上面对信息先处理,然后设置了线程池的配置,创建transport,并且用transport来进行创建客户端。</p>
<h4>(四)GrizzlyServer</h4>
<p>该类是Grizzly的服务器实现类,继承了AbstractServer类。</p>
<pre><code class="java">/**
* 连接该服务器的客户端通道集合
*/
private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>(); // <ip:port, channel>
/**
* 传输实例
*/
private TCPNIOTransport transport;</code></pre>
<p>该类中有两个属性,其中transport用法跟GrizzlyClient中的一样。</p>
<p>我也就讲解一个doOpen方法:</p>
<pre><code class="java">@Override
protected void doOpen() throws Throwable {
// 增加过滤器,来处理信息
FilterChainBuilder filterChainBuilder = FilterChainBuilder.stateless();
filterChainBuilder.add(new TransportFilter());
filterChainBuilder.add(new GrizzlyCodecAdapter(getCodec(), getUrl(), this));
filterChainBuilder.add(new GrizzlyHandler(getUrl(), this));
TCPNIOTransportBuilder builder = TCPNIOTransportBuilder.newInstance();
// 获得线程池配置
ThreadPoolConfig config = builder.getWorkerThreadPoolConfig();
config.setPoolName(SERVER_THREAD_POOL_NAME).setQueueLimit(-1);
// 获得url配置中线程池类型
String threadpool = getUrl().getParameter(Constants.THREADPOOL_KEY, Constants.DEFAULT_THREADPOOL);
if (Constants.DEFAULT_THREADPOOL.equals(threadpool)) {
// 优先从url获得线程池的线程数,默认线程数为200
int threads = getUrl().getPositiveParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
// 设置线程池配置
config.setCorePoolSize(threads).setMaxPoolSize(threads)
.setKeepAliveTime(0L, TimeUnit.SECONDS);
// 如果是cached类型的线程池
} else if ("cached".equals(threadpool)) {
int threads = getUrl().getPositiveParameter(Constants.THREADS_KEY, Integer.MAX_VALUE);
// 设置核心线程数为0、最大线程数为threads
config.setCorePoolSize(0).setMaxPoolSize(threads)
.setKeepAliveTime(60L, TimeUnit.SECONDS);
} else {
throw new IllegalArgumentException("Unsupported threadpool type " + threadpool);
}
builder.setKeepAlive(true).setReuseAddress(false)
.setIOStrategy(SameThreadIOStrategy.getInstance());
// 创建transport
transport = builder.build();
transport.setProcessor(filterChainBuilder.build());
transport.bind(getBindAddress());
// 开启服务器
transport.start();
}</code></pre>
<p>该方法是创建服务器,可以看到操作跟GrizzlyClient中的差不多。</p>
<h4>(五)GrizzlyTransporter</h4>
<p>该类实现了Transporter接口,是基于Grizzly的传输层实现。</p>
<pre><code class="java">public class GrizzlyTransporter implements Transporter {
public static final String NAME = "grizzly";
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
// 返回GrizzlyServer实例
return new GrizzlyServer(url, listener);
}
@Override
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
// // 返回GrizzlyClient实例
return new GrizzlyClient(url, listener);
}
}</code></pre>
<p>可以看到,bind和connect方法分别就是创建了GrizzlyServer和GrizzlyClient实例。这里我建议查看一下《<a href="https://segmentfault.com/a/1190000017390253">dubbo源码解析(九)远程通信——Transport层</a>》。</p>
<h4>(六)GrizzlyCodecAdapter</h4>
<p>该类是Grizzly编解码类,继承了BaseFilter。</p>
<h5>1.属性和构造方法</h5>
<pre><code class="java">/**
* 编解码器
*/
private final Codec2 codec;
/**
* url
*/
private final URL url;
/**
* 通道处理器
*/
private final ChannelHandler handler;
/**
* 缓存大小
*/
private final int bufferSize;
/**
* 空缓存区
*/
private ChannelBuffer previousData = ChannelBuffers.EMPTY_BUFFER;
public GrizzlyCodecAdapter(Codec2 codec, URL url, ChannelHandler handler) {
this.codec = codec;
this.url = url;
this.handler = handler;
int b = url.getPositiveParameter(Constants.BUFFER_KEY, Constants.DEFAULT_BUFFER_SIZE);
// 如果缓存区大小在16k以内,则设置配置大小,如果不是,则设置8k的缓冲区大小
this.bufferSize = b >= Constants.MIN_BUFFER_SIZE && b <= Constants.MAX_BUFFER_SIZE ? b : Constants.DEFAULT_BUFFER_SIZE;
}</code></pre>
<h5>2.handleWrite</h5>
<pre><code class="java">@Override
public NextAction handleWrite(FilterChainContext context) throws IOException {
Connection<?> connection = context.getConnection();
GrizzlyChannel channel = GrizzlyChannel.getOrAddChannel(connection, url, handler);
try {
// 分配一个1024的动态缓冲区
ChannelBuffer channelBuffer = ChannelBuffers.dynamicBuffer(1024); // Do not need to close
// 获得消息
Object msg = context.getMessage();
// 编码
codec.encode(channel, channelBuffer, msg);
// 检测是否连接
GrizzlyChannel.removeChannelIfDisconnected(connection);
// 分配缓冲区
Buffer buffer = connection.getTransport().getMemoryManager().allocate(channelBuffer.readableBytes());
// 把channelBuffer的数据写到buffer
buffer.put(channelBuffer.toByteBuffer());
buffer.flip();
buffer.allowBufferDispose(true);
// 设置到上下文
context.setMessage(buffer);
} finally {
GrizzlyChannel.removeChannelIfDisconnected(connection);
}
return context.getInvokeAction();
}</code></pre>
<p>该方法是写数据,可以发现编码调用的是 codec.encode,其他的我都在注释里写明了,关键还是对前面两篇文章的一些内容需要理解。</p>
<h5>3.handleRead</h5>
<pre><code class="java">@Override
public NextAction handleRead(FilterChainContext context) throws IOException {
Object message = context.getMessage();
Connection<?> connection = context.getConnection();
Channel channel = GrizzlyChannel.getOrAddChannel(connection, url, handler);
try {
// 如果接收的是一个数据包
if (message instanceof Buffer) { // receive a new packet
Buffer grizzlyBuffer = (Buffer) message; // buffer
ChannelBuffer frame;
// 如果缓冲区可读
if (previousData.readable()) {
// 如果该缓冲区是动态的缓冲区
if (previousData instanceof DynamicChannelBuffer) {
// 写入数据
previousData.writeBytes(grizzlyBuffer.toByteBuffer());
frame = previousData;
} else {
// 获得需要的缓冲区大小
int size = previousData.readableBytes() + grizzlyBuffer.remaining();
// 新建一个动态缓冲区
frame = ChannelBuffers.dynamicBuffer(size > bufferSize ? size : bufferSize);
// 写入previousData中的数据
frame.writeBytes(previousData, previousData.readableBytes());
// 写入grizzlyBuffer中的数据
frame.writeBytes(grizzlyBuffer.toByteBuffer());
}
} else {
// 否则是基于Java NIO的ByteBuffer生成的缓冲区
frame = ChannelBuffers.wrappedBuffer(grizzlyBuffer.toByteBuffer());
}
Object msg;
int savedReadIndex;
do {
savedReadIndex = frame.readerIndex();
try {
// 解码
msg = codec.decode(channel, frame);
} catch (Exception e) {
previousData = ChannelBuffers.EMPTY_BUFFER;
throw new IOException(e.getMessage(), e);
}
// 拆包
if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {
frame.readerIndex(savedReadIndex);
// 结束调用链
return context.getStopAction();
} else {
if (savedReadIndex == frame.readerIndex()) {
// 没有可读内容
previousData = ChannelBuffers.EMPTY_BUFFER;
throw new IOException("Decode without read data.");
}
if (msg != null) {
// 把解码后信息放入上下文
context.setMessage(msg);
// 继续下面的调用链
return context.getInvokeAction();
} else {
return context.getInvokeAction();
}
}
} while (frame.readable());
} else { // Other events are passed down directly
return context.getInvokeAction();
}
} finally {
GrizzlyChannel.removeChannelIfDisconnected(connection);
}
}</code></pre>
<p>该方法是读数据,直接调用了codec.decode进行解码。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=xidZEbx9zFKGI5taNZ60uQ%3D%3D.7xc0V%2FymUd%2F%2B6QRAojIpy3MLhv9WOrwtuagM13lZsx8fEM6U%2B%2FG%2BZKZJ97Hrinm36tjWo8P76RuEx%2BMhtedonF5YZfmZacUlF7%2Fo3bBLqZ405ZqjuPLCjRyphTg0kfA4wYgZ0f4dR4NefNhTAU2IEnW7JOPj5NWRo2JKuXPQyCyr4KWgaScuLvH9%2BS1kGaEg4DXN3GYFxTFjDbbhvli4SA%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了基于grizzly的来实现的远程通信、介绍dubbo-remoting-grizzly内的源码解析,关键需要对grizzly有所了解。下一篇我会讲解基于http实现远程通信部分。</p>
Dubbo源码解析(十二)远程通信——Telnet
https://segmentfault.com/a/1190000017485091
2018-12-23T12:21:31+08:00
2018-12-23T12:21:31+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
7
<h2>远程通讯——Telnet</h2>
<blockquote>目标:介绍telnet的相关实现逻辑、介绍dubbo-remoting-api中的telnet包内的源码解析。</blockquote>
<h3>前言</h3>
<p>从dubbo 2.0.5开始,dubbo开始支持通过 telnet 命令来进行服务治理。本文就是讲解一些公用的telnet命令的实现。下面来看一下telnet实现的类图:</p>
<p><img src="/img/remote/1460000017485094" alt="telnet类图" title="telnet类图"></p>
<p>可以看到,实现了TelnetHandler接口的有六个类,除了TelnetHandlerAdapter是以外,其他五个分别对应了clear、exit、help、log、status命令的实现,具体用来干嘛,请看官方文档的介绍。</p>
<h3>源码分析</h3>
<h4>(一)TelnetHandler</h4>
<pre><code class="java">@SPI
public interface TelnetHandler {
/**
* telnet.
* 处理对应的telnet命令
* @param channel
* @param message telnet命令
*/
String telnet(Channel channel, String message) throws RemotingException;
}</code></pre>
<p>该接口上telnet命令处理器接口,是一个可扩展接口。它定义了一个方法,就是处理相关的telnet命令。</p>
<h4>(二)TelnetHandlerAdapter</h4>
<p>该类继承了ChannelHandlerAdapter,实现了TelnetHandler接口,是TelnetHandler的适配器类,负责在接收到HeaderExchangeHandler发来的telnet命令后分发给对应的TelnetHandler实现类去实现,并且返回命令结果。</p>
<pre><code class="java">public class TelnetHandlerAdapter extends ChannelHandlerAdapter implements TelnetHandler {
/**
* 扩展加载器
*/
private final ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class);
@Override
public String telnet(Channel channel, String message) throws RemotingException {
// 获得提示键配置,用于nc获取信息时不显示提示符
String prompt = channel.getUrl().getParameterAndDecoded(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);
boolean noprompt = message.contains("--no-prompt");
message = message.replace("--no-prompt", "");
StringBuilder buf = new StringBuilder();
// 删除头尾空白符的字符串
message = message.trim();
String command;
// 获得命令
if (message.length() > 0) {
int i = message.indexOf(' ');
if (i > 0) {
// 获得命令
command = message.substring(0, i).trim();
// 获得参数
message = message.substring(i + 1).trim();
} else {
command = message;
message = "";
}
} else {
command = "";
}
if (command.length() > 0) {
// 如果有该命令的扩展实现类
if (extensionLoader.hasExtension(command)) {
try {
// 执行相应命令的实现类的telnet
String result = extensionLoader.getExtension(command).telnet(channel, message);
if (result == null) {
return null;
}
// 返回结果
buf.append(result);
} catch (Throwable t) {
buf.append(t.getMessage());
}
} else {
buf.append("Unsupported command: ");
buf.append(command);
}
}
if (buf.length() > 0) {
buf.append("\r\n");
}
// 添加 telnet 提示语
if (prompt != null && prompt.length() > 0 && !noprompt) {
buf.append(prompt);
}
return buf.toString();
}
}</code></pre>
<p>该类只实现了telnet方法,其中的逻辑还是比较清晰,就是根据对应的命令去让对应的实现类产生命令结果。</p>
<h4>(三)ClearTelnetHandler</h4>
<p>该类实现了TelnetHandler接口,封装了clear命令的实现。</p>
<pre><code class="java">@Activate
@Help(parameter = "[lines]", summary = "Clear screen.", detail = "Clear screen.")
public class ClearTelnetHandler implements TelnetHandler {
@Override
public String telnet(Channel channel, String message) {
// 清除屏幕上的内容行数
int lines = 100;
if (message.length() > 0) {
// 如果不是一个数字
if (!StringUtils.isInteger(message)) {
return "Illegal lines " + message + ", must be integer.";
}
lines = Integer.parseInt(message);
}
StringBuilder buf = new StringBuilder();
// 一行一行清除
for (int i = 0; i < lines; i++) {
buf.append("\r\n");
}
return buf.toString();
}
}</code></pre>
<h4>(四)ExitTelnetHandler</h4>
<p>该类实现了TelnetHandler接口,封装了exit命令的实现。</p>
<pre><code class="java">@Activate
@Help(parameter = "", summary = "Exit the telnet.", detail = "Exit the telnet.")
public class ExitTelnetHandler implements TelnetHandler {
@Override
public String telnet(Channel channel, String message) {
// 关闭通道
channel.close();
return null;
}
}</code></pre>
<h4>(五)HelpTelnetHandler</h4>
<p>该类实现了TelnetHandler接口,封装了help命令的实现。</p>
<pre><code class="java">@Activate
@Help(parameter = "[command]", summary = "Show help.", detail = "Show help.")
public class HelpTelnetHandler implements TelnetHandler {
/**
* 扩展加载器
*/
private final ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class);
@Override
public String telnet(Channel channel, String message) {
// 如果需要查看某一个命令的帮助
if (message.length() > 0) {
if (!extensionLoader.hasExtension(message)) {
return "No such command " + message;
}
// 获得对应的扩展实现类
TelnetHandler handler = extensionLoader.getExtension(message);
Help help = handler.getClass().getAnnotation(Help.class);
StringBuilder buf = new StringBuilder();
// 生成命令和帮助信息
buf.append("Command:\r\n ");
buf.append(message + " " + help.parameter().replace("\r\n", " ").replace("\n", " "));
buf.append("\r\nSummary:\r\n ");
buf.append(help.summary().replace("\r\n", " ").replace("\n", " "));
buf.append("\r\nDetail:\r\n ");
buf.append(help.detail().replace("\r\n", " \r\n").replace("\n", " \n"));
return buf.toString();
// 如果查看所有命令的帮助
} else {
List<List<String>> table = new ArrayList<List<String>>();
// 获得所有命令的提示信息
List<TelnetHandler> handlers = extensionLoader.getActivateExtension(channel.getUrl(), "telnet");
if (handlers != null && !handlers.isEmpty()) {
for (TelnetHandler handler : handlers) {
Help help = handler.getClass().getAnnotation(Help.class);
List<String> row = new ArrayList<String>();
String parameter = " " + extensionLoader.getExtensionName(handler) + " " + (help != null ? help.parameter().replace("\r\n", " ").replace("\n", " ") : "");
row.add(parameter.length() > 50 ? parameter.substring(0, 50) + "..." : parameter);
String summary = help != null ? help.summary().replace("\r\n", " ").replace("\n", " ") : "";
row.add(summary.length() > 50 ? summary.substring(0, 50) + "..." : summary);
table.add(row);
}
}
return "Please input \"help [command]\" show detail.\r\n" + TelnetUtils.toList(table);
}
}
}</code></pre>
<p>help分为了需要查看某一个命令的帮助还是查看全部命令的帮助。</p>
<h4>(六)LogTelnetHandler</h4>
<p>该类实现了TelnetHandler接口,封装了log命令的实现。</p>
<pre><code class="java">@Activate
@Help(parameter = "level", summary = "Change log level or show log ", detail = "Change log level or show log")
public class LogTelnetHandler implements TelnetHandler {
public static final String SERVICE_KEY = "telnet.log";
@Override
public String telnet(Channel channel, String message) {
long size = 0;
File file = LoggerFactory.getFile();
StringBuffer buf = new StringBuffer();
if (message == null || message.trim().length() == 0) {
buf.append("EXAMPLE: log error / log 100");
} else {
String str[] = message.split(" ");
if (!StringUtils.isInteger(str[0])) {
// 设置日志级别
LoggerFactory.setLevel(Level.valueOf(message.toUpperCase()));
} else {
// 获得日志长度
int SHOW_LOG_LENGTH = Integer.parseInt(str[0]);
if (file != null && file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
try {
FileChannel filechannel = fis.getChannel();
try {
size = filechannel.size();
ByteBuffer bb;
if (size <= SHOW_LOG_LENGTH) {
// 分配缓冲区
bb = ByteBuffer.allocate((int) size);
// 读日志数据
filechannel.read(bb, 0);
} else {
int pos = (int) (size - SHOW_LOG_LENGTH);
// 分配缓冲区
bb = ByteBuffer.allocate(SHOW_LOG_LENGTH);
// 读取日志数据
filechannel.read(bb, pos);
}
bb.flip();
String content = new String(bb.array()).replace("<", "&lt;")
.replace(">", "&gt;").replace("\n", "<br/><br/>");
buf.append("\r\ncontent:" + content);
buf.append("\r\nmodified:" + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date(file.lastModified()))));
buf.append("\r\nsize:" + size + "\r\n");
} finally {
filechannel.close();
}
} finally {
fis.close();
}
} catch (Exception e) {
buf.append(e.getMessage());
}
} else {
size = 0;
buf.append("\r\nMESSAGE: log file not exists or log appender is console .");
}
}
}
buf.append("\r\nCURRENT LOG LEVEL:" + LoggerFactory.getLevel())
.append("\r\nCURRENT LOG APPENDER:" + (file == null ? "console" : file.getAbsolutePath()));
return buf.toString();
}
}</code></pre>
<p>log命令实现原理就是从日志文件中把日志信息读取出来。</p>
<h4>(七)StatusTelnetHandler</h4>
<p>该类实现了TelnetHandler接口,封装了status命令的实现。</p>
<pre><code class="java">@Activate
@Help(parameter = "[-l]", summary = "Show status.", detail = "Show status.")
public class StatusTelnetHandler implements TelnetHandler {
private final ExtensionLoader<StatusChecker> extensionLoader = ExtensionLoader.getExtensionLoader(StatusChecker.class);
@Override
public String telnet(Channel channel, String message) {
// 显示状态列表
if (message.equals("-l")) {
List<StatusChecker> checkers = extensionLoader.getActivateExtension(channel.getUrl(), "status");
String[] header = new String[]{"resource", "status", "message"};
List<List<String>> table = new ArrayList<List<String>>();
Map<String, Status> statuses = new HashMap<String, Status>();
if (checkers != null && !checkers.isEmpty()) {
// 遍历各个资源的状态,如果一个当全部 OK 时则显示 OK,只要有一个 ERROR 则显示 ERROR,只要有一个 WARN 则显示 WARN
for (StatusChecker checker : checkers) {
String name = extensionLoader.getExtensionName(checker);
Status stat;
try {
stat = checker.check();
} catch (Throwable t) {
stat = new Status(Status.Level.ERROR, t.getMessage());
}
statuses.put(name, stat);
if (stat.getLevel() != null && stat.getLevel() != Status.Level.UNKNOWN) {
List<String> row = new ArrayList<String>();
row.add(name);
row.add(String.valueOf(stat.getLevel()));
row.add(stat.getMessage() == null ? "" : stat.getMessage());
table.add(row);
}
}
}
Status stat = StatusUtils.getSummaryStatus(statuses);
List<String> row = new ArrayList<String>();
row.add("summary");
row.add(String.valueOf(stat.getLevel()));
row.add(stat.getMessage());
table.add(row);
return TelnetUtils.toTable(header, table);
} else if (message.length() > 0) {
return "Unsupported parameter " + message + " for status.";
}
String status = channel.getUrl().getParameter("status");
Map<String, Status> statuses = new HashMap<String, Status>();
if (status != null && status.length() > 0) {
String[] ss = Constants.COMMA_SPLIT_PATTERN.split(status);
for (String s : ss) {
StatusChecker handler = extensionLoader.getExtension(s);
Status stat;
try {
stat = handler.check();
} catch (Throwable t) {
stat = new Status(Status.Level.ERROR, t.getMessage());
}
statuses.put(s, stat);
}
}
Status stat = StatusUtils.getSummaryStatus(statuses);
return String.valueOf(stat.getLevel());
}
}</code></pre>
<h4>(八)Help</h4>
<p>该接口是帮助文档接口</p>
<pre><code class="java">@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Help {
String parameter() default "";
String summary();
String detail() default "";
}</code></pre>
<p>可以看上在每个命令的实现类上都加上了@Help注解,为了添加一些帮助文案。</p>
<h4>(九)TelnetUtils</h4>
<p>该类是Telnet命令的工具类,其中逻辑我就不介绍了。</p>
<h4>(十)TelnetCodec</h4>
<p>该类继承了TransportCodec,是telnet的编解码类。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(TelnetCodec.class);
/**
* 历史命令列表
*/
private static final String HISTORY_LIST_KEY = "telnet.history.list";
/**
* 历史命令位置,就是用上下键来找历史命令
*/
private static final String HISTORY_INDEX_KEY = "telnet.history.index";
/**
* 向上键
*/
private static final byte[] UP = new byte[]{27, 91, 65};
/**
* 向下键
*/
private static final byte[] DOWN = new byte[]{27, 91, 66};
/**
* 回车
*/
private static final List<?> ENTER = Arrays.asList(new Object[]{new byte[]{'\r', '\n'} /* Windows Enter */, new byte[]{'\n'} /* Linux Enter */});
/**
* 退出
*/
private static final List<?> EXIT = Arrays.asList(new Object[]{new byte[]{3} /* Windows Ctrl+C */, new byte[]{-1, -12, -1, -3, 6} /* Linux Ctrl+C */, new byte[]{-1, -19, -1, -3, 6} /* Linux Pause */});</code></pre>
<h5>2.getCharset</h5>
<pre><code class="java">private static Charset getCharset(Channel channel) {
if (channel != null) {
// 获得属性设置
Object attribute = channel.getAttribute(Constants.CHARSET_KEY);
// 返回指定字符集的charset对象。
if (attribute instanceof String) {
try {
return Charset.forName((String) attribute);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
} else if (attribute instanceof Charset) {
return (Charset) attribute;
}
URL url = channel.getUrl();
if (url != null) {
String parameter = url.getParameter(Constants.CHARSET_KEY);
if (parameter != null && parameter.length() > 0) {
try {
return Charset.forName(parameter);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
}
// 默认的编码是utf-8
try {
return Charset.forName(Constants.DEFAULT_CHARSET);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
return Charset.defaultCharset();
}</code></pre>
<p>该方法是获得通道的字符集,根据url中编码来获得字符集,默认是utf-8。</p>
<h5>3.encode</h5>
<pre><code class="java">@Override
public void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException {
// 如果需要编码的是 telnet 命令结果
if (message instanceof String) {
//如果为客户端侧的通道message直接返回
if (isClientSide(channel)) {
message = message + "\r\n";
}
// 获得字节数组
byte[] msgData = ((String) message).getBytes(getCharset(channel).name());
// 写入缓冲区
buffer.writeBytes(msgData);
} else {
super.encode(channel, buffer, message);
}
}</code></pre>
<p>该方法是编码方法。</p>
<h5>4.decode</h5>
<pre><code class="java">@Override
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
// 获得缓冲区可读的字节
int readable = buffer.readableBytes();
byte[] message = new byte[readable];
// 从缓冲区读数据
buffer.readBytes(message);
return decode(channel, buffer, readable, message);
}
@SuppressWarnings("unchecked")
protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] message) throws IOException {
// 如果是客户端侧,直接返回结果
if (isClientSide(channel)) {
return toString(message, getCharset(channel));
}
// 检验消息长度
checkPayload(channel, readable);
if (message == null || message.length == 0) {
return DecodeResult.NEED_MORE_INPUT;
}
// 如果回退
if (message[message.length - 1] == '\b') { // Windows backspace echo
try {
boolean doublechar = message.length >= 3 && message[message.length - 3] < 0; // double byte char
channel.send(new String(doublechar ? new byte[]{32, 32, 8, 8} : new byte[]{32, 8}, getCharset(channel).name()));
} catch (RemotingException e) {
throw new IOException(StringUtils.toString(e));
}
return DecodeResult.NEED_MORE_INPUT;
}
// 如果命令是退出
for (Object command : EXIT) {
if (isEquals(message, (byte[]) command)) {
if (logger.isInfoEnabled()) {
logger.info(new Exception("Close channel " + channel + " on exit command: " + Arrays.toString((byte[]) command)));
}
// 关闭通道
channel.close();
return null;
}
}
boolean up = endsWith(message, UP);
boolean down = endsWith(message, DOWN);
// 如果用上下键找历史命令
if (up || down) {
LinkedList<String> history = (LinkedList<String>) channel.getAttribute(HISTORY_LIST_KEY);
if (history == null || history.isEmpty()) {
return DecodeResult.NEED_MORE_INPUT;
}
Integer index = (Integer) channel.getAttribute(HISTORY_INDEX_KEY);
Integer old = index;
if (index == null) {
index = history.size() - 1;
} else {
// 向上
if (up) {
index = index - 1;
if (index < 0) {
index = history.size() - 1;
}
} else {
// 向下
index = index + 1;
if (index > history.size() - 1) {
index = 0;
}
}
}
// 获得历史命令,并发送给客户端
if (old == null || !old.equals(index)) {
// 设置当前命令位置
channel.setAttribute(HISTORY_INDEX_KEY, index);
// 获得历史命令
String value = history.get(index);
// 清除客户端原有命令,用查到的历史命令替代
if (old != null && old >= 0 && old < history.size()) {
String ov = history.get(old);
StringBuilder buf = new StringBuilder();
for (int i = 0; i < ov.length(); i++) {
buf.append("\b");
}
for (int i = 0; i < ov.length(); i++) {
buf.append(" ");
}
for (int i = 0; i < ov.length(); i++) {
buf.append("\b");
}
value = buf.toString() + value;
}
try {
channel.send(value);
} catch (RemotingException e) {
throw new IOException(StringUtils.toString(e));
}
}
// 返回,需要更多指令
return DecodeResult.NEED_MORE_INPUT;
}
// 关闭命令
for (Object command : EXIT) {
if (isEquals(message, (byte[]) command)) {
if (logger.isInfoEnabled()) {
logger.info(new Exception("Close channel " + channel + " on exit command " + command));
}
channel.close();
return null;
}
}
byte[] enter = null;
// 如果命令是回车
for (Object command : ENTER) {
if (endsWith(message, (byte[]) command)) {
enter = (byte[]) command;
break;
}
}
if (enter == null) {
return DecodeResult.NEED_MORE_INPUT;
}
LinkedList<String> history = (LinkedList<String>) channel.getAttribute(HISTORY_LIST_KEY);
Integer index = (Integer) channel.getAttribute(HISTORY_INDEX_KEY);
// 移除历史命令
channel.removeAttribute(HISTORY_INDEX_KEY);
// 将历史命令拼接
if (history != null && !history.isEmpty() && index != null && index >= 0 && index < history.size()) {
String value = history.get(index);
if (value != null) {
byte[] b1 = value.getBytes();
byte[] b2 = new byte[b1.length + message.length];
System.arraycopy(b1, 0, b2, 0, b1.length);
System.arraycopy(message, 0, b2, b1.length, message.length);
message = b2;
}
}
// 将命令字节数组,转成具体的一条命令
String result = toString(message, getCharset(channel));
if (result.trim().length() > 0) {
if (history == null) {
history = new LinkedList<String>();
channel.setAttribute(HISTORY_LIST_KEY, history);
}
if (history.isEmpty()) {
history.addLast(result);
} else if (!result.equals(history.getLast())) {
history.remove(result);
// 添加当前命令到历史尾部
history.addLast(result);
// 超过上限,移除历史的头部
if (history.size() > 10) {
history.removeFirst();
}
}
}
return result;
}</code></pre>
<p>该方法是编码。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=iS8wwcobllzbiKwi9vsQlw%3D%3D.oHtugGMOD4r3EUbPy0In71FSyYg1yLiMHExOmM8GDFS5laWpZhiN%2Byt4cP4hq9EY0aDND3aYUQME2%2FbON%2Bj3rGN82zhNBsuxcN8Sc34e2A2IQHAtURCC8wlvA2snRckvPiH%2BWh0WXqVRSJceJKHjtXJnd7YFpbDIj96etaUPrxe3Ce13zcJ4DxRrKF%2FNxpaCJWZZoO0KCXoBWfvj79yh4Q%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了telnet的相关实现逻辑,本文有兴趣的朋友可以看看。下一篇我会讲解基于grizzly实现远程通信部分。</p>
Dubbo源码解析(十一)远程通信——Buffer
https://segmentfault.com/a/1190000017483889
2018-12-23T00:24:32+08:00
2018-12-23T00:24:32+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
9
<h2>远程通讯——Buffer</h2>
<blockquote>目标:介绍Buffer的相关实现逻辑、介绍dubbo-remoting-api中的buffer包内的源码解析。</blockquote>
<h3>前言</h3>
<p>缓存区在NIO框架中非常重要,它作为字节容器,每个NIO框架都有自己的相应的设计实现。比如Java NIO有ByteBuffer的设计,Mina有IoBuffer的设计,Netty4有ByteBuf的设计。 那么在本文讲到的内容是dubbo对于缓冲区做的一些接口定义,并且做了不同的框架实现缓冲区公共的逻辑。下面是本文要讲到的类图:</p>
<p><img src="/img/remote/1460000017483892" alt="buffer类图" title="buffer类图"></p>
<p>接下来我就按照类图上的一个一个分析,忽略test类。</p>
<h3>源码分析</h3>
<h4>(一)ChannelBuffer</h4>
<p>该接口继承了Comparable接口,该接口是通道缓存接口,是字节容器,在netty中也有通道缓存的设计,也就是io.netty.buffer.ByteBuf,该接口的方法定义和设计跟ByteBuf几乎一样,连注释都一样,所以我就不再细说了。</p>
<h4>(二)AbstractChannelBuffer</h4>
<p>该类实现了ChannelBuffer接口,是通道缓存的抽象类,它实现了ChannelBuffer所有方法,但是它实现的方法都是需要被重写的方法,具体的实现都是需要子类来实现。现在我们来恶补一下这个通道缓存的原理,当然这个原理跟netty的ByteBuf原理是分不开的。AbstractChannelBuffer维护了两个索引,一个用于读取,另一个用于写入当你从通道缓存中读取时,readerIndex将会被递增已经被读取的字节数,同样的当你写入的时候writerIndex也会被递增。</p>
<pre><code class="java">/**
* 读索引
*/
private int readerIndex;
/**
* 写索引
*/
private int writerIndex;
/**
* 标记读索引
*/
private int markedReaderIndex;
/**
* 标记写索引
*/
private int markedWriterIndex;</code></pre>
<p>可以看到该类有四个属性,读索引和写索引的作用就是我上述介绍的,读索引和写索引的起始位置都为索引位置0。而标记读索引和标记写索引是为了做备份回滚,当对缓冲区进行读写操作时,可能需要对之前的操作进行回滚,我们就需要将当前的读写索引备份到相应的标记索引中。</p>
<p>该类的其他方法都是利用四个属性来操作,无非就是检测是否有数据可读或者还是否有空间可写等方法,做一些前置条件的校验以及索引的设置,具体的实现都是需要子类来实现,所以我就不贴代码,因为逻辑比较简单。</p>
<h4>(三)DynamicChannelBuffer</h4>
<p>该类继承了AbstractChannelBuffer类,该类是动态的通道缓存区类,也就是该类是从ChannelBufferFactory工厂中动态的生成缓冲区,默认使用的工厂是HeapChannelBufferFactory。</p>
<h5>1.属性和构造方法</h5>
<pre><code class="java">/**
* 通道缓存区工厂
*/
private final ChannelBufferFactory factory;
/**
* 通道缓存区
*/
private ChannelBuffer buffer;
public DynamicChannelBuffer(int estimatedLength) {
// 默认是HeapChannelBufferFactory
this(estimatedLength, HeapChannelBufferFactory.getInstance());
}
public DynamicChannelBuffer(int estimatedLength, ChannelBufferFactory factory) {
// 如果预计长度小于0 则抛出异常
if (estimatedLength < 0) {
throw new IllegalArgumentException("estimatedLength: " + estimatedLength);
}
// 如果工厂为空,则抛出空指针异常
if (factory == null) {
throw new NullPointerException("factory");
}
// 设置工厂
this.factory = factory;
// 创建缓存区
buffer = factory.getBuffer(estimatedLength);
}</code></pre>
<p>可以看到,该类有两个属性,所有的实现方法都是调用了buffer的方法,不过该buffer产生是通过工厂动态生成的。并且从构造方法来看,默认使用HeapChannelBufferFactory。</p>
<h5>2.ensureWritableBytes</h5>
<pre><code class="java">@Override
public void ensureWritableBytes(int minWritableBytes) {
// 如果最小写入的字节数不大于可写的字节数,则结束
if (minWritableBytes <= writableBytes()) {
return;
}
// 新增容量
int newCapacity;
// 此缓冲区可包含的字节数等于0。
if (capacity() == 0) {
// 新增容量设置为1
newCapacity = 1;
} else {
// 新增容量设置为缓冲区可包含的字节数
newCapacity = capacity();
}
// 最小新增容量 = 当前的写索引+最小写入的字节数
int minNewCapacity = writerIndex() + minWritableBytes;
// 当新增容量比最小新增容量小
while (newCapacity < minNewCapacity) {
// 新增容量左移1位,也就是加倍
newCapacity <<= 1;
}
// 通过工厂创建该容量大小当缓冲区
ChannelBuffer newBuffer = factory().getBuffer(newCapacity);
// 从buffer中读取数据到newBuffer中
newBuffer.writeBytes(buffer, 0, writerIndex());
// 替换原来到缓冲区
buffer = newBuffer;
}</code></pre>
<p>该方法是确保数组有可写的容量,该方法是重写了父类的方法,通过传入一个最小写入的字节数,来对缓冲区进行扩容,可以看到,当现有的缓冲区不够大的时候,会对缓冲区进行加倍对扩容,直到buffer的大小大于传入的最小可写字节数。</p>
<h5>3.copy</h5>
<pre><code class="java">@Override
public ChannelBuffer copy(int index, int length) {
// 创建缓冲区,预计长度最小为64,或者更大
DynamicChannelBuffer copiedBuffer = new DynamicChannelBuffer(Math.max(length, 64), factory());
// 复制数据
copiedBuffer.buffer = buffer.copy(index, length);
// 设置索引,读索引设置为0,写索引设置为copy的数据长度
copiedBuffer.setIndex(0, length);
// 返回缓存区
return copiedBuffer;
}</code></pre>
<p>该方法是复制数据,在创建缓冲区的时候,预计长度最小是64,,然后重新设置读索引写索引。</p>
<p>其他方法都调用了buffer的方法或者调用了父类的方法,所以不再这里多说。</p>
<h4>(四)ByteBufferBackedChannelBuffer</h4>
<p>该方法继承AbstractChannelBuffer,该类是基于 Java NIO中的ByteBuffer来实现相关的读写数据等操作。</p>
<pre><code class="java">/**
* ByteBuffer实例
*/
private final ByteBuffer buffer;
/**
* 容量
*/
private final int capacity;
public ByteBufferBackedChannelBuffer(ByteBuffer buffer) {
if (buffer == null) {
throw new NullPointerException("buffer");
}
// 创建一个新的字节缓冲区,新缓冲区的大小将是此缓冲区的剩余容量
this.buffer = buffer.slice();
// 返回buffer的剩余容量
capacity = buffer.remaining();
// 设置写索引
writerIndex(capacity);
}</code></pre>
<p>上述就是该类的属性和构造函数,可以看到它有一个ByteBuffer类型的实例,并且capacity是buffer的剩余容量。</p>
<p>还有其他的方法比如getByte方法是从buffer中读取数据方法,setBytes方法是把数据写入buffer,它们都有很多重载方法,为就不一一讲解了,它们都是调用了ByteBuffer中的一些方法,如果对于Java NIO中的ByteBuffer方法不是很熟悉的朋友,需要先了解一下Java NIO中的ByteBuffer。</p>
<h4>(五)HeapChannelBuffer</h4>
<p>该方法继承了AbstractChannelBuffer,该类中buffer是基于字节数组实现</p>
<pre><code class="java">/**
* The underlying heap byte array that this buffer is wrapping.
* 此缓冲区包装的基础堆字节数组。
*/
protected final byte[] array;
/**
* Creates a new heap buffer with a newly allocated byte array.
* 使用新分配的字节数组创建新的堆缓冲区。
*
* @param length the length of the new byte array
*/
public HeapChannelBuffer(int length) {
this(new byte[length], 0, 0);
}
/**
* Creates a new heap buffer with an existing byte array.
* 使用现有字节数组创建新的堆缓冲区。
*
* @param array the byte array to wrap
*/
public HeapChannelBuffer(byte[] array) {
this(array, 0, array.length);
}
/**
* Creates a new heap buffer with an existing byte array.
* 使用现有字节数组创建新的堆缓冲区。
*
* @param array the byte array to wrap
* @param readerIndex the initial reader index of this buffer
* @param writerIndex the initial writer index of this buffer
*/
protected HeapChannelBuffer(byte[] array, int readerIndex, int writerIndex) {
if (array == null) {
throw new NullPointerException("array");
}
this.array = array;
setIndex(readerIndex, writerIndex);
}</code></pre>
<p>该类有好几个构造函数,都是基于字节数组的,也就是在该类中包装了一个字节数组,把构造函数传入的字节数组传入到该属性中。其他方法逻辑比较简单。</p>
<h4>(六)ChannelBufferFactory</h4>
<pre><code class="java">public interface ChannelBufferFactory {
/**
* 获得缓冲区实例
* @param capacity
* @return
*/
ChannelBuffer getBuffer(int capacity);
ChannelBuffer getBuffer(byte[] array, int offset, int length);
ChannelBuffer getBuffer(ByteBuffer nioBuffer);
}</code></pre>
<p>该接口是通道缓冲区工厂,其中就只定义了获得通道缓冲区的方法,比较好理解,它有两个实现类,我后续会讲到。</p>
<h4>(七)HeapChannelBufferFactory</h4>
<p>该类实现了ChannelBufferFactory,该类就是基于字节数组来创建缓冲区的工厂。</p>
<pre><code class="java">public class HeapChannelBufferFactory implements ChannelBufferFactory {
/**
* 单例
*/
private static final HeapChannelBufferFactory INSTANCE = new HeapChannelBufferFactory();
public HeapChannelBufferFactory() {
super();
}
public static ChannelBufferFactory getInstance() {
return INSTANCE;
}
@Override
public ChannelBuffer getBuffer(int capacity) {
// 创建一个capacity容量的缓冲区
return ChannelBuffers.buffer(capacity);
}
@Override
public ChannelBuffer getBuffer(byte[] array, int offset, int length) {
return ChannelBuffers.wrappedBuffer(array, offset, length);
}
@Override
public ChannelBuffer getBuffer(ByteBuffer nioBuffer) {
// 判断该缓冲区是否有字节数组支持
if (nioBuffer.hasArray()) {
// 使用
return ChannelBuffers.wrappedBuffer(nioBuffer);
}
// 创建一个nioBuffer剩余容量的缓冲区
ChannelBuffer buf = getBuffer(nioBuffer.remaining());
// 记录下nioBuffer的位置
int pos = nioBuffer.position();
// 写入数据到buf
buf.writeBytes(nioBuffer);
// 把nioBuffer的位置重置到pos
nioBuffer.position(pos);
return buf;
}
}</code></pre>
<p>该类利用了单例模式,其中的方法比较简单,就是调用了ChannelBuffers中的方法,调用的方法实际上还是使用了HeapChannelBuffer中创建缓冲区的方法。</p>
<h4>(八)DirectChannelBufferFactory</h4>
<p>该类实现了ChannelBufferFactory接口,是直接缓冲区工厂,用来创建直接缓冲区。</p>
<pre><code class="java">public class DirectChannelBufferFactory implements ChannelBufferFactory {
/**
* 单例
*/
private static final DirectChannelBufferFactory INSTANCE = new DirectChannelBufferFactory();
public DirectChannelBufferFactory() {
super();
}
public static ChannelBufferFactory getInstance() {
return INSTANCE;
}
@Override
public ChannelBuffer getBuffer(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("capacity: " + capacity);
}
if (capacity == 0) {
return ChannelBuffers.EMPTY_BUFFER;
}
// 生成直接缓冲区
return ChannelBuffers.directBuffer(capacity);
}
@Override
public ChannelBuffer getBuffer(byte[] array, int offset, int length) {
if (array == null) {
throw new NullPointerException("array");
}
if (offset < 0) {
throw new IndexOutOfBoundsException("offset: " + offset);
}
if (length == 0) {
return ChannelBuffers.EMPTY_BUFFER;
}
if (offset + length > array.length) {
throw new IndexOutOfBoundsException("length: " + length);
}
ChannelBuffer buf = getBuffer(length);
buf.writeBytes(array, offset, length);
return buf;
}
@Override
public ChannelBuffer getBuffer(ByteBuffer nioBuffer) {
// 如果nioBuffer不是只读,并且它是直接缓冲区
if (!nioBuffer.isReadOnly() && nioBuffer.isDirect()) {
// 创建一个缓冲区
return ChannelBuffers.wrappedBuffer(nioBuffer);
}
// 创建一个nioBuffer剩余容量的缓冲区
ChannelBuffer buf = getBuffer(nioBuffer.remaining());
// 记录下nioBuffer的位置
int pos = nioBuffer.position();
// 写入数据到buf
buf.writeBytes(nioBuffer);
// 把nioBuffer的位置重置到pos
nioBuffer.position(pos);
return buf;
}</code></pre>
<p>该类中的实现方式与HeapChannelBufferFactory中的实现方式差不多,唯一的区别就是它创建的是一个直接缓冲区。</p>
<h4>(九)ChannelBuffers</h4>
<p>该类是缓冲区的工具类,提供创建、比较 ChannelBuffer 等公用方法。我在这里举两个方法来讲:</p>
<pre><code class="java">public static ChannelBuffer wrappedBuffer(ByteBuffer buffer) {
// 如果缓冲区没有剩余容量
if (!buffer.hasRemaining()) {
return EMPTY_BUFFER;
}
// 如果是字节数组生成的缓冲区
if (buffer.hasArray()) {
// 使用buffer的字节数组生成一个新的缓冲区
return wrappedBuffer(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
} else {
// 基于ByteBuffer创建一个缓冲区(利用buffer的剩余容量创建)
return new ByteBufferBackedChannelBuffer(buffer);
}
}</code></pre>
<p>该方法通过buffer来创建一个新的缓冲区。可以看出来调用的就是上述生成缓冲区的三个类中的方法,ChannelBuffers中很多方法都是这样去实现的,逻辑比较简单。</p>
<pre><code class="java">public static boolean equals(ChannelBuffer bufferA, ChannelBuffer bufferB) {
// 获得bufferA的可读数据
final int aLen = bufferA.readableBytes();
// 如果两个缓冲区的可读数据大小不一样,则不是同一个
if (aLen != bufferB.readableBytes()) {
return false;
}
final int byteCount = aLen & 7;
// 获得两个比较的缓冲区的读索引
int aIndex = bufferA.readerIndex();
int bIndex = bufferB.readerIndex();
// 最多比较缓冲区中的7个数据
for (int i = byteCount; i > 0; i--) {
// 一旦有一个数据不一样,则不是同一个
if (bufferA.getByte(aIndex) != bufferB.getByte(bIndex)) {
return false;
}
aIndex++;
bIndex++;
}
return true;
}</code></pre>
<p>该方法就是比较两个缓冲区是否为同一个,重写了equals。</p>
<h4>(十)ChannelBufferOutputStream</h4>
<p>该类继承了OutputStream</p>
<h5>1.属性和构造方法</h5>
<pre><code class="java">/**
* 缓冲区
*/
private final ChannelBuffer buffer;
/**
* 记录开始写入的索引
*/
private final int startIndex;
public ChannelBufferOutputStream(ChannelBuffer buffer) {
if (buffer == null) {
throw new NullPointerException("buffer");
}
this.buffer = buffer;
// 把开始写入数据的索引记录下来
startIndex = buffer.writerIndex();
}</code></pre>
<p>该类中包装了一个缓冲区对象和startIndex,startIndex是记录开始写入的索引。</p>
<h5>2.writtenBytes</h5>
<pre><code class="java">public int writtenBytes() {
return buffer.writerIndex() - startIndex;
}</code></pre>
<p>该方法是返回写入了多少数据。</p>
<p>该类里面还有write方法,都是调用了buffer.writeBytes。</p>
<h4>(十一)ChannelBufferInputStream</h4>
<p>该类继承了InputStream</p>
<h5>1.属性和构造函数</h5>
<pre><code class="java">/**
* 缓冲区
*/
private final ChannelBuffer buffer;
/**
* 记录开始读数据的索引
*/
private final int startIndex;
/**
* 结束读数据的索引
*/
private final int endIndex;
public ChannelBufferInputStream(ChannelBuffer buffer) {
this(buffer, buffer.readableBytes());
}
public ChannelBufferInputStream(ChannelBuffer buffer, int length) {
if (buffer == null) {
throw new NullPointerException("buffer");
}
if (length < 0) {
throw new IllegalArgumentException("length: " + length);
}
if (length > buffer.readableBytes()) {
throw new IndexOutOfBoundsException();
}
this.buffer = buffer;
// 记录开始读数据的索引
startIndex = buffer.readerIndex();
// 设置结束读数据的索引
endIndex = startIndex + length;
// 标记读索引
buffer.markReaderIndex();
}</code></pre>
<p>该类里面包装了读开始索引和结束索引,并且在构造方法中初始化这些属性。</p>
<h5>2.readBytes</h5>
<pre><code class="java">public int readBytes() {
return buffer.readerIndex() - startIndex;
}</code></pre>
<p>该方法是返回读了多少数据。</p>
<h5>3.available</h5>
<pre><code class="java">@Override
public int available() throws IOException {
return endIndex - buffer.readerIndex();
}</code></pre>
<p>该方法是返回还剩多少数据没读</p>
<h5>4.read</h5>
<pre><code class="java">@Override
public int read() throws IOException {
if (!buffer.readable()) {
return -1;
}
return buffer.readByte() & 0xff;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
// 判断是否还有数据可读
int available = available();
if (available == 0) {
return -1;
}
// 获得需要读取的数据长度
len = Math.min(available, len);
buffer.readBytes(b, off, len);
return len;
}</code></pre>
<p>该方法是读数据,返回读了数据长度。</p>
<h5>5.skip</h5>
<pre><code class="java">@Override
public long skip(long n) throws IOException {
if (n > Integer.MAX_VALUE) {
return skipBytes(Integer.MAX_VALUE);
} else {
return skipBytes((int) n);
}
}
private int skipBytes(int n) throws IOException {
int nBytes = Math.min(available(), n);
// 跳过一些数据
buffer.skipBytes(nBytes);
return nBytes;
}</code></pre>
<p>该方法是跳过n长度来读数据。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=0b8huY%2BRa6NjrmtBTO7sXA%3D%3D.nAo2D%2F5yOB0f2gezo8LQdfEtx9pZSpa5QpknpQNon%2FUvVSC6jYLuLjfOaHKcbmhXTD%2Byy%2FT2nvSCVeDzgcVuD6k8%2Bn64ic%2BlPrpyGwy5lwRf70RyLgNpEKFTVm3%2Fxui%2FkxxPUFyg3Qo0Hyxa5ZN0bTcSUDc1Y41TL4w1yCZCIbl0RcDx2QWYa74PxDaZQRrLWBCPhUiirNtIZIgFCrTnSA%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了Buffer的相关实现逻辑,我很多方法都没有贴出源码,因为很多都是基于Java NIO的ByteBuffer都设计实现,并且要注意AbstractChannelBuffer的三个子类,也就是生成缓冲区的三种形式,还有就是要注意两个创建缓冲区实例的工厂。下一篇我会讲解telnet部分。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(十)远程通信——Exchange层
https://segmentfault.com/a/1190000017467343
2018-12-21T10:06:30+08:00
2018-12-21T10:06:30+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
12
<h2>远程通讯——Exchange层</h2>
<blockquote>目标:介绍Exchange层的相关设计和逻辑、介绍dubbo-remoting-api中的exchange包内的源码解析。</blockquote>
<h3>前言</h3>
<p>上一篇文章我讲的是dubbo框架设计中Transport层,这篇文章我要讲的是它的上一层Exchange层,也就是信息交换层。官方文档对这一层的解释是封装请求响应模式,同步转异步,以 Request, Response为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer。</p>
<p>这一层的设计意图是什么?它应该算是在信息传输层上又做了部分装饰,为了适应rpc调用的一些需求,比如rpc调用中一次请求只关心它所对应的响应,这个时候只是一个message消息传输过来,是无法区分这是新的请求还是上一个请求的响应,这种类似于幂等性的问题以及rpc异步处理返回结果、内置事件等特性都是在Transport层无法解决满足的,所有在Exchange层讲message分成了request和response两种类型,并且在这两个模型上增加一些系统字段来处理问题。具体我会在下面讲到。而dubbo把一条消息分为了协议头和内容两部分:协议头包括系统字段,例如编号等,内容包括具体请求的参数和响应的结果等。在exchange层中大量逻辑都是基于协议头的。</p>
<p>现在对这一层的设计意图大致应该有所了解了吧,现在来看看exchange的类图:</p>
<p><img src="/img/remote/1460000017467346" alt="exchange类图" title="exchange类图"></p>
<p>我讲解的顺序还是按照类图从上而下,分块讲解,忽略绿色的test类。</p>
<h3>源码解析</h3>
<h4>(一)ExchangeChannel</h4>
<pre><code class="java">public interface ExchangeChannel extends Channel {
ResponseFuture request(Object request) throws RemotingException;
ResponseFuture request(Object request, int timeout) throws RemotingException;
ExchangeHandler getExchangeHandler();
@Override
void close(int timeout);
}</code></pre>
<p>该接口是信息交换通道接口,有四个方法,前两个是发送请求消息,区别就是第二个发送请求有超时的参数,getExchangeHandler方法就是返回一个信息交换处理器,第四个是需要覆写父类的方法。</p>
<h4>(二)HeaderExchangeChannel</h4>
<p>该类实现了ExchangeChannel,是基于协议头的信息交换通道。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(HeaderExchangeChannel.class);
/**
* 通道的key值
*/
private static final String CHANNEL_KEY = HeaderExchangeChannel.class.getName() + ".CHANNEL";
/**
* 通道
*/
private final Channel channel;
/**
* 是否关闭
*/
private volatile boolean closed = false;</code></pre>
<p>上述属性比较简单,还是放一下这个类的属性是因为该类中有channel属性,也就是说HeaderExchangeChannel是Channel的装饰器,每个实现方法都会调用channel的方法。</p>
<h5>2.静态方法</h5>
<pre><code class="java">static HeaderExchangeChannel getOrAddChannel(Channel ch) {
if (ch == null) {
return null;
}
// 获得通道中的HeaderExchangeChannel
HeaderExchangeChannel ret = (HeaderExchangeChannel) ch.getAttribute(CHANNEL_KEY);
if (ret == null) {
// 创建一个HeaderExchangeChannel实例
ret = new HeaderExchangeChannel(ch);
// 如果通道连接
if (ch.isConnected()) {
// 加入属性值
ch.setAttribute(CHANNEL_KEY, ret);
}
}
return ret;
}
static void removeChannelIfDisconnected(Channel ch) {
// 如果通道断开连接
if (ch != null && !ch.isConnected()) {
// 移除属性值
ch.removeAttribute(CHANNEL_KEY);
}
}</code></pre>
<p>该静态方法做了HeaderExchangeChannel的创建和销毁,并且生命周期随channel销毁而销毁。</p>
<h5>3.send</h5>
<pre><code class="java">@Override
public void send(Object message) throws RemotingException {
send(message, getUrl().getParameter(Constants.SENT_KEY, false));
}
@Override
public void send(Object message, boolean sent) throws RemotingException {
// 如果通道关闭,抛出异常
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send message " + message + ", cause: The channel " + this + " is closed!");
}
// 判断消息的类型
if (message instanceof Request
|| message instanceof Response
|| message instanceof String) {
// 发送消息
channel.send(message, sent);
} else {
// 新建一个request实例
Request request = new Request();
// 设置信息的版本
request.setVersion(Version.getProtocolVersion());
// 该请求不需要响应
request.setTwoWay(false);
// 把消息传入
request.setData(message);
// 发送消息
channel.send(request, sent);
}
}</code></pre>
<p>该方法是在channel的send方法上加上了request和response模型,最后再调用channel.send,起到了装饰器的作用。</p>
<h5>4.request</h5>
<pre><code class="java">@Override
public ResponseFuture request(Object request) throws RemotingException {
return request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
}
@Override
public ResponseFuture request(Object request, int timeout) throws RemotingException {
// 如果通道关闭,则抛出异常
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.创建请求
Request req = new Request();
// 设置版本号
req.setVersion(Version.getProtocolVersion());
// 设置需要响应
req.setTwoWay(true);
// 把请求数据传入
req.setData(request);
// 创建DefaultFuture对象,可以从future中主动获得请求对应的响应信息
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try {
// 发送请求消息
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}</code></pre>
<p>该方法是请求方法,用Request模型把请求内容装饰起来,然后发送一个Request类型的消息,并且返回DefaultFuture实例,DefaultFuture我会在后面讲到。</p>
<p>cloes方法也重写了,我就不再多说,因为比较简单,没有重点,其他方法都是直接调用channel属性的方法。</p>
<h4>(三)ExchangeClient</h4>
<p>该接口继承了Client和ExchangeChannel,是信息交换客户端接口,其中没有定义多余的方法。</p>
<h4>(四)HeaderExchangeClient</h4>
<p> 该类实现了ExchangeClient接口,是基于协议头的信息交互客户端类,同样它是Client、Channel的适配器。在该类的源码中可以看到所有的实现方法都是调用了client和channel属性的方法。该类主要的作用就是增加了心跳功能,为什么要增加心跳功能呢,对于长连接,一些拔网线等物理层的断开,会导致TCP的FIN消息来不及发送,对方收不到断开事件,那么就需要用到发送心跳包来检测连接是否断开。consumer和provider断开,处理措施不一样,会分别做出重连和关闭通道的操作。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(HeaderExchangeClient.class);
/**
* 定时器线程池
*/
private static final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("dubbo-remoting-client-heartbeat", true));
/**
* 客户端
*/
private final Client client;
/**
* 信息交换通道
*/
private final ExchangeChannel channel;
// heartbeat timer
/**
* 心跳定时器
*/
private ScheduledFuture<?> heartbeatTimer;
// heartbeat(ms), default value is 0 , won't execute a heartbeat.
/**
* 心跳周期,间隔多久发送心跳消息检测一次
*/
private int heartbeat;
/**
* 心跳超时时间
*/
private int heartbeatTimeout;</code></pre>
<p>该类的属性除了需要适配的属性外,其他都是跟心跳相关属性。</p>
<h5>2.构造函数</h5>
<pre><code class="java">public HeaderExchangeClient(Client client, boolean needHeartbeat) {
if (client == null) {
throw new IllegalArgumentException("client == null");
}
this.client = client;
// 创建信息交换通道
this.channel = new HeaderExchangeChannel(client);
// 获得dubbo版本
String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY);
//获得心跳周期配置,如果没有配置,并且dubbo是1.0版本的,则这只为1分钟,否则设置为0
this.heartbeat = client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith("1.0.") ? Constants.DEFAULT_HEARTBEAT : 0);
// 获得心跳超时配置,默认是心跳周期的三倍
this.heartbeatTimeout = client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);
// 如果心跳超时时间小于心跳周期的两倍,则抛出异常
if (heartbeatTimeout < heartbeat * 2) {
throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
}
if (needHeartbeat) {
// 开启心跳
startHeartbeatTimer();
}
}</code></pre>
<p>构造函数就是对一些属性初始化设置,优先从url中获取。心跳超时时间小于心跳周期的两倍就抛出异常,意思就是至少重试两次心跳检测。</p>
<h5>3.startHeartbeatTimer</h5>
<pre><code class="java">private void startHeartbeatTimer() {
// 停止现有的心跳线程
stopHeartbeatTimer();
// 如果需要心跳
if (heartbeat > 0) {
// 创建心跳定时器
heartbeatTimer = scheduled.scheduleWithFixedDelay(
// 新建一个心跳线程
new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
@Override
public Collection<Channel> getChannels() {
// 返回一个只包含HeaderExchangeClient对象的不可变列表
return Collections.<Channel>singletonList(HeaderExchangeClient.this);
}
}, heartbeat, heartbeatTimeout),
heartbeat, heartbeat, TimeUnit.MILLISECONDS);
}
}</code></pre>
<p>该方法就是开启心跳。利用心跳定时器来做到定时检测心跳。因为这是信息交换客户端类,所有这里的只是返回包含HeaderExchangeClient对象的不可变列表,因为客户端跟channel是一一对应的,只有这一个该客户端本身的channel需要心跳。</p>
<h5>4.stopHeartbeatTimer</h5>
<pre><code class="java">private void stopHeartbeatTimer() {
if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) {
try {
// 取消定时器
heartbeatTimer.cancel(true);
// 取消大量已排队任务,用于回收空间
scheduled.purge();
} catch (Throwable e) {
if (logger.isWarnEnabled()) {
logger.warn(e.getMessage(), e);
}
}
}
heartbeatTimer = null;
}</code></pre>
<p>该方法是停止现有心跳,也就是停止定时器,释放空间。</p>
<p>其他方法都是调用channel和client属性的方法。</p>
<h4>(五)HeartBeatTask</h4>
<p>该类实现了Runnable接口,实现的是心跳任务,里面包含了核心的心跳策略。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 通道管理
*/
private ChannelProvider channelProvider;
/**
* 心跳间隔 单位:ms
*/
private int heartbeat;
/**
* 心跳超时时间 单位:ms
*/
private int heartbeatTimeout;</code></pre>
<p>后两个属性跟HeaderExchangeClient中的属性含义一样,第一个是该类自己内部的一个接口:</p>
<pre><code class="java">interface ChannelProvider {
// 获得所有的通道集合,需要心跳的通道数组
Collection<Channel> getChannels();
}</code></pre>
<p>该接口就定义了一个方法,获得需要心跳的通道集合。可想而知,会对集合内的通道都做心跳检测。</p>
<h5>2.run</h5>
<pre><code class="java">@Override
public void run() {
try {
long now = System.currentTimeMillis();
// 遍历所有通道
for (Channel channel : channelProvider.getChannels()) {
// 如果通道关闭了,则跳过
if (channel.isClosed()) {
continue;
}
try {
// 最后一次接收到消息的时间戳
Long lastRead = (Long) channel.getAttribute(
HeaderExchangeHandler.KEY_READ_TIMESTAMP);
// 最后一次发送消息的时间戳
Long lastWrite = (Long) channel.getAttribute(
HeaderExchangeHandler.KEY_WRITE_TIMESTAMP);
// 如果最后一次接收或者发送消息到时间到现在的时间间隔超过了心跳间隔时间
if ((lastRead != null && now - lastRead > heartbeat)
|| (lastWrite != null && now - lastWrite > heartbeat)) {
// 创建一个request
Request req = new Request();
// 设置版本号
req.setVersion(Version.getProtocolVersion());
// 设置需要得到响应
req.setTwoWay(true);
// 设置事件类型,为心跳事件
req.setEvent(Request.HEARTBEAT_EVENT);
// 发送心跳请求
channel.send(req);
if (logger.isDebugEnabled()) {
logger.debug("Send heartbeat to remote channel " + channel.getRemoteAddress()
+ ", cause: The channel has no data-transmission exceeds a heartbeat period: " + heartbeat + "ms");
}
}
// 如果最后一次接收消息的时间到现在已经超过了超时时间
if (lastRead != null && now - lastRead > heartbeatTimeout) {
logger.warn("Close channel " + channel
+ ", because heartbeat read idle time out: " + heartbeatTimeout + "ms");
// 如果该通道是客户端,也就是请求的服务器挂掉了,客户端尝试重连服务器
if (channel instanceof Client) {
try {
// 重新连接服务器
((Client) channel).reconnect();
} catch (Exception e) {
//do nothing
}
} else {
// 如果不是客户端,也就是是服务端返回响应给客户端,但是客户端挂掉了,则服务端关闭客户端连接
channel.close();
}
}
} catch (Throwable t) {
logger.warn("Exception when heartbeat to remote channel " + channel.getRemoteAddress(), t);
}
}
} catch (Throwable t) {
logger.warn("Unhandled exception when heartbeat, cause: " + t.getMessage(), t);
}
}</code></pre>
<p>该方法中是心跳机制的核心逻辑。注意以下几个点:</p>
<ol>
<li>如果需要心跳的通道本身如果关闭了,那么跳过,不添加心跳机制。</li>
<li>无论是接收消息还是发送消息,只要超过了设置的心跳间隔,就发送心跳消息来测试是否断开</li>
<li>如果最后一次接收到消息到到现在已经超过了心跳超时时间,那就认定对方的确断开,分两种情况来处理对方断开的情况。分别是服务端断开,客户端重连以及客户端断开,服务端断开这个客户端的连接。,这里要好好品味一下谁是发送方,谁在等谁的响应,苦苦没有等到。</li>
</ol>
<h4>(六)ResponseFuture</h4>
<pre><code class="java">public interface ResponseFuture {
Object get() throws RemotingException;
Object get(int timeoutInMillis) throws RemotingException;
void setCallback(ResponseCallback callback);
boolean isDone();
}</code></pre>
<p>该接口是响应future接口,该接口的设计意图跟java.util.concurrent.Future很类似。发送出去的消息,泼出去的水,只有等到对方主动响应才能得到结果,但是请求方需要去主动回去该请求的结果,就显得有些艰难,所有产生了这样一个接口,它能够获取任务执行结果、可以核对请求消息是否被响应,还能设置回调来支持异步。</p>
<h4>(七)DefaultFuture</h4>
<p>该类实现了ResponseFuture接口,其中封装了处理响应的逻辑。你可以把DefaultFuture看成是一个中介,买房和卖房都通过这个中介进行沟通,中介拥有着买房者的信息request和卖房者的信息response,并且促成他们之间的买卖。</p>
<h5>1.属性</h5>
<pre><code class="java">private static final Logger logger = LoggerFactory.getLogger(DefaultFuture.class);
/**
* 通道集合
*/
private static final Map<Long, Channel> CHANNELS = new ConcurrentHashMap<Long, Channel>();
/**
* Future集合,key为请求编号
*/
private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture>();
// invoke id.
/**
* 请求编号
*/
private final long id;
/**
* 通道
*/
private final Channel channel;
/**
* 请求
*/
private final Request request;
/**
* 超时
*/
private final int timeout;
/**
* 锁
*/
private final Lock lock = new ReentrantLock();
/**
* 完成情况,控制多线程的休眠与唤醒
*/
private final Condition done = lock.newCondition();
/**
* 创建开始时间
*/
private final long start = System.currentTimeMillis();
/**
* 发送请求时间
*/
private volatile long sent;
/**
* 响应
*/
private volatile Response response;
/**
* 回调
*/
private volatile ResponseCallback callback;</code></pre>
<p>可以看到,该类的属性包含了request、response、channel三个实例,在该类中,把请求和响应通过唯一的id一一对应起来。做到异步处理返回结果时能给准确的返回给对应的请求。可以看到属性中有两个集合,分别是通道集合和future集合,也就是该类本身也是所有 DefaultFuture 的管理容器。</p>
<h5>2.构造函数</h5>
<pre><code class="java">public DefaultFuture(Channel channel, Request request, int timeout) {
this.channel = channel;
this.request = request;
// 设置请求编号
this.id = request.getId();
this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// put into waiting map.,加入到等待集合中
FUTURES.put(id, this);
CHANNELS.put(id, channel);
}</code></pre>
<p>构造函数比较简单,每一个DefaultFuture实例都跟每一个请求一一对应,被存入到集合中管理起来。</p>
<h5>3.closeChannel</h5>
<pre><code class="java">public static void closeChannel(Channel channel) {
// 遍历通道集合
for (long id : CHANNELS.keySet()) {
if (channel.equals(CHANNELS.get(id))) {
// 通过请求id获得future
DefaultFuture future = getFuture(id);
if (future != null && !future.isDone()) {
// 创建一个关闭通道的响应
Response disconnectResponse = new Response(future.getId());
disconnectResponse.setStatus(Response.CHANNEL_INACTIVE);
disconnectResponse.setErrorMessage("Channel " +
channel +
" is inactive. Directly return the unFinished request : " +
future.getRequest());
// 接收该关闭通道并且请求未完成的响应
DefaultFuture.received(channel, disconnectResponse);
}
}
}
}</code></pre>
<p>该方法是关闭不活跃的通道,并且返回请求未完成。也就是关闭指定channel的请求,返回的是请求未完成。</p>
<h5>4.received</h5>
<pre><code class="java">public static void received(Channel channel, Response response) {
try {
// future集合中移除该请求的future,(响应id和请求id一一对应的)
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
// 接收响应结果
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at "
+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
+ ", response " + response
+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()));
}
} finally {
// 通道集合移除该请求对应的通道,代表着这一次请求结束
CHANNELS.remove(response.getId());
}
}</code></pre>
<p>该方法是接收响应,也就是某个请求得到了响应,那么代表这次请求任务完成,所有需要把future从集合中移除。具体的接收响应结果在doReceived方法中实现。</p>
<h5>5.doReceived</h5>
<pre><code class="java">private void doReceived(Response res) {
// 获得锁
lock.lock();
try {
// 设置响应
response = res;
if (done != null) {
// 唤醒等待
done.signal();
}
} finally {
// 释放锁
lock.unlock();
}
if (callback != null) {
// 执行回调
invokeCallback(callback);
}
}</code></pre>
<p>可以看到,当接收到响应后,会把等待的线程唤醒,然后执行回调来处理该响应结果。</p>
<h5>6.invokeCallback</h5>
<pre><code class="java">private void invokeCallback(ResponseCallback c) {
ResponseCallback callbackCopy = c;
if (callbackCopy == null) {
throw new NullPointerException("callback cannot be null.");
}
c = null;
Response res = response;
if (res == null) {
throw new IllegalStateException("response cannot be null. url:" + channel.getUrl());
}
// 如果响应成功,返回码是20
if (res.getStatus() == Response.OK) {
try {
// 使用响应结果执行 完成 后的逻辑
callbackCopy.done(res.getResult());
} catch (Exception e) {
logger.error("callback invoke error .reasult:" + res.getResult() + ",url:" + channel.getUrl(), e);
}
//超时,回调处理成超时异常
} else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
try {
TimeoutException te = new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());
// 回调处理异常
callbackCopy.caught(te);
} catch (Exception e) {
logger.error("callback invoke error ,url:" + channel.getUrl(), e);
}
// 其他情况处理成RemotingException异常
} else {
try {
RuntimeException re = new RuntimeException(res.getErrorMessage());
callbackCopy.caught(re);
} catch (Exception e) {
logger.error("callback invoke error ,url:" + channel.getUrl(), e);
}
}
}</code></pre>
<p>该方法是执行回调来处理响应结果。分为了三种情况:</p>
<ol>
<li>响应成功,那么执行完成后的逻辑。</li>
<li>超时,会按照超时异常来处理</li>
<li>其他,按照RuntimeException异常来处理</li>
</ol>
<p>具体的处理都在ResponseCallback接口的实现类里执行,后面我会讲到。</p>
<h5>7.get</h5>
<pre><code class="java">@Override
public Object get() throws RemotingException {
return get(timeout);
}
@Override
public Object get(int timeout) throws RemotingException {
// 超时时间默认为1s
if (timeout <= 0) {
timeout = Constants.DEFAULT_TIMEOUT;
}
// 如果请求没有完成,也就是还没有响应返回
if (!isDone()) {
long start = System.currentTimeMillis();
// 获得锁
lock.lock();
try {
// 轮询 等待请求是否完成
while (!isDone()) {
// 线程阻塞等待
done.await(timeout, TimeUnit.MILLISECONDS);
// 如果请求完成或者超时,则结束
if (isDone() || System.currentTimeMillis() - start > timeout) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 释放锁
lock.unlock();
}
// 如果没有收到响应,则抛出超时的异常
if (!isDone()) {
throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
}
}
// 返回响应
return returnFromResponse();
}</code></pre>
<p>该方法是实现了ResponseFuture定义的方法,是获得该future对应的请求对应的响应结果,其实future、请求、响应都是一一对应的。其中如果还没得到响应,则会线程阻塞等待,等到有响应结果或者超时,才返回。返回的逻辑在returnFromResponse中实现。</p>
<h5>8.returnFromResponse</h5>
<pre><code class="java">private Object returnFromResponse() throws RemotingException {
Response res = response;
if (res == null) {
throw new IllegalStateException("response cannot be null");
}
// 如果正常返回,则返回响应结果
if (res.getStatus() == Response.OK) {
return res.getResult();
}
// 如果超时,则抛出超时异常
if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());
}
// 其他 抛出RemotingException异常
throw new RemotingException(channel, res.getErrorMessage());
}</code></pre>
<p>这代码跟invokeCallback方法中差不多,都是把响应分了三种情况。</p>
<h5>9.cancel</h5>
<pre><code class="java">public void cancel() {
// 创建一个取消请求的响应
Response errorResult = new Response(id);
errorResult.setErrorMessage("request future has been canceled.");
response = errorResult;
// 从集合中删除该请求
FUTURES.remove(id);
CHANNELS.remove(id);
}</code></pre>
<p>该方法是取消一个请求,可以直接关闭一个请求,也就是值创建一个响应来回应该请求,把response值设置到该请求对于到future中,做到了中断请求的作用。该方法跟closeChannel的区别是closeChannel中对response的状态设置了CHANNEL_INACTIVE,而cancel方法是中途被主动取消的,虽然有response值,但是并没有一个响应状态。</p>
<h5>10.RemotingInvocationTimeoutScan</h5>
<pre><code class="java">private static class RemotingInvocationTimeoutScan implements Runnable {
@Override
public void run() {
while (true) {
try {
for (DefaultFuture future : FUTURES.values()) {
// 已经完成,跳过扫描
if (future == null || future.isDone()) {
continue;
}
// 超时
if (System.currentTimeMillis() - future.getStartTimestamp() > future.getTimeout()) {
// create exception response.,创建一个超时的响应
Response timeoutResponse = new Response(future.getId());
// set timeout status.,设置超时状态,是服务端侧超时还是客户端侧超时
timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT);
// 设置错误信息
timeoutResponse.setErrorMessage(future.getTimeoutMessage(true));
// handle response.,接收创建的超时响应
DefaultFuture.received(future.getChannel(), timeoutResponse);
}
}
// 睡眠
Thread.sleep(30);
} catch (Throwable e) {
logger.error("Exception when scan the timeout invocation of remoting.", e);
}
}
}
}</code></pre>
<p>该方法是扫描调用超时任务的线程,每次都会遍历future集合,检测请求是否超时了,如果超时则创建一个超时响应来回应该请求。</p>
<pre><code class="java">static {
// 开启一个后台扫描调用超时任务
Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
th.setDaemon(true);
th.start();
}
</code></pre>
<p>开启一个后台线程进行扫描的逻辑写在了静态代码块里面,只开启一次。</p>
<h4>(八)SimpleFuture</h4>
<p>该类实现了ResponseFuture,目前没有用到,很简单的实现,我就不多说了。</p>
<h4>(九)ExchangeHandler</h4>
<p>该接口继承了ChannelHandler, TelnetHandler接口,是信息交换处理器接口。</p>
<pre><code class="java">public interface ExchangeHandler extends ChannelHandler, TelnetHandler {
/**
* reply.
* 回复请求结果
* @param channel
* @param request
* @return response
* @throws RemotingException
*/
Object reply(ExchangeChannel channel, Object request) throws RemotingException;
}</code></pre>
<p>该接口只定义了一个回复请求结果的方法,返回的是请求结果。</p>
<h4>(十)ExchangeHandlerDispatcher</h4>
<p>该类实现了ExchangeHandler接口, 是信息交换处理器调度器类,也就是对应不同的事件,选择不同的处理器去处理。该类中有三个属性,分别对应了三种事件:</p>
<pre><code class="java">/**
* 回复者调度器
*/
private final ReplierDispatcher replierDispatcher;
/**
* 通道处理器调度器
*/
private final ChannelHandlerDispatcher handlerDispatcher;
/**
* Telnet 命令处理器
*/
private final TelnetHandler telnetHandler;</code></pre>
<p>如果事件是跟通道处理器有关的,就调用通道处理器来处理,比如:</p>
<pre><code class="java">@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Object reply(ExchangeChannel channel, Object request) throws RemotingException {
return ((Replier) replierDispatcher).reply(channel, request);
}
@Override
public void connected(Channel channel) {
handlerDispatcher.connected(channel);
}
@Override
public String telnet(Channel channel, String message) throws RemotingException {
return telnetHandler.telnet(channel, message);
}</code></pre>
<p>可以看到以上三种事件,回复请求结果需要回复者调度器来处理,连接需要通道处理器调度器来处理,telnet消息需要Telnet命令处理器来处理。</p>
<h4>(十一)ExchangeHandlerAdapter</h4>
<p>该类继承了TelnetHandlerAdapter,实现了ExchangeHandler,是信息交换处理器的适配器类。</p>
<pre><code class="java">public abstract class ExchangeHandlerAdapter extends TelnetHandlerAdapter implements ExchangeHandler {
@Override
public Object reply(ExchangeChannel channel, Object msg) throws RemotingException {
// 直接返回null
return null;
}
}</code></pre>
<p>该类直接让ExchangeHandler定义的方法reply返回null,交由它的子类选择性的去实现具体的回复请求结果。</p>
<h4>(十二)ExchangeServer</h4>
<p>该接口继承了Server接口,定义了两个方法:</p>
<pre><code class="java">public interface ExchangeServer extends Server {
/**
* get channels.
* 获得通道集合
* @return channels
*/
Collection<ExchangeChannel> getExchangeChannels();
/**
* get channel.
* 根据远程地址获得对应的信息通道
* @param remoteAddress
* @return channel
*/
ExchangeChannel getExchangeChannel(InetSocketAddress remoteAddress);
}</code></pre>
<p>该接口比较好理解,并且在Server接口基础上新定义了两个方法。直接来看看它的实现类吧。</p>
<h4>(十三)HeaderExchangeServer</h4>
<p>该类实现了ExchangeServer接口,是基于协议头的信息交换服务器实现类,HeaderExchangeServer是Server的装饰器,每个实现方法都会调用server的方法。</p>
<h5>1.属性</h5>
<pre><code class="java">protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 线程池
*/
private final ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1,
new NamedThreadFactory(
"dubbo-remoting-server-heartbeat",
true));
/**
* 服务器
*/
private final Server server;
// heartbeat timer
/**
* 心跳定时器
*/
private ScheduledFuture<?> heartbeatTimer;
// heartbeat timeout (ms), default value is 0 , won't execute a heartbeat.
/**
* 心跳周期
*/
private int heartbeat;
/**
* 心跳超时时间
*/
private int heartbeatTimeout;
/**
* 信息交换服务器是否关闭
*/
private AtomicBoolean closed = new AtomicBoolean(false);</code></pre>
<p>该类里面的很多实现跟HeaderExchangeClient差不多,包括心跳检测等逻辑。看得懂上述我讲的HeaderExchangeClient的属性,想必这里的属性应该也很简单了。</p>
<h5>2.构造函数</h5>
<pre><code class="java">public HeaderExchangeServer(Server server) {
if (server == null) {
throw new IllegalArgumentException("server == null");
}
this.server = server;
//获得心跳周期配置,如果没有配置,默认设置为0
this.heartbeat = server.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);
// 获得心跳超时配置,默认是心跳周期的三倍
this.heartbeatTimeout = server.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);
// 如果心跳超时时间小于心跳周期的两倍,则抛出异常
if (heartbeatTimeout < heartbeat * 2) {
throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
}
// 开始心跳
startHeartbeatTimer();
}
public Server getServer() {
return server;
}</code></pre>
<p>构造函数就是对属性的设置,心跳的机制以及默认值都跟HeaderExchangeClient中的一模一样。</p>
<h5>3.isRunning</h5>
<pre><code class="java">private boolean isRunning() {
Collection<Channel> channels = getChannels();
// 遍历所有连接该服务器的通道
for (Channel channel : channels) {
/**
* If there are any client connections,
* our server should be running.
*/
// 只要有任何一个客户端连接,则服务器还运行着
if (channel.isConnected()) {
return true;
}
}
return false;
}</code></pre>
<p>该方法是检测服务器是否还运行,只要有一个客户端连接着,就算服务器运行着。</p>
<h5>4.close</h5>
<pre><code class="java">@Override
public void close() {
// 关闭线程池和心跳检测
doClose();
// 关闭服务器
server.close();
}
@Override
public void close(final int timeout) {
// 开始关闭
startClose();
if (timeout > 0) {
final long max = (long) timeout;
final long start = System.currentTimeMillis();
if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
// 发送 READONLY_EVENT事件给所有连接该服务器的客户端,表示 Server 不可读了。
sendChannelReadOnlyEvent();
}
// 当服务器还在运行,并且没有超时,睡眠,也就是等待timeout左右时间在进行关闭
while (HeaderExchangeServer.this.isRunning()
&& System.currentTimeMillis() - start < max) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
}
// 关闭线程池和心跳检测
doClose();
// 延迟关闭
server.close(timeout);
}</code></pre>
<p>两个close方法,第二个close方法是优雅的关闭,有一定的延时来让一些响应或者操作做完。关闭分两个步骤,第一个就是关闭信息交换服务器中的线程池和心跳检测,然后才是关闭服务器。</p>
<h5>5.sendChannelReadOnlyEvent</h5>
<pre><code class="java">private void sendChannelReadOnlyEvent() {
// 创建一个READONLY_EVENT事件的请求
Request request = new Request();
request.setEvent(Request.READONLY_EVENT);
// 不需要响应
request.setTwoWay(false);
// 设置版本
request.setVersion(Version.getProtocolVersion());
Collection<Channel> channels = getChannels();
// 遍历连接的通道,进行通知
for (Channel channel : channels) {
try {
// 通过通道还连接着,则发送通知
if (channel.isConnected())
channel.send(request, getUrl().getParameter(Constants.CHANNEL_READONLYEVENT_SENT_KEY, true));
} catch (RemotingException e) {
logger.warn("send cannot write message error.", e);
}
}
}</code></pre>
<p>在关闭服务器中有一个操作就是发送事件READONLY_EVENT,告诉客户端该服务器不可读了,就是该方法实现的,逐个通知连接的客户端该事件。</p>
<h5>6.doClose</h5>
<pre><code class="java">private void doClose() {
if (!closed.compareAndSet(false, true)) {
return;
}
// 停止心跳检测
stopHeartbeatTimer();
try {
// 关闭线程池
scheduled.shutdown();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}</code></pre>
<p>该方法就是close方法调用到的停止心跳检测和关闭线程池。</p>
<h5>7.getExchangeChannels</h5>
<pre><code class="java">@Override
public Collection<ExchangeChannel> getExchangeChannels() {
Collection<ExchangeChannel> exchangeChannels = new ArrayList<ExchangeChannel>();
// 获得连接该服务器通道集合
Collection<Channel> channels = server.getChannels();
if (channels != null && !channels.isEmpty()) {
// 遍历通道集合,为每个通道都创建信息交换通道,并且加入信息交换通道集合
for (Channel channel : channels) {
exchangeChannels.add(HeaderExchangeChannel.getOrAddChannel(channel));
}
}
return exchangeChannels;
}</code></pre>
<p>该方法是返回连接该服务器信息交换通道集合。逻辑就是先获得通道集合,在根据通道来创建信息交换通道,然后返回信息通道集合。</p>
<h5>8.reset</h5>
<pre><code class="java">@Override
public void reset(URL url) {
// 重置属性
server.reset(url);
try {
// 重置的逻辑跟构造函数一样设置
if (url.hasParameter(Constants.HEARTBEAT_KEY)
|| url.hasParameter(Constants.HEARTBEAT_TIMEOUT_KEY)) {
int h = url.getParameter(Constants.HEARTBEAT_KEY, heartbeat);
int t = url.getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, h * 3);
if (t < h * 2) {
throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
}
if (h != heartbeat || t != heartbeatTimeout) {
heartbeat = h;
heartbeatTimeout = t;
// 重新开始心跳
startHeartbeatTimer();
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}</code></pre>
<p>该方法就是重置属性,重置后,重新开始心跳,设置心跳属性的机制跟构造函数一样。</p>
<h5>9.startHeartbeatTimer</h5>
<pre><code class="java">private void startHeartbeatTimer() {
// 先停止现有的心跳检测
stopHeartbeatTimer();
if (heartbeat > 0) {
// 创建心跳定时器
heartbeatTimer = scheduled.scheduleWithFixedDelay(
new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
@Override
public Collection<Channel> getChannels() {
// 返回一个不可修改的连接该服务器的信息交换通道集合
return Collections.unmodifiableCollection(
HeaderExchangeServer.this.getChannels());
}
}, heartbeat, heartbeatTimeout),
heartbeat, heartbeat, TimeUnit.MILLISECONDS);
}
}</code></pre>
<p>该方法是开始心跳,跟HeaderExchangeClient类中的开始心跳方法唯一区别是获得的通道不一样,客户端跟通道是一一对应的,所有只要对一个通道进行心跳检测,而服务端跟通道是一对多的关系,所有需要对该服务器连接的所有通道进行心跳检测。</p>
<h5>10.stopHeartbeatTimer</h5>
<pre><code class="java">private void stopHeartbeatTimer() {
if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) {
try {
// 取消定时器
heartbeatTimer.cancel(true);
// 取消大量已排队任务,用于回收空间
scheduled.purge();
} catch (Throwable e) {
if (logger.isWarnEnabled()) {
logger.warn(e.getMessage(), e);
}
}
}
heartbeatTimer = null;
}</code></pre>
<p>该方法是停止当前的心跳检测。</p>
<h4>(十四)ExchangeServerDelegate</h4>
<p>该类实现了ExchangeServer接口,是信息交换服务器装饰者,是ExchangeServer的装饰器。该类就一个属性ExchangeServer server,所有实现方法都调用了server属性的方法。目前只有在p2p中被用到,代码为就不贴了,很简单。</p>
<h4>(十五)Exchanger</h4>
<pre><code class="java">@SPI(HeaderExchanger.NAME)
public interface Exchanger {
/**
* bind.
* 绑定一个服务器
* @param url 服务器url
* @param handler 数据交换处理器
* @return message server 数据交换服务器
*/
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException;
/**
* connect.
* 连接一个服务器,也就是创建一个客户端
* @param url 服务器url
* @param handler 数据交换处理器
* @return message channel 返回数据交换客户端
*/
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;
}</code></pre>
<p>该接口是数据交换者接口,该接口是一个可扩展接口默认实现的是HeaderExchanger类,并且用到了dubbo SPI的Adaptive机制,优先实现url携带的配置。如果不了解dubbo SPI机制的可以看<a href="https://segmentfault.com/a/1190000016842868">《dubbo源码解析(二)Dubbo扩展机制SPI》</a>。那么回到该接口定义的方法,定义了绑定和连接两个方法,分别返回信息交互服务器和客户端实例。</p>
<h4>(十六)HeaderExchanger</h4>
<pre><code class="java">public class HeaderExchanger implements Exchanger {
public static final String NAME = "header";
@Override
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
// 用传输层连接返回的client 创建对应的信息交换客户端,默认开启心跳检测
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
// 用传输层绑定返回的server 创建对应的信息交换服务端
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
}</code></pre>
<p>该类继承了Exchanger接口,是Exchanger接口的默认实现,实现了Exchanger接口定义的两个方法,分别调用的是Transporters的连接和绑定方法,再利用这这两个方法返回的客户端和服务端实例来创建信息交换的客户端和服务端。</p>
<h4>(十七)Replier</h4>
<p>我们知道Request对应的是ExchangeHandler接口实现对象来处理,但有些时候我们需要不同数据类型对应不同的处理器,该类就是为了支持这一需求所设计的。</p>
<pre><code class="java">public interface Replier<T> {
/**
* reply.
* 回复请求结果
* @param channel
* @param request
* @return response
* @throws RemotingException
*/
Object reply(ExchangeChannel channel, T request) throws RemotingException;
}</code></pre>
<p>可以看到该接口跟ExchangeHandler定义的方法也一一,只有请求的类型改为了范型。</p>
<h4>(十八)ReplierDispatcher</h4>
<p>该类实现了Replier接口,是回复者调度器实现类。</p>
<pre><code class="java">/**
* 默认回复者
*/
private final Replier<?> defaultReplier;
/**
* 回复者集合
*/
private final Map<Class<?>, Replier<?>> repliers = new ConcurrentHashMap<Class<?>, Replier<?>>();</code></pre>
<p>这是该类的两个属性,缓存了回复者集合和默认的回复者。</p>
<pre><code class="java">/**
* 从回复者集合中找到该类型的回复者,并且返回
* @param type
* @return
*/
private Replier<?> getReplier(Class<?> type) {
for (Map.Entry<Class<?>, Replier<?>> entry : repliers.entrySet()) {
if (entry.getKey().isAssignableFrom(type)) {
return entry.getValue();
}
}
if (defaultReplier != null) {
return defaultReplier;
}
throw new IllegalStateException("Replier not found, Unsupported message object: " + type);
}
/**
* 回复请求
* @param channel
* @param request
* @return
* @throws RemotingException
*/
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Object reply(ExchangeChannel channel, Object request) throws RemotingException {
return ((Replier) getReplier(request.getClass())).reply(channel, request);
}</code></pre>
<p>上述是该类中关键的两个方法,reply还是调用实现类的reply。根据请求的数据类型来使用指定的回复者进行回复。</p>
<h4>(十九)MultiMessage</h4>
<p>该类实现了实现 Iterable 接口,是多消息的封装,我们直接看它的属性:</p>
<pre><code class="java">/**
* 消息集合
*/
private final List messages = new ArrayList();</code></pre>
<p>该类要和<a href="https://segmentfault.com/a/1190000017390253">《dubbo源码解析(九)远程通信——Transport层》</a>的(八)MultiMessageHandler联合着看。</p>
<h4>(二十)HeartbeatHandler</h4>
<p>该类继承了AbstractChannelHandlerDelegate类,是心跳处理器。是用来处理心跳事件的,也接收消息上增加了对心跳消息的处理。该类是</p>
<pre><code class="java">@Override
public void received(Channel channel, Object message) throws RemotingException {
// 设置接收时间的时间戳属性值
setReadTimestamp(channel);
// 如果是心跳请求
if (isHeartbeatRequest(message)) {
Request req = (Request) message;
// 如果需要响应
if (req.isTwoWay()) {
// 创建一个响应
Response res = new Response(req.getId(), req.getVersion());
// 设置为心跳事件的响应
res.setEvent(Response.HEARTBEAT_EVENT);
// 发送消息,也就是返回响应
channel.send(res);
if (logger.isInfoEnabled()) {
int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);
if (logger.isDebugEnabled()) {
logger.debug("Received heartbeat from remote channel " + channel.getRemoteAddress()
+ ", cause: The channel has no data-transmission exceeds a heartbeat period"
+ (heartbeat > 0 ? ": " + heartbeat + "ms" : ""));
}
}
}
return;
}
// 如果是心跳响应,则直接return
if (isHeartbeatResponse(message)) {
if (logger.isDebugEnabled()) {
logger.debug("Receive heartbeat response in thread " + Thread.currentThread().getName());
}
return;
}
handler.received(channel, message);
}</code></pre>
<p>该方法是就是在handler处理消息上增加了处理心跳消息的功能,做到了功能增强。</p>
<h4>(二十一)Exchangers</h4>
<p>该类跟Transporters的设计意图是一样的,Transporters我在<a href="https://segmentfault.com/a/1190000017274525">《dubbo源码解析(八)远程通信——开篇》</a>的(十)Transporters已经讲到了。Exchangers也用到了外观模式。代码为就不贴了,可以对照着Transporters来看,很简单。</p>
<h4>(二十二)Request</h4>
<p>请求模型类,最重要的肯定是模型的属性,我们来看看属性:</p>
<pre><code class="java">/**
* 心跳事件
*/
public static final String HEARTBEAT_EVENT = null;
/**
* 只读事件
*/
public static final String READONLY_EVENT = "R";
/**
* 请求编号自增序列
*/
private static final AtomicLong INVOKE_ID = new AtomicLong(0);
/**
* 请求编号
*/
private final long mId;
/**
* dubbo版本
*/
private String mVersion;
/**
* 是否需要响应
*/
private boolean mTwoWay = true;
/**
* 是否是事件
*/
private boolean mEvent = false;
/**
* 是否是异常的请求
*/
private boolean mBroken = false;
/**
* 请求数据
*/
private Object mData;</code></pre>
<ol>
<li>由于心跳事件比较常用,所有设置为null。</li>
<li>请求编号使用INVOKE_ID生成,是JVM 进程内唯一的。</li>
<li>其他属性比较简单</li>
</ol>
<h4>(二十三)Response</h4>
<p>响应模型,来看看它的属性:</p>
<pre><code class="java">/**
* 心跳事件
*/
public static final String HEARTBEAT_EVENT = null;
/**
* 只读事件
*/
public static final String READONLY_EVENT = "R";
/**
* ok.
* 成功状态码
*/
public static final byte OK = 20;
/**
* clien side timeout.
* 客户端侧的超时状态码
*/
public static final byte CLIENT_TIMEOUT = 30;
/**
* server side timeout.
* 服务端侧超时的状态码
*/
public static final byte SERVER_TIMEOUT = 31;
/**
* channel inactive, directly return the unfinished requests.
* 通道不活跃,返回未完成请求的状态码
*/
public static final byte CHANNEL_INACTIVE = 35;
/**
* request format error.
* 请求格式错误状态码
*/
public static final byte BAD_REQUEST = 40;
/**
* response format error.
* 响应格式错误状态码
*/
public static final byte BAD_RESPONSE = 50;
/**
* service not found.
* 服务找不到状态码
*/
public static final byte SERVICE_NOT_FOUND = 60;
/**
* service error.
* 服务错误状态码
*/
public static final byte SERVICE_ERROR = 70;
/**
* internal server error.
* 内部服务器错误状态码
*/
public static final byte SERVER_ERROR = 80;
/**
* internal server error.
* 客户端错误状态码
*/
public static final byte CLIENT_ERROR = 90;
/**
* server side threadpool exhausted and quick return.
* 服务器端线程池耗尽并快速返回状态码
*/
public static final byte SERVER_THREADPOOL_EXHAUSTED_ERROR = 100;
/**
* 响应编号
*/
private long mId = 0;
/**
* dubbo 版本
*/
private String mVersion;
/**
* 状态
*/
private byte mStatus = OK;
/**
* 是否是事件
*/
private boolean mEvent = false;
/**
* 错误信息
*/
private String mErrorMsg;
/**
* 返回结果
*/
private Object mResult;</code></pre>
<p>很多属性跟Request模型的属性一样,并且含义也一样,不过该模型多了很多的状态码。关键的是id跟请求一一对应。</p>
<h4>(二十四)ResponseCallback</h4>
<pre><code class="java">public interface ResponseCallback {
/**
* done.
* 处理请求
* @param response
*/
void done(Object response);
/**
* caught exception.
* 处理异常
* @param exception
*/
void caught(Throwable exception);
}</code></pre>
<p>该接口是回调的接口,定义了两个方法,分别是处理正常的响应结果和处理异常。</p>
<h4>(二十五)ExchangeCodec</h4>
<p>该类继承了TelnetCodec,是信息交换编解码器。在本文的开头,我就写到,dubbo将一条消息分成了协议头和协议体,用来解决粘包拆包问题,但是头跟体在编解码上有区别,我们先来看看dubbo 的协议头的配置:</p>
<p><img src="/img/remote/1460000017467347" alt="dubbo_protocol_header" title="dubbo_protocol_header"></p>
<p>上图是官方文档的图片,能够清晰的看出协议中各个数据所占的位数:</p>
<ol>
<li>0-7位和8-15位:Magic High和Magic Low,类似java字节码文件里的魔数,用来判断是不是dubbo协议的数据包,就是一个固定的数字</li>
<li>16位:Req/Res:请求还是响应标识。</li>
<li>17位:2way:单向还是双向</li>
<li>18位:Event:是否是事件</li>
<li>19-23位:Serialization 编号</li>
<li>24-31位:status状态</li>
<li>32-95位:id编号</li>
<li>96-127位:body数据</li>
<li>128-…位:上图表格内的数据</li>
</ol>
<p>可以看到一个该协议中前65位是协议头,后面的都是协议体数据。那么在编解码中,协议头是通过 Codec 编解码,而body部分是用Serialization序列化和反序列化的。下面我们就来看看该类对协议头的编解码。</p>
<h5>1.属性</h5>
<pre><code class="java">// header length.
/**
* 协议头长度:16字节 = 128Bits
*/
protected static final int HEADER_LENGTH = 16;
// magic header.
/**
* MAGIC二进制:1101101010111011,十进制:55995
*/
protected static final short MAGIC = (short) 0xdabb;
/**
* Magic High,也就是0-7位:11011010
*/
protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];
/**
* Magic Low 8-15位 :10111011
*/
protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];
// message flag.
/**
* 128 二进制:10000000
*/
protected static final byte FLAG_REQUEST = (byte) 0x80;
/**
* 64 二进制:1000000
*/
protected static final byte FLAG_TWOWAY = (byte) 0x40;
/**
* 32 二进制:100000
*/
protected static final byte FLAG_EVENT = (byte) 0x20;
/**
* 31 二进制:11111
*/
protected static final int SERIALIZATION_MASK = 0x1f;</code></pre>
<p>可以看到 MAGIC是个固定的值,用来判断是不是dubbo协议的数据包,并且MAGIC_LOW和MAGIC_HIGH分别是MAGIC的低位和高位。其他的属性用来干嘛后面会讲到。</p>
<h5>2.encode</h5>
<pre><code class="java">@Override
public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
if (msg instanceof Request) {
// 如果消息是Request类型,对请求消息编码
encodeRequest(channel, buffer, (Request) msg);
} else if (msg instanceof Response) {
// 如果消息是Response类型,对响应消息编码
encodeResponse(channel, buffer, (Response) msg);
} else {
// 直接让父类( Telnet ) 处理,目前是 Telnet 命令的结果。
super.encode(channel, buffer, msg);
}
}</code></pre>
<p>该方法是根据消息的类型来分别进行编码,分为三种情况:Request类型、Response类型以及其他</p>
<h5>3.encodeRequest</h5>
<pre><code class="java">protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
Serialization serialization = getSerialization(channel);
// header.
// 创建16字节的字节数组
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
// 设置前16位数据,也就是设置header[0]和header[1]的数据为Magic High和Magic Low
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
// 16-23位为serialization编号,用到或运算10000000|serialization编号,例如serialization编号为11111,则为00011111
header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
// 继续上面的例子,00011111|1000000 = 01011111
if (req.isTwoWay()) header[2] |= FLAG_TWOWAY;
// 继续上面的例子,01011111|100000 = 011 11111 可以看到011代表请求标记、双向、是事件,这样就设置了16、17、18位,后面19-23位是Serialization 编号
if (req.isEvent()) header[2] |= FLAG_EVENT;
// set request id.
// 设置32-95位请求id
Bytes.long2bytes(req.getId(), header, 4);
// encode request data.
// // 编码 `Request.data` 到 Body ,并写入到 Buffer
int savedWriteIndex = buffer.writerIndex();
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
// 对body数据序列化
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
// 如果该请求是事件
if (req.isEvent()) {
// 特殊事件编码
encodeEventData(channel, out, req.getData());
} else {
// 正常请求编码
encodeRequestData(channel, out, req.getData(), req.getVersion());
}
// 释放资源
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
//检验消息长度
checkPayload(channel, len);
// 设置96-127位:Body值
Bytes.int2bytes(len, header, 12);
// write
// 把header写入到buffer
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}</code></pre>
<p>该方法是对Request类型的消息进行编码,仔细阅读上述我写的注解,结合协议头各个位数的含义,好好品味我举的例子。享受二进制位运算带来的快乐,也可以看到前半部分逻辑是对协议头的编码,后面还有对body值的序列化。</p>
<h5>4.encodeResponse</h5>
<pre><code class="java">protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
Serialization serialization = getSerialization(channel);
// header.
// 创建16字节的字节数组
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
// 设置前16位数据,也就是设置header[0]和header[1]的数据为Magic High和Magic Low
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
// 16-23位为serialization编号,用到或运算10000000|serialization编号,例如serialization编号为11111,则为00011111
header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
// 继续上面的例子,00011111|1000000 = 01011111
if (req.isTwoWay()) header[2] |= FLAG_TWOWAY;
// 继续上面的例子,01011111|100000 = 011 11111 可以看到011代表请求标记、双向、是事件,这样就设置了16、17、18位,后面19-23位是Serialization 编号
if (req.isEvent()) header[2] |= FLAG_EVENT;
// set request id.
// 设置32-95位请求id
Bytes.long2bytes(req.getId(), header, 4);
// encode request data.
// // 编码 `Request.data` 到 Body ,并写入到 Buffer
int savedWriteIndex = buffer.writerIndex();
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
// 对body数据序列化
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
// 如果该请求是事件
if (req.isEvent()) {
// 特殊事件编码
encodeEventData(channel, out, req.getData());
} else {
// 正常请求编码
encodeRequestData(channel, out, req.getData(), req.getVersion());
}
// 释放资源
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
//检验消息长度
checkPayload(channel, len);
// 设置96-127位:Body值
Bytes.int2bytes(len, header, 12);
// write
// 把header写入到buffer
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
int savedWriteIndex = buffer.writerIndex();
try {
Serialization serialization = getSerialization(channel);
// header.
// 创建16字节大小的字节数组
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
// 设置前0-15位为魔数
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
// 设置响应标志和序列化id
header[2] = serialization.getContentTypeId();
// 如果是心跳事件,则设置第18位为事件
if (res.isHeartbeat()) header[2] |= FLAG_EVENT;
// set response status.
// 设置24-31位为状态码
byte status = res.getStatus();
header[3] = status;
// set request id.
// 设置32-95位为请求id
Bytes.long2bytes(res.getId(), header, 4);
// 写入数据
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
// 对body进行序列化
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
// encode response data or error message.
if (status == Response.OK) {
if (res.isHeartbeat()) {
// 对心跳事件编码
encodeHeartbeatData(channel, out, res.getResult());
} else {
// 对普通响应编码
encodeResponseData(channel, out, res.getResult(), res.getVersion());
}
} else out.writeUTF(res.getErrorMessage());
// 释放
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
checkPayload(channel, len);
Bytes.int2bytes(len, header, 12);
// write
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
} catch (Throwable t) {
// clear buffer
buffer.writerIndex(savedWriteIndex);
// send error message to Consumer, otherwise, Consumer will wait till timeout.
//如果在写入数据失败,则返回响应格式错误的返回码
if (!res.isEvent() && res.getStatus() != Response.BAD_RESPONSE) {
Response r = new Response(res.getId(), res.getVersion());
r.setStatus(Response.BAD_RESPONSE);
if (t instanceof ExceedPayloadLimitException) {
logger.warn(t.getMessage(), t);
try {
r.setErrorMessage(t.getMessage());
// 发送响应
channel.send(r);
return;
} catch (RemotingException e) {
logger.warn("Failed to send bad_response info back: " + t.getMessage() + ", cause: " + e.getMessage(), e);
}
} else {
// FIXME log error message in Codec and handle in caught() of IoHanndler?
logger.warn("Fail to encode response: " + res + ", send bad_response info instead, cause: " + t.getMessage(), t);
try {
r.setErrorMessage("Failed to send response: " + res + ", cause: " + StringUtils.toString(t));
channel.send(r);
return;
} catch (RemotingException e) {
logger.warn("Failed to send bad_response info back: " + res + ", cause: " + e.getMessage(), e);
}
}
}
// Rethrow exception
if (t instanceof IOException) {
throw (IOException) t;
} else if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new RuntimeException(t.getMessage(), t);
}
}
}</code></pre>
<p>该方法是对Response类型的消息进行编码,该方法里面我没有举例子演示如何进行编码,不过过程跟encodeRequest类似。</p>
<h5>5.decode</h5>
<pre><code class="java">@Override
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
int readable = buffer.readableBytes();
// 读取前16字节的协议头数据,如果数据不满16字节,则读取全部
byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
buffer.readBytes(header);
// 解码
return decode(channel, buffer, readable, header);
}
@Override
protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
// check magic number.
// 核对魔数(该数字固定)
if (readable > 0 && header[0] != MAGIC_HIGH
|| readable > 1 && header[1] != MAGIC_LOW) {
int length = header.length;
// 将 buffer 完全复制到 `header` 数组中
if (header.length < readable) {
header = Bytes.copyOf(header, readable);
buffer.readBytes(header, length, readable - length);
}
for (int i = 1; i < header.length - 1; i++) {
if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) {
buffer.readerIndex(buffer.readerIndex() - header.length + i);
header = Bytes.copyOf(header, i);
break;
}
}
return super.decode(channel, buffer, readable, header);
}
// check length.
// Header 长度不够,返回需要更多的输入,解决拆包现象
if (readable < HEADER_LENGTH) {
return DecodeResult.NEED_MORE_INPUT;
}
// get data length.
int len = Bytes.bytes2int(header, 12);
// 检查信息头长度
checkPayload(channel, len);
int tt = len + HEADER_LENGTH;
// 总长度不够,返回需要更多的输入,解决拆包现象
if (readable < tt) {
return DecodeResult.NEED_MORE_INPUT;
}
// limit input stream.
ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);
try {
// 对body反序列化
return decodeBody(channel, is, header);
} finally {
// 如果不可用
if (is.available() > 0) {
try {
// 打印错误日志
if (logger.isWarnEnabled()) {
logger.warn("Skip input stream " + is.available());
}
// 跳过未读完的流
StreamUtils.skipUnusedStream(is);
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
}</code></pre>
<p>该方法就是解码前的一些核对过程,包括检测是否为dubbo协议,是否有拆包现象等,具体的解码在decodeBody方法。</p>
<h5>6.decodeBody</h5>
<pre><code class="java">protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
// 用并运算符
byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
// get request id.
// 获得请求id
long id = Bytes.bytes2long(header, 4);
// 如果第16位为0,则说明是响应
if ((flag & FLAG_REQUEST) == 0) {
// decode response.
Response res = new Response(id);
// 如果第18位不是0,则说明是心跳事件
if ((flag & FLAG_EVENT) != 0) {
res.setEvent(Response.HEARTBEAT_EVENT);
}
// get status.
byte status = header[3];
res.setStatus(status);
try {
ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
// 如果响应是成功的
if (status == Response.OK) {
Object data;
if (res.isHeartbeat()) {
// 如果是心跳事件,则心跳事件的解码
data = decodeHeartbeatData(channel, in);
} else if (res.isEvent()) {
// 如果是事件,则事件的解码
data = decodeEventData(channel, in);
} else {
// 否则执行普通解码
data = decodeResponseData(channel, in, getRequestData(id));
}
// 重新设置响应结果
res.setResult(data);
} else {
res.setErrorMessage(in.readUTF());
}
} catch (Throwable t) {
res.setStatus(Response.CLIENT_ERROR);
res.setErrorMessage(StringUtils.toString(t));
}
return res;
} else {
// decode request.
// 对请求类型解码
Request req = new Request(id);
// 设置版本号
req.setVersion(Version.getProtocolVersion());
// 如果第17位不为0,则是双向
req.setTwoWay((flag & FLAG_TWOWAY) != 0);
// 如果18位不为0,则是心跳事件
if ((flag & FLAG_EVENT) != 0) {
req.setEvent(Request.HEARTBEAT_EVENT);
}
try {
// 反序列化
ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
Object data;
if (req.isHeartbeat()) {
// 如果请求是心跳事件,则心跳事件解码
data = decodeHeartbeatData(channel, in);
} else if (req.isEvent()) {
// 如果是事件,则事件解码
data = decodeEventData(channel, in);
} else {
// 否则,用普通解码
data = decodeRequestData(channel, in);
}
// 把重新设置请求数据
req.setData(data);
} catch (Throwable t) {
// bad request
// 设置是异常请求
req.setBroken(true);
req.setData(t);
}
return req;
}
}</code></pre>
<p>该方法就是解码的过程,并且对协议头和协议体分开解码,协议头编码是做或运算,而解码则是做并运算,协议体用反序列化的方式解码,同样也是分为了Request类型、Response类型进行解码。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=zg4JaAhzFpp1ix6toQlsqg%3D%3D.3AU9UChVqS9bAhMlLJwf19pVqoYdh0zDzuamA5jpWq%2Bwxv%2FvGoQU3HmOhtevquzbgabp%2BqHWcUFoCYC2tRxCGy78QqG6D3Wl0w0cjV4tNSBIxhIA4yxnutZTHdOud6PT6sHvREiaLsOoU%2BxiZZTU65YJDVf7%2FWKfc9Cjuxx9llGUP2xePcl6ZKfHsadu2CmSxJseKaysSvhuNtcCOgg3yA%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了Exchange层的相关设计和逻辑、介绍dubbo-remoting-api中的exchange包内的源码解,其中关键的是设计了Request和Response模型,整个信息交换都围绕这两大模型,并且设计了dubbo协议,解决拆包粘包问题,在信息交换中协议头携带的信息起到了关键作用,也满足了rpc调用的一些需求。下一篇我会讲解远程通信的buffer部分。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(九)远程通信——Transport层
https://segmentfault.com/a/1190000017390253
2018-12-16T01:01:48+08:00
2018-12-16T01:01:48+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
10
<h2>远程通讯——Transport层</h2>
<blockquote>目标:介绍Transport层的相关设计和逻辑、介绍dubbo-remoting-api中的transport包内的源码解析。</blockquote>
<h3>前言</h3>
<p>先预警一下,该文篇幅会很长,做好心理准备。Transport层也就是网络传输层,在远程通信中必然会涉及到传输。它在dubbo 的框架设计中也处于倒数第二层,当然最底层是序列化,这个后面介绍。官方文档对Transport层的解释是抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、Client、Server、Codec。那我们现在先来看这个包下面的类图:</p>
<p><img src="/img/remote/1460000017390256" alt="transport包类图" title="transport包类图"></p>
<p>可以看到有四个包继承了AbstractChannel、AbstractServer、AbstractClient。也就是说现在Transport层是抽象mina、netty以及grizzly为统一接口。看完类图,再来看看包结构:</p>
<p><img src="/img/remote/1460000017390257" alt="transport包结构" title="transport包结构"></p>
<p>下面的讲解大致会按照类图中类的顺序往下讲,尽量把client、server、channel、codec、dispacher五部分涉及到的内容一起讲解。</p>
<h3>源码解析</h3>
<h4>(一)AbstractPeer</h4>
<pre><code class="java">public abstract class AbstractPeer implements Endpoint, ChannelHandler {
private final ChannelHandler handler;
private volatile URL url;
/**
* 是否正在关闭
*/
// closing closed means the process is being closed and close is finished
private volatile boolean closing;
/**
* 是否关闭完成
*/
private volatile boolean closed;
public AbstractPeer(URL url, ChannelHandler handler) {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
this.url = url;
this.handler = handler;
}
}</code></pre>
<p>该类实现了Endpoint和ChannelHandler两个接口,要关注的两个点:</p>
<ol>
<li>实现ChannelHandler接口并且有在属性中还有一个handler,下面很多实现方法也是直接调用了handler方法,这种模式叫做装饰模式,这样做可以对装饰对象灵活的增强功能。对装饰模式不懂的朋友可以google一下。有很多例子介绍。</li>
<li>在该类中有closing和closed属性,在Endpoint中有很多关于关闭通道的操作,会有关闭中和关闭完成的状态区分,在该类中就缓存了这两个属性来判断关闭的状态。</li>
</ol>
<p>下面我就介绍该类中的send方法,其他方法比较好理解,到时候可以直接看源码:</p>
<pre><code class="java">@Override
public void send(Object message) throws RemotingException {
// url中sent的配置项
send(message, url.getParameter(Constants.SENT_KEY, false));
}</code></pre>
<p>该配置项是选择是否等待消息发出:</p>
<ol>
<li>sent值为true,等待消息发出,消息发送失败将抛出异常。</li>
<li>sent值为false,不等待消息发出,将消息放入 IO 队列,即刻返回。</li>
</ol>
<p>对该类还有点糊涂的朋友,记住在ChannelHandler接口,该类就做了装饰模式中装饰角色,在Endpoint接口,只是维护了通道的正在关闭和关闭完成两个状态。</p>
<h4>(二)AbstractEndpoint</h4>
<pre><code class="java">public abstract class AbstractEndpoint extends AbstractPeer implements Resetable {
/**
* 日志记录
*/
private static final Logger logger = LoggerFactory.getLogger(AbstractEndpoint.class);
/**
* 编解码器
*/
private Codec2 codec;
/**
* 超时时间
*/
private int timeout;
/**
* 连接超时时间
*/
private int connectTimeout;
public AbstractEndpoint(URL url, ChannelHandler handler) {
super(url, handler);
this.codec = getChannelCodec(url);
// 优先从url配置中取,如果没有,默认为1s
this.timeout = url.getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 优先从url配置中取,如果没有,默认为3s
this.connectTimeout = url.getPositiveParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT);
}
/**
* 从url中获得编解码器的配置,并且返回该实例
* @param url
* @return
*/
protected static Codec2 getChannelCodec(URL url) {
String codecName = url.getParameter(Constants.CODEC_KEY, "telnet");
// 优先从Codec2的扩展类中找
if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) {
return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
} else {
return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class)
.getExtension(codecName));
}
}
}</code></pre>
<p>该类是端点的抽象类,其中封装了编解码器以及两个超时时间。基于dubbo 的SPI机制,获得相应的编解码器实现对象,编解码器优先从Codec2的扩展类中寻找。</p>
<p>下面来看看该类中的reset方法:</p>
<pre><code class="java">@Override
public void reset(URL url) {
if (isClosed()) {
throw new IllegalStateException("Failed to reset parameters "
+ url + ", cause: Channel closed. channel: " + getLocalAddress());
}
try {
// 判断重置的url中有没有携带timeout,有的话重置
if (url.hasParameter(Constants.TIMEOUT_KEY)) {
int t = url.getParameter(Constants.TIMEOUT_KEY, 0);
if (t > 0) {
this.timeout = t;
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
// 判断重置的url中有没有携带connect.timeout,有的话重置
if (url.hasParameter(Constants.CONNECT_TIMEOUT_KEY)) {
int t = url.getParameter(Constants.CONNECT_TIMEOUT_KEY, 0);
if (t > 0) {
this.connectTimeout = t;
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
// 判断重置的url中有没有携带codec,有的话重置
if (url.hasParameter(Constants.CODEC_KEY)) {
this.codec = getChannelCodec(url);
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
@Deprecated
public void reset(com.alibaba.dubbo.common.Parameters parameters) {
reset(getUrl().addParameters(parameters.getParameters()));
}</code></pre>
<p>这个方法是Resetable接口中的方法,可以看到以前的reset实现方法都加上了@Deprecated注解,不推荐使用了,因为这种实现方式重置太复杂,需要把所有参数都设置一遍,比如我只想重置一个超时时间,但是其他值不变,如果用以前的reset,我需要在url中把所有值都带上,就会很多余。现在用新的reset,每次只关心我需要重置的值,只更改为需要重置的值。比如上面的代码所示,只想修改超时时间,那我就只在url中携带超时时间的参数。</p>
<h4>(三)AbstractServer</h4>
<p>该类继承了AbstractEndpoint并且实现Server接口,是服务器抽象类。重点实现了服务器的公共逻辑,比如发送消息,关闭通道,连接通道,断开连接等。并且抽象了打开和关闭服务器两个方法。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 服务器线程名称
*/
protected static final String SERVER_THREAD_POOL_NAME = "DubboServerHandler";
private static final Logger logger = LoggerFactory.getLogger(AbstractServer.class);
/**
* 线程池
*/
ExecutorService executor;
/**
* 服务地址,也就是本地地址
*/
private InetSocketAddress localAddress;
/**
* 绑定地址
*/
private InetSocketAddress bindAddress;
/**
* 最大可接受的连接数
*/
private int accepts;
/**
* 空闲超时时间,单位是s
*/
private int idleTimeout = 600; //600 seconds</code></pre>
<p>该类的属性比较好理解,就是稍微注意一下idleTimeout的单位是s。</p>
<h5>2.构造函数</h5>
<pre><code class="java">public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
// 从url中获得本地地址
localAddress = getUrl().toInetSocketAddress();
// 从url配置中获得绑定的ip
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
// 从url配置中获得绑定的端口号
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
// 判断url中配置anyhost是否为true或者判断host是否为不可用的本地Host
if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
bindIp = NetUtils.ANYHOST;
}
bindAddress = new InetSocketAddress(bindIp, bindPort);
// 从url中获取配置,默认值为0
this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
// 从url中获取配置,默认600s
this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
try {
// 开启服务器
doOpen();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
}
} catch (Throwable t) {
throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
+ " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
}
// 获得线程池
//fixme replace this with better method
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
}</code></pre>
<p>构造函数大部分逻辑就是从url中取配置,存到缓存中,并且做了开启服务器的操作。具体的看上面的注释,还是比较清晰的。</p>
<h5>3.reset方法</h5>
<pre><code class="java">@Override
public void reset(URL url) {
if (url == null) {
return;
}
try {
// 重置accepts的值
if (url.hasParameter(Constants.ACCEPTS_KEY)) {
int a = url.getParameter(Constants.ACCEPTS_KEY, 0);
if (a > 0) {
this.accepts = a;
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
// 重置idle.timeout的值
if (url.hasParameter(Constants.IDLE_TIMEOUT_KEY)) {
int t = url.getParameter(Constants.IDLE_TIMEOUT_KEY, 0);
if (t > 0) {
this.idleTimeout = t;
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
// 重置线程数配置
if (url.hasParameter(Constants.THREADS_KEY)
&& executor instanceof ThreadPoolExecutor && !executor.isShutdown()) {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
// 获得url配置中的线程数
int threads = url.getParameter(Constants.THREADS_KEY, 0);
// 获得线程池允许的最大线程数
int max = threadPoolExecutor.getMaximumPoolSize();
// 返回核心线程数
int core = threadPoolExecutor.getCorePoolSize();
// 设置最大线程数和核心线程数
if (threads > 0 && (threads != max || threads != core)) {
if (threads < core) {
// 如果设置的线程数比核心线程数少,则直接设置核心线程数
threadPoolExecutor.setCorePoolSize(threads);
if (core == max) {
// 当核心线程数和最大线程数相等的时候,把最大线程数也重置
threadPoolExecutor.setMaximumPoolSize(threads);
}
} else {
// 当大于核心线程数时,直接设置最大线程数
threadPoolExecutor.setMaximumPoolSize(threads);
// 只有当核心线程数和最大线程数相等的时候才设置核心线程数
if (core == max) {
threadPoolExecutor.setCorePoolSize(threads);
}
}
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
// 重置url
super.setUrl(getUrl().addParameters(url.getParameters()));
}</code></pre>
<p>该类中的reset方法做了三个值的重置,分别是最大可连接的客户端数量、空闲超时时间以及线程池的两个配置参数。其中要注意核心线程数和最大线程数的区别。举个例子,核心线程数就像是工厂正式工,最大线程数,就是工厂临时工作量加大,请了一批临时工,临时工加正式工的和就是最大线程数,等这批任务结束后,临时工要辞退的,而正式工会留下。</p>
<p>还有send、close、connected、disconnected等方法比较简单,如果有兴趣,可以到我的GitHub查看,地址文章末尾会给出。</p>
<h4>(四)AbstractClient</h4>
<p>该类是客户端的抽象类,继承了AbstractEndpoint类,实现了Client接口,该类中也是做了客户端公用的重连逻辑,抽象了打开客户端、关闭客户端、连接服务器、断开服务器连接以及获得通道方法,让子类去重点关注这几个方法。</p>
<h5>1.属性</h5>
<pre><code class="java">/**
* 客户端线程名称
*/
protected static final String CLIENT_THREAD_POOL_NAME = "DubboClientHandler";
private static final Logger logger = LoggerFactory.getLogger(AbstractClient.class);
/**
* 线程池id
*/
private static final AtomicInteger CLIENT_THREAD_POOL_ID = new AtomicInteger();
/**
* 重连定时任务执行器
*/
private static final ScheduledThreadPoolExecutor reconnectExecutorService = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("DubboClientReconnectTimer", true));
/**
* 连接锁
*/
private final Lock connectLock = new ReentrantLock();
/**
* 发送消息时,若断开,是否重连
*/
private final boolean send_reconnect;
/**
* 重连次数
*/
private final AtomicInteger reconnect_count = new AtomicInteger(0);
/**
* 在这之前是否调用重新连接的错误日志
*/
// Reconnection error log has been called before?
private final AtomicBoolean reconnect_error_log_flag = new AtomicBoolean(false);
/**
* 重连 warning 的间隔.(waring多少次之后,warning一次),也就是错误多少次后告警一次错误
*/
// reconnect warning period. Reconnect warning interval (log warning after how many times) //for test
private final int reconnect_warning_period;
/**
* 关闭超时时间
*/
private final long shutdown_timeout;
/**
* 线程池
*/
protected volatile ExecutorService executor;
/**
* 重连执行任务
*/
private volatile ScheduledFuture<?> reconnectExecutorFuture = null;
// the last successed connected time
/**
* 最后成功连接的时间
*/
private long lastConnectedTime = System.currentTimeMillis();</code></pre>
<p>上述属性大部分跟重连有关,该类最重要的也是封装了重连的逻辑。</p>
<h5>2.构造函数</h5>
<pre><code class="java">public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
// 从url中获得是否重连的配置,默认为false
send_reconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, false);
// 从url中获得关闭超时时间,默认为900s
shutdown_timeout = url.getParameter(Constants.SHUTDOWN_TIMEOUT_KEY, Constants.DEFAULT_SHUTDOWN_TIMEOUT);
// The default reconnection interval is 2s, 1800 means warning interval is 1 hour.
// 重连的默认值是2s,重连 warning 的间隔默认是1800,当出错的时候,每隔1800*2=3600s报警一次
reconnect_warning_period = url.getParameter("reconnect.waring.period", 1800);
try {
// 打开客户端
doOpen();
} catch (Throwable t) {
close();
throw new RemotingException(url.toInetSocketAddress(), null,
"Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
}
try {
// connect.
// 连接服务器
connect();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + getRemoteAddress());
}
} catch (RemotingException t) {
if (url.getParameter(Constants.CHECK_KEY, true)) {
close();
throw t;
} else {
logger.warn("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + " (check == false, ignore and retry later!), cause: " + t.getMessage(), t);
}
} catch (Throwable t) {
close();
throw new RemotingException(url.toInetSocketAddress(), null,
"Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()
+ " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);
}
// 从缓存中获得线程池
executor = (ExecutorService) ExtensionLoader.getExtensionLoader(DataStore.class)
.getDefaultExtension().get(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));
// 清楚线程池缓存
ExtensionLoader.getExtensionLoader(DataStore.class)
.getDefaultExtension().remove(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));
}</code></pre>
<p>该构造函数中做了一些属性值的设置,并且做了打开客户端和连接服务器的操作。</p>
<h5>3.wrapChannelHandler</h5>
<pre><code class="java">protected static ChannelHandler wrapChannelHandler(URL url, ChannelHandler handler) {
// 加入线程名称
url = ExecutorUtil.setThreadName(url, CLIENT_THREAD_POOL_NAME);
// 设置使用的线程池类型
url = url.addParameterIfAbsent(Constants.THREADPOOL_KEY, Constants.DEFAULT_CLIENT_THREADPOOL);
// 包装
return ChannelHandlers.wrap(handler, url);
}</code></pre>
<p>该方法是包装通道处理器,设置使用的线程池类型是可缓存线程池。</p>
<h5>4.initConnectStatusCheckCommand</h5>
<pre><code class="java">private synchronized void initConnectStatusCheckCommand() {
//reconnect=false to close reconnect
int reconnect = getReconnectParam(getUrl());
// 有连接频率的值,并且当前没有连接任务
if (reconnect > 0 && (reconnectExecutorFuture == null || reconnectExecutorFuture.isCancelled())) {
Runnable connectStatusCheckCommand = new Runnable() {
@Override
public void run() {
try {
if (!isConnected()) {
// 重连
connect();
} else {
// 记录最后一次重连的时间
lastConnectedTime = System.currentTimeMillis();
}
} catch (Throwable t) {
String errorMsg = "client reconnect to " + getUrl().getAddress() + " find error . url: " + getUrl();
// wait registry sync provider list
if (System.currentTimeMillis() - lastConnectedTime > shutdown_timeout) {
// 如果之前没有打印过重连的误日志
if (!reconnect_error_log_flag.get()) {
reconnect_error_log_flag.set(true);
// 打印日志
logger.error(errorMsg, t);
return;
}
}
// 如果到达一次重连日志告警周期,则打印告警日志
if (reconnect_count.getAndIncrement() % reconnect_warning_period == 0) {
logger.warn(errorMsg, t);
}
}
}
};
// 开启重连定时任务
reconnectExecutorFuture = reconnectExecutorService.scheduleWithFixedDelay(connectStatusCheckCommand, reconnect, reconnect, TimeUnit.MILLISECONDS);
}
}</code></pre>
<p>该方法是初始化重连线程,其中做了重连失败后的告警日志和错误日志打印策略。</p>
<h5>5.reconnect</h5>
<pre><code class="java">@Override
public void reconnect() throws RemotingException {
disconnect();
connect();
}</code></pre>
<p>单独放该方法是因为这是该类关注的重点。实现了客户端的重连逻辑。</p>
<h5>6.其他</h5>
<p>connect、disconnect、close等方法都是调用了对应的抽象方法,而具体的逻辑需要看具体的子类如何去实现相关的抽象方法,这几个方法逻辑比较简单,我不在这里贴出源码,有兴趣可以看我的GitHub,地址文章末尾会给出。</p>
<h4>(四)AbstractChannel</h4>
<p>该类是通道的抽象类,该类里面做的逻辑很简单,具体的发送消息逻辑在它 的子类中实现。</p>
<pre><code class="java">@Override
public void send(Object message, boolean sent) throws RemotingException {
// 检测通道是否关闭
if (isClosed()) {
throw new RemotingException(this, "Failed to send message "
+ (message == null ? "" : message.getClass().getName()) + ":" + message
+ ", cause: Channel closed. channel: " + getLocalAddress() + " -> " + getRemoteAddress());
}
}</code></pre>
<p>可以看到send方法,其中只做了检测通道是否关闭的状态检测,没有实现具体的发送消息的逻辑。</p>
<h4>(五)ChannelHandlerDelegate</h4>
<p>该类继承了ChannelHandler,从它的名字可以看出是ChannelHandler的代表,它就是作为装饰模式中的Component角色,后面讲到的AbstractChannelHandlerDelegate作为装饰模式中的Decorator角色。</p>
<pre><code class="java">public interface ChannelHandlerDelegate extends ChannelHandler {
/**
* 获得通道
* @return
*/
ChannelHandler getHandler();
}</code></pre>
<h4>(六)AbstractChannelHandlerDelegate</h4>
<p>属性:</p>
<pre><code class="java">protected ChannelHandler handler</code></pre>
<p>该类实现了ChannelHandlerDelegate接口,并且有一个属性是ChannelHandler,上述已经说到这是装饰模式中的装饰角色,其中的所有实现方法都直接调用被装饰的handler属性的方法。</p>
<h4>(七)DecodeHandler</h4>
<p>该类为解码处理器,继承了AbstractChannelHandlerDelegate,对接收到的消息进行解码,在父类处理接收消息的功能上叠加了解码功能。</p>
<p>我们来看看received方法:</p>
<pre><code class="java">@Override
public void received(Channel channel, Object message) throws RemotingException {
// 如果是Decodeable类型的消息,则对整个消息解码
if (message instanceof Decodeable) {
decode(message);
}
// 如果是Request请求类型消息,则对请求中对请求数据解码
if (message instanceof Request) {
decode(((Request) message).getData());
}
// 如果是Response返回类型的消息,则对返回消息中对结果进行解码
if (message instanceof Response) {
decode(((Response) message).getResult());
}
// 继续将消息委托给handler,继续处理
handler.received(channel, message);
}</code></pre>
<p>可以看到做了三次判断,根据消息的不同会对消息的不同数据做解码。可以看到,这里用到装饰模式后,在处理消息的前面做了解码的处理,并且还能继续委托给handler来处理消息,通过组合做到了功能的叠加。</p>
<pre><code class="java">private void decode(Object message) {
// 如果消息类型是Decodeable,进一步调用Decodeable的decode来解码
if (message != null && message instanceof Decodeable) {
try {
((Decodeable) message).decode();
if (log.isDebugEnabled()) {
log.debug("Decode decodeable message " + message.getClass().getName());
}
} catch (Throwable e) {
if (log.isWarnEnabled()) {
log.warn("Call Decodeable.decode failed: " + e.getMessage(), e);
}
} // ~ end of catch
} // ~ end of if
} // ~ end of method decode</code></pre>
<p>可以看到这是解析消息的逻辑,当消息是Decodeable类型,还会继续调用Decodeable的decode方法来进行解析。它的实现类后续会讲解到。</p>
<h4>(八)MultiMessageHandler</h4>
<p>该类是多消息处理器的抽象类。同样继承了AbstractChannelHandlerDelegate类,我们来看看它的received方法:</p>
<pre><code class="java">@SuppressWarnings("unchecked")
@Override
public void received(Channel channel, Object message) throws RemotingException {
// 当消息为多消息时 循环交给handler处理接收到当消息
if (message instanceof MultiMessage) {
MultiMessage list = (MultiMessage) message;
for (Object obj : list) {
handler.received(channel, obj);
}
} else {
// 如果是单消息,就直接交给handler处理器
handler.received(channel, message);
}
}</code></pre>
<p>逻辑很简单,当消息是多消息类型时,也就是一次性接收到多条消息的情况,循环去处理消息,当消息是单消息时候,直接交给handler去处理。</p>
<h4>(九)WrappedChannelHandler</h4>
<p>该类跟AbstractChannelHandlerDelegate的作用类似,都是装饰模式中的装饰角色,其中的所有实现方法都直接调用被装饰的handler属性的方法,该类是为了添加线程池的功能,它的子类都是去关心哪些消息是需要分发到线程池的,哪些消息直接由I / O线程执行,现在版本有四种场景,也就是它的四个子类,下面我一一描述。</p>
<pre><code class="java">public WrappedChannelHandler(ChannelHandler handler, URL url) {
this.handler = handler;
this.url = url;
// 创建线程池
executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);
// 设置组件的key
String componentKey = Constants.EXECUTOR_SERVICE_COMPONENT_KEY;
if (Constants.CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(Constants.SIDE_KEY))) {
componentKey = Constants.CONSUMER_SIDE;
}
// 获得dataStore实例
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
// 把线程池放到dataStore中缓存
dataStore.put(componentKey, Integer.toString(url.getPort()), executor);
}</code></pre>
<p>可以看到构造方法除了属性的填充以外,线程池是基于dubbo 的SPI Adaptive机制创建的,在dataStore中把线程池加进去, 该线程池就是AbstractClient 或 AbstractServer 从 DataStore 获得的线程池。</p>
<pre><code class="java">public ExecutorService getExecutorService() {
// 首先返回的不是共享线程池,是该类的线程池
ExecutorService cexecutor = executor;
// 如果该类的线程池关闭或者为空,则返回的是共享线程池
if (cexecutor == null || cexecutor.isShutdown()) {
cexecutor = SHARED_EXECUTOR;
}
return cexecutor;
}</code></pre>
<p>该方法是获得线程池的实例,不过该类里面有两个线程池,还加入了一个共享线程池,共享线程池优先级较低。</p>
<h4>(十)ExecutionChannelHandler</h4>
<p>该类继承了WrappedChannelHandler,也是增强了功能,处理的是接收请求消息时,把请求消息分发到线程池,而除了请求消息以外,其他消息类型都直接通过I / O线程直接执行。</p>
<pre><code class="java">@Override
public void received(Channel channel, Object message) throws RemotingException {
// 获得线程池实例
ExecutorService cexecutor = getExecutorService();
// 如果消息是request类型,才会分发到线程池,其他消息,如响应,连接,断开连接,心跳将由I / O线程直接执行。
if (message instanceof Request) {
try {
// 把请求消息分发到线程池
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
// FIXME: when the thread pool is full, SERVER_THREADPOOL_EXHAUSTED_ERROR cannot return properly,
// therefore the consumer side has to wait until gets timeout. This is a temporary solution to prevent
// this scenario from happening, but a better solution should be considered later.
// 当线程池满了,SERVER_THREADPOOL_EXHAUSTED_ERROR错误无法正常返回
// 因此消费者方必须等到超时。这是一种预防的临时解决方案,所以这里直接返回该错误
if (t instanceof RejectedExecutionException) {
Request request = (Request) message;
if (request.isTwoWay()) {
String msg = "Server side(" + url.getIp() + "," + url.getPort()
+ ") thread pool is exhausted, detail msg:" + t.getMessage();
Response response = new Response(request.getId(), request.getVersion());
response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
response.setErrorMessage(msg);
channel.send(response);
return;
}
}
throw new ExecutionException(message, channel, getClass() + " error when process received event.", t);
}
} else {
// 如果消息不是request类型,则直接处理
handler.received(channel, message);
}
}</code></pre>
<p>上述就可以都看到对于请求消息的处理,其中有个打补丁的方式是当线程池满了的时候,消费者只能等待请求超时,所以这里直接返回线程池满的错误。</p>
<h4>(十一)AllChannelHandler</h4>
<p>该类也继承了WrappedChannelHandler,也是为了增强功能,处理的是连接、断开连接、捕获异常以及接收到的所有消息都分发到线程池。</p>
<pre><code class="java">@Override
public void connected(Channel channel) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
// 把连接操作分发到线程池处理
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
} catch (Throwable t) {
throw new ExecutionException("connect event", channel, getClass() + " error when process connected event .", t);
}
}
@Override
public void disconnected(Channel channel) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
// 把断开连接操作分发到线程池处理
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED));
} catch (Throwable t) {
throw new ExecutionException("disconnect event", channel, getClass() + " error when process disconnected event .", t);
}
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
// 把所有消息分发到线程池处理
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
//TODO A temporary solution to the problem that the exception information can not be sent to the opposite end after the thread pool is full. Need a refactoring
//fix The thread pool is full, refuses to call, does not return, and causes the consumer to wait for time out
// 这里处理线程池满的问题,只有在请求时候会出现。
//复线程池已满,拒绝调用,不返回,并导致使用者等待超时
if(message instanceof Request && t instanceof RejectedExecutionException){
Request request = (Request)message;
if(request.isTwoWay()){
String msg = "Server side(" + url.getIp() + "," + url.getPort() + ") threadpool is exhausted ,detail msg:" + t.getMessage();
Response response = new Response(request.getId(), request.getVersion());
response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
response.setErrorMessage(msg);
channel.send(response);
return;
}
}
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
}
@Override
public void caught(Channel channel, Throwable exception) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
// 把捕获异常作分发到线程池处理
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CAUGHT, exception));
} catch (Throwable t) {
throw new ExecutionException("caught event", channel, getClass() + " error when process caught event .", t);
}
}</code></pre>
<p>可以看到,所有操作以及消息都分到到线程池中。并且注意操作不同,传入的状态也不同。</p>
<h4>(十二)ConnectionOrderedChannelHandler</h4>
<p>该类也是继承了WrappedChannelHandler,增强功能,该类是把连接、取消连接以及接收到的消息都分发到线程池,但是不同的是,该类自己创建了一个跟连接相关的线程池,把连接操作和断开连接操分发到该线程池,而接收到的消息则分发到WrappedChannelHandler的线程池中。来看看具体的实现。</p>
<pre><code class="java">/**
* 连接线程池
*/
protected final ThreadPoolExecutor connectionExecutor;
/**
* 连接队列大小限制
*/
private final int queuewarninglimit;
public ConnectionOrderedChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
// 获得线程名,默认是Dubbo
String threadName = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
// 创建连接线程池
connectionExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(url.getPositiveParameter(Constants.CONNECT_QUEUE_CAPACITY, Integer.MAX_VALUE)),
new NamedThreadFactory(threadName, true),
new AbortPolicyWithReport(threadName, url)
); // FIXME There's no place to release connectionExecutor!
// 设置工作队列限制,默认是1000
queuewarninglimit = url.getParameter(Constants.CONNECT_QUEUE_WARNING_SIZE, Constants.DEFAULT_CONNECT_QUEUE_WARNING_SIZE);
}</code></pre>
<p>可以属性中有一个连接线程池,看到在构造函数里创建了该线程池,而queuewarninglimit是用来限制连接线程池的工作队列长度,比较简单。来看看连接和断开连接到逻辑。</p>
<pre><code class="java">@Override
public void connected(Channel channel) throws RemotingException {
try {
// 核对工作队列长度
checkQueueLength();
// 分发连接操作
connectionExecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
} catch (Throwable t) {
throw new ExecutionException("connect event", channel, getClass() + " error when process connected event .", t);
}
}
@Override
public void disconnected(Channel channel) throws RemotingException {
try {
// 核对工作队列长度
checkQueueLength();
// 分发断开连接操作
connectionExecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED));
} catch (Throwable t) {
throw new ExecutionException("disconnected event", channel, getClass() + " error when process disconnected event .", t);
}
}</code></pre>
<p>可以看到,这两个操作都是分发到连接线程池connectionExecutor中,和AllChannelHandle类r中的分发的线程池不是同一个。而ConnectionOrderedChannelHandler的received方法跟AllChannelHandle一样,我就不贴出来。</p>
<h4>(十三)MessageOnlyChannelHandler</h4>
<p>该类也是继承了WrappedChannelHandler,是WrappedChannelHandler的最后一个子类,也是增强功能,不过该类只是处理了所有的消息分发到线程池。可以看到源码,比较简单:</p>
<pre><code class="java">@Override
public void received(Channel channel, Object message) throws RemotingException {
// 获得线程池实例
ExecutorService cexecutor = getExecutorService();
try {
// 把消息分发到线程池
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
}</code></pre>
<p>下面我讲讲解五种线程池的调度策略,也就是我在<a href="https://segmentfault.com/a/1190000017274525">《dubbo源码解析(八)远程通信——开篇》</a>中提到的Dispatcher接口的五种实现,分别是AllDispatcher、DirectDispatcher、MessageOnlyDispatcher、ExecutionDispatcher、ConnectionOrderedDispatcher。</p>
<h4>(十四)AllDispatcher</h4>
<pre><code class="java">public class AllDispatcher implements Dispatcher {
public static final String NAME = "all";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
// 线程池调度方法:任何消息以及操作都分发到线程池中
return new AllChannelHandler(handler, url);
}
}</code></pre>
<p>对照着上述讲到的AllChannelHandler,是不是很清晰这种线程池的调度方法。并且该调度方法是默认的调度方法。</p>
<h4>(十五)ConnectionOrderedDispatcher</h4>
<pre><code class="java">public class ConnectionOrderedDispatcher implements Dispatcher {
public static final String NAME = "connection";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
// 线程池调度方法:连接、断开连接分发到到线程池和其他消息分发到线程池不是同一个
return new ConnectionOrderedChannelHandler(handler, url);
}
}</code></pre>
<p>对照上述讲到的ConnectionOrderedChannelHandler,也很清晰该线程池调度方法。</p>
<h4>(十六)DirectDispatcher</h4>
<pre><code class="java">public class DirectDispatcher implements Dispatcher {
public static final String NAME = "direct";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
// 直接处理消息,不分发到线程池
return handler;
}
}</code></pre>
<p>该线程池调度方法是不调度线程池,直接执行。</p>
<h4>(十七)ExecutionDispatcher</h4>
<pre><code class="java">public class ExecutionDispatcher implements Dispatcher {
public static final String NAME = "execution";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
// 线程池调度方法:只有请求消息分发到线程池,其他都直接执行
return new ExecutionChannelHandler(handler, url);
}
}</code></pre>
<p>对照着上述的ExecutionChannelHandler讲解,也可以很清晰的看出该线程池调度策略。</p>
<h4>(十八)MessageOnlyDispatcher</h4>
<pre><code class="java">public class MessageOnlyDispatcher implements Dispatcher {
public static final String NAME = "message";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
// 只要是接收到的消息,都分发到线程池
return new MessageOnlyChannelHandler(handler, url);
}
}</code></pre>
<p>对照着上述讲到的MessageOnlyChannelHandler,可以很清晰该线程池调度策略。</p>
<h4>(十九)ChannelHandlers</h4>
<p>该类是通道处理器工厂,会对传入的handler进行一次包装,无论是Client还是Server都会做这样的处理,也就是做了一些功能上的增强,就像上述我说到的装饰模式中的那些功能。</p>
<p>我们来看看源码:</p>
<pre><code class="java">public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
// 调用了多消息处理器,对心跳消息进行了功能加强
return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
.getAdaptiveExtension().dispatch(handler, url)));
}</code></pre>
<p>最关键的是这两个方法,看第二个方法,其实就是包装了MultiMessageHandler功能,增加了多消息处理的功能,以及对心跳消息做了功能增强。</p>
<h4>(二十)AbstractCodec</h4>
<p>实现 Codec<strong>2</strong> 接口,,其中实现了一些编解码的公共逻辑。</p>
<h5>1.checkPayload</h5>
<pre><code class="java">protected static void checkPayload(Channel channel, long size) throws IOException {
// 默认长度
int payload = Constants.DEFAULT_PAYLOAD;
if (channel != null && channel.getUrl() != null) {
// 优先从url中获得消息长度配置,如果没有则用默认长度
payload = channel.getUrl().getParameter(Constants.PAYLOAD_KEY, Constants.DEFAULT_PAYLOAD);
}
// 如果消息长度过长,则报错
if (payload > 0 && size > payload) {
ExceedPayloadLimitException e = new ExceedPayloadLimitException("Data length too large: " + size + ", max payload: " + payload + ", channel: " + channel);
logger.error(e);
throw e;
}
}</code></pre>
<p>该方法是检验消息长度。</p>
<h5>2.getSerialization</h5>
<pre><code class="java">protected Serialization getSerialization(Channel channel) {
return CodecSupport.getSerialization(channel.getUrl());
}</code></pre>
<p>该方法是获得序列化对象。</p>
<h5>3.isClientSide</h5>
<pre><code class="java">protected boolean isClientSide(Channel channel) {
// 获得是side对应的value
String side = (String) channel.getAttribute(Constants.SIDE_KEY);
if ("client".equals(side)) {
return true;
} else if ("server".equals(side)) {
return false;
} else {
InetSocketAddress address = channel.getRemoteAddress();
URL url = channel.getUrl();
// 判断url的主机地址是否和远程地址一样,如果是,则判断为client,如果不是,则判断为server
boolean client = url.getPort() == address.getPort()
&& NetUtils.filterLocalHost(url.getIp()).equals(
NetUtils.filterLocalHost(address.getAddress()
.getHostAddress()));
// 把value设置进去
channel.setAttribute(Constants.SIDE_KEY, client ? "client"
: "server");
return client;
}
}</code></pre>
<p>该方法是判断是否为客户端侧的通道。</p>
<h5>4.isServerSide</h5>
<pre><code class="java">protected boolean isServerSide(Channel channel) {
return !isClientSide(channel);
}</code></pre>
<p>该方法是判断是否为服务端侧的通道。</p>
<h4>(二十一)TransportCodec</h4>
<p>该类是传输编解码器,使用 Serialization 进行序列化/反序列化,直接编解码。关于序列化为会在后续文章中介绍。</p>
<pre><code class="java">@Override
public void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException {
// 获得序列化的 ObjectOutput 对象
OutputStream output = new ChannelBufferOutputStream(buffer);
ObjectOutput objectOutput = getSerialization(channel).serialize(channel.getUrl(), output);
// 写入 ObjectOutput
encodeData(channel, objectOutput, message);
objectOutput.flushBuffer();
// 释放
if (objectOutput instanceof Cleanable) {
((Cleanable) objectOutput).cleanup();
}
}
@Override
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
// 获得反序列化的 ObjectInput 对象
InputStream input = new ChannelBufferInputStream(buffer);
ObjectInput objectInput = getSerialization(channel).deserialize(channel.getUrl(), input);
// 读取 ObjectInput
Object object = decodeData(channel, objectInput);
// 释放
if (objectInput instanceof Cleanable) {
((Cleanable) objectInput).cleanup();
}
return object;
}</code></pre>
<p>该类关键方法就是编码和解码,比较好理解,直接进行了序列化和反序列化。</p>
<h4>(二十二)CodecAdapter</h4>
<p>该类是Codec 的适配器,用到了适配器模式,把Codec适配成Codec2。将Codec的编码和解码方法都适配成Codec2。比如很多时候都只能用Codec2的编解码器,但是有的时候需要用Codec,但是不能满足导致只能加入适配器来完成使用。</p>
<pre><code class="java">@Override
public void encode(Channel channel, ChannelBuffer buffer, Object message)
throws IOException {
UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(1024);
// 调用旧的编解码器的编码
codec.encode(channel, os, message);
buffer.writeBytes(os.toByteArray());
}
@Override
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
byte[] bytes = new byte[buffer.readableBytes()];
int savedReaderIndex = buffer.readerIndex();
buffer.readBytes(bytes);
UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream(bytes);
// 调用旧的编解码器的解码
Object result = codec.decode(channel, is);
buffer.readerIndex(savedReaderIndex + is.position());
return result == Codec.NEED_MORE_INPUT ? DecodeResult.NEED_MORE_INPUT : result;
}</code></pre>
<p>可以看到,在编码和解码的方法中都调用了codec的方法。</p>
<h4>(二十三)ChannelDelegate、ServerDelegate、ClientDelegate</h4>
<p>ChannelDelegate实现类Channel,ServerDelegate实现了Server,ClientDelegate实现了Client,都用到了装饰模式,都作为装饰模式中的装饰角色,所以类中的所有实现方法都调用了属性的方法。具体代码就不贴了,朋友们可以自行查看。</p>
<h4>(二十四)ChannelHandlerAdapter</h4>
<p>该类实现了ChannelHandler接口,是通道处理器适配类,该类中所有实现方法都是空的,所有想实现ChannelHandler接口的类可以直接继承该类,选择需要实现的方法进行实现,不需要实现ChannelHandler接口中所有方法。</p>
<h4>(二十五)ChannelHandlerDispatcher</h4>
<p>该类是通道处理器调度器,其中缓存了所有通道处理器,有一个通道处理器集合。并且每个操作都会去遍历该集合,执行相应的操作,例如:</p>
<pre><code class="java">@Override
public void connected(Channel channel) {
// 遍历通道处理器集合
for (ChannelHandler listener : channelHandlers) {
try {
// 连接
listener.connected(channel);
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
}</code></pre>
<h4>(二十六)CodecSupport</h4>
<p>该类是编解码工具类,提供查询 Serialization 的功能。</p>
<pre><code class="java">/**
* 序列化对象集合 key为序列化类型编号
*/
private static Map<Byte, Serialization> ID_SERIALIZATION_MAP = new HashMap<Byte, Serialization>();
/**
* 序列化扩展名集合 key为序列化类型编号 value为序列化扩展名
*/
private static Map<Byte, String> ID_SERIALIZATIONNAME_MAP = new HashMap<Byte, String>();
static {
// 利用dubbo 的SPI机制获得序列化扩展名
Set<String> supportedExtensions = ExtensionLoader.getExtensionLoader(Serialization.class).getSupportedExtensions();
for (String name : supportedExtensions) {
// 获得相应扩展名的序列化实现
Serialization serialization = ExtensionLoader.getExtensionLoader(Serialization.class).getExtension(name);
byte idByte = serialization.getContentTypeId();
if (ID_SERIALIZATION_MAP.containsKey(idByte)) {
logger.error("Serialization extension " + serialization.getClass().getName()
+ " has duplicate id to Serialization extension "
+ ID_SERIALIZATION_MAP.get(idByte).getClass().getName()
+ ", ignore this Serialization extension");
continue;
}
// 缓存序列化实现
ID_SERIALIZATION_MAP.put(idByte, serialization);
// 缓存序列化编号和扩展名
ID_SERIALIZATIONNAME_MAP.put(idByte, name);
}
}</code></pre>
<p>可以看到该类中缓存了所有的序列化对象和序列化扩展名。可以从中拿到Serialization。</p>
<h4>(二十七)ExceedPayloadLimitException</h4>
<p>该类是消息长度限制异常。</p>
<pre><code class="java">public class ExceedPayloadLimitException extends IOException {
private static final long serialVersionUID = -1112322085391551410L;
public ExceedPayloadLimitException(String message) {
super(message);
}
}</code></pre>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=3GXikFHR%2BJc%2BbZKsoRw3xw%3D%3D.PVXlU9Cg19DW4F6qj2I2odmu32ub5ptxZqaNHVA%2FcdK21PTz989I9%2FefB0GP%2BSH1L1yoLNNhTr57XE%2B%2B8Rc00HdJtV%2FVGJWMYhLd%2FMjW2GoD3P9RkY9WC8QheUPoD953mJUtdDeSkIqjjpTFF%2FCwaRkO%2BgFOgWF6aXzPhKtnmfWaFZD8jGkCpGii%2BBwhEl5p7EpmTcW7JydExZ7EMQH9bg%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了Transport层的相关设计和逻辑、介绍dubbo-remoting-api中的transport包内的源码解,其中关键的是整个设计都在使用装饰模式,传输层中关键的编解码器以及客户端、服务的、通道的抽象,还有关键的就是线程池的调度方法,熟悉那五种调度方法,对消息的处理。整个传输层核心的消息,很多操作围绕着消息展开。下一篇我会讲解交换层exchange部分。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(八)远程通信——开篇
https://segmentfault.com/a/1190000017274525
2018-12-06T01:21:17+08:00
2018-12-06T01:21:17+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
18
<h2>远程通讯——开篇</h2>
<blockquote>目标:介绍之后解读远程通讯模块的内容如何编排、介绍dubbo-remoting-api中的包结构设计以及最外层的的源码解析。</blockquote>
<h3>前言</h3>
<p>服务治理框架中可以大致分为服务通信和服务管理两个部分,前面我先讲到有关注册中心的内容,也就是服务管理,当然dubbo的服务管理还包括监控中心、 telnet 命令,它们起到的是人工的服务管理作用,这个后续再介绍。接下来我要讲解的就是跟服务通信有关的部分,也就是远程通讯模块。我在<a href="https://segmentfault.com/a/1190000016741532?share_user=1030000009586134">《dubbo源码解析(一)Hello,Dubbo》</a>的"(六)dubbo-remoting——远程通信模块“中提到过一些内容。该模块中提供了多种客户端和服务端通信的功能,而在对NIO框架选型上,dubbo交由用户选择,它集成了mina、netty、grizzly等各类NIO框架来搭建NIO服务器和客户端,并且利用dubbo的SPI扩展机制可以让用户自定义选择。如果对SPI不太了解的朋友可以查看<a href="https://segmentfault.com/a/1190000016842868">《dubbo源码解析(二)Dubbo扩展机制SPI》</a>。</p>
<p>接下来我们先来看看dubbo-remoting的包结构:</p>
<p><img src="/img/bVbioK0?w=714&h=422" alt="remoting目录" title="remoting目录"></p>
<p>我接下来解读远程通讯模块的内容并不是按照一个包一篇文章的编排,先来看看dubbo-remoting-api的包结构:</p>
<p><img src="/img/bVbkD3v?w=688&h=1072" alt="dubbo-remoting-api" title="dubbo-remoting-api"></p>
<p>可以看到,大篇幅的逻辑在dubbo-remoting-api中,所以我对于dubbo-remoting-api的解读会分为下面五个部分来说明,其中第五点会在本文介绍,其他四点会分别用四篇文章来介绍:</p>
<ol>
<li>buffer包:缓冲在NIO框架中是很重要的存在,各个NIO框架都实现了自己相应的缓存操作。这个buffer包下包括了缓冲区的接口以及抽象</li>
<li>exchange包:信息交换层,其中封装了请求响应模式,在传输层之上重新封装了 Request-Response 语义,为了满足RPC的需求。这层可以认为专注在Request和Response携带的信息上。该层是RPC调用的通讯基础之一。</li>
<li>telnet包:dubbo支持通过telnet命令来进行服务治理,该包下就封装了这些通用指令的逻辑实现。</li>
<li>transport包:网络传输层,它只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输。该层是RPC调用的通讯基础之一。</li>
<li>最外层的源码:该部分我会在下面之间给出介绍。</li>
</ol>
<p>为什么我要把一个api分成这么多文章来讲解,我们先来看看下面的图:</p>
<p><img src="/img/bVbkD3w?w=900&h=674" alt="dubbo-framework" title="dubbo-framework"></p>
<p>我们可以看到红框内的是远程通讯的框架,序列化我会在后面的主题中介绍,而Exchange层和Transport层在框架设计中起到了很重要的作用,也是支撑Remoting的核心,所以我要分开来介绍。</p>
<p>除了上述的五点外,根据惯例,我还是会分别介绍dubbo支持的实现客户端和服务端通信的七种方案,也就是说该远程通讯模块我会用12篇文章详细的讲解。</p>
<h3>最外层源码解析</h3>
<h4>(一)接口Endpoint</h4>
<p>dubbo抽象出一个端的概念,也就是Endpoint接口,这个端就是一个点,而点对点之间是可以双向传输。在端的基础上在衍生出通道、客户端以及服务端的概念,也就是下面要介绍的Channel、Client、Server三个接口。在传输层,其实Client和Server的区别只是在语义上区别,并不区分请求和应答职责,在交换层客户端和服务端也是一个点,但是已经是有方向的点,所以区分了明确的请求和应答职责。两者都具备发送的能力,只是客户端和服务端所关注的事情不一样,这个在后面会分开介绍,而Endpoint接口抽象的方法就是它们共同拥有的方法。这也就是它们都能被抽象成端的原因。</p>
<p>来看一下它的源码:</p>
<pre><code class="java">public interface Endpoint {
// 获得该端的url
URL getUrl();
// 获得该端的通道处理器
ChannelHandler getChannelHandler();
// 获得该端的本地地址
InetSocketAddress getLocalAddress();
// 发送消息
void send(Object message) throws RemotingException;
// 发送消息,sent是是否已经发送的标记
void send(Object message, boolean sent) throws RemotingException;
// 关闭
void close();
// 优雅的关闭,也就是加入了等待时间
void close(int timeout);
// 开始关闭
void startClose();
// 判断是否已经关闭
boolean isClosed();
}</code></pre>
<ol>
<li>前三个方法是获得该端本身的一些属性,</li>
<li>两个send方法是发送消息,其中第二个方法多了一个sent的参数,为了区分是否是第一次发送消息。</li>
<li>后面几个方法是提供了关闭通道的操作以及判断通道是否关闭的操作。</li>
</ol>
<h4>(二)接口Channel</h4>
<p>该接口是通道接口,通道是通讯的载体。还是用自动贩卖机的例子,自动贩卖机就好比是一个通道,消息发送端会往通道输入消息,而接收端会从通道读消息。并且接收端发现通道没有消息,就去做其他事情了,不会造成阻塞。所以channel可以读也可以写,并且可以异步读写。channel是client和server的传输桥梁。channel和client是一一对应的,也就是一个client对应一个channel,但是channel和server是多对一对关系,也就是一个server可以对应多个channel。</p>
<pre><code class="java">public interface Channel extends Endpoint {
// 获得远程地址
InetSocketAddress getRemoteAddress();
// 判断通道是否连接
boolean isConnected();
// 判断是否有该key的值
boolean hasAttribute(String key);
// 获得该key对应的值
Object getAttribute(String key);
// 添加属性
void setAttribute(String key, Object value);
// 移除属性
void removeAttribute(String key);
}</code></pre>
<p>可以看到Channel继承了Endpoint,也就是端抽象出来的方法也同样是channel所需要的。上面的几个方法很好理解,我就不多介绍了。</p>
<h4>(三)接口ChannelHandler</h4>
<pre><code class="java">@SPI
public interface ChannelHandler {
// 连接该通道
void connected(Channel channel) throws RemotingException;
// 断开该通道
void disconnected(Channel channel) throws RemotingException;
// 发送给这个通道消息
void sent(Channel channel, Object message) throws RemotingException;
// 从这个通道内接收消息
void received(Channel channel, Object message) throws RemotingException;
// 从这个通道内捕获异常
void caught(Channel channel, Throwable exception) throws RemotingException;
}</code></pre>
<p>该接口是负责channel中的逻辑处理,并且可以看到这个接口有注解@SPI,是个可扩展接口,到时候都会在下面介绍各类NIO框架的时候会具体讲到它的实现类。</p>
<h4>(四)接口Client</h4>
<pre><code class="java">public interface Client extends Endpoint, Channel, Resetable {
// 重连
void reconnect() throws RemotingException;
// 重置,不推荐使用
@Deprecated
void reset(com.alibaba.dubbo.common.Parameters parameters);
}</code></pre>
<p>客户端接口,可以看到它继承了Endpoint、Channel和Resetable接口,继承Endpoint的原因上面我已经提到过了,客户端和服务端其实只是语义上的不同,客户端就是一个点。继承Channel是因为客户端跟通道是一一对应的,所以做了这样的设计,还继承了Resetable接口是为了实现reset方法,该方法,不过已经打上@Deprecated注解,不推荐使用。除了这些客户端就只需要关注一个重连的操作。</p>
<p>这里插播一个公共模块下的接口Resetable:</p>
<pre><code class="java">public interface Resetable {
// 用于根据新传入的 url 属性,重置自己内部的一些属性
void reset(URL url);
}</code></pre>
<p>该方法就是根据新的url来重置内部的属性。</p>
<h4>(五)接口Server</h4>
<pre><code class="java">public interface Server extends Endpoint, Resetable {
// 判断是否绑定到本地端口,也就是该服务器是否启动成功,能够连接、接收消息,提供服务。
boolean isBound();
// 获得连接该服务器的通道
Collection<Channel> getChannels();
// 通过远程地址获得该地址对应的通道
Channel getChannel(InetSocketAddress remoteAddress);
@Deprecated
void reset(com.alibaba.dubbo.common.Parameters parameters);
}</code></pre>
<p>该接口是服务端接口,继承了Endpoint和Resetable,继承Endpoint是因为服务端也是一个点,继承Resetable接口是为了继承reset方法。除了这些以外,服务端独有的是检测是否启动成功,还有事获得连接该服务器上所有通道,这里获得所有通道其实就意味着获得了所有连接该服务器的客户端,因为客户端和通道是一一对应的。</p>
<h4>(六)接口Codec && Codec2</h4>
<p>这两个都是编解码器,那么什么叫做编解码器,在网络中只是讲数据看成是原始的字节序列,但是我们的应用程序会把这些字节组织成有意义的信息,那么网络字节流和数据间的转化就是很常见的任务。而编码器是讲应用程序的数据转化为网络格式,解码器则是讲网络格式转化为应用程序,同时具备这两种功能的单一组件就叫编解码器。在dubbo中Codec是老的编解码器接口,而Codec2是新的编解码器接口,并且dubbo已经用CodecAdapter把Codec适配成Codec2了。所以在这里我就介绍Codec2接口,毕竟人总要往前看。</p>
<pre><code class="java">@SPI
public interface Codec2 {
//编码
@Adaptive({Constants.CODEC_KEY})
void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException;
//解码
@Adaptive({Constants.CODEC_KEY})
Object decode(Channel channel, ChannelBuffer buffer) throws IOException;
enum DecodeResult {
// 需要更多输入和忽略一些输入
NEED_MORE_INPUT, SKIP_SOME_INPUT
}
}</code></pre>
<p>因为是编解码器,所以有两个方法分别是编码和解码,上述有以下几个关注的:</p>
<ol>
<li>Codec2是一个可扩展的接口,因为有@SPI注解。</li>
<li>用到了Adaptive机制,首先去url中寻找key为codec的value,来加载url携带的配置中指定的codec的实现。</li>
<li>该接口中有个枚举类型DecodeResult,因为解码过程中,需要解决 TCP 拆包、粘包的场景,所以增加了这两种解码结果,关于TCP 拆包、粘包的场景我就不多解释,不懂得朋友可以google一下。</li>
</ol>
<h4>(七)接口Decodeable</h4>
<pre><code class="java">public interface Decodeable {
//解码
public void decode() throws Exception;
}</code></pre>
<p>该接口是可解码的接口,该接口有两个作用,第一个是在调用真正的decode方法实现的时候会有一些校验,判断是否可以解码,并且对解码失败会有一些消息设置;第二个是被用来message核对用的。后面看具体的实现会更了解该接口的作用。</p>
<h4>(八)接口Dispatcher</h4>
<pre><code class="java">@SPI(AllDispatcher.NAME)
public interface Dispatcher {
// 调度
@Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"})
// The last two parameters are reserved for compatibility with the old configuration
ChannelHandler dispatch(ChannelHandler handler, URL url);
}</code></pre>
<p>该接口是调度器接口,dispatch是线程池的调度方法,这边有几个注意点:</p>
<ol>
<li>该接口是一个可扩展接口,并且默认实现AllDispatcher,也就是所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。</li>
<li>用了Adaptive注解,也就是按照URL中配置来加载实现类,后面两个参数是为了兼容老版本,如果这是三个key对应的值都为空,就选择AllDispatcher来实现。</li>
</ol>
<h4>(九)接口Transporter</h4>
<pre><code class="java">@SPI("netty")
public interface Transporter {
// 绑定一个服务器
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
Server bind(URL url, ChannelHandler handler) throws RemotingException;
// 连接一个服务器,即创建一个客户端
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}</code></pre>
<p>该接口是网络传输接口,有以下几个注意点:</p>
<ol>
<li>该接口是一个可扩展的接口,并且默认实现NettyTransporter。</li>
<li>用了dubbo SPI扩展机制中的Adaptive注解,加载对应的bind方法,使用url携带的server或者transporter属性值,加载对应的connect方法,使用url携带的client或者transporter属性值,不了解SPI扩展机制的可以查看<a href="https://segmentfault.com/a/1190000016842868">《dubbo源码解析(二)Dubbo扩展机制SPI》</a>。</li>
</ol>
<h4>(十)Transporters</h4>
<pre><code class="java">public class Transporters {
static {
// check duplicate jar package
// 检查重复的 jar 包
Version.checkDuplicate(Transporters.class);
Version.checkDuplicate(RemotingException.class);
}
private Transporters() {
}
public static Server bind(String url, ChannelHandler... handler) throws RemotingException {
return bind(URL.valueOf(url), handler);
}
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
// 创建handler
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
// 调用Transporter的实现类对象的bind方法。
// 例如实现NettyTransporter,则调用NettyTransporter的connect,并且返回相应的server
return getTransporter().bind(url, handler);
}
public static Client connect(String url, ChannelHandler... handler) throws RemotingException {
return connect(URL.valueOf(url), handler);
}
public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
ChannelHandler handler;
if (handlers == null || handlers.length == 0) {
handler = new ChannelHandlerAdapter();
} else if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
// 调用Transporter的实现类对象的connect方法。
// 例如实现NettyTransporter,则调用NettyTransporter的connect,并且返回相应的client
return getTransporter().connect(url, handler);
}
public static Transporter getTransporter() {
return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
}
}</code></pre>
<ol>
<li>该类用到了设计模式的外观模式,通过该类的包装,我们就不会看到内部具体的实现细节,这样降低了程序的复杂度,也提高了程序的可维护性。比如这个类,包装了调用各种实现Transporter接口的方法,通过getTransporter来获得Transporter的实现对象,具体实现哪个实现类,取决于url中携带的配置信息,如果url中没有相应的配置,则默认选择@SPI中的默认值netty。</li>
<li>bind和connect方法分别有两个重载方法,其中的操作只是把把字符串的url转化为URL对象。</li>
<li>静态代码块中检测了一下jar包是否有重复。</li>
</ol>
<h4>(十一)RemotingException && ExecutionException && TimeoutException</h4>
<p>这三个类是远程通信的异常类:</p>
<ol>
<li>RemotingException继承了Exception类,是远程通信的基础异常。</li>
<li>ExecutionException继承了RemotingException类,ExecutionException是远程通信的执行异常。</li>
<li>TimeoutException继承了RemotingException类,TimeoutException是超时异常。</li>
</ol>
<p>为了不影响篇幅,这三个类源码我就不介绍了,因为比较简单。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=67X%2BI4mbqkb3jkUOfFbQEA%3D%3D.cvB5IW3HRb8moyqrYeA8VL%2BlXi3NeLD7vrNpGdUkNKcZfSm9kl5agM6QUFhBV5LsU7UeKfcQtWKghESvUjY8MWHY0Q5%2FtbtknLNJgsXqU8iLXuxDZyve4EI9lFvoauliVrzLsFBE7nFdAIRyLZ4op7LyYmDOZBTILBz7Ztc14%2FG%2BmX5w8wgfPeVD6mFHzB2f" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了dubbo-remoting-api中的包结构设计以及最外层的的源码解析,其中关键的是理解端的概念,明白在哪一层才区分了发送和接收的职责,后续文章会按照我上面的编排去写。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(七)注册中心——zookeeper
https://segmentfault.com/a/1190000017132620
2018-11-25T08:43:55+08:00
2018-11-25T08:43:55+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
8
<h2>注册中心——zookeeper</h2>
<blockquote>目标:解释以为zookeeper实现的注册中心原理,解读duubo-registry-zookeeper的源码</blockquote>
<p>这篇文章是讲解注册中心的最后一篇文章。这篇文章讲的是dubbo的注册中心用zookeeper来实现。这种实现注册中心的方法也是dubbo推荐的方法。为了能更加理解zookeeper在dubbo中的应用,接下来我先简单的介绍一下zookeeper。</p>
<p>因为dubbo是一个分布式的RPC开源框架,各个服务之间单独部署,就会出现资源之间不一致的问题。而zookeeper就有保证分布式一致性的特性。ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务。关于dubbo为什么会推荐使用zookeeper作为它的注册中心实现,有很多书籍以及博客讲解了zookeeper的特性以及优势,这不是本章的重点,我要讲的是zookeeper的数据结构,dubbo服务是如何被zookeeper的数据结构存储管理的,因为这影响到下面源码的解读。zookeeper采用的是树形结构来组织数据节点,它类似于一个标准的文件系统。先来看看下面这张图:</p>
<p><img src="/img/bVbbUbN?w=500&h=288" alt="zookeeper" title="zookeeper"></p>
<p>该图是官方文档里面的一张图,展示了dubbo在zookeeper中存储的形式以及节点层级,</p>
<ol>
<li>dubbo的Root层是根目录,通过<dubbo:registry group="dubbo" />的“group”来设置zookeeper的根节点,缺省值是“dubbo”。</li>
<li>Service层是服务接口的全名。</li>
<li>Type层是分类,一共有四种分类,分别是providers(服务提供者列表)、consumers(服务消费者列表)、routes(路由规则列表)、configurations(配置规则列表)。</li>
<li>URL层:根据不同的Type目录:可以有服务提供者 URL 、服务消费者 URL 、路由规则 URL 、配置规则 URL 。不同的Type关注的URL不同。</li>
</ol>
<p>zookeeper以每个斜杠来分割每一层的znode,比如第一层根节点dubbo就是“/dubbo”,而第二层的Service层就是/com.foo.Barservice,zookeeper的每个节点通过路径来表示以及访问,例如服务提供者启动时,向/dubbo/com.foo.Barservice/providers目录下写入自己的URL地址。关于流程调用说明,见官方文档:</p>
<blockquote>文档地址:<a href="https://link.segmentfault.com/?enc=Kx7sc5f4igRNA5W2lX%2F15Q%3D%3D.AptbzqnhIKdbjxjs711y5yu8cABcbXa5XubQmqLwih5Y5do9vvaWddNwI%2FHFCoqBc19WAuZCxAcVTTjAv2DsjL1alBu4coX%2Bv7U4WMn%2B5jY%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<p>了解了dubbo在zookeeper中的节点层级,就可以看相关的源码了,下图是包的结构:</p>
<p><img src="/img/bVbj28J?w=842&h=424" alt="register-zookeeper目录" title="register-zookeeper目录"></p>
<p>跟前面三种实现方式一样的目录,也就两个类,看起来非常的舒服,接下来就来解析这两个类。</p>
<h3>(一)ZookeeperRegistry</h3>
<p>该类继承了FailbackRegistry类,该类就是针对注册中心核心的功能注册、订阅、取消注册、取消订阅,查询注册列表进行展开,基于zookeeper来实现。</p>
<h4>1.属性</h4>
<pre><code class="java">// 日志记录
private final static Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);
// 默认的zookeeper端口
private final static int DEFAULT_ZOOKEEPER_PORT = 2181;
// 默认zookeeper根节点
private final static String DEFAULT_ROOT = "dubbo";
// zookeeper根节点
private final String root;
// 服务接口集合
private final Set<String> anyServices = new ConcurrentHashSet<String>();
// 监听器集合
private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners = new ConcurrentHashMap<URL, ConcurrentMap<NotifyListener, ChildListener>>();
// zookeeper客户端实例
private final ZookeeperClient zkClient;</code></pre>
<p>其实你会发现zookeeper虽然是最被推荐的,反而它的实现逻辑相对简单,因为调用了zookeeper服务组件,很多的逻辑不需要在dubbo中自己去实现。上面的属性介绍也很简单,不需要多说,更多的是调用zookeeper客户端。</p>
<h4>2.构造方法</h4>
<pre><code class="java">public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
// 获得url携带的分组配置,并且作为zookeeper的根节点
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(Constants.PATH_SEPARATOR)) {
group = Constants.PATH_SEPARATOR + group;
}
this.root = group;
// 创建zookeeper client
zkClient = zookeeperTransporter.connect(url);
// 添加状态监听器,当状态为重连的时候调用恢复方法
zkClient.addStateListener(new StateListener() {
@Override
public void stateChanged(int state) {
if (state == RECONNECTED) {
try {
// 恢复
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
}</code></pre>
<p>这里有以下几个关注点:</p>
<ol>
<li>参数中ZookeeperTransporter是一个接口,并且在dubbo中有ZkclientZookeeperTransporter和CuratorZookeeperTransporter两个实现类,ZookeeperTransporter还是一个可扩展的接口,基于 Dubbo SPI Adaptive 机制,会根据url中携带的参数去选择用哪个实现类。</li>
<li>上面我说明了dubbo在zookeeper节点层级有一层是root层,该层是通过group属性来设置的。</li>
<li>给客户端添加一个监听器,当状态为重连的时候调用FailbackRegistry的恢复方法</li>
</ol>
<h4>3.appendDefaultPort</h4>
<pre><code class="java">static String appendDefaultPort(String address) {
if (address != null && address.length() > 0) {
int i = address.indexOf(':');
// 如果地址本身没有端口,则使用默认端口2181
if (i < 0) {
return address + ":" + DEFAULT_ZOOKEEPER_PORT;
} else if (Integer.parseInt(address.substring(i + 1)) == 0) {
return address.substring(0, i + 1) + DEFAULT_ZOOKEEPER_PORT;
}
}
return address;
}</code></pre>
<p>该方法是拼接使用默认的zookeeper端口,就是方地址本身没有端口的时候才使用默认端口。</p>
<h4>4.isAvailable && destroy</h4>
<pre><code class="java">@Override
public boolean isAvailable() {
return zkClient.isConnected();
}
@Override
public void destroy() {
super.destroy();
try {
zkClient.close();
} catch (Exception e) {
logger.warn("Failed to close zookeeper client " + getUrl() + ", cause: " + e.getMessage(), e);
}
}</code></pre>
<p>这里两个方法分别是检测zookeeper是否连接以及销毁连接,很简单,都是调用了zookeeper客户端封装好的方法。</p>
<h4>5.doRegister && doUnregister</h4>
<pre><code class="java">@Override
protected void doRegister(URL url) {
try {
// 创建URL节点,也就是URL层的节点
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
@Override
protected void doUnregister(URL url) {
try {
// 删除节点
zkClient.delete(toUrlPath(url));
} catch (Throwable e) {
throw new RpcException("Failed to unregister " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}</code></pre>
<p>这两个方法分别是注册和取消注册,也很简单,调用都是客户端create和delete方法,一个是创建一个节点,另一个是删除节点,该操作都在URL层。</p>
<h4>6.doSubscribe</h4>
<pre><code class="java">@Override
protected void doSubscribe(final URL url, final NotifyListener listener) {
try {
// 处理所有Service层发起的订阅,例如监控中心的订阅
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
// 获得根目录
String root = toRootPath();
// 获得url对应的监听器集合
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
// 不存在就创建监听器集合
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);
}
// 获得节点监听器
ChildListener zkListener = listeners.get(listener);
// 如果该节点监听器为空,则创建
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
@Override
public void childChanged(String parentPath, List<String> currentChilds) {
// 遍历现有的节点,如果现有的服务集合中没有该节点,则加入该节点,然后订阅该节点
for (String child : currentChilds) {
// 解码
child = URL.decode(child);
if (!anyServices.contains(child)) {
anyServices.add(child);
subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
}
});
// 重新获取,为了保证一致性
zkListener = listeners.get(listener);
}
// 创建service节点,该节点为持久节点
zkClient.create(root, false);
// 向zookeeper的service节点发起订阅,获得Service接口全名数组
List<String> services = zkClient.addChildListener(root, zkListener);
if (services != null && !services.isEmpty()) {
// 遍历Service接口全名数组
for (String service : services) {
service = URL.decode(service);
anyServices.add(service);
// 发起该service层的订阅
subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
} else {
// 处理指定 Service 层的发起订阅,例如服务消费者的订阅
List<URL> urls = new ArrayList<URL>();
// 遍历分类数组
for (String path : toCategoriesPath(url)) {
// 获得监听器集合
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
// 如果没有则创建
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);
}
// 获得节点监听器
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
@Override
public void childChanged(String parentPath, List<String> currentChilds) {
// 通知服务变化 回调NotifyListener
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
// 重新获取节点监听器,保证一致性
zkListener = listeners.get(listener);
}
// 创建type节点,该节点为持久节点
zkClient.create(path, false);
// 向zookeeper的type节点发起订阅
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
// 加入到自子节点数据数组
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
// 通知数据变化
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}</code></pre>
<p>这个方法是订阅,逻辑实现比较多,可以分两段来看,这里的实现把所有Service层发起的订阅以及指定的Service层发起的订阅分开处理。所有Service层类似于监控中心发起的订阅。指定的Service层发起的订阅可以看作是服务消费者的订阅。订阅的大致逻辑类似,不过还是有几个区别:</p>
<ol>
<li>所有Service层发起的订阅中的ChildListener是在在 Service 层发生变更时,才会做出解码,用anyServices属性判断是否是新增的服务,最后调用父类的subscribe订阅。而指定的Service层发起的订阅是在URL层发生变更的时候,调用notify,回调回调NotifyListener的逻辑,做到通知服务变更。</li>
<li>所有Service层发起的订阅中客户端创建的节点是Service节点,该节点为持久节点,而指定的Service层发起的订阅中创建的节点是Type节点,该节点也是持久节点。这里补充一下zookeeper的持久节点是节点创建后,就一直存在,直到有删除操作来主动清除这个节点,不会因为创建该节点的客户端会话失效而消失。而临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。</li>
<li>指定的Service层发起的订阅中调用了两次notify,第一次是增量的通知,也就是只是通知这次增加的服务节点,而第二个是全量的通知。</li>
</ol>
<h4>7.doUnsubscribe</h4>
<pre><code class="java">@Override
protected void doUnsubscribe(URL url, NotifyListener listener) {
// 获得监听器集合
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners != null) {
// 获得子节点的监听器
ChildListener zkListener = listeners.get(listener);
if (zkListener != null) {
// 如果为全部的服务接口,例如监控中心
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
// 获得根目录
String root = toRootPath();
// 移除监听器
zkClient.removeChildListener(root, zkListener);
} else {
// 遍历分类数组进行移除监听器
for (String path : toCategoriesPath(url)) {
zkClient.removeChildListener(path, zkListener);
}
}
}
}
}</code></pre>
<p>该方法是取消订阅,也是分为两种情况,所有的Service发起的取消订阅还是指定的Service发起的取消订阅。可以看到所有的Service发起的取消订阅就直接移除了根目录下所有的监听器,而指定的Service发起的取消订阅是移除了该Service层下面的所有Type节点监听器。如果不太明白再回去看看前面的那个节点层级图。</p>
<h4>8.lookup</h4>
<pre><code class="java">@Override
public List<URL> lookup(URL url) {
if (url == null) {
throw new IllegalArgumentException("lookup url == null");
}
try {
List<String> providers = new ArrayList<String>();
// 遍历分组类别
for (String path : toCategoriesPath(url)) {
// 获得子节点
List<String> children = zkClient.getChildren(path);
if (children != null) {
providers.addAll(children);
}
}
// 获得 providers 中,和 consumer 匹配的 URL 数组
return toUrlsWithoutEmpty(url, providers);
} catch (Throwable e) {
throw new RpcException("Failed to lookup " + url + " from zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}</code></pre>
<p>该方法就是查询符合条件的已经注册的服务。调用了toUrlsWithoutEmpty方法,在后面会讲到。</p>
<h4>9.toServicePath</h4>
<pre><code class="java">private String toServicePath(URL url) {
String name = url.getServiceInterface();
// 如果是包括所有服务,则返回根节点
if (Constants.ANY_VALUE.equals(name)) {
return toRootPath();
}
return toRootDir() + URL.encode(name);
}</code></pre>
<p>该方法是获得服务路径,拼接规则:Root + Type。</p>
<h4>10.toCategoriesPath</h4>
<pre><code class="java">private String[] toCategoriesPath(URL url) {
String[] categories;
// 如果url携带的分类配置为*,则创建包括所有分类的数组
if (Constants.ANY_VALUE.equals(url.getParameter(Constants.CATEGORY_KEY))) {
categories = new String[]{Constants.PROVIDERS_CATEGORY, Constants.CONSUMERS_CATEGORY,
Constants.ROUTERS_CATEGORY, Constants.CONFIGURATORS_CATEGORY};
} else {
// 返回url携带的分类配置
categories = url.getParameter(Constants.CATEGORY_KEY, new String[]{Constants.DEFAULT_CATEGORY});
}
String[] paths = new String[categories.length];
for (int i = 0; i < categories.length; i++) {
// 加上服务路径
paths[i] = toServicePath(url) + Constants.PATH_SEPARATOR + categories[i];
}
return paths;
}
private String toCategoryPath(URL url) {
return toServicePath(url) + Constants.PATH_SEPARATOR + url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
}</code></pre>
<p>第一个方法是获得分类数组,也就是url携带的服务下的所有Type节点数组。第二个是获得分类路径,分类路径拼接规则:Root + Service + Type</p>
<h4>11.toUrlPath</h4>
<pre><code class="java">private String toUrlPath(URL url) {
return toCategoryPath(url) + Constants.PATH_SEPARATOR + URL.encode(url.toFullString());
}</code></pre>
<p>该方法是获得URL路径,拼接规则是Root + Service + Type + URL</p>
<h4>12.toUrlsWithoutEmpty && toUrlsWithEmpty</h4>
<pre><code class="java">private List<URL> toUrlsWithoutEmpty(URL consumer, List<String> providers) {
List<URL> urls = new ArrayList<URL>();
if (providers != null && !providers.isEmpty()) {
// 遍历服务提供者
for (String provider : providers) {
// 解码
provider = URL.decode(provider);
if (provider.contains("://")) {
// 把服务转化成url的形式
URL url = URL.valueOf(provider);
// 判断是否匹配,如果匹配, 则加入到集合中
if (UrlUtils.isMatch(consumer, url)) {
urls.add(url);
}
}
}
}
return urls;
}
private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {
// 返回和服务消费者匹配的服务提供者url
List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
// 如果不存在,则创建`empty://` 的 URL返回
if (urls == null || urls.isEmpty()) {
int i = path.lastIndexOf('/');
String category = i < 0 ? path : path.substring(i + 1);
URL empty = consumer.setProtocol(Constants.EMPTY_PROTOCOL).addParameter(Constants.CATEGORY_KEY, category);
urls.add(empty);
}
return urls;
}</code></pre>
<p>第一个toUrlsWithoutEmpty方法是获得 providers 中,和 consumer 匹配的 URL 数组,第二个toUrlsWithEmpty方法是调用了第一个方法后增加了若不存在匹配,则创建 <code>empty://</code> 的 URL返回。通过这样的方式,可以处理类似服务提供者为空的情况。</p>
<h3>(二)ZookeeperRegistryFactory</h3>
<p>该类继承了AbstractRegistryFactory类,实现了AbstractRegistryFactory抽象出来的createRegistry方法,看一下原代码:</p>
<pre><code class="java">public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
private ZookeeperTransporter zookeeperTransporter;
public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
}
@Override
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
}</code></pre>
<p>可以看到就是实例化了ZookeeperRegistry而已,所有这里就不解释了。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=LeAPCi5gNAArDbRDU5KiXQ%3D%3D.%2BI3GcLMuSFkN06VGBNR1AdWvPcll9yZTFw9J3m9fR25MIDO9mSmztngPLzwy7gR94cZhBJeS5nkMJ52rgQSMmCQNjYpEdm4dj26U1PakHsWclsfTecM0lHKqpKy3ovlz8sXIkU4980QAf7GNpZ4oTmtx6Q%2FjLuAW%2F5tCIgKyV0jUkMeOddJIspwkptfxradSsB5Sh0rPEZ%2BUpSDrjy7Ohw%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了dubbo利用zookeeper来实现注册中心,其中关键的是需要弄明白dubbo在zookeeper中存储的节点层级意义,也就是root层、service层、type层以及url层分别代表什么,其他的逻辑并不复杂大多数调用了zookeeper客户端的能力,有兴趣的同学也可以深入的去了解zookeeper。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(六)注册中心——redis
https://segmentfault.com/a/1190000017062594
2018-11-19T15:07:52+08:00
2018-11-19T15:07:52+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
5
<h2>注册中心——redis</h2>
<blockquote>目标:解释以为redis实现的注册中心原理,解读duubo-registry-redis的源码</blockquote>
<p>Redis是一个key-value存储系统,交换数据非常快,redis以内存作为数据存储的介质,所以读写数据的效率极高,远远超过数据库。redis支持丰富的数据类型,dubbo就利用了redis的value支持map的数据类型。redis的key为服务名称和服务的类型。map中的key为URL地址,map中的value为过期时间,用于判断脏数据,脏数据由监控中心删除。</p>
<p>dubbo利用JRedis来连接到Redis分布式哈希键-值数据库,因为Jedis实例不是线程安全的,所以不可以多个线程共用一个Jedis实例,但是创建太多的实现也不好因为这意味着会建立很多sokcet连接。 所以dubbo又用了JedisPool,JedisPool是一个线程安全的网络连接池。可以用JedisPool创建一些可靠Jedis实例,可以从池中获取Jedis实例,使用完后再把Jedis实例还回JedisPool。这种方式可以避免创建大量socket连接并且会实现高效的性能。</p>
<p>上述稍微介绍了dubbo用redis实现注册中心的依赖,接下来让我们来看看具体的实现逻辑。下图是包的结构:</p>
<p><img src="/img/bVbjKUB?w=746&h=430" alt="注册中心redis目录" title="注册中心redis目录"></p>
<p>包结构非常类似。接下来我们就来解读一下这两个类。</p>
<h3>(一)RedisRegistry</h3>
<p>该类继承了FailbackRegistry类,该类就是针对注册中心核心的功能注册、订阅、取消注册、取消订阅,查询注册列表进行展开,基于redis来实现。</p>
<h4>1.属性</h4>
<pre><code class="java">// 日志记录
private static final Logger logger = LoggerFactory.getLogger(RedisRegistry.class);
// 默认的redis连接端口
private static final int DEFAULT_REDIS_PORT = 6379;
// 默认 Redis 根节点,涉及到的是dubbo的分组配置
private final static String DEFAULT_ROOT = "dubbo";
// 任务调度器
private final ScheduledExecutorService expireExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryExpireTimer", true));
//Redis Key 过期机制执行器
private final ScheduledFuture<?> expireFuture;
// Redis 根节点
private final String root;
// JedisPool集合,map 的key为 "ip:port"的形式
private final Map<String, JedisPool> jedisPools = new ConcurrentHashMap<String, JedisPool>();
// 通知器集合,key为 Root + Service的形式
// 例如 /dubbo/com.alibaba.dubbo.demo.DemoService
private final ConcurrentMap<String, Notifier> notifiers = new ConcurrentHashMap<String, Notifier>();
// 重连时间间隔,单位:ms
private final int reconnectPeriod;
// 过期周期,单位:ms
private final int expirePeriod;
// 是否通过监控中心,用于判断脏数据,脏数据由监控中心删除
private volatile boolean admin = false;
// 是否复制模式
private boolean replicate;</code></pre>
<p>可以从属性中看到基于redis的注册中心可以被监控中心监控,并且对过期的节点有清理的机制。</p>
<h4>2.构造方法</h4>
<pre><code class="java">public RedisRegistry(URL url) {
super(url);
// 判断地址是否为空
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
// 实例化对象池
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
// 如果 testOnBorrow 被设置,pool 会在 borrowObject 返回对象之前使用 PoolableObjectFactory的 validateObject 来验证这个对象是否有效
// 要是对象没通过验证,这个对象会被丢弃,然后重新选择一个新的对象。
config.setTestOnBorrow(url.getParameter("test.on.borrow", true));
// 如果 testOnReturn 被设置, pool 会在 returnObject 的时候通过 PoolableObjectFactory 的validateObject 方法验证对象
// 如果对象没通过验证,对象会被丢弃,不会被放到池中。
config.setTestOnReturn(url.getParameter("test.on.return", false));
// 指定空闲对象是否应该使用 PoolableObjectFactory 的 validateObject 校验,如果校验失败,这个对象会从对象池中被清除。
// 这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0) 的时候才会生效。
config.setTestWhileIdle(url.getParameter("test.while.idle", false));
if (url.getParameter("max.idle", 0) > 0)
// 控制一个pool最多有多少个状态为空闲的jedis实例。
config.setMaxIdle(url.getParameter("max.idle", 0));
if (url.getParameter("min.idle", 0) > 0)
// 控制一个pool最少有多少个状态为空闲的jedis实例。
config.setMinIdle(url.getParameter("min.idle", 0));
if (url.getParameter("max.active", 0) > 0)
// 控制一个pool最多有多少个jedis实例。
config.setMaxTotal(url.getParameter("max.active", 0));
if (url.getParameter("max.total", 0) > 0)
config.setMaxTotal(url.getParameter("max.total", 0));
if (url.getParameter("max.wait", url.getParameter("timeout", 0)) > 0)
//表示当引入一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;
config.setMaxWaitMillis(url.getParameter("max.wait", url.getParameter("timeout", 0)));
if (url.getParameter("num.tests.per.eviction.run", 0) > 0)
// 设置驱逐线程每次检测对象的数量。这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0)的时候才会生效。
config.setNumTestsPerEvictionRun(url.getParameter("num.tests.per.eviction.run", 0));
if (url.getParameter("time.between.eviction.runs.millis", 0) > 0)
// 指定驱逐线程的休眠时间。如果这个值不是正数( >0),不会有驱逐线程运行。
config.setTimeBetweenEvictionRunsMillis(url.getParameter("time.between.eviction.runs.millis", 0));
if (url.getParameter("min.evictable.idle.time.millis", 0) > 0)
// 指定最小的空闲驱逐的时间间隔(空闲超过指定的时间的对象,会被清除掉)。
// 这个设置仅在 timeBetweenEvictionRunsMillis 被设置成正值( >0)的时候才会生效。
config.setMinEvictableIdleTimeMillis(url.getParameter("min.evictable.idle.time.millis", 0));
// 获取url中的集群配置
String cluster = url.getParameter("cluster", "failover");
if (!"failover".equals(cluster) && !"replicate".equals(cluster)) {
throw new IllegalArgumentException("Unsupported redis cluster: " + cluster + ". The redis cluster only supported failover or replicate.");
}
// 设置是否为复制模式
replicate = "replicate".equals(cluster);
List<String> addresses = new ArrayList<String>();
addresses.add(url.getAddress());
// 备用地址
String[] backups = url.getParameter(Constants.BACKUP_KEY, new String[0]);
if (backups != null && backups.length > 0) {
addresses.addAll(Arrays.asList(backups));
}
for (String address : addresses) {
int i = address.indexOf(':');
String host;
int port;
// 分割地址和端口号
if (i > 0) {
host = address.substring(0, i);
port = Integer.parseInt(address.substring(i + 1));
} else {
// 没有端口的设置默认端口
host = address;
port = DEFAULT_REDIS_PORT;
}
// 创建连接池并加入集合
this.jedisPools.put(address, new JedisPool(config, host, port,
url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT), StringUtils.isEmpty(url.getPassword()) ? null : url.getPassword(),
url.getParameter("db.index", 0)));
}
// 设置url携带的连接超时时间,如果没有配置,则设置默认为3s
this.reconnectPeriod = url.getParameter(Constants.REGISTRY_RECONNECT_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RECONNECT_PERIOD);
// 获取url中的分组配置,如果没有配置,则默认为dubbo
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(Constants.PATH_SEPARATOR)) {
group = Constants.PATH_SEPARATOR + group;
}
if (!group.endsWith(Constants.PATH_SEPARATOR)) {
group = group + Constants.PATH_SEPARATOR;
}
// 设置redis 的根节点
this.root = group;
// 获取过期周期配置,如果没有,则默认为60s
this.expirePeriod = url.getParameter(Constants.SESSION_TIMEOUT_KEY, Constants.DEFAULT_SESSION_TIMEOUT);
// 创建过期机制执行器
this.expireFuture = expireExecutor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
// 延长到期时间
deferExpired(); // Extend the expiration time
} catch (Throwable t) { // Defensive fault tolerance
logger.error("Unexpected exception occur at defer expire time, cause: " + t.getMessage(), t);
}
}
}, expirePeriod / 2, expirePeriod / 2, TimeUnit.MILLISECONDS);
}</code></pre>
<p>构造方法首先是调用了父类的构造函数,然后是对对象池的一些配置进行了初始化,具体的我已经在注释中写明。在构造方法中还做了连接池的创建、过期机制执行器的创建,其中过期会进行延长到期时间的操作具体是在deferExpired方法中实现。还有一个关注点事该执行器的时间是取周期的一半。</p>
<h4>3.deferExpired</h4>
<pre><code class="java">private void deferExpired() {
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
// 获得连接池中的Jedis实例
Jedis jedis = jedisPool.getResource();
try {
// 遍历已经注册的服务url集合
for (URL url : new HashSet<URL>(getRegistered())) {
// 如果是非动态管理模式
if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
// 获得分类路径
String key = toCategoryPath(url);
// 以hash 散列表的形式存储
if (jedis.hset(key, url.toFullString(), String.valueOf(System.currentTimeMillis() + expirePeriod)) == 1) {
// 发布 Redis 注册事件
jedis.publish(key, Constants.REGISTER);
}
}
}
// 如果通过监控中心
if (admin) {
// 删除过时的脏数据
clean(jedis);
}
// 如果服务器端已同步数据,只需写入单台机器
if (!replicate) {
break;// If the server side has synchronized data, just write a single machine
}
} finally {
jedis.close();
}
} catch (Throwable t) {
logger.warn("Failed to write provider heartbeat to redis registry. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
}
}
}</code></pre>
<p>该方法实现了延长到期时间的逻辑,遍历了已经注册的服务url,这里会有一个是否为非动态管理模式的判断,也就是判断该节点是否为动态节点,只有动态节点是需要延长过期时间,因为动态节点需要人工删除节点。延长过期时间就是重新注册一次。而其他的节点则会被监控中心清除,也就是调用了clean方法。clean方法下面会讲到。</p>
<h4>4.clean</h4>
<pre><code class="java">// The monitoring center is responsible for deleting outdated dirty data
private void clean(Jedis jedis) {
// 获得所有的服务
Set<String> keys = jedis.keys(root + Constants.ANY_VALUE);
if (keys != null && !keys.isEmpty()) {
// 遍历所有的服务
for (String key : keys) {
// 返回hash表key对应的所有域和值
// redis的key为服务名称和服务的类型。map中的key为URL地址,map中的value为过期时间,用于判断脏数据,脏数据由监控中心删除
Map<String, String> values = jedis.hgetAll(key);
if (values != null && values.size() > 0) {
boolean delete = false;
long now = System.currentTimeMillis();
for (Map.Entry<String, String> entry : values.entrySet()) {
URL url = URL.valueOf(entry.getKey());
// 是否为动态节点
if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
long expire = Long.parseLong(entry.getValue());
// 判断是否过期
if (expire < now) {
// 删除记录
jedis.hdel(key, entry.getKey());
delete = true;
if (logger.isWarnEnabled()) {
logger.warn("Delete expired key: " + key + " -> value: " + entry.getKey() + ", expire: " + new Date(expire) + ", now: " + new Date(now));
}
}
}
}
// 取消注册
if (delete) {
jedis.publish(key, Constants.UNREGISTER);
}
}
}
}
}</code></pre>
<p>该方法就是用来清理过期数据的,之前我提到过dubbo在redis存储数据的数据结构形式,就是redis的key为服务名称和服务的类型。map中的key为URL地址,map中的value为过期时间,用于判断脏数据,脏数据由监控中心删除,那么判断过期就是通过map中的value来判别。逻辑就是在redis中先把记录删除,然后在取消订阅。</p>
<h4>5.isAvailable</h4>
<pre><code class="java">@Override
public boolean isAvailable() {
// 遍历连接池集合
for (JedisPool jedisPool : jedisPools.values()) {
try {
// 从连接池中获得jedis实例
Jedis jedis = jedisPool.getResource();
try {
// 判断是否有redis服务器被连接着
// 只要有一台连接,则算注册中心可用
if (jedis.isConnected()) {
return true; // At least one single machine is available.
}
} finally {
jedis.close();
}
} catch (Throwable t) {
}
}
return false;
}</code></pre>
<p>该方法是判断注册中心是否可用,通过redis是否连接来判断,只要有一台redis可连接,就算注册中心可用。</p>
<h4>6.destroy</h4>
<pre><code class="java">@Override
public void destroy() {
super.destroy();
try {
// 关闭过期执行器
expireFuture.cancel(true);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
// 关闭通知器
for (Notifier notifier : notifiers.values()) {
notifier.shutdown();
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
// 销毁连接池
jedisPool.destroy();
} catch (Throwable t) {
logger.warn("Failed to destroy the redis registry client. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
}
}
// 关闭任务调度器
ExecutorUtil.gracefulShutdown(expireExecutor, expirePeriod);
}</code></pre>
<p>这是销毁的方法,逻辑很清晰,gracefulShutdown方法在<a href="https://segmentfault.com/a/1190000016921721">《dubbo源码解析(四)注册中心——dubbo》</a>中已经讲到。</p>
<h4>7.doRegister</h4>
<pre><code class="java">@Override
public void doRegister(URL url) {
// 获得分类路径
String key = toCategoryPath(url);
// 获得URL字符串作为 Value
String value = url.toFullString();
// 计算过期时间
String expire = String.valueOf(System.currentTimeMillis() + expirePeriod);
boolean success = false;
RpcException exception = null;
// 遍历连接池集合
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
Jedis jedis = jedisPool.getResource();
try {
// 写入 Redis Map 键
jedis.hset(key, value, expire);
// 发布 Redis 注册事件
// 这样订阅该 Key 的服务消费者和监控中心,就会实时从 Redis 读取该服务的最新数据。
jedis.publish(key, Constants.REGISTER);
success = true;
// 如果服务器端已同步数据,只需写入单台机器
if (!replicate) {
break; // If the server side has synchronized data, just write a single machine
}
} finally {
jedis.close();
}
} catch (Throwable t) {
exception = new RpcException("Failed to register service to redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
}
}
if (exception != null) {
if (success) {
logger.warn(exception.getMessage(), exception);
} else {
throw exception;
}
}
}</code></pre>
<p>该方法是实现了父类FailbackRegistry的抽象方法,主要是实现了注册的功能,具体的逻辑是先将需要注册的服务信息保存到redis中,然后发布redis注册事件。</p>
<h4>8.doUnregister</h4>
<pre><code class="java">@Override
public void doUnregister(URL url) {
// 获得分类路径
String key = toCategoryPath(url);
// 获得URL字符串作为 Value
String value = url.toFullString();
RpcException exception = null;
boolean success = false;
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
Jedis jedis = jedisPool.getResource();
try {
// 删除redis中的记录
jedis.hdel(key, value);
// 发布redis取消注册事件
jedis.publish(key, Constants.UNREGISTER);
success = true;
// 如果服务器端已同步数据,只需写入单台机器
if (!replicate) {
break; // If the server side has synchronized data, just write a single machine
}
} finally {
jedis.close();
}
} catch (Throwable t) {
exception = new RpcException("Failed to unregister service to redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
}
}
if (exception != null) {
if (success) {
logger.warn(exception.getMessage(), exception);
} else {
throw exception;
}
}
}</code></pre>
<p>该方法也是实现了父类的抽象方法,当服务消费者或者提供者关闭时,会调用该方法来取消注册。逻辑就是跟注册方法方法,先从redis中删除服务相关记录,然后发布取消注册的事件,从而实时通知订阅者们。</p>
<h4>9.doSubscribe</h4>
<pre><code class="java">@Override
public void doSubscribe(final URL url, final NotifyListener listener) {
// 返回服务地址
String service = toServicePath(url);
// 获得通知器
Notifier notifier = notifiers.get(service);
// 如果没有该服务的通知器,则创建一个
if (notifier == null) {
Notifier newNotifier = new Notifier(service);
notifiers.putIfAbsent(service, newNotifier);
notifier = notifiers.get(service);
// 保证并发情况下,有且只有一个通知器启动
if (notifier == newNotifier) {
notifier.start();
}
}
boolean success = false;
RpcException exception = null;
// 遍历连接池集合进行订阅,直到有一个订阅成功,仅仅向一个redis进行订阅
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
Jedis jedis = jedisPool.getResource();
try {
// 如果服务地址为*结尾,也就是处理所有的服务层发起的订阅
if (service.endsWith(Constants.ANY_VALUE)) {
admin = true;
// 获得分类层的集合 例如:/dubbo/com.alibaba.dubbo.demo.DemoService/providers
Set<String> keys = jedis.keys(service);
if (keys != null && !keys.isEmpty()) {
// 按照服务聚合url
Map<String, Set<String>> serviceKeys = new HashMap<String, Set<String>>();
for (String key : keys) {
// 获得服务路径,截掉多余部分
String serviceKey = toServicePath(key);
Set<String> sk = serviceKeys.get(serviceKey);
if (sk == null) {
sk = new HashSet<String>();
serviceKeys.put(serviceKey, sk);
}
sk.add(key);
}
// 按照每个服务层进行发起通知,因为服务地址为*结尾
for (Set<String> sk : serviceKeys.values()) {
doNotify(jedis, sk, url, Arrays.asList(listener));
}
}
} else {
// 处理指定的服务层发起的通知
doNotify(jedis, jedis.keys(service + Constants.PATH_SEPARATOR + Constants.ANY_VALUE), url, Arrays.asList(listener));
}
// 只在一个redis上进行订阅
success = true;
break; // Just read one server's data
} finally {
jedis.close();
}
} catch (Throwable t) { // Try the next server
exception = new RpcException("Failed to subscribe service from redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
}
}
if (exception != null) {
// 虽然发生异常,但结果仍然成功
if (success) {
logger.warn(exception.getMessage(), exception);
} else {
throw exception;
}
}
}</code></pre>
<p>该方法是实现了订阅的功能。注意以下几个点:</p>
<ol>
<li>服务只会向一个redis进行订阅,只要有一个订阅成功就结束订阅。</li>
<li>根据url携带的服务地址来调用doNotify的两个重载方法。其中一个只是遍历通知了所有服务的监听器,doNotify方法我会在后面讲到。</li>
</ol>
<h4>10.doUnsubscribe</h4>
<pre><code class="java">@Override
public void doUnsubscribe(URL url, NotifyListener listener) {
}</code></pre>
<p>该方法本来是取消订阅的实现,不过dubbo中并未实现该逻辑。</p>
<h4>11.doNotify</h4>
<pre><code class="java">private void doNotify(Jedis jedis, String key) {
// 遍历所有的通知器,调用重载方法今天通知
for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>(getSubscribed()).entrySet()) {
doNotify(jedis, Arrays.asList(key), entry.getKey(), new HashSet<NotifyListener>(entry.getValue()));
}
}
private void doNotify(Jedis jedis, Collection<String> keys, URL url, Collection<NotifyListener> listeners) {
if (keys == null || keys.isEmpty()
|| listeners == null || listeners.isEmpty()) {
return;
}
long now = System.currentTimeMillis();
List<URL> result = new ArrayList<URL>();
// 获得分类集合
List<String> categories = Arrays.asList(url.getParameter(Constants.CATEGORY_KEY, new String[0]));
// 通过url获得服务接口
String consumerService = url.getServiceInterface();
// 遍历分类路径,例如/dubbo/com.alibaba.dubbo.demo.DemoService/providers
for (String key : keys) {
// 判断服务是否匹配
if (!Constants.ANY_VALUE.equals(consumerService)) {
String prvoiderService = toServiceName(key);
if (!prvoiderService.equals(consumerService)) {
continue;
}
}
// 从分类路径上获得分类名
String category = toCategoryName(key);
// 判断订阅的分类是否包含该分类
if (!categories.contains(Constants.ANY_VALUE) && !categories.contains(category)) {
continue;
}
List<URL> urls = new ArrayList<URL>();
// 返回所有的URL集合
Map<String, String> values = jedis.hgetAll(key);
if (values != null && values.size() > 0) {
for (Map.Entry<String, String> entry : values.entrySet()) {
URL u = URL.valueOf(entry.getKey());
// 判断是否为动态节点,因为动态节点不受过期限制。并且判断是否过期
if (!u.getParameter(Constants.DYNAMIC_KEY, true)
|| Long.parseLong(entry.getValue()) >= now) {
// 判断url是否合法
if (UrlUtils.isMatch(url, u)) {
urls.add(u);
}
}
}
}
// 若不存在匹配的url,则创建 `empty://` 的 URL返回,用于清空该服务的该分类。
if (urls.isEmpty()) {
urls.add(url.setProtocol(Constants.EMPTY_PROTOCOL)
.setAddress(Constants.ANYHOST_VALUE)
.setPath(toServiceName(key))
.addParameter(Constants.CATEGORY_KEY, category));
}
result.addAll(urls);
if (logger.isInfoEnabled()) {
logger.info("redis notify: " + key + " = " + urls);
}
}
if (result == null || result.isEmpty()) {
return;
}
// 全部数据完成后,调用通知方法,来通知监听器
for (NotifyListener listener : listeners) {
notify(url, listener, result);
}
}</code></pre>
<p>该方法实现了通知的逻辑,有两个重载方法,第二个比第一个多了几个参数,其实唯一的区别就是第一个重载方法是通知了所有的监听器,内部逻辑中调用了getSubscribed方法获取所有的监听器,该方法的解释可以查看<a href="https://segmentfault.com/a/1190000016905715">《dubbo源码解析(三)注册中心——开篇》</a>中关于subscribed属性的解释。而第二个重载方法就是对一个指定的监听器进行通知。</p>
<p>具体的逻辑在第二个重载的方法中,其中有以下几个需要注意的点:</p>
<ol>
<li>通知的事件要和监听器匹配。</li>
<li>不同的角色会关注不同的分类,服务消费者会关注providers、configurations、routes这几个分类,而服务提供者会关注consumers分类,监控中心会关注所有分类。</li>
<li>遍历分类路径,分类路径是Root + Service + Type。</li>
</ol>
<h4>12.toServiceName</h4>
<pre><code class="java">private String toServiceName(String categoryPath) {
String servicePath = toServicePath(categoryPath);
return servicePath.startsWith(root) ? servicePath.substring(root.length()) : servicePath;
}</code></pre>
<p>该方法很简单,就是从服务路径上获得服务名,这里就不多做解释了。</p>
<h4>13.toCategoryName</h4>
<pre><code class="java">private String toCategoryName(String categoryPath) {
int i = categoryPath.lastIndexOf(Constants.PATH_SEPARATOR);
return i > 0 ? categoryPath.substring(i + 1) : categoryPath;
}</code></pre>
<p>该方法的作用是从分类路径上获得分类名。</p>
<h4>14.toServicePath</h4>
<pre><code class="java">private String toServicePath(String categoryPath) {
int i;
if (categoryPath.startsWith(root)) {
i = categoryPath.indexOf(Constants.PATH_SEPARATOR, root.length());
} else {
i = categoryPath.indexOf(Constants.PATH_SEPARATOR);
}
return i > 0 ? categoryPath.substring(0, i) : categoryPath;
}
private String toServicePath(URL url) {
return root + url.getServiceInterface();
}</code></pre>
<p>这两个方法都是获得服务地址,第一个方法主要是截掉多余的部分,第二个方法主要是从url配置中获取关于服务地址的值跟根节点拼接。</p>
<h4>15.toCategoryPath</h4>
<pre><code class="java">private String toCategoryPath(URL url) {
return toServicePath(url) + Constants.PATH_SEPARATOR + url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
}</code></pre>
<p>该方法是获得分类路径,格式是Root + Service + Type。</p>
<h4>16.内部类NotifySub</h4>
<pre><code class="java">private class NotifySub extends JedisPubSub {
private final JedisPool jedisPool;
public NotifySub(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
@Override
public void onMessage(String key, String msg) {
if (logger.isInfoEnabled()) {
logger.info("redis event: " + key + " = " + msg);
}
// 如果是注册事件或者取消注册事件
if (msg.equals(Constants.REGISTER)
|| msg.equals(Constants.UNREGISTER)) {
try {
Jedis jedis = jedisPool.getResource();
try {
// 通知监听器
doNotify(jedis, key);
} finally {
jedis.close();
}
} catch (Throwable t) { // TODO Notification failure does not restore mechanism guarantee
logger.error(t.getMessage(), t);
}
}
}
@Override
public void onPMessage(String pattern, String key, String msg) {
onMessage(key, msg);
}
@Override
public void onSubscribe(String key, int num) {
}
@Override
public void onPSubscribe(String pattern, int num) {
}
@Override
public void onUnsubscribe(String key, int num) {
}
@Override
public void onPUnsubscribe(String pattern, int num) {
}
}</code></pre>
<p>NotifySub是RedisRegistry的一个内部类,继承了JedisPubSub类,JedisPubSub类中定义了publish/subsribe的回调方法。通过继承JedisPubSub类并重新实现这些回调方法,当publish/subsribe事件发生时,我们可以定制自己的处理逻辑。这里实现了onMessage和onPMessage两个方法,当收到注册和取消注册的事件的时候通知相关的监听器数据变化,从而实现实时更新数据。</p>
<h4>17.内部类Notifier</h4>
<p>该类继承 Thread 类,负责向 Redis 发起订阅逻辑。</p>
<h5>1.属性</h5>
<pre><code class="java">// 服务名:Root + Service
private final String service;
// 需要忽略连接的次数
private final AtomicInteger connectSkip = new AtomicInteger();
// 已经忽略连接的次数
private final AtomicInteger connectSkiped = new AtomicInteger();
// 随机数
private final Random random = new Random();
// jedis实例
private volatile Jedis jedis;
// 是否是首次通知
private volatile boolean first = true;
// 是否运行中
private volatile boolean running = true;
// 连接次数随机数
private volatile int connectRandom;</code></pre>
<p>上述属性中,部分属性都是为了redis的重连策略,用于在和redis断开链接时,忽略一定的次数和redis的连接,避免空跑。</p>
<h5>2.resetSkip</h5>
<pre><code class="java">private void resetSkip() {
connectSkip.set(0);
connectSkiped.set(0);
connectRandom = 0;
}</code></pre>
<p>该方法就是重置忽略连接的信息。</p>
<h5>3.isSkip</h5>
<pre><code class="java">private boolean isSkip() {
// 获得忽略次数
int skip = connectSkip.get(); // Growth of skipping times
// 如果忽略次数超过10次,那么取随机数,加上一个10以内的随机数
// 连接失败的次数越多,每一轮加大需要忽略的总次数,并且带有一定的随机性。
if (skip >= 10) { // If the number of skipping times increases by more than 10, take the random number
if (connectRandom == 0) {
connectRandom = random.nextInt(10);
}
skip = 10 + connectRandom;
}
// 自增忽略次数。若忽略次数不够,则继续忽略。
if (connectSkiped.getAndIncrement() < skip) { // Check the number of skipping times
return true;
}
// 增加需要忽略的次数
connectSkip.incrementAndGet();
// 重置已忽略次数和随机数
connectSkiped.set(0);
connectRandom = 0;
return false;
}</code></pre>
<p>该方法是用来判断忽略本次对redis的连接。首先获得需要忽略的次数,如果忽略次数不小于10次,则加上一个10以内的随机数,然后判断自增的忽略次数,如果次数不够,则继续忽略,如果次数够了,增加需要忽略的次数,重置已经忽略的次数和随机数。主要的思想是连接失败的次数越多,每一轮加大需要忽略的总次数,并且带有一定的随机性。</p>
<h5>4.run</h5>
<pre><code class="java">@Override
public void run() {
// 当通知器正在运行中时
while (running) {
try {
// 如果不忽略连接
if (!isSkip()) {
try {
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
jedis = jedisPool.getResource();
try {
// 是否为监控中心
if (service.endsWith(Constants.ANY_VALUE)) {
// 如果不是第一次通知
if (!first) {
first = false;
Set<String> keys = jedis.keys(service);
if (keys != null && !keys.isEmpty()) {
for (String s : keys) {
// 通知
doNotify(jedis, s);
}
}
// 重置
resetSkip();
}
// 批准订阅
jedis.psubscribe(new NotifySub(jedisPool), service); // blocking
} else {
// 如果不是监控中心,并且不是第一次通知
if (!first) {
first = false;
// 单独通知一个服务
doNotify(jedis, service);
// 重置
resetSkip();
}
// 批准订阅
jedis.psubscribe(new NotifySub(jedisPool), service + Constants.PATH_SEPARATOR + Constants.ANY_VALUE); // blocking
}
break;
} finally {
jedis.close();
}
} catch (Throwable t) { // Retry another server
logger.warn("Failed to subscribe service from redis registry. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
// If you only have a single redis, you need to take a rest to avoid overtaking a lot of CPU resources
// 发生异常,说明 Redis 连接断开了,需要等待reconnectPeriod时间
//通过这样的方式,避免执行,占用大量的 CPU 资源。
sleep(reconnectPeriod);
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
sleep(reconnectPeriod);
}
}
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
}</code></pre>
<p>该方法是线程的run方法,应该很熟悉,其中做了相关订阅的逻辑,其中根据redis的重连策略做了一些忽略连接的策略,也就是调用了上述讲解的isSkip方法,订阅就是调用了jedis.psubscribe方法,它是订阅给定模式相匹配的所有频道。</p>
<h5>4.shutdown</h5>
<pre><code class="java">public void shutdown() {
try {
// 更改状态
running = false;
// jedis断开连接
jedis.disconnect();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}</code></pre>
<p>该方法是断开连接的方法。</p>
<h3>(二)RedisRegistryFactory</h3>
<p>该类继承了AbstractRegistryFactory类,实现了AbstractRegistryFactory抽象出来的createRegistry方法,看一下原代码:</p>
<pre><code class="java">public class RedisRegistryFactory extends AbstractRegistryFactory {
@Override
protected Registry createRegistry(URL url) {
return new RedisRegistry(url);
}
}</code></pre>
<p>可以看到就是实例化了RedisRegistry而已,所有这里就不解释了。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=hk4sVayK4PG0wh3NKQk%2B0g%3D%3D.yWtJmq171gdXOvYJleCUplVaMmnr1dkNBsZ4akgSNFmc5PDNab%2FkAFJMEStdGR6vO1%2Bn0OF47Tg4hjJBe7MPorcU4h7CrChJvQ0AGJbYqikFj8kojerUO%2FccBgHs2nxt5crG0qrwAB1lkfCLoLsmzga2Yz1EKSRSw9VDyCXge7BGj7omipF%2FETLTM2A0hgCn5hZcfxlHcC9crzuV4ld2Dw%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了dubbo利用redis来实现注册中心,其中关键的是需要弄明白dubbo在redis中存储的数据结构,也就是key-value中key代表什么,value代表什么。还有就是需要了解JRedis和JedisPool,其他的逻辑并不复杂。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(五)注册中心——multicast
https://segmentfault.com/a/1190000016970061
2018-11-10T11:55:27+08:00
2018-11-10T11:55:27+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
7
<h2>注册中心——multicast</h2>
<blockquote>目标:解释以为multicast实现的注册中心原理,理解单播、广播、多播区别,解读duubo-registry-multicast的源码</blockquote>
<p>这是dubbo实现注册中心的第二种方式,也是dubbo的demo模块中用的注册中心实现方式。multicast其实是用到了MulticastSocket来实现的。</p>
<p>我这边稍微补充一点关于多点广播,也就是MulticastSocket的介绍。MulticastSocket类是继承了DatagramSocket类,DatagramSocket只允许把数据报发送给一个指定的目标地址,而MulticastSocket可以将数据报以广播的形式发送给多个客户端。它的思想是MulticastSocket会把一个数据报发送给一个特定的多点广播地址,这个多点广播地址是一组特殊的网络地址,当客户端需要发送或者接收广播信息时,只要加入该组就好。IP协议为多点广播提供了一批特殊的IP地址,地址范围是224.0.0.0至239.255.255.255。MulticastSocket类既可以将数据报发送到多点广播地址,也可以接收其他主机的广播信息。</p>
<p>以上是对multicast背景的简略介绍,接下来让我们具体的来看dubbo怎么把MulticastSocket运用到注册中心的实现中。</p>
<p>我们先来看看包下面有哪些类:</p>
<p><img src="/img/bVbjmQJ?w=770&h=422" alt="multicast目录" title="multicast目录"></p>
<p>可以看到跟默认的注册中心的包结构非常类似。接下来我们就来解读一下这两个类。</p>
<h3>(一)MulticastRegistry</h3>
<p>该类继承了FailbackRegistry类,该类就是针对注册中心核心的功能注册、订阅、取消注册、取消订阅,查询注册列表进行展开,利用广播的方式去实现。</p>
<h4>1.属性</h4>
<pre><code class="java">// logging output
// 日志记录输出
private static final Logger logger = LoggerFactory.getLogger(MulticastRegistry.class);
// 默认的多点广播端口
private static final int DEFAULT_MULTICAST_PORT = 1234;
// 多点广播的地址
private final InetAddress mutilcastAddress;
// 多点广播
private final MulticastSocket mutilcastSocket;
// 多点广播端口
private final int mutilcastPort;
//收到的URL
private final ConcurrentMap<URL, Set<URL>> received = new ConcurrentHashMap<URL, Set<URL>>();
// 任务调度器
private final ScheduledExecutorService cleanExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboMulticastRegistryCleanTimer", true));
// 定时清理执行器,一定时间清理过期的url
private final ScheduledFuture<?> cleanFuture;
// 清理的间隔时间
private final int cleanPeriod;
// 管理员权限
private volatile boolean admin = false;</code></pre>
<p>看上面的属性,需要关注以下几个点:</p>
<ol>
<li>mutilcastSocket,该类是muticast注册中心实现的关键,这里补充一下单播、广播、以及多播的区别,因为下面会涉及到。单播是每次只有两个实体相互通信,发送端和接收端都是唯一确定的;广播目的地址为网络中的全体目标,而多播的目的地址是一组目标,加入该组的成员均是数据包的目的地。</li>
<li>关注任务调度器和清理计时器,该类封装了定时清理过期的服务的策略。</li>
</ol>
<h4>2.构造方法</h4>
<pre><code class="java">public MulticastRegistry(URL url) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
if (!isMulticastAddress(url.getHost())) {
throw new IllegalArgumentException("Invalid multicast address " + url.getHost() + ", scope: 224.0.0.0 - 239.255.255.255");
}
try {
mutilcastAddress = InetAddress.getByName(url.getHost());
// 如果url携带的配置中没有端口号,则使用默认端口号
mutilcastPort = url.getPort() <= 0 ? DEFAULT_MULTICAST_PORT : url.getPort();
mutilcastSocket = new MulticastSocket(mutilcastPort);
// 禁用多播数据报的本地环回
mutilcastSocket.setLoopbackMode(false);
// 加入同一组广播
mutilcastSocket.joinGroup(mutilcastAddress);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
byte[] buf = new byte[2048];
// 实例化数据报
DatagramPacket recv = new DatagramPacket(buf, buf.length);
while (!mutilcastSocket.isClosed()) {
try {
// 接收数据包
mutilcastSocket.receive(recv);
String msg = new String(recv.getData()).trim();
int i = msg.indexOf('\n');
if (i > 0) {
msg = msg.substring(0, i).trim();
}
// 接收消息请求,根据消息并相应操作,比如注册,订阅等
MulticastRegistry.this.receive(msg, (InetSocketAddress) recv.getSocketAddress());
Arrays.fill(buf, (byte) 0);
} catch (Throwable e) {
if (!mutilcastSocket.isClosed()) {
logger.error(e.getMessage(), e);
}
}
}
}
}, "DubboMulticastRegistryReceiver");
// 设置为守护进程
thread.setDaemon(true);
// 开启线程
thread.start();
} catch (IOException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 优先从url中获取清理延迟配置,若没有,则默认为60s
this.cleanPeriod = url.getParameter(Constants.SESSION_TIMEOUT_KEY, Constants.DEFAULT_SESSION_TIMEOUT);
// 如果配置了需要清理
if (url.getParameter("clean", true)) {
// 开启计时器
this.cleanFuture = cleanExecutor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
// 清理过期的服务
clean(); // Remove the expired
} catch (Throwable t) { // Defensive fault tolerance
logger.error("Unexpected exception occur at clean expired provider, cause: " + t.getMessage(), t);
}
}
}, cleanPeriod, cleanPeriod, TimeUnit.MILLISECONDS);
} else {
this.cleanFuture = null;
}
}</code></pre>
<p>这个构造器最关键的就是一个线程和一个定时清理任务。</p>
<ol>
<li>线程中做的工作是根据接收到的消息来判定是什么请求,作出对应的操作,只要mutilcastSocket没有断开,就一直接收消息,内部的实现体现在receive方法中,下文会展开讲述。</li>
<li>定时清理任务是清理过期的注册的服务。通过两次socket的尝试来判定是否过期。clean方法下文会展开讲述</li>
</ol>
<h4>3.isMulticastAddress</h4>
<pre><code class="java">private static boolean isMulticastAddress(String ip) {
int i = ip.indexOf('.');
if (i > 0) {
String prefix = ip.substring(0, i);
if (StringUtils.isInteger(prefix)) {
int p = Integer.parseInt(prefix);
return p >= 224 && p <= 239;
}
}
return false;
}</code></pre>
<p>该方法很简单,为也没写注释,就是判断是否为多点广播地址,地址范围是224.0.0.0至239.255.255.255。</p>
<h4>4.clean</h4>
<pre><code class="java">private void clean() {
// 当url中携带的服务接口配置为是*时候,才可以执行清理
if (admin) {
for (Set<URL> providers : new HashSet<Set<URL>>(received.values())) {
for (URL url : new HashSet<URL>(providers)) {
// 判断是否过期
if (isExpired(url)) {
if (logger.isWarnEnabled()) {
logger.warn("Clean expired provider " + url);
}
//取消注册
doUnregister(url);
}
}
}
}
}</code></pre>
<p>该方法也比较简单,关机的是如何判断过期以及做的取消注册的操作。下面会展开讲解这几个方法。</p>
<h4>5.isExpired</h4>
<pre><code class="java">private boolean isExpired(URL url) {
// 如果为非动态管理模式或者协议是consumer、route或者override,则没有过期
if (!url.getParameter(Constants.DYNAMIC_KEY, true)
|| url.getPort() <= 0
|| Constants.CONSUMER_PROTOCOL.equals(url.getProtocol())
|| Constants.ROUTE_PROTOCOL.equals(url.getProtocol())
|| Constants.OVERRIDE_PROTOCOL.equals(url.getProtocol())) {
return false;
}
Socket socket = null;
try {
// 利用url携带的主机地址和端口号实例化socket
socket = new Socket(url.getHost(), url.getPort());
} catch (Throwable e) {
// 如果实例化失败,等待100ms重试第二次,如果还失败,则判定已过期
try {
// 等待100ms
Thread.sleep(100);
} catch (Throwable e2) {
}
Socket socket2 = null;
try {
socket2 = new Socket(url.getHost(), url.getPort());
} catch (Throwable e2) {
return true;
} finally {
if (socket2 != null) {
try {
socket2.close();
} catch (Throwable e2) {
}
}
}
} finally {
if (socket != null) {
try {
socket.close();
} catch (Throwable e) {
}
}
}
return false;
}</code></pre>
<p>这个方法就是判断服务是否过期,有两次尝试socket的操作,如果尝试失败,则判断为过期。</p>
<h4>6.receive</h4>
<pre><code class="java">private void receive(String msg, InetSocketAddress remoteAddress) {
if (logger.isInfoEnabled()) {
logger.info("Receive multicast message: " + msg + " from " + remoteAddress);
}
// 如果这个消息是以register、unregister、subscribe开头的,则进行相应的操作
if (msg.startsWith(Constants.REGISTER)) {
URL url = URL.valueOf(msg.substring(Constants.REGISTER.length()).trim());
// 注册服务
registered(url);
} else if (msg.startsWith(Constants.UNREGISTER)) {
URL url = URL.valueOf(msg.substring(Constants.UNREGISTER.length()).trim());
// 取消注册服务
unregistered(url);
} else if (msg.startsWith(Constants.SUBSCRIBE)) {
URL url = URL.valueOf(msg.substring(Constants.SUBSCRIBE.length()).trim());
// 获得以及注册的url集合
Set<URL> urls = getRegistered();
if (urls != null && !urls.isEmpty()) {
for (URL u : urls) {
// 判断是否合法
if (UrlUtils.isMatch(url, u)) {
String host = remoteAddress != null && remoteAddress.getAddress() != null
? remoteAddress.getAddress().getHostAddress() : url.getIp();
// 建议服务提供者和服务消费者在不同机器上运行,如果在同一机器上,需设置unicast=false
// 同一台机器中的多个进程不能单播单播,或者只有一个进程接收信息,发给消费者的单播消息可能被提供者抢占,两个消费者在同一台机器也一样,
// 只有multicast注册中心有此问题
if (url.getParameter("unicast", true) // Whether the consumer's machine has only one process
&& !NetUtils.getLocalHost().equals(host)) { // Multiple processes in the same machine cannot be unicast with unicast or there will be only one process receiving information
unicast(Constants.REGISTER + " " + u.toFullString(), host);
} else {
broadcast(Constants.REGISTER + " " + u.toFullString());
}
}
}
}
}/* else if (msg.startsWith(UNSUBSCRIBE)) {
}*/
}</code></pre>
<p>可以很清楚的看到,根据接收到的消息开头的数据来判断需要做什么类型的操作,重点在于订阅,可以选择单播订阅还是广播订阅,这个取决于url携带的配置是什么。</p>
<h4>7.broadcast</h4>
<pre><code class="java">private void broadcast(String msg) {
if (logger.isInfoEnabled()) {
logger.info("Send broadcast message: " + msg + " to " + mutilcastAddress + ":" + mutilcastPort);
}
try {
byte[] data = (msg + "\n").getBytes();
// 实例化数据报,重点是目的地址是mutilcastAddress
DatagramPacket hi = new DatagramPacket(data, data.length, mutilcastAddress, mutilcastPort);
// 发送数据报
mutilcastSocket.send(hi);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}</code></pre>
<p>这是广播的实现方法,重点是数据报的目的地址是mutilcastAddress。代表着一组地址</p>
<h4>8.unicast</h4>
<pre><code class="java">private void unicast(String msg, String host) {
if (logger.isInfoEnabled()) {
logger.info("Send unicast message: " + msg + " to " + host + ":" + mutilcastPort);
}
try {
byte[] data = (msg + "\n").getBytes();
// 实例化数据报,重点是目的地址是只是单个地址
DatagramPacket hi = new DatagramPacket(data, data.length, InetAddress.getByName(host), mutilcastPort);
// 发送数据报
mutilcastSocket.send(hi);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}</code></pre>
<p>这是单播的实现,跟广播的区别就只是目的地址不一样,单播的目的地址就只是一个地址,而广播的是一组地址。</p>
<h4>9.doRegister && doUnregister && doSubscribe && doUnsubscribe</h4>
<pre><code class="java">@Override
protected void doRegister(URL url) {
broadcast(Constants.REGISTER + " " + url.toFullString());
}
@Override
protected void doUnregister(URL url) {
broadcast(Constants.UNREGISTER + " " + url.toFullString());
}
@Override
protected void doSubscribe(URL url, NotifyListener listener) {
// 当url中携带的服务接口配置为是*时候,才可以执行清理,类似管理员权限
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
admin = true;
}
broadcast(Constants.SUBSCRIBE + " " + url.toFullString());
// 对监听器进行同步锁
synchronized (listener) {
try {
listener.wait(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
} catch (InterruptedException e) {
}
}
}
@Override
protected void doUnsubscribe(URL url, NotifyListener listener) {
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
unregister(url);
}
broadcast(Constants.UNSUBSCRIBE + " " + url.toFullString());
}</code></pre>
<p>这几个方法就是实现了父类FailbackRegistry的抽象方法。都是调用了broadcast方法。</p>
<h4>10.destroy</h4>
<pre><code class="java">@Override
public void destroy() {
super.destroy();
try {
// 取消清理任务
if (cleanFuture != null) {
cleanFuture.cancel(true);
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
// 把该地址从组内移除
mutilcastSocket.leaveGroup(mutilcastAddress);
// 关闭mutilcastSocket
mutilcastSocket.close();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
// 关闭线程池
ExecutorUtil.gracefulShutdown(cleanExecutor, cleanPeriod);
}</code></pre>
<p>该方法的逻辑跟dubbo注册中心的destroy方法类似,就多了把该地址从组内移除的操作。gracefulShutdown方法我在<a href="https://segmentfault.com/a/1190000016921721">《dubbo源码解析(四)注册中心——dubbo》</a>中已经讲到。</p>
<h4>11.register</h4>
<pre><code class="java">@Override
public void register(URL url) {
super.register(url);
registered(url);
}</code></pre>
<pre><code class="java">protected void registered(URL url) {
// 遍历订阅的监听器集合
for (Map.Entry<URL, Set<NotifyListener>> entry : getSubscribed().entrySet()) {
URL key = entry.getKey();
// 判断是否合法
if (UrlUtils.isMatch(key, url)) {
// 通过消费者url获得接收到的服务url集合
Set<URL> urls = received.get(key);
if (urls == null) {
received.putIfAbsent(key, new ConcurrentHashSet<URL>());
urls = received.get(key);
}
// 加入服务url
urls.add(url);
List<URL> list = toList(urls);
for (NotifyListener listener : entry.getValue()) {
// 把服务url的变化通知监听器
notify(key, listener, list);
synchronized (listener) {
listener.notify();
}
}
}
}
}</code></pre>
<p>可以看到该类重写了父类的register方法,不过逻辑没有过多的变化,就是把需要注册的url放入缓存中,如果通知监听器url的变化。</p>
<h4>12.unregister</h4>
<pre><code class="java">@Override
public void unregister(URL url) {
super.unregister(url);
unregistered(url);
}</code></pre>
<pre><code class="java">protected void unregistered(URL url) {
// 遍历订阅的监听器集合
for (Map.Entry<URL, Set<NotifyListener>> entry : getSubscribed().entrySet()) {
URL key = entry.getKey();
if (UrlUtils.isMatch(key, url)) {
Set<URL> urls = received.get(key);
// 缓存中移除
if (urls != null) {
urls.remove(url);
}
if (urls == null || urls.isEmpty()){
if (urls == null){
urls = new ConcurrentHashSet<URL>();
}
// 设置携带empty协议的url
URL empty = url.setProtocol(Constants.EMPTY_PROTOCOL);
urls.add(empty);
}
List<URL> list = toList(urls);
// 通知监听器 服务url变化
for (NotifyListener listener : entry.getValue()) {
notify(key, listener, list);
}
}
}
}</code></pre>
<p>这个逻辑也比较清晰,把需要取消注册的服务url从缓存中移除,然后如果没有接收的服务url了,就加入一个携带empty协议的url,然后通知监听器服务变化。</p>
<h4>13.lookup</h4>
<pre><code class="java">@Override
public List<URL> lookup(URL url) {
List<URL> urls = new ArrayList<URL>();
// 通过消费者url获得订阅的服务的监听器
Map<String, List<URL>> notifiedUrls = getNotified().get(url);
// 获得注册的服务url集合
if (notifiedUrls != null && notifiedUrls.size() > 0) {
for (List<URL> values : notifiedUrls.values()) {
urls.addAll(values);
}
}
// 如果为空,则从内存缓存properties获得相关value,并且返回为注册的服务
if (urls.isEmpty()) {
List<URL> cacheUrls = getCacheUrls(url);
if (cacheUrls != null && !cacheUrls.isEmpty()) {
urls.addAll(cacheUrls);
}
}
// 如果还是为空则从缓存registered中获得已注册 服务URL 集合
if (urls.isEmpty()) {
for (URL u : getRegistered()) {
if (UrlUtils.isMatch(url, u)) {
urls.add(u);
}
}
}
// 如果url携带的配置服务接口为*,也就是所有服务,则从缓存subscribed获得已注册 服务URL 集合
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
for (URL u : getSubscribed().keySet()) {
if (UrlUtils.isMatch(url, u)) {
urls.add(u);
}
}
}
return urls;
}</code></pre>
<p>该方法是返回注册的服务url列表,可以看到有很多种获得的方法这些缓存都保存在AbstractRegistry类中,相关的介绍可以查看<a href="https://segmentfault.com/a/1190000016905715">《dubbo源码解析(三)注册中心——开篇》</a>。</p>
<h4>14.subscribe && unsubscribe</h4>
<pre><code class="java">@Override
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
subscribed(url, listener);
}
@Override
public void unsubscribe(URL url, NotifyListener listener) {
super.unsubscribe(url, listener);
received.remove(url);
}</code></pre>
<pre><code class="java">protected void subscribed(URL url, NotifyListener listener) {
// 查询注册列表
List<URL> urls = lookup(url);
// 通知url
notify(url, listener, urls);
}</code></pre>
<p>这两个重写了父类的方法,分别是订阅和取消订阅。逻辑很简单。</p>
<h3>(二)MulticastRegistryFactory</h3>
<p>该类继承了AbstractRegistryFactory类,实现了AbstractRegistryFactory抽象出来的createRegistry方法,看一下原代码:</p>
<pre><code class="java">public class MulticastRegistryFactory extends AbstractRegistryFactory {
@Override
public Registry createRegistry(URL url) {
return new MulticastRegistry(url);
}
}</code></pre>
<p>可以看到就是实例化了MulticastRegistry而已,所有这里就不解释了。</p>
<h3>后记</h3>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=AAW3o15g0nhSgNOc9dOx0g%3D%3D.0REiNdT6lKFOG1CvfHMh4IYUW69%2BbRptIC2RdoraK5WMRwlORPjRW1OKRIgQbYfgilthzS2lHkFOnAwKNf0Ym2OD4QbU9iqJKaD76XKVfdEkMei6xe30lWMFtgMOnNfD99VLmX2tVyJpjd6UHMOLqaIJOUBs8fl54dyRA4ZkTBHPKeKbL8nlwAxnN95zjsxChPIwgjYNMAAic0DHETrkQg%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了dubbo利用multicast来实现注册中心,其中关键的是需要弄明白MulticastSocket以及单播、广播、多播的概念,其他的逻辑并不复杂。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(四)注册中心——dubbo
https://segmentfault.com/a/1190000016921721
2018-11-06T13:32:16+08:00
2018-11-06T13:32:16+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
8
<h2>注册中心——dubbo</h2>
<blockquote>目标:解释以为dubbo实现的注册中心原理,解读duubo-registry-default源码</blockquote>
<p>dubbo内置的注册中心实现方式有四种,这是第一种,也是dubbo默认的注册中心实现方式。我们可以从上篇文章中看到RegistryFactory接口的@SPI默认值是dubbo。</p>
<p>我们先来看看包下面有哪些类:</p>
<p><img src="/img/bVbjagT?w=750&h=454" alt="dubbo-registry-fault目录" title="dubbo-registry-fault目录"></p>
<p>可以看到该包下就两个类,下面就来解读这两个类。</p>
<h5>(一)DubboRegistry</h5>
<p>该类继承了FailbackRegistry类,该类里面封装了一个重连机制,而注册中心核心的功能注册、订阅、取消注册、取消订阅,查询注册列表都是调用了我上一篇文章<a href="https://segmentfault.com/a/1190000016905715">《dubbo源码解析(三)注册中心——开篇》</a>中讲到的实现方法,毕竟这种实现注册中心的方式是dubbo默认的方式,不过dubbo推荐使用zookeeper,这个后续讲解。</p>
<h6>1.属性</h6>
<pre><code class="java">// 日志记录
private final static Logger logger = LoggerFactory.getLogger(DubboRegistry.class);
// Reconnecting detection cycle: 3 seconds (unit:millisecond)
// 重新连接周期:3秒
private static final int RECONNECT_PERIOD_DEFAULT = 3 * 1000;
// Scheduled executor service
// 任务调度器
private final ScheduledExecutorService reconnectTimer = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryReconnectTimer", true));
// Reconnection timer, regular check connection is available. If unavailable, unlimited reconnection.
// 重新连接执行器,定期检查连接可用,如果不可用,则无限制重连
private final ScheduledFuture<?> reconnectFuture;
// The lock for client acquisition process, lock the creation process of the client instance to prevent repeated clients
// 客户端的锁,保证客户端的原子性,可见行,线程安全。
private final ReentrantLock clientLock = new ReentrantLock();
// 注册中心Invoker
private final Invoker<RegistryService> registryInvoker;
// 注册中心服务对象
private final RegistryService registryService;
// 任务调度器reconnectTimer将等待的时间
private final int reconnectPeriod;</code></pre>
<p>看上面的源码,可以看到这里的重连是建立了一个计时器,并且会定期检查连接是否可用,如果不可用,就无限重连。只要懂一点线程相关的知识,这里的属性还是比较好理解的。</p>
<h6>2.构造函数DubboRegistry</h6>
<p>先来看看源码:</p>
<pre><code class="java">public DubboRegistry(Invoker<RegistryService> registryInvoker, RegistryService registryService) {
// 调用父类FailbackRegistry的构造函数
super(registryInvoker.getUrl());
this.registryInvoker = registryInvoker;
this.registryService = registryService;
// Start reconnection timer
// 优先取url中key为reconnect.perio的配置,如果没有,则使用默认的3s
this.reconnectPeriod = registryInvoker.getUrl().getParameter(Constants.REGISTRY_RECONNECT_PERIOD_KEY, RECONNECT_PERIOD_DEFAULT);
// 每reconnectPeriod秒去连接,首次连接也延迟reconnectPeriod秒
reconnectFuture = reconnectTimer.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
// Check and connect to the registry
try {
connect();
} catch (Throwable t) { // Defensive fault tolerance
logger.error("Unexpected error occur at reconnect, cause: " + t.getMessage(), t);
}
}
}, reconnectPeriod, reconnectPeriod, TimeUnit.MILLISECONDS);
}</code></pre>
<p>这个构造方法中有两个关键点:</p>
<ol>
<li>关于等待时间优先从url配置中取得,如果没有这个值,再设置为默认值3s。</li>
<li>创建了一个重连计时器,一定的间隔时间去检查是否断开,如果断开就进行连接。</li>
</ol>
<h6>3.connect</h6>
<p>该方法是连接注册中心的实现,来看看源码:</p>
<pre><code class="java">protected final void connect() {
try {
// Check whether or not it is connected
// 检查注册中心是否已连接
if (isAvailable()) {
return;
}
if (logger.isInfoEnabled()) {
logger.info("Reconnect to registry " + getUrl());
}
// 获得客户端锁
clientLock.lock();
try {
// Double check whether or not it is connected
// 二次查询注册中心是否已经连接
if (isAvailable()) {
return;
}
// 恢复注册和订阅
recover();
} finally {
// 释放锁
clientLock.unlock();
}
} catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
if (getUrl().getParameter(Constants.CHECK_KEY, true)) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
throw new RuntimeException(t.getMessage(), t);
}
logger.error("Failed to connect to registry " + getUrl().getAddress() + " from provider/consumer " + NetUtils.getLocalHost() + " use dubbo " + Version.getVersion() + ", cause: " + t.getMessage(), t);
}
}</code></pre>
<p>我们可以看到这里的重连机制其实就是调用了父类FailbackRegistry的recover方法,关于recover方法我在<a href="https://segmentfault.com/a/1190000016905715">《dubbo源码解析(三)注册中心——开篇》</a>中已经讲解过了。还有要关注的就是需要保证客户端线程安全。需要获得锁和释放锁。</p>
<h6>4.isAvailable</h6>
<p>该方法就是用来检查注册中心是否连接,源码如下:</p>
<pre><code class="java">public boolean isAvailable() {
if (registryInvoker == null)
return false;
return registryInvoker.isAvailable();
}</code></pre>
<h6>5.destroy</h6>
<p>该方法是销毁方法,主要是销毁重连计时器、注册中心的Invoker和任务调度器,源码如下:</p>
<pre><code class="java">@Override
public void destroy() {
super.destroy();
try {
// Cancel the reconnection timer
// 取消重新连接计时器
if (!reconnectFuture.isCancelled()) {
reconnectFuture.cancel(true);
}
} catch (Throwable t) {
logger.warn("Failed to cancel reconnect timer", t);
}
// 销毁注册中心的Invoker
registryInvoker.destroy();
// 关闭任务调度器
ExecutorUtil.gracefulShutdown(reconnectTimer, reconnectPeriod);
}</code></pre>
<p>这里用到了ExecutorUtil中的gracefulShutdown,因为ExecutorUtil是common模块中的类,我在第一篇中讲到我会穿插在各个文章中介绍这个模块,所以我咋这里介绍一下这个gracefulShutdown方法,我们可以看一下这个源码:</p>
<pre><code class="java">public static void gracefulShutdown(Executor executor, int timeout) {
if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
return;
}
final ExecutorService es = (ExecutorService) executor;
try {
// Disable new tasks from being submitted
// 停止接收新的任务并且等待已经提交的任务(包含提交正在执行和提交未执行)执行完成
// 当所有提交任务执行完毕,线程池即被关闭
es.shutdown();
} catch (SecurityException ex2) {
return;
} catch (NullPointerException ex2) {
return;
}
try {
// Wait a while for existing tasks to terminate
// 当等待超过设定时间时,会监测ExecutorService是否已经关闭,如果没关闭,再关闭一次
if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
// 试图停止所有正在执行的线程,不再处理还在池队列中等待的任务
// ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
es.shutdownNow();
}
} catch (InterruptedException ex) {
es.shutdownNow();
Thread.currentThread().interrupt();
}
if (!isTerminated(es)) {
newThreadToCloseExecutor(es);
}
}</code></pre>
<p>可以看到这个销毁任务调度器,也就是退出线程池,调用了shutdown、shutdownNow方法,这里也替大家恶补了一下线程池关闭的方法区别。</p>
<h6>6.doRegister && doUnregister && doSubscribe && doUnsubscribe && lookup</h6>
<p>关于这个五个方法都是调用了RegistryService的方法,读者可自主查看<a href="https://segmentfault.com/a/1190000016905715">《dubbo源码解析(三)注册中心——开篇》</a>来理解内部实现。</p>
<h5>(二)DubboRegistryFactory</h5>
<p>该类继承了AbstractRegistryFactory类,实现了AbstractRegistryFactory抽象出来的createRegistry方法,是dubbo这种实现的注册中心的工厂类,里面做了一些初始化的处理,以及创建注册中心DubboRegistry的对象实例。因为该类的属性比较好理解,所以下面就不在展开讲解了。</p>
<h6>1.getRegistryURL</h6>
<p>获取注册中心url,类似于初始化注册中心url的方法。</p>
<pre><code class="java">private static URL getRegistryURL(URL url) {
return url.setPath(RegistryService.class.getName())
// 移除暴露服务和引用服务的参数
.removeParameter(Constants.EXPORT_KEY).removeParameter(Constants.REFER_KEY)
// 添加注册中心服务接口class值
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
// 启用sticky 粘性连接,让客户端总是连接同一提供者
.addParameter(Constants.CLUSTER_STICKY_KEY, "true")
// 决定在创建客户端时建立连接
.addParameter(Constants.LAZY_CONNECT_KEY, "true")
// 不重连
.addParameter(Constants.RECONNECT_KEY, "false")
// 方法调用超时时间为10s
.addParameterIfAbsent(Constants.TIMEOUT_KEY, "10000")
// 每个客户端上一个接口的回调服务实例的限制为10000个
.addParameterIfAbsent(Constants.CALLBACK_INSTANCES_LIMIT_KEY, "10000")
// 注册中心连接超时时间10s
.addParameterIfAbsent(Constants.CONNECT_TIMEOUT_KEY, "10000")
// 添加方法级配置
.addParameter(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(Wrapper.getWrapper(RegistryService.class).getDeclaredMethodNames())), ","))
//.addParameter(Constants.STUB_KEY, RegistryServiceStub.class.getName())
//.addParameter(Constants.STUB_EVENT_KEY, Boolean.TRUE.toString()) //for event dispatch
//.addParameter(Constants.ON_DISCONNECT_KEY, "disconnect")
.addParameter("subscribe.1.callback", "true")
.addParameter("unsubscribe.1.callback", "false");
}</code></pre>
<p>看上面的源码可以很直白的看书就是对url中的配置做一些初始化设置。几乎每个key对应的意义我都在上面展示了,会比较好理解。</p>
<h6>2.createRegistry</h6>
<p>该方法就是实现了AbstractRegistryFactory抽象出来的createRegistry方法,该子类就只关注createRegistry方法,其他公共的逻辑都在AbstractRegistryFactory已经实现。看一下源码:</p>
<pre><code class="java">@Override
public Registry createRegistry(URL url) {
// 类似于初始化注册中心
url = getRegistryURL(url);
List<URL> urls = new ArrayList<URL>();
// 移除备用的值
urls.add(url.removeParameter(Constants.BACKUP_KEY));
String backup = url.getParameter(Constants.BACKUP_KEY);
if (backup != null && backup.length() > 0) {
// 分割备用地址
String[] addresses = Constants.COMMA_SPLIT_PATTERN.split(backup);
for (String address : addresses) {
urls.add(url.setAddress(address));
}
}
// 创建RegistryDirectory,里面有多个Registry的Invoker
RegistryDirectory<RegistryService> directory = new RegistryDirectory<RegistryService>(RegistryService.class, url.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName()).addParameterAndEncoded(Constants.REFER_KEY, url.toParameterString()));
// 将directory中的多个Invoker伪装成一个Invoker
Invoker<RegistryService> registryInvoker = cluster.join(directory);
// 代理
RegistryService registryService = proxyFactory.getProxy(registryInvoker);
// 创建注册中心对象
DubboRegistry registry = new DubboRegistry(registryInvoker, registryService);
directory.setRegistry(registry);
directory.setProtocol(protocol);
// 通知监听器
directory.notify(urls);
// 订阅
directory.subscribe(new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, RegistryService.class.getName(), url.getParameters()));
return registry;
}</code></pre>
<p>在这个方法中做了实例化了DubboRegistry,并且做了通知和订阅的操作。相关集群、代理以及包含了很多Invoker的Directory我会在后续文章中讲到。</p>
<h4>后记</h4>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=scYXdiEBrqTvIbjgkuZtIA%3D%3D.nqKe3nqBMKtwPzt8erUc10%2FLFFcOt7pXQ6%2FknOXRpghgQHaBEkgRaC0E8i4mfAAMiyBBHiIJCe3Gzedgo6FyXPxqkh3%2BR%2FLS9I4w4%2BibU3h62yzpB1iE0mL2k56Rnz9A9c1f3EiiMZaO%2B4GdNPV4A2Hk4CA%2BRBj8MzE5mmI4Zgp%2FsRlhFFTI6GC76pG%2BUCcwP82Ms1Iy%2F9mlmbQx%2F1V2EA%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了dubbo的注册中心默认实现,默认实现很多都是调用了之前api中讲解到的方法,并没有重写过多的方法,其中已经涉及到集群之类的内容,请关注我后续的文章,讲解这些内容。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(三)注册中心——开篇
https://segmentfault.com/a/1190000016905715
2018-11-05T10:58:16+08:00
2018-11-05T10:58:16+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
21
<h2>注册中心——开篇</h2>
<blockquote>目标:解释注册中心在dubbo框架中作用,dubbo-registry-api源码解读</blockquote>
<h4>注册中心是什么?</h4>
<p>服务治理框架中可以大致分为服务通信和服务管理两个部分,服务管理可以分为服务注册、服务发现以及服务被热加工介入,服务提供者Provider会往注册中心注册服务,而消费者Consumer会从注册中心中订阅相关的服务,并不会订阅全部的服务。</p>
<p>官方文档给出了Provider、Consumer以及Registry之间的依赖关系:</p>
<p><img src="/img/bVbi55z?w=500&h=330" alt="dubbo-relation" title="dubbo-relation"></p>
<p>从上图看,可以清晰的看到Registry所起到的作用,我举个例子,Registry类似于一个自动售货机,服务提供者类似于一个商品生产者,他会往这个自动售卖机中添加商品,也就是注册服务,而消费者则会到注册中心中购买自己需要的商品,也就是订阅对应的服务。这样解释应该就可以比较直观的感受到注册中心所担任的是什么角色。</p>
<h4>dubbo-registry-api的解读</h4>
<p>首先我们来看看这个包下的结构:</p>
<p><img src="/img/bVbi55G?w=1446&h=952" alt="registry-api总类图" title="registry-api总类图"></p>
<p>可以很清晰的看到dubbo内部支持的四种注册中心实现方式,分别是dubbo、multicast、zookeeper、redis。他们都依赖于support包下面的类。根据上图的依赖关系,我会从上往下讲解dubbo中对于注册中心的设计以及实现。</p>
<h5>(一)RegistryService</h5>
<p>该接口是注册中心模块的服务接口,提供了注册、取消注册、订阅、取消订阅以及查询符合条件的已注册数据。它的源代码我就不贴出来了,可以查看官方文档中相关部分,还给出了中文注释。</p>
<blockquote>RegistryService源码地址:<a href="https://link.segmentfault.com/?enc=bsHjuRH8oGqFQt6b4WLYdA%3D%3D.SYENNcPSC9%2FRzcMUq0kU6KGE8jD%2FD%2BDoNqVur4oF038Lh%2Bhj6ydrHsUpWSImBHoSzANNha2gdB1xdmpTQlIhVw%3D%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<p>我们可以从注释中看到各个方法要处理的契约都在上面写明了。这个接口就是协定了注册中心的功能,这里统一说明一下URL,又再次提到URL了,在上篇文章中就说明了dubbo是以总线模式来时刻传递和保存配置信息的,也就是配置信息都被放在URL上进行传递,随时可以取得相关配置信息,而这里提到了URL有别的作用,就是作为类似于节点的作用,首先服务提供者(Provider)启动时需要提供服务,就会向注册中心写下自己的URL地址。然后消费者启动时需要去订阅该服务,则会订阅Provider注册的地址,并且消费者也会写下自己的URL。继续拿我上面的例子,商品生产者生产完商品,它会在把该商品放在自动售卖机的某一个栏目内,二消费者需要买该商品的时候,就是通过该地址去购买,并且会留下自己的购买记录。下面来讲讲各个方法:</p>
<ol>
<li>
<p>注册,如果看懂我上面说的url的作用,那么就很清楚该方法的作用了,这里强调一点,就是注释中讲到的允许URI相同但参数不同的URL并存,不能覆盖,也就是说url值必须唯一的,不能有一模一样。</p>
<pre><code class="java">void register(URL url);</code></pre>
</li>
<li>
<p>取消注册,该方法也很简单,就是取消注册,也就是商品生产者不在销售该商品, 需要把东西从自动售卖机上取下来,栏目也要取出,这里强调按全URL匹配取消注册。</p>
<pre><code class="java">void unregister(URL url);</code></pre>
</li>
<li>
<p>订阅,这里不是根据全URL匹配订阅的,而是根据条件去订阅,也就是说可以订阅多个服务。listener是用来监听处理注册数据变更的事件。</p>
<pre><code class="java">void subscribe(URL url, NotifyListener listener);</code></pre>
</li>
<li>
<p>取消订阅,这是按照全URL匹配去取消订阅的。</p>
<pre><code class="java">void unsubscribe(URL url, NotifyListener listener);</code></pre>
</li>
<li>
<p>查询注册列表,通过url进行条件查询所匹配的所有URL集合。</p>
<pre><code class="java">List<URL> lookup(URL url);</code></pre>
</li>
</ol>
<h5>(二)Registry</h5>
<p>注册中心接口,该接口很好理解,就是把节点以及注册中心服务的方法整合在了这个接口里面。我们来看看源代码:</p>
<pre><code class="java">public interface Registry extends Node, RegistryService {
}</code></pre>
<p>可以看到该接口并没有自己的方法,就是继承了Node和RegistryService接口。这里的Node是节点的接口,里面协定了关于节点的一些操作方法,我们可以来看看源代码:</p>
<pre><code class="java">public interface Node {
//获得节点地址
URL getUrl();
//判断节点是否可用
boolean isAvailable();
//销毁节点
void destroy();
}</code></pre>
<h5>(三)RegistryFactory</h5>
<p>这个接口是注册中心的工厂接口,用来返回注册中心的对象。来看看它的源码:</p>
<pre><code class="java">@SPI("dubbo")
public interface RegistryFactory {
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}</code></pre>
<p>本来方法上有一些英文注释,写的是关于连接注册中心需处理的契约,具体的可以直接看官方文档,还是中文的。</p>
<blockquote>地址:<a href="https://link.segmentfault.com/?enc=R2t8365OzHhYYmfuqvXrwg%3D%3D.EmGjaI%2FAB4YFrKGSYJrDdz3l5LW9ol%2BvcmJTRn%2F%2BnJkJTJIrigjejGW0DqpX8aPRcQCCVRx79AS5T0UP0a79ug%3D%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<p>该接口是一个可扩展接口,可以看到该接口上有个@SPI注解,并且默认值为dubbo,也就是默认扩展的是DubboRegistryFactory,并且可以在getRegistry方法上可以看到有@Adaptive注解,那么该接口会动态生成一个适配器RegistryFactory$Adaptive,并且会去首先扩展url.protocol的值对应的实现类。关于SPI扩展机制请观看<a href="https://segmentfault.com/a/1190000016842868">《dubbo源码解析(二)Dubbo扩展机制SPI》</a>。</p>
<h5>(四)NotifyListener</h5>
<p>该接口只有一个notify方法,通知监听器。当收到服务变更通知时触发。来看看它的源码:</p>
<pre><code class="java">public interface NotifyListener {
/**
* 当收到服务变更通知时触发。
* <p>
* 通知需处理契约:<br>
* 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。<br>
* 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。<br>
* 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。<br>
* 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。<br>
* 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。<br>
*
* @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。
*/
void notify(List<URL> urls);
}</code></pre>
<h5>(五)support包下的AbstractRegistry</h5>
<p>AbstractRegistry实现的是Registry接口,是Registry的抽象类。为了减轻注册中心的压力,在该类中实现了把本地URL缓存到property文件中的机制,并且实现了注册中心的注册、订阅等方法。</p>
<blockquote>源码注释地址:<a href="https://link.segmentfault.com/?enc=F9tY0mLzmvO9F%2BOgJhrGCg%3D%3D.PHStNxg02oCHnEF2BQF%2FseAkF%2Bkk3KC4633iATVqgtNuUj7DHVz3yaHengx2dlhCn7n6RAWNApgNqBsEZqOEKkSJ1qJocJvCQ9xJcAH4ho6zPr5jmaDV%2F0vPI0TeGT9msz85KAjjFhSdiCs%2FAg%2F1fGLXM0FBodyFaSllfr%2FpgQCN3OpWGwXEnjkr6CWmojRAD6Gs9qYCyxkvqsw6ECHoGkE8FlweUuCdtBiuK2J050A%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<h6>1.属性</h6>
<pre><code class="java"> // URL的地址分隔符,在缓存文件中使用,服务提供者的URL分隔
private static final char URL_SEPARATOR = ' ';
// URL地址分隔正则表达式,用于解析文件缓存中服务提供者URL列表
private static final String URL_SPLIT = "\\s+";
// 日志输出
protected final Logger logger = LoggerFactory.getLogger(getClass());
// 本地磁盘缓存,有一个特殊的key值为registies,记录的是注册中心列表,其他记录的都是服务提供者列表
private final Properties properties = new Properties();
// 缓存写入执行器
private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));
// 是否同步保存文件标志
private final boolean syncSaveFile;
//数据版本号
private final AtomicLong lastCacheChanged = new AtomicLong();
// 已注册 URL 集合
// 注册的 URL 不仅仅可以是服务提供者的,也可以是服务消费者的
private final Set<URL> registered = new ConcurrentHashSet<URL>();
// 订阅URL的监听器集合
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
// 某个消费者被通知的某一类型的 URL 集合
// 第一个key是消费者的URL,对应的就是哪个消费者。
// value是一个map集合,该map集合的key是分类的意思,例如providers、routes等,value就是被通知的URL集合
private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<URL, Map<String, List<URL>>>();
// 注册中心 URL
private URL registryUrl;
// 本地磁盘缓存文件,缓存注册中心的数据
private File file;</code></pre>
<p>理解属性的含义对于后面去解读方法很有帮助,从上面可以看到除了注册中心相关的一些属性外,可以看到好几个是个属性跟磁盘缓存文件和读写文件有关的,这就是上面提到的把URL缓存到本地property的相关属性这里有几个需要关注的点:</p>
<ol>
<li>properties:properties的数据跟本地文件的数据同步,当启动时,会从文件中读取数据到properties,而当properties中数据变化时,会写入到file。而properties是一个key对应一个列表,比如说key就是消费者的url,而值就是服务提供者列表、路由规则列表、配置规则列表。就是类似属性notified的含义。需要注意的是properties有一个特殊的key为registies,记录的是注册中心列表。</li>
<li>lastCacheChanged:因为每次写入file都是全部覆盖的写入,不是增量的去写入到文件,所以需要有这个版本号来避免老版本覆盖新版本。</li>
<li>notified:跟properties的区别是第一数据来源不是文件,而是从注册中心中读取,第二个notified根据分类把同一类的值做了聚合。</li>
</ol>
<h6>2.构造方法AbstractRegistry</h6>
<p>先来看看源码:</p>
<pre><code class="java"> public AbstractRegistry(URL url) {
// 把url放到registryUrl中
setUrl(url);
// Start file save timer
// 从url中读取是否同步保存文件的配置,如果没有值默认用异步保存文件
syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);
// 获得file路径
String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(Constants.APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
//创建文件
file = new File(filename);
if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
if (!file.getParentFile().mkdirs()) {
throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
}
}
}
this.file = file;
// 把文件里面的数据写入properties
loadProperties();
// 通知监听器,URL 变化结果
notify(url.getBackupUrls());
}</code></pre>
<p>需要关注的几个点:</p>
<ol>
<li>比如是否同步保存文件、比如保存的文件路径都优先选择URL上的配置,如果没有相关的配置,再选用默认配置。</li>
<li>构造AbstractRegistry会有把文件里面的数据写入到properties的操作以及通知监听器url变化结果,相关方法介绍在下面给出。</li>
</ol>
<h6>3.filterEmpty</h6>
<pre><code class="java"> protected static List<URL> filterEmpty(URL url, List<URL> urls) {
if (urls == null || urls.isEmpty()) {
List<URL> result = new ArrayList<URL>(1);
result.add(url.setProtocol(Constants.EMPTY_PROTOCOL));
return result;
}
return urls;
}</code></pre>
<p>这个方法的源码都不需要解释了,很简单,就是判断url集合是否为空,如果为空,则把url中key为empty的值加入到集合。该方法只有在notify方法中用到,为了防止通知的URL变化结果为空。</p>
<h6>4.doSaveProperties</h6>
<p>该方法比较长,我这里不贴源码了,需要的就查看github上的分析,该方法主要是将内存缓存properties中的数据存储到文件中,并且在里面做了版本号的控制,防止老的版本数据覆盖了新版本数据。数据流向是跟loadProperties方法相反。</p>
<h6>5.loadProperties</h6>
<pre><code class="java"> private void loadProperties() {
if (file != null && file.exists()) {
InputStream in = null;
try {
in = new FileInputStream(file);
// 把数据写入到内存缓存中
properties.load(in);
if (logger.isInfoEnabled()) {
logger.info("Load registry store file " + file + ", data: " + properties);
}
} catch (Throwable e) {
logger.warn("Failed to load registry store file " + file, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
}
}</code></pre>
<p>该方法就是加载本地磁盘缓存文件到内存缓存,也就是把文件里面的数据写入properties,可以对比doSaveProperties方法,其中关键的实现就是properties.load和properties.store的区别,逻辑并不难。跟doSaveProperties的数据流向相反。</p>
<h6>6.getCacheUrls</h6>
<pre><code class="java"> public List<URL> getCacheUrls(URL url) {
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
// key为某个分类,例如服务提供者分类
String key = (String) entry.getKey();
// value为某个分类的列表,例如服务提供者列表
String value = (String) entry.getValue();
if (key != null && key.length() > 0 && key.equals(url.getServiceKey())
&& (Character.isLetter(key.charAt(0)) || key.charAt(0) == '_')
&& value != null && value.length() > 0) {
//分割出列表的每个值
String[] arr = value.trim().split(URL_SPLIT);
List<URL> urls = new ArrayList<URL>();
for (String u : arr) {
urls.add(URL.valueOf(u));
}
return urls;
}
}
return null;
}</code></pre>
<p>该方法是获得内存缓存properties中相关value,并且返回为一个集合,从该方法中可以很清楚的看出properties中是存储的什么数据格式。</p>
<h6>7.lookup</h6>
<p>来看看源码:</p>
<pre><code class="java"> @Override
public List<URL> lookup(URL url) {
List<URL> result = new ArrayList<URL>();
// 获得该消费者url订阅的 所有被通知的 服务URL集合
Map<String, List<URL>> notifiedUrls = getNotified().get(url);
// 判断该消费者是否订阅服务
if (notifiedUrls != null && notifiedUrls.size() > 0) {
for (List<URL> urls : notifiedUrls.values()) {
for (URL u : urls) {
// 判断协议是否为空
if (!Constants.EMPTY_PROTOCOL.equals(u.getProtocol())) {
// 添加 该消费者订阅的服务URL
result.add(u);
}
}
}
} else {
// 原子类 避免在获取注册在注册中心的服务url时能够保证是最新的url集合
final AtomicReference<List<URL>> reference = new AtomicReference<List<URL>>();
// 通知监听器。当收到服务变更通知时触发
NotifyListener listener = new NotifyListener() {
@Override
public void notify(List<URL> urls) {
reference.set(urls);
}
};
// 订阅服务,就是消费者url订阅已经 注册在注册中心的服务(也就是添加该服务的监听器)
subscribe(url, listener); // Subscribe logic guarantees the first notify to return
List<URL> urls = reference.get();
if (urls != null && !urls.isEmpty()) {
for (URL u : urls) {
if (!Constants.EMPTY_PROTOCOL.equals(u.getProtocol())) {
result.add(u);
}
}
}
}
return result;
}</code></pre>
<p>该方法是实现了RegistryService接口的方法,作用是获得消费者url订阅的服务URL列表。该方法有几个地方有些绕我在这里重点讲解一下:</p>
<ol>
<li>URL可能是消费者URL,也可能是注册在注册中心的服务URL,我在注释中在URL加了修饰,为了能更明白的区分。</li>
<li>订阅了的服务URL一定是在注册中心中注册了的。</li>
<li>关于订阅服务subscribe方法和通知监听器NotifyListener,我会在下面解释。</li>
</ol>
<h6>8.register && unregister</h6>
<p>这两个方法实现了RegistryService接口的方法,里面的逻辑很简单,所有我就不贴代码了,以免影响篇幅,如果真想看,可以进到我github查看,下面我会贴出这部分注释github的地址。其中注册的逻辑就是把url加入到属性registered,而取消注册的逻辑就是把url从该属性中移除,该属性在上面有介绍。真正的实现是在FailbackRegistry类中,FailbackRegistry类我会在下面介绍。</p>
<h6>9.subscribe && unsubscribe</h6>
<p>这两个方法实现了RegistryService接口的方法,分别是订阅和取消订阅,我就贴一个订阅的代码:</p>
<pre><code class="java"> @Override
public void subscribe(URL url, NotifyListener listener) {
if (url == null) {
throw new IllegalArgumentException("subscribe url == null");
}
if (listener == null) {
throw new IllegalArgumentException("subscribe listener == null");
}
if (logger.isInfoEnabled()) {
logger.info("Subscribe: " + url);
}
// 获得该消费者url 已经订阅的服务 的监听器集合
Set<NotifyListener> listeners = subscribed.get(url);
if (listeners == null) {
subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
listeners = subscribed.get(url);
}
// 添加某个服务的监听器
listeners.add(listener);
}</code></pre>
<p>从源代码可以看到,其实订阅也就是把服务通知监听器加入到subscribed中,具体的实现也是在FailbackRegistry类中。</p>
<h6>10.recover</h6>
<p>恢复方法,在注册中心断开,重连成功的时候,会恢复注册和订阅。</p>
<pre><code class="java"> protected void recover() throws Exception {
// register
//把内存缓存中的registered取出来遍历进行注册
Set<URL> recoverRegistered = new HashSet<URL>(getRegistered());
if (!recoverRegistered.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Recover register url " + recoverRegistered);
}
for (URL url : recoverRegistered) {
register(url);
}
}
// subscribe
//把内存缓存中的subscribed取出来遍历进行订阅
Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
if (!recoverSubscribed.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Recover subscribe url " + recoverSubscribed.keySet());
}
for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
URL url = entry.getKey();
for (NotifyListener listener : entry.getValue()) {
subscribe(url, listener);
}
}
}
}</code></pre>
<h6>11.notify</h6>
<pre><code class="java">protected void notify(List<URL> urls) {
if (urls == null || urls.isEmpty()) return;
// 遍历订阅URL的监听器集合,通知他们
for (Map.Entry<URL, Set<NotifyListener>> entry : getSubscribed().entrySet()) {
URL url = entry.getKey();
// 匹配
if (!UrlUtils.isMatch(url, urls.get(0))) {
continue;
}
// 遍历监听器集合,通知他们
Set<NotifyListener> listeners = entry.getValue();
if (listeners != null) {
for (NotifyListener listener : listeners) {
try {
notify(url, listener, filterEmpty(url, urls));
} catch (Throwable t) {
logger.error("Failed to notify registry event, urls: " + urls + ", cause: " + t.getMessage(), t);
}
}
}
}
}
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((urls == null || urls.isEmpty())
&& !Constants.ANY_VALUE.equals(url.getServiceInterface())) {
logger.warn("Ignore empty notify urls for subscribe url " + url);
return;
}
if (logger.isInfoEnabled()) {
logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
}
Map<String, List<URL>> result = new HashMap<String, List<URL>>();
// 将urls进行分类
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
// 按照url中key为category对应的值进行分类,如果没有该值,就找key为providers的值进行分类
String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
List<URL> categoryList = result.get(category);
if (categoryList == null) {
categoryList = new ArrayList<URL>();
// 分类结果放入result
result.put(category, categoryList);
}
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
}
// 获得某一个消费者被通知的url集合(通知的 URL 变化结果)
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified == null) {
// 添加该消费者对应的url
notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
categoryNotified = notified.get(url);
}
// 处理通知监听器URL 变化结果
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
// 把分类标实和分类后的列表放入notified的value中
// 覆盖到 `notified`
// 当某个分类的数据为空时,会依然有 urls 。其中 `urls[0].protocol = empty` ,通过这样的方式,处理所有服务提供者为空的情况。
categoryNotified.put(category, categoryList);
// 保存到文件
saveProperties(url);
//通知监听器
listener.notify(categoryList);
}
}</code></pre>
<p>notify方法是通知监听器,url的变化结果,不过变化的是全量数据,全量数据意思就是是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。这里要注意几个重点:</p>
<ol>
<li>发起订阅后,会获取全量数据,此时会调用notify方法。即Registry 获取到了全量数据</li>
<li>每次注册中心发生变更时会调用notify方法虽然变化是增量,调用这个方法的调用方,已经进行处理,传入的urls依然是全量的。</li>
<li>listener.notify,通知监听器,例如,有新的服务提供者启动时,被通知,创建新的 Invoker 对象。</li>
</ol>
<h6>12.saveProperties</h6>
<p>先来看看源码:</p>
<pre><code class="java">private void saveProperties(URL url) {
if (file == null) {
return;
}
try {
// 拼接url
StringBuilder buf = new StringBuilder();
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified != null) {
for (List<URL> us : categoryNotified.values()) {
for (URL u : us) {
if (buf.length() > 0) {
buf.append(URL_SEPARATOR);
}
buf.append(u.toFullString());
}
}
}
// 设置到properties中
properties.setProperty(url.getServiceKey(), buf.toString());
// 增加版本号
long version = lastCacheChanged.incrementAndGet();
if (syncSaveFile) {
// 将集合中的数据存储到文件中
doSaveProperties(version);
} else {
//异步开启保存到文件
registryCacheExecutor.execute(new SaveProperties(version));
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}</code></pre>
<p>该方法是单个消费者url对应在notified中的数据,保存在到文件,而保存到文件的操作是调用了doSaveProperties方法,该方法跟doSaveProperties的区别是doSaveProperties方法将properties数据全部覆盖性的保存到文件,而saveProperties只是保存单个消费者url的数据。</p>
<h6>13.destroy</h6>
<p>该方法在JVM关闭时调用,进行取消注册和订阅的操作。具体逻辑就是调用了unregister和unsubscribe方法,有需要看源码的可以进入github查看。</p>
<h5>(六)support包下的FailbackRegistry</h5>
<p>我在上面讲AbstractRegistry类的时候已经提到了FailbackRegistry,FailbackRegistry继承了AbstractRegistry,AbstractRegistry中的注册订阅等方法,实际上就是一些内存缓存的变化,而真正的注册订阅的实现逻辑在FailbackRegistry实现,并且FailbackRegistry提供了失败重试的机制。</p>
<blockquote>源码注释地址:<a href="https://link.segmentfault.com/?enc=3A5rhD2JnSMozrzN0BeoaA%3D%3D.lBDrdFYjsqLVLLJfzlCk%2Fqgu32AekizbOsGBt3MaXMI5eTmsxDONvv3YE1kFpUExTdKZGzDEFv6XTe68Y9nTUzfdzLTxPc1hTYaJn7EVI3iGH35DHY7AHkVnS8AbujPOm4vBqIEaznqTZzdS9imOqqP38FVNJ66I8eeRMowiJUIXbrmKSTUonZlFJmMRFOTMzWPrBN9PNKh%2FdzhEIdgj9aTEtiAhrxyzs3BxLUO9ZeQ%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<h6>1.属性</h6>
<pre><code class="java">// Scheduled executor service
// 定时任务执行器
private final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryFailedRetryTimer", true));
// Timer for failure retry, regular check if there is a request for failure, and if there is, an unlimited retry
// 失败重试定时器,定时去检查是否有请求失败的,如有,无限次重试。
private final ScheduledFuture<?> retryFuture;
// 注册失败的URL集合
private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();
// 取消注册失败的URL集合
private final Set<URL> failedUnregistered = new ConcurrentHashSet<URL>();
// 订阅失败的监听器集合
private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
// 取消订阅失败的监听器集合
private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
// 通知失败的URL集合
private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();</code></pre>
<p>该类的属性比较好理解,也可以很明显看出这些属性都是跟失败重试机制相关。</p>
<h6>2.构造函数</h6>
<pre><code class="java">public FailbackRegistry(URL url) {
super(url);
// 从url中读取重试频率,如果为空,则默认5000ms
this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
// 创建失败重试定时器
this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
// Check and connect to the registry
try {
//重试
retry();
} catch (Throwable t) { // Defensive fault tolerance
logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
}
}
}, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
}</code></pre>
<p>构造函数主要是创建了失败重试的定时器,重试频率从URL取,如果没有设置,则默认为5000ms。</p>
<h6>3.register && unregister && subscribe && unsubscribe</h6>
<p>这四个方法就是注册、取消注册、订阅、取消订阅的具体实现,因为代码逻辑极其相似,所以为放在一起,下面为只贴出注册的源码:</p>
<pre><code class="java">public void register(URL url) {
super.register(url);
//首先从失败的缓存中删除该url
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
// Sending a registration request to the server side
// 向注册中心发送一个注册请求
doRegister(url);
} catch (Exception e) {
Throwable t = e;
// If the startup detection is opened, the Exception is thrown directly.
// 如果开启了启动时检测,则直接抛出异常
boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
&& url.getParameter(Constants.CHECK_KEY, true)
&& !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
boolean skipFailback = t instanceof SkipFailbackWrapperException;
if (check || skipFailback) {
if (skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
} else {
logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
// Record a failed registration request to a failed list, retry regularly
// 把这个注册失败的url放入缓存,并且定时重试。
failedRegistered.add(url);
}
}</code></pre>
<p>可以看到,逻辑很清晰,就是做了一个doRegister的操作,如果失败抛出异常,则加入到失败的缓存中进行重试。为这里要解释的是doRegister,与之对应的还有doUnregister、doSubscribe、doUnsubscribe三个方法,是FailbackRegistry抽象出来的方法,意图在于每种实现注册中心的方法不一样,相对应的注册、订阅等操作也会有所区别,而把这四个方法抽象出现,为了让子类只去关注这四个的实现,比如说redis实现的注册中心跟zookeeper实现的注册中心方式肯定不一样,那么对应的注册订阅等操作也有所不同,那么各自只要去实现该抽象方法即可。</p>
<p>其他的三个方法有需要的可以查看github上的我写的注释。</p>
<h6>4.notify</h6>
<pre><code class="java">@Override
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
try {
// 通知 url 数据变化
doNotify(url, listener, urls);
} catch (Exception t) {
// Record a failed registration request to a failed list, retry regularly
// 放入失败的缓存中,重试
Map<NotifyListener, List<URL>> listeners = failedNotified.get(url);
if (listeners == null) {
failedNotified.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, List<URL>>());
listeners = failedNotified.get(url);
}
listeners.put(listener, urls);
logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
}
protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
super.notify(url, listener, urls);
}</code></pre>
<p>可以看到notify不一样,他还是又回去调用了父类AbstractRegistry的notify,与上述四个方法不一样。</p>
<h6>5.revocer</h6>
<pre><code class="java">@Override
protected void recover() throws Exception {
// register
// register 恢复注册,添加到 `failedRegistered` ,定时重试
Set<URL> recoverRegistered = new HashSet<URL>(getRegistered());
if (!recoverRegistered.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Recover register url " + recoverRegistered);
}
for (URL url : recoverRegistered) {
failedRegistered.add(url);
}
}
// subscribe
// subscribe 恢复订阅,添加到 `failedSubscribed` ,定时重试
Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
if (!recoverSubscribed.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Recover subscribe url " + recoverSubscribed.keySet());
}
for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
URL url = entry.getKey();
for (NotifyListener listener : entry.getValue()) {
addFailedSubscribed(url, listener);
}
}
}
}</code></pre>
<p>重写了父类的recover,将注册和订阅放入到对应的失败缓存中,然后定时重试。</p>
<h6>6.retry</h6>
<p>该方法中实现了重试的逻辑,分别对注册失败failedRegistered、取消注册失败failedUnregistered、订阅失败failedSubscribed、取消订阅失败failedUnsubscribed、通知监听器失败failedNotified这五个缓存中的元素进行重试,重试的逻辑就是调用了相关的方法,然后从缓存中删除,例如重试注册,先进行doRegister,然后把该url从failedRegistered移除。具体的注释请到GitHub查看。</p>
<h5>(七)support包下的AbstractRegistryFactory</h5>
<p>该类实现了RegistryFactory接口,抽象了createRegistry方法,它实现了Registry的容器管理。</p>
<h6>1.属性</h6>
<pre><code class="java">// Log output
// 日志记录
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRegistryFactory.class);
// The lock for the acquisition process of the registry
// 锁,对REGISTRIES访问对竞争控制
private static final ReentrantLock LOCK = new ReentrantLock();
// Registry Collection Map<RegistryAddress, Registry>
// Registry 集合
private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();</code></pre>
<h6>2.destroyAll</h6>
<pre><code class="java">public static void destroyAll() {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Close all registries " + getRegistries());
}
// Lock up the registry shutdown process
// 获得锁
LOCK.lock();
try {
for (Registry registry : getRegistries()) {
try {
// 销毁
registry.destroy();
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
}
}
// 清空缓存
REGISTRIES.clear();
} finally {
// Release the lock
// 释放锁
LOCK.unlock();
}
}</code></pre>
<p>该方法作用是销毁所有的Registry对象,并且清除内存缓存,逻辑比较简单,关键就是对REGISTRIES进行同步的操作。</p>
<h6>3.getRegistry</h6>
<pre><code class="java">@Override
public Registry getRegistry(URL url) {
// 修改url
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
// 计算key值
String key = url.toServiceString();
// Lock the registry access process to ensure a single instance of the registry
// 获得锁
LOCK.lock();
try {
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
// 创建Registry对象
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
// 添加到缓存。
REGISTRIES.put(key, registry);
return registry;
} finally {
// Release the lock
// 释放锁
LOCK.unlock();
}
}</code></pre>
<p>该方法是实现了RegistryFactory接口中的方法,关于key值的计算我会在后续讲解URL的文章中讲到,这里最要注意的是createRegistry,因为AbstractRegistryFactory类把这个方法抽象出来,为了让子类只要关注该方法,比如说redis实现的注册中心和zookeeper实现的注册中心创建方式肯定不同,而他们相同的一些操作都已经在AbstractRegistryFactory中实现。所以只要关注并且实现该抽象方法即可。</p>
<h5>(八)support包下的ConsumerInvokerWrapper && ProviderInvokerWrapper</h5>
<p>这两个类实现了Invoker接口,分别是服务消费者和服务提供者的Invoker的包装器,其中就包装了一些属性,我们来看看源码:</p>
<h6>1.ConsumerInvokerWrapper属性</h6>
<pre><code class="java">// Invoker 对象
private Invoker<T> invoker;
// 原始url
private URL originUrl;
// 注册中心url
private URL registryUrl;
// 消费者url
private URL consumerUrl;
// 注册中心 Directory
private RegistryDirectory registryDirectory;</code></pre>
<h6>2.ProviderInvokerWrapper属性</h6>
<pre><code class="java">// Invoker对象
private Invoker<T> invoker;
// 原始url
private URL originUrl;
// 注册中心url
private URL registryUrl;
// 服务提供者url
private URL providerUrl;
// 是否注册
private volatile boolean isReg;</code></pre>
<p>这两个类都被运用在Dubbo QOS中,需要了解Dubbo QOS的可以到官方文档里面查看</p>
<blockquote>QOS网址:<a href="https://link.segmentfault.com/?enc=vMl%2F4SpJig3a4bYvYIFMfQ%3D%3D.3qLgqIxAmJHVED48xoYHZ8w%2FCVnYqLKbzKnhVMjhgTlwaeZZIyB4%2Brs7%2B0NzB4yuanzpQRdeyFlLPaHlfyzSEQ%3D%3D" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<h5>(九)support包下的ProviderConsumerRegTable</h5>
<p>服务提供者和消费者注册表,存储JVM进程中服务提供者和消费者的Invoker,该类也是被运用在QOS中,包括上面的两个类,都跟QOS中的Offline下线服务命令和ls列出消费者和提供者逻辑实现有关系。我们可以看看它的属性:</p>
<pre><code class="java">// 服务提供者Invoker集合,key 为服务提供者的url 计算的key,就是url.toServiceString()方法得到的
public static ConcurrentHashMap<String, Set<ProviderInvokerWrapper>> providerInvokers = new ConcurrentHashMap<String, Set<ProviderInvokerWrapper>>();
// 服务消费者的Invoker集合,key 为服务消费者的url 计算的key,url.toServiceString()方法得到的
public static ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>> consumerInvokers = new ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>>();</code></pre>
<p>可以看到,其实记录的服务提供者、消费者、注册中心中间的调用链,为了从一方出发能够很直观的找到跟它相关联的所有调用链。</p>
<p>该类中的其他方法请自行查看,这部分跟运维命令的实现相关,所以为不在这里讲解。</p>
<h5>(十)support包下的SkipFailbackWrapperException</h5>
<p>该类是一个dubbo单独创建的异常,在FailbackRegistry中被使用到,自定义的是一个跳过失败重试的异常。</p>
<h5>(十一)status包下的RegistryStatusChecker</h5>
<p>该类实现了StatusChecker,StatusChecker是一个状态校验的接口,RegistryStatusChecker是它的扩展类,做了一些跟注册中心有关的状态检查和设置。我们来看看源码:</p>
<pre><code class="java">@Activate
public class RegistryStatusChecker implements StatusChecker {
@Override
public Status check() {
// 获得所有的注册中心对象
Collection<Registry> registries = AbstractRegistryFactory.getRegistries();
if (registries.isEmpty()) {
return new Status(Status.Level.UNKNOWN);
}
Status.Level level = Status.Level.OK;
StringBuilder buf = new StringBuilder();
// 拼接注册中心url中的地址
for (Registry registry : registries) {
if (buf.length() > 0) {
buf.append(",");
}
buf.append(registry.getUrl().getAddress());
// 如果注册中心的节点不可用,则拼接disconnected,并且状态设置为error
if (!registry.isAvailable()) {
level = Status.Level.ERROR;
buf.append("(disconnected)");
} else {
buf.append("(connected)");
}
}
// 返回状态检查结果
return new Status(level, buf.toString());
}
}</code></pre>
<p>第一个关注点就是@Activate注解,也就是RegistryStatusChecker类会自动激活加载。该类就实现了接口的check方法,作用就是给注册中心进行状态检查,并且返回检查结果。</p>
<hr>
<p>下面讲的是integration下面的两个类RegistryProtocol和RegistryDirectory,这两个类与注册中心核心的逻辑关系没有那么强。RegistryProtocol是对dubbo-rpc-api的依赖集成,RegistryDirectory是对dubbo-cluster的依赖集成。如果看了下面的解析有点糊涂,可以先跳过这部分,等我出了rpc和cluster相关的文章后再回来看就会比较清晰。</p>
<h5>(十二)integration包下的RegistryProtocol && RegistryDirectory</h5>
<ul>
<li>RegistryProtocol实现了Protocol接口,也是Protocol接口等扩展类,但是它可以认为并不是一个真正的协议,他是实际的协议(dubbo . rmi)包装者,这样客户端的请求在一开始如果没有服务端的信息,会先从注册中心拉取服务的注册信息,然后再和服务端直连。RegistryProtocol是基于注册中心发现服务提供者的实现协议。</li>
<li>RegistryDirectory:注册中心服务,维护着所有可用的远程Invoker或者本地的Invoker。 它的Invoker集合是从注册中心获取的, 它实现了NotifyListener接口实现了回调接口notify方法。比如消费方要调用某远程服务,会向注册中心订阅这个服务的所有服务提供方,订阅时和服务提供方数据有变动时回调消费方的NotifyListener服务的notify方法,回调接口传入所有服务的提供方的url地址然后将urls转化为invokers, 也就是refer应用远程服务。</li>
</ul>
<p>这两个类等我讲解完rpc和cluster模块之后再进行补充源码解析。</p>
<h4>后记</h4>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=VBR4Qbt96aa6eXNPzxY6Fw%3D%3D.SJi%2BGDBA%2FKGzHFP6h0nGYs4Bk%2FELB9agMi5iVFzc3TxJ2DrH9ddiyW0WTBuzD7iwAHnb75xpKpnHWKGnwji85Pf6qxvcaguy7HUZNbZ%2Fc8q9fZ6NfK1sUphPlYfLopk4j1sK93LoV9yvEciNFQKIppB07j5HDN4t7h3ndMoPS1kyWB67GeQtojTCLmsFBhjS" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了dubbo的注册中心关于服务注册、订阅、服务变更通知等内部逻辑实现,接下来四篇文章我将会讲解dubbo、multicast、zookeeper、redis四种实现注册中心策略的逻辑实现。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>
Dubbo源码解析(二)Dubbo扩展机制SPI
https://segmentfault.com/a/1190000016842868
2018-10-29T22:01:20+08:00
2018-10-29T22:01:20+08:00
加点代码调调味
https://segmentfault.com/u/crazyhzm
50
<h2>Dubbo扩展机制SPI</h2>
<p>前一篇文章<a href="https://segmentfault.com/a/1190000016741532?share_user=1030000009586134">《dubbo源码解析(一)Hello,Dubbo》</a>是对dubbo整个项目大体的介绍,而从这篇文章开始,我将会从源码来解读dubbo再各个模块的实现原理以及特点,由于全部由截图的方式去解读源码会导致文章很杂乱,所以我只会放部分截图,全部的解读会同步更新在我github上fork的dubbo源码中,同时我也会在文章一些关键的地方加上超链接,方便读者快速查阅。</p>
<p><strong>我会在之后的每篇文章前都写一个目标,为了让读者一眼就能知道本文是否是你需要寻找的资料。</strong></p>
<hr>
<blockquote>目标:让读者知道JDK的SPI思想,dubbo的SPI思想,dubbo扩展机制SPI的原理,能够读懂实现扩展机制的源码。</blockquote>
<p>第一篇源码分析的文章就先来讲讲dubbo扩展机制spi的原理,浏览过dubbo官方文档的朋友肯定知道,dubbo有大量的spi扩展实现,包括协议扩展、调用拦截扩展、路由扩展等26个扩展,并且spi机制运用到了各个模块设计中。所以我打算先讲解dubbo的扩展机制spi。</p>
<h4>JDK的SPI思想</h4>
<p>SPI的全名为Service Provider Interface,面向对象的设计里面,模块之间推荐基于接口编程,而不是对实现类进行硬编码,这样做也是为了模块设计的可拔插原则。为了在模块装配的时候不在程序里指明是哪个实现,就需要一种服务发现的机制,jdk的spi就是为某个接口寻找服务实现。jdk提供了服务实现查找的工具类:java.util.ServiceLoader,它会去加载META-INF/service/目录下的配置文件。具体的内部实现逻辑为这里先不展开,主要还是讲解dubbo关于spi的实现原理。</p>
<h4>Dubbo的SPI扩展机制原理</h4>
<p>dubbo自己实现了一套SPI机制,改进了JDK标准的SPI机制:</p>
<ol>
<li>JDK标准的SPI只能通过遍历来查找扩展点和实例化,有可能导致一次性加载所有的扩展点,如果不是所有的扩展点都被用到,就会导致资源的浪费。dubbo每个扩展点都有多种实现,例如com.alibaba.dubbo.rpc.Protocol接口有InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol等实现,如果只是用到其中一个实现,可是加载了全部的实现,会导致资源的浪费。</li>
<li>把配置文件中扩展实现的格式修改,例如META-INF/dubbo/com.xxx.Protocol里的com.foo.XxxProtocol格式改为了xxx = com.foo.XxxProtocol这种以键值对的形式,这样做的目的是为了让我们更容易的定位到问题,比如由于第三方库不存在,无法初始化,导致无法加载扩展名(“A”),当用户配置使用A时,dubbo就会报无法加载扩展名的错误,而不是报哪些扩展名的实现加载失败以及错误原因,这是因为原来的配置格式没有把扩展名的id记录,导致dubbo无法抛出较为精准的异常,这会加大排查问题的难度。所以改成key-value的形式来进行配置。</li>
<li>dubbo的SPI机制增加了对IOC、AOP的支持,一个扩展点可以直接通过setter注入到其他扩展点。</li>
</ol>
<p>我们先来看看SPI扩展机制实现的结构目录:</p>
<p><img src="/img/bVbiPIi?w=830&h=420" alt="extension目录" title="extension目录"></p>
<h5>(一)注解@SPI</h5>
<p>在某个接口上加上@SPI注解后,表明该接口为可扩展接口。我用协议扩展接口Protocol来举例子,如果使用者在<dubbo:protocol />、<dubbo:service />、<dubbo:reference />都没有指定protocol属性的话,那么就会默认DubboProtocol就是接口Protocol,因为在Protocol上有@SPI("dubbo")注解。而这个protocol属性值或者默认值会被当作该接口的实现类中的一个key,dubbo会去META-INFdubbointernalcom.alibaba.dubbo.rpc.Protocol文件中找该key对应的value,看下图:</p>
<p><img src="/img/bVbiPIo?w=1398&h=202" alt="protocol的配置" title="protocol的配置"></p>
<p>value就是该Protocol接口的实现类DubboProtocol,这样就做到了SPI扩展。</p>
<h5>(二)注解@Adaptive</h5>
<p>该注解为了保证dubbo在内部调用具体实现的时候不是硬编码来指定引用哪个实现,也就是为了适配一个接口的多种实现,这样做符合模块接口设计的可插拔原则,也增加了整个框架的灵活性,<u>该注解也实现了扩展点自动装配的特性</u>。</p>
<p>dubbo提供了两种方式来实现接口的适配器:</p>
<ol>
<li>在实现类上面加上@Adaptive注解,表明该实现类是该接口的适配器。<p>举个例子dubbo中的ExtensionFactory接口就有一个实现类AdaptiveExtensionFactory,加了@Adaptive注解,AdaptiveExtensionFactory就不提供具体业务支持,用来适配ExtensionFactory的SpiExtensionFactory和SpringExtensionFactory这两种实现。AdaptiveExtensionFactory会根据在运行时的一些状态来选择具体调用ExtensionFactory的哪个实现,具体的选择可以看下文Adaptive的代码解析。</p>
</li>
<li>在接口方法上加@Adaptive注解,dubbo会动态生成适配器类。<p>我们从Transporter接口的源码来解释这种方法:</p>
</li>
</ol>
<p><img src="/img/bVbiPIv?w=1902&h=1308" alt="Transporter源码" title="Transporter源码"></p>
<p>我们可以看到在这个接口的bind和connect方法上都有@Adaptive注解,有该注解的方法的参数必须包含URL,ExtensionLoader会通过createAdaptiveExtensionClassCode方法动态生成一个Transporter$Adaptive类,生成的代码如下:</p>
<pre><code class="java">package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
//URL参数为空则抛出异常。
if (arg0 == null)
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
//这里的getParameter方法可以在源码中具体查看
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
//这里我在后面会有详细介绍
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.connect(arg0, arg1);
}
public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.bind(arg0, arg1);
}
}</code></pre>
<p>可以看到该类的两个方法就是Transporter接口中有注解的两个方法,我来解释一下第一个方法connect:</p>
<ol>
<li>所有扩展点都通过传递URL携带配置信息,所以适配器中的方法必须携带URL参数,才能根据URL中的配置来选择对应的扩展实现。</li>
<li>@Adaptive注解中有一些key值,比如connect方法的注解中有两个key,分别为“client”和“transporter”,URL会首先去取client对应的value来作为我上述<strong>(一)注解@SPI</strong>中写到的key值,如果为空,则去取transporter对应的value,如果还是为空,则会根据SPI默认的key,也就是netty去调用扩展的实现类,如果@SPI没有设定默认值,则会抛出IllegalStateException异常。</li>
</ol>
<p>这样就比较清楚这个适配器如何去选择哪个实现类作为本次需要调用的类,这里最关键的还是强调了dubbo以URL为总线,运行过程中所有的状态数据信息都可以通过URL来获取,比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过URL的参数来呈现的,所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的Key从URL的参数列表中获取。</p>
<h5>(三)注解@Activate</h5>
<p>扩展点自动激活加载的注解,就是用条件来控制该扩展点实现是否被自动激活加载,在扩展实现类上面使用,<u>实现了扩展点自动激活的特性</u>,它可以设置两个参数,分别是group和value。具体的介绍可以参照官方文档。</p>
<blockquote>扩展点自动激活地址:<a href="https://link.segmentfault.com/?enc=pMq7eWZOYzm3e%2BILZAQnag%3D%3D.3xVE4VxZ0S1UGuyjRY7l%2F81nI0lbfcNqAPDt6wM70AqHRlvqHHN4PaYn71lkXaqz" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
<h5>(四)接口ExtensionFactory</h5>
<p>先来看看它的源码:</p>
<p><img src="/img/bVbiPIF?w=1454&h=872" alt="ExtensionFactory源码" title="ExtensionFactory源码"></p>
<p>该接口是扩展工厂接口类,它本身也是一个扩展接口,有SPI的注解。该工厂接口提供的就是获取实现类的实例,它也有两种扩展实现,分别是SpiExtensionFactory和SpringExtensionFactory代表着两种不同方式去获取实例。而具体选择哪种方式去获取实现类的实例,则在适配器AdaptiveExtensionFactory中制定了规则。具体规则看下面的源码解析。</p>
<h5>(五)ExtensionLoader</h5>
<p>该类是扩展加载器,这是dubbo实现SPI扩展机制等核心,几乎所有实现的逻辑都被封装在ExtensionLoader中。</p>
<blockquote>详细代码注释见github:<a href="https://link.segmentfault.com/?enc=KH5YvFEy1yz6zmrBvTPzTg%3D%3D.faiVQTNXi8lD3QbxDMjCiiW%2BXeKkJsoJ2AH2ObQNIaEpj4FtUicIb2Ctxu5YccvgeEAqjGqTu8NWHUll8b2x1Q1kVtwC2uBNJFrpu2QOPzfyyE20AuefUJvNyfbmZ1WbBQ1mwp2vJHm6uLDa2RqWWxW67ss0SDknJF%2FDBp0ayws0Wh1gfi85ImF%2B61M4PMQ1q3LaUsT2WKf0ztHovcxSFw%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<ol>
<li>
<h6>属性(选取关键属性进行展开讲解,其余见github注释)</h6>
<ol>
<li>
<p>关于存放配置文件的路径变量:</p>
<pre><code class="java"> private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";</code></pre>
<p>"META-INF/services/"、"META-INF/dubbo/"、"META-INF/dubbo/internal/"三个值,都是dubbo寻找扩展实现类的配置文件存放路径,也就是我在上述<strong>(一)注解@SPI</strong>中讲到的以接口全限定名命名的配置文件存放的路径。区别在于"META-INF/services/"是dubbo为了兼容jdk的SPI扩展机制思想而设存在的,"META-INF/dubbo/internal/"是dubbo内部提供的扩展的配置文件路径,而"META-INF/dubbo/"是为了给用户自定义的扩展实现配置文件存放。</p>
</li>
<li>
<p>扩展加载器集合,key为扩展接口,例如Protocol等:</p>
<pre><code class="java"> private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();</code></pre>
</li>
<li>
<p>扩展实现类集合,key为扩展实现类,value为扩展对象,例如key为Class<DubboProtocol>,value为DubboProtocol对象</p>
<pre><code class="java"> private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();</code></pre>
</li>
<li>
<p>以下属性都是cache开头的,都是出于性能和资源的优化,才做的缓存,读取扩展配置后,会先进行缓存,等到真正需要用到某个实现时,再对该实现类的对象进行初始化,然后对该对象也进行缓存。</p>
<pre><code class="java"> //以下提到的扩展名就是在配置文件中的key值,类似于“dubbo”等
//缓存的扩展名与拓展类映射,和cachedClasses的key和value对换。
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
//缓存的扩展实现类集合
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
//扩展名与加有@Activate的自动激活类的映射
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
//缓存的扩展对象集合,key为扩展名,value为扩展对象
//例如Protocol扩展,key为dubbo,value为DubboProcotol
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object
//缓存的自适应( Adaptive )扩展对象,例如例如AdaptiveExtensionFactory类的对象
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
//缓存的自适应扩展对象的类,例如AdaptiveExtensionFactory类
private volatile Class<?> cachedAdaptiveClass = null;
//缓存的默认扩展名,就是@SPI中设置的值
private String cachedDefaultName;
//创建cachedAdaptiveInstance异常
private volatile Throwable createAdaptiveInstanceError;
//拓展Wrapper实现类集合
private Set<Class<?>> cachedWrapperClasses;
//拓展名与加载对应拓展类发生的异常的映射
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
</code></pre>
<p>这里提到了Wrapper类的概念。那我就解释一下:Wrapper类也实现了扩展接口,但是Wrapper类的用途是ExtensionLoader 返回扩展点时,包装在真正的扩展点实现外,<u>这实现了扩展点自动包装的特性</u>。通俗点说,就是一个接口有很多的实现类,这些实现类会有一些公共的逻辑,如果在每个实现类写一遍这个公共逻辑,那么代码就会重复,所以增加了这个Wrapper类来包装,把公共逻辑写到Wrapper类中,有点类似AOP切面编程思想。这部分解释也可以结合官方文档:</p>
<blockquote>扩展点自动包装的特性地址:<a href="https://link.segmentfault.com/?enc=HwZY1IQigqUTsgoBYMl7dw%3D%3D.e6yu4coglz%2BLS8HuBCeh%2Fu7q3khlYdzZoJoN0zh36xHrPM%2BkVZV9BO4GPvHbAxdH" rel="nofollow">http://dubbo.apache.org/zh-cn...</a>
</blockquote>
</li>
</ol>
</li>
<li>
<h6>getExtensionLoader(Class<T> type):根据扩展点接口来获得扩展加载器。</h6>
<pre><code class="java"> public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//扩展点接口为空,抛出异常
if (type == null)
throw new IllegalArgumentException("Extension type == null");
//判断type是否是一个接口类
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
//判断是否为可扩展的接口
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//从扩展加载器集合中取出扩展接口对应的扩展加载器
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
//如果为空,则创建该扩展接口的扩展加载器,并且添加到EXTENSION_LOADERS
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}</code></pre>
<p>这个方法的源码解析看上面,解读起来还是没有太多难点的。就是把几个属性的含义弄清楚就好了。</p>
</li>
<li>
<h6>getActivateExtension方法:获得符合自动激活条件的扩展实现类对象集合</h6>
<pre><code class="java"> public List<T> getActivateExtension(URL url, String key) {
return getActivateExtension(url, key, null);
}
//弃用
public List<T> getActivateExtension(URL url, String[] values) {
return getActivateExtension(url, values, null);
}
public List<T> getActivateExtension(URL url, String key, String group) {
String value = url.getParameter(key);
// 获得符合自动激活条件的拓展对象数组
return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
}
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
//判断不存在配置 `"-name"` 。
//例如,<dubbo:service filter="-default" /> ,代表移除所有默认过滤器。
if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
//获得扩展实现类数组,把扩展实现类放到cachedClasses中
getExtensionClasses();
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Activate activate = entry.getValue();
//判断group值是否存在所有自动激活类中group组中,匹配分组
if (isMatchGroup(group, activate.group())) {
//通过扩展名获得拓展对象
T ext = getExtension(name);
//不包含在自定义配置里。如果包含,会在下面的代码处理。
//判断是否配置移除。例如 <dubbo:service filter="-monitor" />,则 MonitorFilter 会被移除
//判断是否激活
if (!names.contains(name)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
&& isActive(activate, url)) {
exts.add(ext);
}
}
}
//排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
List<T> usrs = new ArrayList<T>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
//还是判断是否是被移除的配置
if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
//在配置中把自定义的配置放在自动激活的扩展对象前面,可以让自定义的配置先加载
//例如,<dubbo:service filter="demo,default,demo2" /> ,则 DemoFilter 就会放在默认的过滤器前面。
if (Constants.DEFAULT_KEY.equals(name)) {
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
T ext = getExtension(name);
usrs.add(ext);
}
}
}
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}</code></pre>
<p>可以看到getActivateExtension重载了四个方法,其实最终的实现都是在最后一个重载方法,因为自动激活类的条件可以分为无条件、只有value以及有group和value三种,具体的可以回顾上述<strong>(三)注解@Activate</strong>。</p>
<p>最后一个getActivateExtension方法有几个关键点:</p>
<ol>
<li>group的值合法判断,因为group可选"provider"或"consumer"。</li>
<li>判断该配置是否被移除。</li>
<li>如果有自定义配置,并且需要放在自动激活扩展实现对象加载前,那么需要先存放自定义配置。</li>
</ol>
</li>
<li>
<h6>getExtension方法: 获得通过扩展名获得扩展对象</h6>
<pre><code class="java"> @SuppressWarnings("unchecked")
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
//查找默认的扩展实现,也就是@SPI中的默认值作为key
if ("true".equals(name)) {
return getDefaultExtension();
}
//缓存中获取对应的扩展对象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//通过扩展名创建接口实现类的对象
instance = createExtension(name);
//把创建的扩展对象放入缓存
holder.set(instance);
}
}
}
return (T) instance;
}</code></pre>
<p>这个方法中涉及到getDefaultExtension方法和createExtension方法,会在后面讲到。其他逻辑比较简单,就是从缓存中取,如果没有,就创建,然后放入缓存。</p>
</li>
<li>
<h6>getDefaultExtension方法:查找默认的扩展实现</h6>
<pre><code class="java"> public T getDefaultExtension() {
//获得扩展接口的实现类数组
getExtensionClasses();
if (null == cachedDefaultName || cachedDefaultName.length() == 0
|| "true".equals(cachedDefaultName)) {
return null;
}
//又重新去调用了getExtension
return getExtension(cachedDefaultName);
}</code></pre>
<p>这里涉及到getExtensionClasses方法,会在后面讲到。获得默认的扩展实现类对象就是通过缓存中默认的扩展名去获得实现类对象。</p>
</li>
<li>
<h6>addExtension方法:扩展接口的实现类</h6>
<pre><code class="java"> public void addExtension(String name, Class<?> clazz) {
getExtensionClasses(); // load classes
//该类是否是接口的本身或子类
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Input type " +
clazz + "not implement Extension " + type);
}
//该类是否被激活
if (clazz.isInterface()) {
throw new IllegalStateException("Input type " +
clazz + "can not be interface!");
}
//判断是否为适配器
if (!clazz.isAnnotationPresent(Adaptive.class)) {
if (StringUtils.isBlank(name)) {
throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
}
if (cachedClasses.get().containsKey(name)) {
throw new IllegalStateException("Extension name " +
name + " already existed(Extension " + type + ")!");
}
//把扩展名和扩展接口的实现类放入缓存
cachedNames.put(clazz, name);
cachedClasses.get().put(name, clazz);
} else {
if (cachedAdaptiveClass != null) {
throw new IllegalStateException("Adaptive Extension already existed(Extension " + type + ")!");
}
cachedAdaptiveClass = clazz;
}
}</code></pre>
</li>
<li>
<h6>getAdaptiveExtension方法:获得自适应扩展对象,也就是接口的适配器对象</h6>
<pre><code class="java"> @SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//创建适配器对象
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}</code></pre>
<p>思路就是先从缓存中取适配器类的对象,如果没有,则创建一个适配器对象,然后放入缓存,createAdaptiveExtension方法解释在后面给出。</p>
</li>
<li>
<h6>createExtension方法:通过扩展名创建扩展接口实现类的对象</h6>
<pre><code class="java"> @SuppressWarnings("unchecked")
private T createExtension(String name) {
//获得扩展名对应的扩展实现类
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//看缓存中是否有该类的对象
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//向对象中注入依赖的属性(自动装配)
injectExtension(instance);
//创建 Wrapper 扩展对象(自动包装)
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}</code></pre>
<p>这里运用到了两个扩展点的特性,分别是自动装配和自动包装。injectExtension方法解析在下面给出。</p>
</li>
<li>
<h6>injectExtension方法:向创建的拓展注入其依赖的属性</h6>
<pre><code class="java"> private T injectExtension(T instance) {
try {
if (objectFactory != null) {
//反射获得该类中所有的方法
for (Method method : instance.getClass().getMethods()) {
//如果是set方法
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
//获得属性,比如StubProxyFactoryWrapper类中有Protocol protocol属性,
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
//获得属性值,比如Protocol对象,也可能是Bean对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//注入依赖属性
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}</code></pre>
<p>思路就是是先通过反射获得类中的所有方法,然后找到set方法,找到需要依赖注入的属性,然后把对象注入进去。</p>
</li>
<li>
<h6>getExtensionClass方法:获得扩展名对应的扩展实现类</h6>
<pre><code class="java"> private Class<?> getExtensionClass(String name) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (name == null)
throw new IllegalArgumentException("Extension name == null");
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null)
throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
return clazz;
}</code></pre>
<p>这边就是调用了getExtensionClasses的方法,该方法解释在下面给出。</p>
</li>
<li>
<h6>getExtensionClasses方法:获得扩展实现类数组</h6>
<pre><code class="java"> private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//从配置文件中,加载扩展实现类数组
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}</code></pre>
<p>这里思路就是先从缓存中取,如果缓存为空,则从配置文件中读取扩展实现类,loadExtensionClasses方法解析在下面给出。</p>
</li>
<li>
<p>loadExtensionClasses方法:从配置文件中,加载拓展实现类数组</p>
<pre><code class="java"> private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
//@SPI内的默认值
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
//只允许有一个默认值
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
//从配置文件中加载实现类数组
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}</code></pre>
<p>前一部分逻辑是在把SPI注解中的默认值放到缓存中去,加载实现类数组的逻辑是在后面几行,关键的就是loadDirectory方法(解析在下面给出),并且这里可以看出去找配置文件访问的资源路径顺序。</p>
</li>
<li>
<h6>loadDirectory方法:从一个配置文件中,加载拓展实现类数组</h6>
<pre><code class="java"> private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
//拼接接口全限定名,得到完整的文件名
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
//获取ExtensionLoader类信息
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//遍历文件
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}</code></pre>
<p>这边的思路是先获得完整的文件名,遍历每一个文件,在loadResource方法中去加载每个文件的内容。</p>
</li>
<li>
<h6>loadResource方法:加载文件中的内容</h6>
<pre><code class="java"> private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
String line;
while ((line = reader.readLine()) != null) {
//跳过被#注释的内容
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
//根据"="拆分key跟value
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
//加载扩展类
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}</code></pre>
<p>该类的主要的逻辑就是读取里面的内容,跳过“#”注释的内容,根据配置文件中的key=value的形式去分割,然后去加载value对应的类。</p>
</li>
<li>
<h6>loadClass方法:根据配置文件中的value加载扩展类</h6>
<pre><code class="java"> private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//该类是否实现扩展接口
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
}
//判断该类是否为扩展接口的适配器
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
}
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
//通过反射获得构造器对象
clazz.getConstructor();
//未配置扩展名,自动生成,例如DemoFilter为 demo,主要用于兼容java SPI的配置。
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
// 获得扩展名,可以是数组,有多个拓扩展名。
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
//如果是自动激活的实现类,则加入到缓存
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
//缓存扩展实现类
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}</code></pre>
<p>重点关注该方法中兼容了jdk的SPI思想。因为jdk的SPI相关的配置文件中是xx.yyy.DemoFilter,并没有key,也就是没有扩展名的概念,所有为了兼容,通过xx.yyy.DemoFilter生成的扩展名为demo。</p>
</li>
<li>
<h6>createAdaptiveExtensionClass方法:创建适配器类,类似于dubbo动态生成的Transporter$Adpative这样的类</h6>
<pre><code class="java"> private Class<?> createAdaptiveExtensionClass() {
//创建动态生成的适配器类代码
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//编译代码,返回该类
return compiler.compile(code, classLoader);
}</code></pre>
<p>这个方法中就做了编译代码的逻辑,生成代码在createAdaptiveExtensionClassCode方法中,createAdaptiveExtensionClassCode方法由于过长,我不在这边列出,下面会给出github的网址,读者可自行查看相关的源码解析。createAdaptiveExtensionClassCode生成的代码逻辑可以对照我上述讲的<strong>(二)注解@Adaptive</strong>中的Transporter$Adpative类来看。</p>
</li>
<li>
<h6>部分方法比较浅显易懂,并且没有影响主功能,所有我不在列举,该类的其他方法请在一下网址中查看,这里强调一点,其中的逻辑不难,难的是属性的含义要充分去品读理解,弄清楚各个属性的含义后,再看一些逻辑就很浅显易懂了。如果真的看不懂属性的含义,可以进入到调用的地方,结合“语境”去理解。</h6>
<blockquote>ExtensionLoader类源码解析地址:<a href="https://link.segmentfault.com/?enc=mgRW%2Fml6fVpI0jJU2lhKEg%3D%3D.nF8Hk4bNk1%2BzzoAIjapj8jhxRGgmHZt9rI%2BZf3haPVBf9w0n%2FJYFigeVaPKc6xduPFe3OyA3s2sa9Ch4Yc9TG948UjSN6DCtFbZ16qfJEr%2Bx9TNZk5aKzh6Cqml6BgAkJTPW%2FwuRp2udQOZx9OwqC5BOG%2FCrWHSNKp23eqAl%2FhZUEuzZCJMsVBer8AqmolpjcvX1eTCqZKENGfFwDr9fWg%3D%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
</li>
</ol>
<h5>(六)AdaptiveExtensionFactory</h5>
<p>该类是ExtensionFactory的适配器类,也就是我在<strong>(二)注解@Adaptive</strong>中提到的第一种适配器类的使用。来看看该类的源码:</p>
<pre><code class="java">@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
//扩展对象的集合,默认的可以分为dubbo 的SPI中接口实现类对象或者Spring bean对象
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
//遍历所有支持的扩展名
for (String name : loader.getSupportedExtensions()) {
//扩展对象加入到集合中
list.add(loader.getExtension(name));
}
//返回一个不可修改的集合
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
//通过扩展接口和扩展名获得扩展对象
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}</code></pre>
<ol>
<li>factories是扩展对象的集合,当用户没有自己实现ExtensionFactory接口,则这个属性就只会有两种对象,分别是 SpiExtensionFactory 和 SpringExtensionFactory 。</li>
<li>构造器中是把所有支持的扩展名的扩展对象加入到集合</li>
<li>实现了接口的getExtension方法,通过接口和扩展名来获取扩展对象。</li>
</ol>
<h5>(七)SpiExtensionFactory</h5>
<p>SPI ExtensionFactory 拓展实现类,看看源码:</p>
<pre><code class="java">public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
//判断是否为接口,接口上是否有@SPI注解
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
//获得扩展加载器
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
//返回适配器类的对象
return loader.getAdaptiveExtension();
}
}
return null;
}
}</code></pre>
<h5>(八)ActivateComparator</h5>
<p>该类在ExtensionLoader类的getActivateExtension方法中被运用到,作为自动激活拓展对象的排序器。</p>
<pre><code class="java">public class ActivateComparator implements Comparator<Object> {
public static final Comparator<Object> COMPARATOR = new ActivateComparator();
@Override
public int compare(Object o1, Object o2) {
//基本排序
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
if (o1.equals(o2)) {
return 0;
}
Activate a1 = o1.getClass().getAnnotation(Activate.class);
Activate a2 = o2.getClass().getAnnotation(Activate.class);
//使用Activate注解的 `after` 和 `before` 属性,排序
if ((a1.before().length > 0 || a1.after().length > 0
|| a2.before().length > 0 || a2.after().length > 0)
&& o1.getClass().getInterfaces().length > 0
&& o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) {
ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
if (a1.before().length > 0 || a1.after().length > 0) {
String n2 = extensionLoader.getExtensionName(o2.getClass());
for (String before : a1.before()) {
if (before.equals(n2)) {
return -1;
}
}
for (String after : a1.after()) {
if (after.equals(n2)) {
return 1;
}
}
}
if (a2.before().length > 0 || a2.after().length > 0) {
String n1 = extensionLoader.getExtensionName(o1.getClass());
for (String before : a2.before()) {
if (before.equals(n1)) {
return 1;
}
}
for (String after : a2.after()) {
if (after.equals(n1)) {
return -1;
}
}
}
}
// 使用Activate注解的 `order` 属性,排序。
int n1 = a1 == null ? 0 : a1.order();
int n2 = a2 == null ? 0 : a2.order();
// never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
return n1 > n2 ? 1 : -1;
}
}
</code></pre>
<p>关键的还是通过@Activate注解中的值来进行排序。</p>
<h4>后记</h4>
<blockquote>该部分相关的源码解析地址:<a href="https://link.segmentfault.com/?enc=F78v%2BwpnmrFVCCvPPmRjHg%3D%3D.O%2FhVpc04xD5lVCxsB2ya3IzKY3Br8%2BRQwW44GfsUzLfYICNUVrT%2B4SSuVShXW7kAgRg%2FO0qdf6DKMz0CRqKG0sSzramxGSPTmUf274%2FJCavjMzW7oDMkTTRo09B69d410TUdAAoXzgcopo7cuhzT4Y3C0kvSYHcym%2FhXZoWGxkc%3D" rel="nofollow">https://github.com/CrazyHZM/i...</a>
</blockquote>
<p>该文章讲解了dubbo的SPI扩展机制的实现原理,最关键的是弄清楚dubbo跟jdk在实现SPI的思想上做了哪些改进和优化,解读dubbo SPI扩展机制最关键的是弄清楚@SPI、@Adaptive、@Activate三个注解的含义,大部分逻辑都被封装在ExtensionLoader类中。dubbo的很多接口都是扩展接口,解读该文,也能让读者在后续文章中更加容易的去了解dubbo的架构设计。如果我在哪一部分写的不够到位或者写错了,欢迎给我提意见,我的私人微信号码:HUA799695226。</p>