Spring Cloud Alibaba 教程 | Dubbo(七):服务引用

服务引用流程图

下图是Dubbo框架服务引用的整体流程图:
在这里插入图片描述
Dubbo框架通过<dubbo:reference>标签开启一个服务引用,例如:<dubbo:reference id="userService" interface="com.luke.dubbo.api.service.UserService" />,服务引用的目的是为引用服务生成一个远程代理对象,这样可以像使用一个本地Bean一样去使用远程服务接口,对用户屏蔽掉远程调用和处理过程。

服务引用的整体流程可以分为两步骤,第一步通过RegistryProtocol将引用的服务接口Class生成Invoker,如果注册中心是多实例,那么将生成多个Invoker(一个注册中心生成一个Invoker),接着通过Cluster转换成一个Invoker;第二步是通过ProxyFactory(代理工厂)将Invoker转换成最终的代理对象(InvokerInvocationHandler)。

源码解析服务引用流程

Dubbo框架中一个服务引用就会对应有一个ReferenceBean,该类继承了ReferenceConfig并且实现了FactoryBean接口,所以ReferenceBean将作为Dubbo服务接口的工厂Bean(可以生成代理对象),即通过利用了Spring的FactoryBean机制,当我们代码引用到服务接口时,Spring框架会回调ReferenceBean的getObject()方法。

Spring的FactoryBean机制:https://www.jianshu.com/p/f4dca40c55c1

紧接着代码会依次执行ReferenceConfig类三个方法get()-->init()-->createProxy(map)

private T createProxy(Map<String, String> map) {
    //......省略部分代理
    
    //加载注册中心地址
    List<URL> us = loadRegistries(false);//@1
    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()));//@2
            }
            urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
        }
    }

    if (urls.size() == 1) {//只有一个注册中心实例
        invoker = refprotocol.refer(interfaceClass, urls.get(0));//@3
    } else {//多个注册中心实例
        List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
        URL registryURL = null;
        for (URL url : urls) {
            //添加多个Invoker
            invokers.add(refprotocol.refer(interfaceClass, url));//@4
            if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                registryURL = url; //使用最后一个注册中心URL
            }
        }
        if (registryURL != null) { // registry url is available
            // use AvailableCluster only when register's cluster is available
            URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
            //通过Cluster将多个Invoker转换成一个Invoker
            invoker = cluster.join(new StaticDirectory(u, invokers));//@5
        }
    }

    //创建服务代理对象
    return (T) proxyFactory.getProxy(invoker);//@6
}

@1:加载出所有注册中心的地址。
@2:如果配置有监控中心,调用过程将上传到监控中心。
@3:执行protocol.refer()方法为服务接口生成Invoker对象。
@4:在多注册中心情况下,将多Invoker存储到invokers集合。
@5:在多注册中心情况下,通过通过Cluster将多个Invoker转换成一个Invoker。
@6:代理工厂ProxyFactory利用Invoker创建服务代理对象。

创建Invoker实例

接下来我们解析refprotocol.refer(Class<T> type, URL url),通过该方法可以获取到Invoker实例,refprotocol是一个动态代理对象Protocol$Adaptive,目标对象是RegistryProtocol。

之前我们说过Protocol有包装扩展类,所以执行顺序是:ProtocolFilterWrapper.refer()-->QosProtocolWarpper.refer()-->ProtocolListenerWrappeer.refer()-->RegistryProtocol.refer()
其中QosProtocolWarpper.refer()负责启动QoS服务,QoS是Dubbo的在线运维命令,可以对服务进行动态的配置、控制及查询。

@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
        //启动QoS服务
        startQosServer(url);
        return protocol.refer(type, url);
    }
    return protocol.refer(type, url);
}

