文章目录
1.概述
上一篇博客中介绍了dubbo服务暴露的原理,还不清楚的读者可以先看上一篇博客。博客地址如下:
本文将探究服务消费者如何引用服务,分析dubbo中服务引用的相关源码。同样的,为了聚焦在服务引用的过程,编写如下的测试代码,把关注点放在服务引用的过程上。
代码如下:
// 客户端
@Test
public void invokeRemote() {
ReferenceConfig<UserService> referenceConfig = new ReferenceConfig();
// 应用名称
ApplicationConfig app = new ApplicationConfig("client");
referenceConfig.setApplication(app);
RegistryConfig registryConfig = new RegistryConfig("zookeeper://127.0.0.1:2181");
referenceConfig.setRegistry(registryConfig);
// URL地址
referenceConfig.setUrl("dubbo://127.0.0.1:8080/coderead.dubbo.test.dubbotest.UserService");
// 指定接口
referenceConfig.setInterface(UserService.class);
// 获取接口服务 (动态代理)
UserService userService = referenceConfig.get();
System.out.println(userService.getUser(1111));
}
上面的代码模拟了一个服务消费者获得服务引用接口并调用。
使用上一篇博客中的代码,开启一个服务。
客户端的测试代码,首先构造一个服务引用配置对象,设置一些必要的服务引用需要的配置信息,例如配置注册中心,设置服务接口信息等。
然后通过referenceConfig.get() 得到服务引用,也就是userService接口的代理对象。
最后通过服务引用代理对象调用getUser方法进行远程调用。
2.服务引用源码分析
ReferenceConfig.get()
ref 是服务引用配置对象的成员属性,代表这一个服务接口代理引用。
如果为空,那么会执行init()方法构建一个ref,然后返回。
ReferenceConfig.init()
该方法代码很长,忽略掉和本文主题内容无关的部分,主要是会调用createProxy创建服务接口代理引用。
之前的部分是设置一些必要的参数,map参数内容如下
ReferenceConfig.createProxy(Map<String, String> map)
分两种情况,如果服务的提供者就在同一个jvm,那么直接使用本地服务引用
否则走远程服务引用的逻辑。
远程服务引用,首先更新urls。
根据urls的大小,如果只有1个的话,那么invoker就是调用Protocol.refer得到的结果。
如果是多个的话,将分别的调用Protocol.refer得到多个invoker。将多个invoker合并成一个cluster invoker。
重点应该放在如何得到invoker,也就是Protocol.refer做了什么
QosProtocolWrapper.refer(Class< T > type, URL url)
首先来到QosProtocolWrapper类,从这里开始会出现一系列的装饰者+责任链模式的代码。
QosProtocolWrapper,ProtocolFilterWrapper,ProtocolListenerWrapper,DubboProtocol这4个类都实现了Protocol接口,内部有属性Protocol,比如:
QosProtocolWrapper.refer的代码如下:
如果当前url是个注册类型的url的话,那么会开启Qos服务,然后以责任链的方式,调用下一个Protocol的refer方法。Qos服务不是本文的重点,暂时掠过。
ProtocolFilterWrapper.refer(Class< T > type, URL url)
同样也是判断是否是注册类型的url,如果是的话,那么调用下一个Protocol的refer方法
否则调用完Protocol的refer方法之后,还需要将invokr封装成一个invoker链条,添加上过滤器的功能。
ProtocolListenerWrapper.refer(Class< T > type, URL url)
和上面的类似,将invoker封装成ListenerInvokerWrapper
AbstractProtocol.refer(Class< T > type, URL url)
基于协议构建服务引用,封装成AsyncToSyncInvoker对象。
这里使用的是Dubbo协议,所以会调用DubboProtocol子类实现的protocolBindingRefer方法
DubboProtocol.protocolBindingRefer(Class< T > serviceType, URL url)
创建一个rpc invoker,重点在于getClients方法,得到的clients在封装成DubboInvokder。
然后添加早invokers集合当中,返回DubboInvokder。
getClients方法如下:
从url中得到参数,连接数。
如果没有配置连接数参数为0,默认使用的是共享连接,否则的话,每次都使用新的rpc连接。
共享连接通过getSharedClient获得,否则使用initClient初始化一个客户端。下面就来看看这两个方法具体的差别。
getSharedClient
从url当中获取ip地址+端口号。
基于ip地址+端口号 从referenceClientMap当中寻找,是否有缓存的客户端服务引用。
有的话,那么检查这些服务引用是否可用。如果可用的话,修改引用数量,然后返回即可。
如果不可用,或者缓存当中没有,那么就会继续下面的代码。
并发处理,双重检查,为了避免构建重复的引用。
如果缓存当中没有,那么基于连接数量构建服务引用。也就是走这一段代码
如果不为空的话,那么说明是部分客户端服务引用关闭了,那么就需要重新的构建服务引用。对应下面的代码
两种情况都差不多,都会调用buildReferenceCountExchangeClient(URL url)
DubboProtocol.buildReferenceCountExchangeClient(Url url)
这里只做了一层额外的封装,下面看看初始化客户端的逻辑。
DubboProtocol.initClient(URL url)
调用Exchangers.connect,下面又是一连串的connect责任链调用。
HeaderExchanger.connect(URL url, ExchangeHandler handler)
参数校验
HeaderExchanger.connect(URL url, ExchangeHandler handler)
构建DecodeHandler,解码处理器
Transporters.connect(URL url, ChannelHandler… handlers)
NettyTransporter.connect(URL url, ChannelHandler handler)
构建Netty客户端
NettyClient构造函数
调用其父类的构造函数
doOpen()开启netty客户端
connect()使用netty客户端连接远程服务
以上就是服务引用的全过程。