Dubbo实践(十二)Refer
Spring在启动Dubbo客户端应用时,会实例化ReferenceBean<T>并设置配置属性,然后调用ReferenceConfig中的get方法:
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
init();
}
return ref;
}
private void init() {
if (initialized) {
return;
}
initialized = true;
// 省略...
ref = createProxy(map); // 这里使用了动态代理生成对象
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods()); // ref保存到consumerModel
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel); // consumerModel保存到concurrentHashMap,内存中
}
ref = createProxy(map); 这里使用了动态代理生成了代理对象(这里也可以成为远程代理,因为在这个代理中进行了远程调用),ref 即getBean返回的对象,这样在本地调用业务Service接口时就会使用代理处理器com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler。
@SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
private T createProxy(Map<String, String> map) {
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {
if (url != null && url.length() > 0) { // if a url is specified, don't do local reference
// 指定URL的情况下,不进行本地引用
isJvmRefer = false;
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
// by default, reference local service if there is
// 默认情况下如果本地有服务暴露,则引用本地服务
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer = isInjvm().booleanValue();
}
if (isJvmRefer) {
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
// 用户指定URL,指定的URL可能是点对点直连地址,也可能是注册中心URL
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(interfaceName);
}
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // assemble URL from register center's configuration
// 通过注册中心配置拼装URL
List<URL> us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls == null || 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.");
}
}
if (urls.size() == 1) {
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
// 用了最后一个registry url
registryURL = url; // use last registry url
}
}
// 有 注册中心协议的URL
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
// 对有注册中心的Cluster 只用 AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
// 不是 注册中心的URL
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = check;
if (c == null && consumer != null) {
c = consumer.isCheck();
}
if (c == null) {
c = true; // default true
}
if (c && !invoker.isAvailable()) {
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());
}
// create service proxy
// 创建服务代理
return (T) proxyFactory.getProxy(invoker);
}
生成Invoker
这里调用 invoker = refprotocol.refer(interfaceClass, urls.get(0)); 来生成invoker,代码如下:
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
目前客户端invoke后,调用DubboInvoker的doInvoke函数,doInvoke函数中,通过currentClient.send或者currentClient.request发送数据。
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
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) {
// 异步方式发送请求,将响应存储在future中
ResponseFuture future = currentClient.request(inv, timeout);
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);
}
}
调用NettyClient的send方法:
public void send(Object message, boolean sent) throws RemotingException {
if (send_reconnect && !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);
}
这里实际上是利用netty的网络通信机制进行通信,而netty的机制保证数据接收处理:
@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", getTimeout());
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
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;
}
});
}
此时若收到数据,则会调用NettyHandler的messageReceived函数:
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
try {
handler.received(channel, e.getMessage());
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
}
}
此时,会发现,最后是调用DubboProtocol中requestHandler的received函数。
Handler的调用链是职责链模式和装饰器模式的混合模式(类似Tomcat的Filter),外层的Handler调用内层的Handler,并在调用前后加上一些逻辑; 最后,在received函数中:
@Override
public void received(Channel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
reply((ExchangeChannel) channel, message);
} else {
super.received(channel, message);
}
}
这里会发现super.received(channel, message)是空函数,因此若是客户端处理到这里,就不做任何处理了; 如果是服务器端,调用的是reply函数:
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
Invoker<?> invoker = getInvoker(channel, inv);
// need to consider backward-compatibility if it's a callback
// 如果是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 {
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());
}
此时getInvoker实际上是从exporter中去获取Invoker。这里通过if (message instanceof Invocation)可知,如果不是调用,则此时不做其他处理,也就是客户端调用在这里是不做任何处理的。 因此我们返回到客户端的调用中,会发现HeaderExchangeClient的如下函数处理:
public ResponseFuture request(Object request) throws RemotingException {
return channel.request(request);
}
此时,转到HeaderExchangeChannel:
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("2.0.0");
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try {
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
实际上这里返回的是DefaultFuture。而从doInvoke函数中, return (Result) currentClient.request(inv, timeout).get(); 返回的是一个Result的子对象。而我们返回到客户端的调用InvokerInvocationHandler:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
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]);
}
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
此时会发现最后结果是从Result的recreate()函数返回而来,实际上这是一个RpcResult。此时我们可以猜测服务器端返回给客户端的是一个Response对象,同时其中的mResult(private Object mResult)是一个实现了Result接口的对象,我搜索了一下代码,目前只有RpcResult对象实现了Result接口,因此可以肯定的是服务器端返回给客户端的是一个RpcResult对象。
Invoker屏蔽了通信相关的细节,此时需要注意的是默认使用的网络实现是Netty。
Dubbo实践(十二)Refer的更多相关文章
- Dubbo实践(二)架构
架构 节点角色说明 节点 角色说明 Provider 暴露服务的服务提供方 Consumer 调用远程服务的服务消费方 Registry 服务注册与发现的注册中心 Monitor 统计服务的调用次数和 ...
- 【机器学习PAI实践十二】机器学习算法基于信用卡消费记录做信用评分
背景 如果你是做互联网金融的,那么一定听说过评分卡.评分卡是信用风险评估领域常用的建模方法,评分卡并不简单对应于某一种机器学习算法,而是一种通用的建模框架,将原始数据通过分箱后进行特征工程变换,继而应 ...
- java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)
java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...
- 十二.HTTPS网站安全访问实践
期中集群架构-第十二章-HTTPS安全证书访问连接实践配置========================================= 01:网络安全涉及的问题: ①. 网络安全问题-数据机密性 ...
- 20155320 2016-2017-2《Java程序设计》第十二周课堂实践项目
20155320 2016-2017-2<Java程序设计>第十二周课堂实践项目 1.修改教材P98 Score2.java, 让执行结果数组填充是自己的学号: 2.在IDEA中以TDD的 ...
- 软件设计师【软件工程:软件开发模型、XP极限编程十二最佳实践】
一.软件开发模型 二.XP极限编程十二最佳实践
- CG基础教程-陈惟老师十二讲笔记
转自 麽洋TinyOcean:http://www.douban.com/people/Tinyocean/notes?start=50&type=note 因为看了陈惟十二讲视频没有课件,边 ...
- java jvm学习笔记十二(访问控制器的栈校验机制)
欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 本节源码:http://download.csdn.net/detail/yfqnihao/4863854 这一节,我们 ...
- 【阿里聚安全·安全周刊】阿里双11技术十二讲直播预约|AWS S3配置错误曝光NSA陆军机密文件
关键词:阿里双11技术十二讲直播丨雪人计划丨亚马逊AWS S3配置错误丨2018威胁预测丨MacOS漏洞丨智能风控平台MTEE3丨黑客窃取<权利的游戏>剧本|Android 8.1 本 ...
- Heroku创始人Adam Wiggins发布十二要素应用宣言
Heroku是业内知名的云应用平台,从对外提供服务以来,他们已经有上百万应用的托管和运营经验.前不久,创始人Adam Wiggins根据这些经验,发布了一个“十二要素应用宣言(The Twelve-F ...
随机推荐
- 高性能分布式锁-redisson的使用
1,概述:在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数量可能被减少到负数,出现超卖的情况,或者 产生唯一的一个递增ID,由于web应用部署在多 ...
- spss C# 二次开发 学习笔记(六)——Spss统计结果的输出
Spss的二次开发可以很简单,实例化一个对象,然后启用服务,接着提交命令,最后停止服务. 其中重点为提交命令,针对各种统计功能需求,以及被统计分析的数据内容等,命令的内容可以很复杂,但也可以简单的为一 ...
- LeetCode GrayCode
class Solution { public: vector<int> grayCode(int n) { vector<int> res; res.push_back(); ...
- 关于this的全面解析(call,apply,new)
我们在写代码的时候,时常会被this弄的傻傻分不清楚,看源码的时候也经常被call啊apply啊弄的头皮发麻.this到底是什么?本文主要根据书上和实际应用做了一些归纳.一般情况下this有4种绑定规 ...
- CSS属性之relative
0.相对定位relative特点 相对定位relative元素总是会占据位置,所占据的位置是在relative元素没有设置left/top/right/bottom属性时的位置: 相对定位relati ...
- js函数的节流与防抖
一.防抖&节流 在前端开发中有一部分用户行为会频繁的触发事件执行,而对于DOM的操作.资源加载等耗费性能的处理,很可能会导致界面卡顿,甚至浏览器奔溃.函数的节流与防抖就是为了解决类似需求而产生 ...
- Nodejs + express post get 参数获取小结
req.params.xxxxx 从path中的变量 req.query.xxxxx 从get中的?xxxx=中 req.body.xxxxx 从post中的变量 Post下别忘了: app.use( ...
- RHEL5.X 重启网卡出现./network-functions: line 78: .: ifcfg-eth0: file not found
错误信息: 红帽RHEL5.5系统,重启网卡报错 [root@localhost network-scripts]# service network restart Shutting down int ...
- WinForm实现Rabbitmq官网6个案例-Routing
代码: namespace RabbitMQDemo { public partial class Routing : Form { private string exchangeName = &qu ...
- FineReport移动端如何获取地址位置
对于企业大多数员工来说,由于其工作位置是固定的,可以有多种方式进行上班打卡签到以保证该员工有按时正常来上班,但是对于经常需要出差,去客户现场的员工来说,就无法保证他们是否有去上班,所以希望能通过手机位 ...