进入到RegistryProtocol.refer()方法:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
	//从url参数里面获取registry参数值,更新protocol属性
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, 
       Constants.DEFAULT_REGISTRY)).
       removeParameter(Constants.REGISTRY_KEY);//@1
    //通过注册工厂获取注册中心实例(连接zookeeper)
    Registry registry = registryFactory.getRegistry(url);//@2
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // group="a,b" or group="*"
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    String group = qs.get(Constants.GROUP_KEY);
    if (group != null && group.length() > 0) {
        if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
                || "*".equals(group)) {
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    //执行doRefer方法
    return doRefer(cluster, registry, type, url);
}
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
	//创建RegistryDirectory实例
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);//@3
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    // all attributes of REFER_KEY
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
    if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) { 
            //执行注册操作(在consumer节点下添加ULR临时节点)
            registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, 
            Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));//@4
    }
    //执行订阅操作(订阅providers、configurators、routers三个节点)
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));//@5
      
    Invoker invoker = cluster.join(directory);//@6
    ProviderConsumerRegTable.registerConsumer(invoker, 
    url, subscribeUrl, directory);//@7
    return invoker;
}

@1:从url参数里面获取registry参数值,更新protocol属性,值为zookeeper。
在这里插入图片描述
@2:通过注册工厂获取注册中心实例ZookeeperRegistry,实例化过程将发起连接Zookeeper注册中心。
@3:创建RegistryDirectory实例。RegistryDirectory实现了NotifyListener,负责管理本地缓存服务提供者、配置、路由规则的变化。
@4:执行注册操作,最终会执行ZookeeperRegistry.doRegister()方法,创建URL临时节点。

@Override
protected void doRegister(URL url) {
  //在consumers节点下添加ULR临时节点。
  zkClient.create(toUrlPath(url), 
    url.getParameter(Constants.DYNAMIC_KEY, true));
}

@5:执行订阅操作,订阅providers、configurators、routers三个节点信息。代码最终会执行到ZookeeperRegistry.doSubscribe(),这部分内容在前面讲解注册中心的文章已经讲解过。方法最后面会执行notify(url, listener, urls)方法:
在这里插入图片描述
因为订阅了三个节点目录,所以urls有三个元素URL值,并且只有节点是providers时URL将以dubbo协议开头,否则以empty开头。
在这里插入图片描述
notify()方法的作用主要有:

  • 本地数据缓存:执行saveProperties(url),将节点信息缓存到本地的Properties对象,同时存储一份数据到本地磁盘目录。
  • 更新本地提供者服务Invoker信息:执行listener.notify(categoryList),更新本地目录信息,内容通过执行RegistryDirectory.refreshInvoker(List invokerUrls)更新服务提供者Invoker信息。
    那么这里的Invoker是如何创建的呢?在refreshInvoker()方法里面执行Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls); 创建Invoker,Invoker的创建交给DubboProtocol:
@Override
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;
}

@6:根据Directory,利用集群策略返回集群Invoker,默认集群策略为默认为failover。
@7:更新ProviderConsumerRegTable消费者注册表信息consumerInvokers,consumerInvokers存储了消费者服务URL和对应的Invoker信息。

处理多注册中心情况

如果是多注册中心,需要将多个Invoker转换成一个Invoker,即需要执行invoker = cluster.join(new StaticDirectory(u, invokers));
首先通过最后一个注册中心地址和invokers实例化StaticDirectory,接着调用FailoverCluster.join(Directory directory)方法,返回FailoverClusterInvoker实例。

public class FailoverCluster implements Cluster {

    public final static String NAME = "failover";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<T>(directory);
    }

}

获取代理对象

获取到Invoker对象之后,执行proxyFactory.getProxy(invoker)通过代理工厂ProxyFactory,获取到代理对象。
默认使用的代理工厂是JavassistProxyFactory,通过JavassistProxyFactory将Invoker对象转换成代理对象InvokerInvocationHandler。

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
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();
        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();
    }

}

关注公众号了解更多原创博文

Alt

发布了122 篇原创文章 · 获赞 127 · 访问量 93万+

猜你喜欢

转载自blog.csdn.net/u010739551/article/details/104345709