zoukankan      html  css  js  c++  java
  • Dubbo——服务引用

    引言

    上一篇我们分析了服务发布的原理,可以看到默认是创建了一个Netty server,并通过Invoker调用服务,同样,在客户端也会创建一个Inovker对象,下面就一起来看看这个引用创建过程。

    正文

    服务订阅

    服务端的dubbo:service配置对应的类为ServiceBean,同样的,dubbo:reference对应有一个ReferenceBean,该类中的getObject方法,就是获取客户端代理类以及订阅服务的开端(默认情况下,Dubbo使用懒加载方式,在ReferenceBean对应的服务被引用或注入到其它类的时候调用getObject方法;否则,在bean初始化完成后就会调用afterPropertiesSet方法,而该方法也会调用getObject方法),所以我们就从这个方法开始。

    public Object getObject() throws Exception {
        return get();
    }
    
    public synchronized T get() {
        if (destroyed){
            throw new IllegalStateException("Already destroyed!");
        }
    	if (ref == null) {
    		init();
    	}
    	return ref;
    }
    

    这两个方法不用多说,判断接口代理类是否已经存在,不存在调用init方法初始化,而该方法中大部分是检查配置,关键点是createProxy方法:

    private T createProxy(Map<String, String> map) {
    	.......
    	// 本地JVM调用
    	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
            if (url != null && url.length() > 0) { 
                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);
                        }
                        // 如果是registry协议,说明是想连接注册中心,就设置refer参数到url
                        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 { 
            	// 加载注册中心url,并将zookeeper协议转为registry协议
            	List<URL> us = loadRegistries(false);
            	if (us != null && us.size() > 0) {
                	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.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())) {
                        registryURL = url; // 用了最后一个registry url
                    }
                }
                if (registryURL != null) { // 有 注册中心协议的URL
                    // 对有注册中心的Cluster 只用 AvailableCluster
                    URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); 
                    invoker = cluster.join(new StaticDirectory(u, invokers));
                }  else { // 不是 注册中心的URL
                    invoker = cluster.join(new StaticDirectory(invokers));
                }
            }
        }
        
        // 创建服务代理
        return (T) proxyFactory.getProxy(invoker);
    }
    

    代码很长,我截取了关键的部分,首先判断是否为本地injvm调用,若不是则加载服务直连的url或注册中心的url,接着根据url数量判断生成相应的invoker(url数量为1说明是只有一个注册中心或者服务直连,则直接调用protocol.refer获得相应的invoker;大于1代表是多个注册中心或者多个服务提供者直连,或者两者混合,则会生成多个invoker,通过cluster.join合并),最后调用proxyFactory.getProxy(invoker)生成相应代理类。所以,这里主要逻辑就在invoker和代理类的生成。

    Invoker的创建

    在上一篇讲过,Invoker在服务端是服务提供类的代理类,通过proxyFactory.getInvoker创建;而在客户端,则用于执行远程调用,通过protocol.refer调用。但是Protocol的实现类有很多,这里主要分析单注册中心和dubbo协议直连的情况。

    单注册中心的Invoker创建

    如果是Zookeeper的单注册中心方式,invoker就是通过invoker = refprotocol.refer(interfaceClass, urls.get(0))这句代码创建的,这里我们能够很快的定位到RegistryProtocol.refer方法中:

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    	// 将registry协议转为zookeeper协议
       url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
       // 这个代码上一篇文章也分析过了,首先这里是通过依赖注入注入的RegistryFactory$Adpative
       // 对象,最终会创建一个Zookeeper连接并返回ZookeeperRegistry对象
       Registry registry = registryFactory.getRegistry(url);
       if (RegistryService.class.equals(type)) {
       	return proxyFactory.getInvoker((T) registry, type, url);
       }
    
       // 分组服务走这里
       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 );
           }
       }
       // 非分组服务走这里,注意这里的cluster也是依赖注入进来的
       return doRefer(cluster, registry, type, url);
    }
    

    需要注意cluster对象,它是通过依赖注入进来的,猜猜它是个什么对象?是FailoverCluster吗?其实并不是的,实际应该为MockClusterWrapper(FailoverCluster)对象(在服务发布一节中已有相应的分析),有什么用,我们后面再分析,这里暂时不讨论。
    在refer中主要是创建zookeeper连接,并获取Registry对象,然后通过doRefer订阅和调用服务。

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    	// 服务目录,暂时不详细分析它,知道它包含了所有的服务url就行了
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // 注册consumer服务
        URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
        if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
                && url.getParameter(Constants.REGISTER_KEY, true)) {
            registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                    Constants.CHECK_KEY, String.valueOf(false)));
        }
        // 监听providers、configurators、routers、category节点的变化
        directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
                Constants.PROVIDERS_CATEGORY 
                + "," + Constants.CONFIGURATORS_CATEGORY 
                + "," + Constants.ROUTERS_CATEGORY));
    	// 一个注册中心可能存在多个相同的服务提供者,需要将其合并为一个Invoker
        return cluster.join(directory);
    }
    

    这个方法首先创建了一个服务目录,该目录会监听除consumer节点以外(本身就是consumer,当然没必要再监听consumer的变化了)的其它节点状态,最后通过cluster合并服务目录并返回invoker(Directory和Cluster等待后文来分析):

    // MockClusterWrapper.join
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    	// 这里的cluster是FailoverCluster
    	return new MockClusterInvoker<T>(directory,
    			this.cluster.join(directory));
    }
    
    // FailoverCluster.join
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<T>(directory);
    }
    

    从上面可以看到,这里返回的是MockClusterInvoker对象,并持有FailoverClusterInvoker的引用,这个在分析服务调用过程时会用到。

    Dubbo直连的Invoker创建

    如果是通过Dubbo协议直连服务的话,也是通过invoker = refprotocol.refer(interfaceClass, urls.get(0))创建Invoker,只不过会进入到DubboProtocol.refer方法中:

    public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
        // create rpc invoker.
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);
        return invoker;
    }
    

    这个方法主要通过getClients获取客户端ExchangeClient的实例,并实例化DubboInvoker返回。但是实际通信的肯定不是ExchangeClient,因为服务端默认使用的是Netty,那么客户端也应该是,我们可以点进去看看如何创建的。

    private ExchangeClient[] getClients(URL url){
        // 是否共享连接
        boolean service_share_connect = false;
        int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
        //如果connections不配置,则共享连接,否则每服务每次新建连接
        if (connections == 0){
            service_share_connect = true;
            connections = 1;
        }
        
        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            if (service_share_connect){
            	// 获取共享连接
                clients[i] = getSharedClient(url);
            } else {
            	// 创建配置的连接数
                clients[i] = initClient(url);
            }
        }
        return clients;
    }
    

    dubbo:reference可以通过connections配置连接数,调用initClient方法创建连接,不配置默认使用共享连接,调用getSharedClient获取,首先来看看getSharedClient方法:

    private ExchangeClient getSharedClient(URL url){
    	// 从缓存中获取客户端,该客户端具有计数功能
        String key = url.getAddress();
        ReferenceCountExchangeClient client = referenceClientMap.get(key);
        if ( client != null ){
            if ( !client.isClosed()){
            	// 每被引用一次,计数就+1
                client.incrementAndGetCount();
                return client;
            } else {
                referenceClientMap.remove(key);
            }
        }
        // 创建客户端实例
        ExchangeClient exchagneclient = initClient(url);
        // 使用ReferenceCountExchangeClient装饰,使其具有引用计数功能
        client = new ReferenceCountExchangeClient(exchagneclient, ghostClientMap);
        referenceClientMap.put(key, client);
        ghostClientMap.remove(key);
        return client; 
    }
    

    该方法中也调用了initClient方法创建客户端,并使用ReferenceCountExchangeClient装饰,使其具有引用计数的功能,每被使用一次计数就+1。再看看客户端是如何被创建的:

    private ExchangeClient initClient(URL url) {
        
        // 获取客户端类型,默认使用netty
        String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
    
        String version = url.getParameter(Constants.DUBBO_VERSION_KEY);
        boolean compatible = (version != null && version.startsWith("1.0."));
        url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() && compatible ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
        //默认开启heartbeat
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
        
        // BIO存在严重性能问题,暂时不允许使用
        if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported client type: " + str + "," +
                    " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
        }
        
        ExchangeClient client ;
        try {
            // 创建客户端时是否立即创建连接,lazy表示不立即创建
            if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)){
                client = new LazyConnectExchangeClient(url ,requestHandler);
            } else {
                client = Exchangers.connect(url ,requestHandler);
            }
        } catch (RemotingException e) {
            throw new RpcException("Fail to create remoting client for service(" + url
                    + "): " + e.getMessage(), e);
        }
        return client;
    }
    

    默认创建一个netty客户端,并根据配置判断是否立即创建连接,若使用懒加载则会在请求服务时才创建连接。但不管是否是懒加载,都是通过Exchangers.connect方法创建的连接:

    public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
        return getExchanger(url).connect(url, handler);
    }
    

    这里的getExchanger获取到的和服务端一样,也是HeaderExchanger

    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
    }
    

    是不是感觉似曾相识,还记得服务端是怎么创建连接的吧,只不过当时是调用的Transporters.bind,而这里是Transporters.connect:

    public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        ChannelHandler handler;
        if (handlers == null || handlers.length == 0) {
            handler = new ChannelHandlerAdapter();
        } else if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            handler = new ChannelHandlerDispatcher(handlers);
        }
        return getTransporter().connect(url, handler);
    }
    

    getTransporter方法也不用分析了,最终会进入到NettyTransporter.connect方法中:

    public Client connect(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyClient(url, listener);
    }
    

    就是去创建一个NettyClient客户端连接Server进行通信(Netty的源码分析不是本篇的重点,就先不分析了),这样DubboInvoker中就持有了NettyClient的引用了。至此,Invoker创建流程分析完成,下面一起来看看代理类的创建。

    创建代理类

    上面用大量的篇幅分析了Invoker的创建,拿到Invoker对象之后通过proxyFactory.getProxy创建代理类对象,这个proxyFactory很熟悉了,也是通过自适应扩展机制获取到的对象,所以应该会调用JavassistProxyFactory的getProxy方法,但是该类中没有与之匹配的方法(方法名相同,但是参数列表不同),所以首先应该进入其父类AbstractProxyFactory的getProxy方法中:

    public <T> T getProxy(Invoker<T> invoker) throws RpcException {
        Class<?>[] interfaces = null;
        // 从url中获取接口
        String config = invoker.getUrl().getParameter("interfaces");
        if (config != null && config.length() > 0) {
            String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
            if (types != null && types.length > 0) {
                interfaces = new Class<?>[types.length + 2];
                interfaces[0] = invoker.getInterface();
                interfaces[1] = EchoService.class;
                for (int i = 0; i < types.length; i ++) {
                    interfaces[i + 1] = ReflectUtils.forName(types[i]);
                }
            }
        }
        // 若url中未配置,则直接从invoker中获取
        if (interfaces == null) {
            interfaces = new Class<?>[] {invoker.getInterface(), EchoService.class};
        }
        // 调用Javaassist的getProxy方法
        return getProxy(invoker, interfaces);
    }
    
    // JavaassistProxyFactory
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        // 生成 Proxy 子类Proxy0
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
    

    这里会通过模板方法调用JavassistProxyFactory.getProxy,然后通过Proxy类生成一个接口代理类proxy0和一个Proxy的子类Proxy0。Proxy0主要是实现父类的抽象方法newInstance(InvocationHandler handler)

    public Object newInstance(java.lang.reflect.InvocationHandler h) { 
    	return new com.alibaba.dubbo.common.bytecode.proxy0($1); 
    }
    

    可以看到该方法就是传入一个InvocationHandler并初始化proxy0对象,刚刚说了proxy0是服务接口的代理类,因此它是实现了我们定义的服务接口及方法:

    private java.lang.reflect.InvocationHandler handler;
    
    public java.lang.String sayHello(java.lang.String arg0) {
    	Object[] args = new Object[1]; 
    	args[0] = ($w)$1; 
    	Object ret = handler.invoke(this, methods[0], args); 
    	return (java.lang.String)ret;
    }
    
    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();
    }
    

    最终就是去调用Invoker的invoker方法,所以这里会进入到相应的MockClusterInvoker.invoke方法(非服务直连创建的Invoker)以及AbstractInvoker.invoke方法(Dubbo直连时,由于DubboInvoker中没有invoker方法,所以调用其父类AbstractInvoker的)中,详细的调用过程下一篇再分析。

  • 相关阅读:
    巧用SQL生成SQL语句
    update,delete与INNER JOIN 以及删除重复数据
    sql判断各种类型的东西存在与否(参考)
    附加数据库报错823
    hibernate配置文件hibernate.cfg.xml的详细解释
    向数据库插入图片以及从数据库中读取图片并显示到jsp(数据库中存储的图片字段类型为Blob或image)
    Hibernate中hibernateTemplate()方法总结
    spring MVC之构造ModelAndView对象
    手机网站初步布局,如何建立你自己的手机网站?
    (MoMoCMS教程2)创建页面——主菜单
  • 原文地址:https://www.cnblogs.com/yewy/p/13111826.html
Copyright © 2011-2022 走看看