dubbo服务引用
ReferenceConfig的创建初始化之路
构造函数 发现并没有啥特别的
public ReferenceConfig() {
super();
this.repository = ApplicationModel.getServiceRepository();
}
但是发现这个实例初始化的时候需要构造这些静态属性对象
- 协议
- 集群(这个没用上不知道为啥不去掉)
- 代理工厂 构造代理类使用
来 我们继续跟踪debug
可以看到这一步 我们是要开始创建代理实例了。
来我们继续分析他是如何创建代理类的
private T createProxy(Map<String, String> map) {
if (shouldJvmRefer(map)) {
.....
} else {
.....
if (urls.size() == 1) {
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
.....
}
}
.....
// create service proxy
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}
可以看到他调用了协议的引用方法获取到了一个invoker,然后再生成invoker的代理。
Invoker是dubbo 非常重要的实体,他是dubbo非常核心的模型,其他模型可以向他靠拢,或者转换成它,它代表一个可执行体,可以发起Invoke调用 他有集群Invoker (ClusterInvoker) 本地Invoker,远程 Invoker
Protocol也是Dubbo是非常重要的实体 它负责服务的暴露和引用,它管理着 **Invoker的生命周期 **
REF_PROTOCOL这个是AdaptiveExtension,他可以根据传参动态选择Protocol实现 那么他目前是哪一个实现呢?或者说他的源头Protocol实现是哪一个呢?来我们继续debug 看看他到底是啥东西?
我发现 dubbo spi加载的时候去获取所有Protocol的实现 有非常多,那到底是哪个呢?
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
webservice=org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol
registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol
service-discovery-registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
首先我们来看 urls.get(0) 这个拿到的url是啥?
可以看到这个url是注册中心的地址
所以当dubbo spi加载的时候应该加载的是 这个
registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol
因为这个url是注册协议 ,所以会带上 getExtend(“registry”),可以通过 org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generateExtNameAssignment 这个来发现
getNameCode = String.format(“( url.getProtocol() == null ? “%s” : url.getProtocol() )”, defaultExtName);
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
// TODO: refactor it
String getNameCode = null;
for (int i = value.length - 1; i >= 0; --i) {
if (i == value.length - 1) {
if (null != defaultExtName) {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
}
} else {
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
}
} else {
getNameCode = "url.getProtocol()";
}
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
}
} else {
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
}
return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}
通过debug看 果然是这样子的
而且到这一步的时候我发现 dubbo spi 再包装一下 他的包装实现
那他怎么判断是否是 包装实现呢?
- 是否有包含这个类型的的构造函数
例如 QosProtocolWrapper是 Protocol的实现,且他的构造函数包含Protocol
public QosProtocolWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
2.@Wrapper 注解修饰
根据顺序 可以看到 最终这个Protocol实例是 QosProtocolWrapper,所以他是我们所有Protocol的调用入口。
来我们继续debug org.apache.dubbo.registry.integration.RegistryProtocol#refer
可以发现 这家伙又包装了一个 MigrationInvoker 无限套娃啊
这个MigrationInvoker 是一个迁移Invoker是负责根据迁移规则,迁移服务使用的。
protected <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url, Map<String, String> parameters) {
URL consumerUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
ClusterInvoker<T> migrationInvoker = getMigrationInvoker(this, cluster, registry, type, url, consumerUrl);
return interceptInvoker(migrationInvoker, url, consumerUrl);
}
我们可以看下他的Invoker实现
最终这个 还是调用了 org.apache.dubbo.registry.integration.RegistryProtocol#getInvoker 来获取Invoker来赋值给 currentAvailableInvoker
来我们继续debug org.apache.dubbo.registry.integration.RegistryProtocol#getInvoker
可以看到是 Cluster 构建了一个 ClusterInvoker。并且 Cluster最顶层是一个包装Cluster(MockClusterInvoker),最基础的还是默认的FailoverCluster,并且 FailoverCluster会构建一个 FailoverClusterInvoker,到此我们发现这个Invoker就到底了。
这个时候我们再理一理 这个完整的Protocol refer链路
再理一理 Invoker的链路
所以ReferenceConfig 最终拿到的这个MigrationInvoker,再进行代理的。我们可以debug来验证一下。可以看到非常正确啊。不容易啊
dubbo服务调用invoker
聪明的朋友们是不是发现了什么 感觉不对啊 我们dubbo协议的最终的协议 Invoker呢?是不是少了点什么?
我们看下他的实现
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// binding attachments into invocation.
Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
}
//获取所有可用的invoker列表
List<Invoker<T>> invokers = list(invocation);
//通过负载均衡来获取到最终的一个Invoker最终调用invoke 进行rpc调用
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
那如何进行获取所有可用的Invoker列表呢?
那当然是根据注册中心啊,去注册中心获取可用的服务提供者列表。
来来来我们继续跟踪一下 debug FailoverClusterInvoker
可以看到最终经过了Directory=>到达了 RouterChain ,RouterChain他有一个Invokers,那他这个Invokers是从哪里来的呢?我发现他下面有个 org.apache.dubbo.rpc.cluster.RouterChain#setInvokers,通过这个来设置的
并且我们可以看到 这个 Invokers里面的Invoker 可以看到 我们心心念念的DubboInvoker,他也是包装了很多层的Invoker,
来来来我们在org.apache.dubbo.rpc.cluster.RouterChain#setInvokers 这里打个断点看看 他是如何设置值的。
可以看到有几个重要节点
1.创建ClusterInvoker时候,通过订阅注册中心,然后注册中心通知我们有哪些可用列表。
protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
URL urlToRegistry = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
if (directory.isShouldRegister()) {
directory.setRegisteredConsumerUrl(urlToRegistry);
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(urlToRegistry);
//服务订阅
directory.subscribe(toSubscribeUrl(urlToRegistry));
return (ClusterInvoker<T>) cluster.join(directory);
}
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe 来们来看看这个方法
@Override
public void doSubscribe(final URL url, final NotifyListener listener) {
try {
if (ANY_VALUE.equals(url.getServiceInterface())) {
.......
} else {
CountDownLatch latch = new CountDownLatch(1);
List<URL> urls = new ArrayList<>();
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
//创建zk监听这个监听实现最终也会调用 notify(url, listener, urls);
ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, k, latch));
if (zkListener instanceof RegistryChildListenerImpl) {
((RegistryChildListenerImpl) zkListener).setLatch(latch);
}
zkClient.create(path, false);
List<String> children = zkClient.addChildListener(path, zkListener);
//发现子节点不为空 那么说明已有可用服务
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
notify(url, listener, urls);
// tells the listener to run only after the sync notification of main thread finishes.
latch.countDown();
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
两个重要信息
1.创建监听,如果有服务更新 就通知服务刷新
2.如果常见监听的时候发现已有可用列表那么就直接通知 可用服务列表
来来来 我们继续追踪我们的invoker
根据我们上面的debug截图 我们跳转 debug 到了 org.apache.dubbo.registry.integration.RegistryDirectory#refreshInvoker
private void refreshInvoker(List<URL> invokerUrls) {
Assert.notNull(invokerUrls, "invokerUrls should not be null");
if (invokerUrls.size() == 1
&& invokerUrls.get(0) != null
&& EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
........
} else {
......
//将注册中心获取到的可用列表转成Invoker
Map<URL, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
.......
List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
// pre-route and build cache, notice that route cache should build on original Invoker list.
// toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
//将转成的Invoker 写入到 routeChain
routerChain.setInvokers(newInvokers);
......
}
.......................
}
至此我们完全看到了 routeChain setInvokers的链路。来我们继续看下 url 可用列表转成Invoker的实现
private Map<URL, Invoker<T>> toInvokers(List<URL> urls) {
Map<URL, Invoker<T>> newUrlInvokerMap = new ConcurrentHashMap<>();
if (CollectionUtils.isEmpty(urls)) {
return newUrlInvokerMap;
}
Set<URL> keys = new HashSet<>();
String queryProtocols = this.queryMap.get(PROTOCOL_KEY);
for (URL providerUrl : urls) {
..........
URL url = mergeUrl(providerUrl);
if (keys.contains(url)) { // Repeated url
continue;
}
keys.add(url);
// 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
Map<URL, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(url);
if (invoker == null) { // Not in the cache, refer again
try {
boolean enabled = true;
if (url.hasParameter(DISABLED_KEY)) {
enabled = !url.getParameter(DISABLED_KEY, false);
} else {
enabled = url.getParameter(ENABLED_KEY, true);
}
if (enabled) {
//可以看到这行 就会发现 这个这个又引用了一次 这个应该是引用DubboInvoker了吧 我们debug验证一下
invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
}
if (invoker != null) { // Put new invoker in cache
newUrlInvokerMap.put(url, invoker);
}
} else {
newUrlInvokerMap.put(url, invoker);
}
}
keys.clear();
return newUrlInvokerMap;
}
可以看到这个是dubbo协议,同样的他也会经过 那三个ProtocolWrapper
QosProtocolWrapper,ProtocolFilterWrapper,ProtocolListenerWrapper
并且ProtocolFilterWrapper 会为Invoker构建一个FilterChain,ProtocolListenerWrapper 会 为Invoker包装一个Listener
最终会调用到DubboProtocol,通过调用DubboProtocol.refer
可以看到 DubboProtocol没有refer实现,所以他是调用了父类的refer,可以看到 AsyncToSyncInvoker 这个异步同步调用Invoker ,并且 DubboProtocol实现了 protocolBindingRefer 来返回 DubboInvoker,至此我们终于梳理出来的链路。
@Override
public <T> Invoker<T> protocolBindingRefer(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;
}
所以最终形成一个这样的Invoker链 ,来我们来总结一下。
总结一下
简单总结一下 Invoker和Protocol是非常重要的Dubbo模型,他们计划贯穿了dubbo非常核心的服务暴露和引用。Protocol是Dubbo是非常重要的实体 它负责服务的暴露和引用,它管理着 Invoker的生命周期,Invoker是dubbo 非常重要的实体,他是dubbo非常核心的模型,其他模型可以向他靠拢,或者转换成它,它代表一个可执行体,可以发起Invoke调用 他有集群Invoker (ClusterInvoker) 本地Invoker,远程 Invoker
再附上两个的核心图
Protocol链路
Invoker链路