星辰计划5-深入解析源码(逐行debug)dubbo服务引用,调用

dubbo服务引用

ReferenceConfig的创建初始化之路

构造函数 发现并没有啥特别的

public ReferenceConfig() {
    super();
    this.repository = ApplicationModel.getServiceRepository();
}

但是发现这个实例初始化的时候需要构造这些静态属性对象

  1. 协议
  2. 集群(这个没用上不知道为啥不去掉)
  3. 代理工厂 构造代理类使用

来 我们继续跟踪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 再包装一下 他的包装实现

那他怎么判断是否是 包装实现呢?

  1. 是否有包含这个类型的的构造函数

例如 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链路

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值