A deep understanding of Dubbo core model Invoker

Reproduced from: A deep understanding of Dubbo core model Invoker

1, Introduction of Invoker in Dubbo

Why is Invoker the core model of Dubbo?

Invoker is the entity domain in Dubbo, which is the real existence. Other models are moving towards it or converting to it, which also represents an executable to which an invoke call can be made. At the service provider, invoker is used to call the service provider class. At the service consumer, invoker is used to perform remote calls.

2, Service provider's Invoker

The Invoker in the service provider is created by ProxyFactory. The default implementation class of dobbo's ProxyFactory is JavassistProxyFactory.

Create the entry method getInvoker of the Invoker:

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // Create Wrapper for target class
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf(36) < 0 ? proxy.getClass() : type);
    // Create an anonymous Invoker class object and implement the doInvoke method.
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class<?>[] parameterTypes,
                                  Object[] arguments) throws Throwable {
            // Call the invokeMethod method of Wrapper, and the invokeMethod will eventually call the target method
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

JavassistProxyFactory creates an anonymous object that inherits from the AbstractProxyInvoker Class and overrides the abstract method doInvoke. The overridden doInvoke logic is relatively simple, only forwarding the call request to the invokeMethod method of the Wrapper Class. And generate invokeMethod method code and some other method code. After the code is generated, Class objects are generated by Javassist, and finally Wrapper instances are created by reflection.

Note: Wapper is a packaging Class. It is mainly used for the "wrap" target Class and can only create subclasses through the getWapper(Class) method. In the process of creating a subclass, the subclass code will parse the Class object passed in and get the Class method, Class member variable and other information. This wrapper Class holds the actual extension point implementation Class. You can also move all the common logic of the extension point to the wrapper Class, which is implemented as AOP.

Create the construction method of the wrapper class:

public static Wrapper getWrapper(Class<?> c) {    
    while (ClassGenerator.isDynamicClass(c))
        c = c.getSuperclass();

    if (c == Object.class)
        return OBJECT_WRAPPER;

    // Get Wrapper instance from cache
    Wrapper ret = WRAPPER_MAP.get(c);
    if (ret == null) {
        // Cache miss, create Wrapper
        ret = makeWrapper(c);
        // Write cache
        WRAPPER_MAP.put(c, ret);
    }
    return ret;
}

If you can't get the Wapper in the cache, you will enter the following method, makeWapper:

private static Wrapper makeWrapper(Class<?> c) {
    // Check whether c is the basic type, if so, throw an exception
    if (c.isPrimitive())
        throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);

    String name = c.getName();
    ClassLoader cl = ClassHelper.getClassLoader(c);

    // c1 is used to store setPropertyValue method code
    StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
    // c2 is used to store getPropertyValue method code
    StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
    // c3 is used to store invokeMethod method code
    StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");

    // Generate type conversion code and exception capture code, such as:
    //   DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }
    c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");

    // pts is used to store member variable names and types
    Map<String, Class<?>> pts = new HashMap<String, Class<?>>();
    // ms is used to store Method description information (which can be understood as Method signature) and Method instance
    Map<String, Method> ms = new LinkedHashMap<String, Method>();
    // mns is the list of method names
    List<String> mns = new ArrayList<String>();
    // dmns is used to store the name of the method defined in the current class
    List<String> dmns = new ArrayList<String>();

    // --------------------------------✨ split line 1 ✨-------------------------------------

    // Get the field of public access level, and generate conditional statements for all fields
    for (Field f : c.getFields()) {
        String fn = f.getName();
        Class<?> ft = f.getType();
        if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers()))
            // Ignore variables decorated with the keyword static or transient
            continue;

        // Generate condition judgment and assignment statements, such as:
        // if( $2.equals("name") ) { w.name = (java.lang.String) $3; return;}
        // if( $2.equals("age") ) { w.age = ((Number) $3).intValue(); return;}
        c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");

        // Generate condition judgment and return statements, such as:
        // if( $2.equals("name") ) { return ($w)w.name; }
        c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");

        // Store < field name, field type > key value pair in pts
        pts.put(fn, ft);
    }

    // --------------------------------✨ split line 2 ✨-------------------------------------

    Method[] methods = c.getMethods();
    // Check whether c contains methods declared in the current class
    boolean hasMethod = hasMethods(methods);
    if (hasMethod) {
        c3.append(" try{");
    }
    for (Method m : methods) {
        if (m.getDeclaringClass() == Object.class)
            // Ignore methods defined in Object
            continue;

        String mn = m.getName();
        // Generate method name judgment statements, such as:
        // if ( "sayHello".equals( $2 )
        c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
        int len = m.getParameterTypes().length;
        // Generate the "number of parameters passed in and length of method parameter list" judgment statement, such as:
        // && $3.length == 2
        c3.append(" && ").append(" $3.length == ").append(len);

        boolean override = false;
        for (Method m2 : methods) {
            // Check whether the method is overloaded, if the method object is different & & the method name is the same
            if (m != m2 && m.getName().equals(m2.getName())) {
                override = true;
                break;
            }
        }
        // To handle overloaded methods, consider the following methods:
        //    1. void sayHello(Integer, String)
        //    2. void sayHello(Integer, Integer)
        // The method name is the same, and the parameter list length is the same, so you cannot judge whether the two methods are equal only through these two items.
        // The parameter type of the method needs to be further determined
        if (override) {
            if (len > 0) {
                for (int l = 0; l < len; l++) {
                    // Generate parameter types to test codes, such as:
                    // && $3[0].getName().equals("java.lang.Integer") 
                    //    && $3[1].getName().equals("java.lang.String")
                    c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")
                            .append(m.getParameterTypes()[l].getName()).append("\")");
                }
            }
        }

        // Add) {, complete the method judgment statement, and the generated code may be as follows (formatted):
        // if ("sayHello".equals($2) 
        //     && $3.length == 2
        //     && $3[0].getName().equals("java.lang.Integer") 
        //     && $3[1].getName().equals("java.lang.String")) {
        c3.append(" ) { ");

        // Generate target method call statement based on return value type
        if (m.getReturnType() == Void.TYPE)
            // w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;
            c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
        else
            // return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);
            c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");

        // Add}, the generated code shape is as follows (formatted):
        // if ("sayHello".equals($2) 
        //     && $3.length == 2
        //     && $3[0].getName().equals("java.lang.Integer") 
        //     && $3[1].getName().equals("java.lang.String")) {
        //
        //     w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); 
        //     return null;
        // }
        c3.append(" }");

        // Add method name to mns set
        mns.add(mn);
        // Checks whether the current method is declared in c
        if (m.getDeclaringClass() == c)
            // If so, add the current method name to dmns
            dmns.add(mn);
        ms.put(ReflectUtils.getDesc(m), m);
    }
    if (hasMethod) {
        // Add exception capture statement
        c3.append(" } catch(Throwable e) { ");
        c3.append("     throw new java.lang.reflect.InvocationTargetException(e); ");
        c3.append(" }");
    }

    // Add NoSuchMethodException exception exception throwing code
    c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }");

    // --------------------------------✨ split line 3 ✨-------------------------------------

    Matcher matcher;
    // Handling get/set methods
    for (Map.Entry<String, Method> entry : ms.entrySet()) {
        String md = entry.getKey();
        Method method = (Method) entry.getValue();
        // Match methods starting with get
        if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            // Get property name
            String pn = propertyName(matcher.group(1));
            // Generate attribute judgment and return statements, as shown in the following example:
            // if( $2.equals("name") ) { return ($w).w.getName(); }
            c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
            pts.put(pn, method.getReturnType());

        // Matching methods starting with is/has/can
        } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            String pn = propertyName(matcher.group(1));
            // Generate attribute judgment and return statements, as shown in the following example:
            // if( $2.equals("dream") ) { return ($w).w.hasDream(); }
            c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
            pts.put(pn, method.getReturnType());

        // Match methods starting with set
        } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            Class<?> pt = method.getParameterTypes()[0];
            String pn = propertyName(matcher.group(1));
            // Generate property judgment and setter call statements, as shown in the following example:
            // if( $2.equals("name") ) { w.setName((java.lang.String)$3); return; }
            c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
            pts.put(pn, pt);
        }
    }

    // Add NoSuchPropertyException exception exception throwing code
    c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");
    c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");

    // --------------------------------✨ split line 4 ✨-------------------------------------

    long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
    // Create class builder
    ClassGenerator cc = ClassGenerator.newInstance(cl);
    // Set class name and superclass
    cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
    cc.setSuperClass(Wrapper.class);

    // Add default construction method
    cc.addDefaultConstructor();

    // Add fields
    cc.addField("public static String[] pns;");
    cc.addField("public static " + Map.class.getName() + " pts;");
    cc.addField("public static String[] mns;");
    cc.addField("public static String[] dmns;");
    for (int i = 0, len = ms.size(); i < len; i++)
        cc.addField("public static Class[] mts" + i + ";");

    // Add method code
    cc.addMethod("public String[] getPropertyNames(){ return pns; }");
    cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
    cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
    cc.addMethod("public String[] getMethodNames(){ return mns; }");
    cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
    cc.addMethod(c1.toString());
    cc.addMethod(c2.toString());
    cc.addMethod(c3.toString());

    try {
        // Generating class
        Class<?> wc = cc.toClass();
        
        // Set field value
        wc.getField("pts").set(null, pts);
        wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
        wc.getField("mns").set(null, mns.toArray(new String[0]));
        wc.getField("dmns").set(null, dmns.toArray(new String[0]));
        int ix = 0;
        for (Method m : ms.values())
            wc.getField("mts" + ix++).set(null, m.getParameterTypes());

        // Create Wrapper instance
        return (Wrapper) wc.newInstance();
    } catch (RuntimeException e) {
        throw e;
    } catch (Throwable e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        cc.release();
        ms.clear();
        mns.clear();
        dmns.clear();
    }
}

The code is long, and there are also many comments. Let's talk about the logic:

  • **Create three strings c1, c2 and c3 to store type conversion code and exception capture code, then pts to store member variable name and type, ms to store Method description information (which can be understood as Method signature) and Method instance, mns to store Method name list, dmns to store the name of "Method defined in current class". **I did some initial work here
  • Obtain all public fields, and use c1 to store conditional judgment and assignment statements. It can be understood that public fields can be assigned through c1, while c2 is conditional judgment and return statement, and the same is to get the value of public fields. Then pts is used to store < field name, field type >. That is to say, now you can operate the target class fields. To operate some private fields, you need to access the methods at the beginning of set and get. Similarly, these methods also use c1 to store set, c2 to store get, pts to store < property name, property type >
  • Now for methods in a class, first check the parameters in the method, and then check whether there are overloaded methods. The statement calling the target method and the exceptions that may be thrown in the method are stored in c3, and then the mns set is used to store the method name. The declared method is stored in ms, and the undeclared but defined method is stored in dmns.
  • **Build the Class class for the newly generated code through ClassGenerator, and create the object through reflection. **The ClassGenerator is encapsulated by Dubbo itself. The core of this Class is the toClass(ClassLoader, ProtectionDomain) overloaded method of toClass(), which builds Class through javassist.

Finally, after creating the Wapper class, go back to the getInvoker method above and use the following statement

return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);

//Go to invokeMethod

public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) throws NoSuchMethodException {
        if ("getClass".equals(mn)) {
            return instance.getClass();
        } else if ("hashCode".equals(mn)) {
            return instance.hashCode();
        } else if ("toString".equals(mn)) {
            return instance.toString();
        } else if ("equals".equals(mn)) {
            if (args.length == 1) {
                return instance.equals(args[0]);
            } else {
                throw new IllegalArgumentException("Invoke method [" + mn + "] argument number error.");
            }
        } else {
            throw new NoSuchMethodException("Method [" + mn + "] not found.");
        }
    }
};

At this point, the Invoker can implement the method that calls the service provider class. That is to say, the creation of the Invoker entity domain of the service provider class is completed. The underlying is to build objects through javassist.

3, Service consumer's Invoker

At the service consumer, invoker is used to perform remote calls. Invoker is built from the Protocol implementation class. There are many but two most commonly used Protocol implementation classes, RegistryProtocol and DubboProtocol.

refer method of DubboProtocol:

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

The above method is relatively simple, and the most important one is getClients. This method is used to get the client instance. The instance type is ExchangeClient. In fact, exchange client does not have the ability to communicate. It needs to communicate based on a lower level client instance. For example, NettyClient, MinaClient, etc. by default, Dubbo uses NettyClient for communication. Every time an invoker is created, it will be added to the invokers collection. That is to say, the Invoker of the service consumer can be regarded as a Netty client with communication capability

getClients method:

private ExchangeClient[] getClients(URL url) {
    // Share connection or not
    boolean service_share_connect = false;
      // Get the number of connections. The default value is 0, indicating that it is not configured
    int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
    // If connections is not configured, the connection is shared
    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) {
            // Get shared client
            clients[i] = getSharedClient(url);
        } else {
            // Initializing a new client
            clients[i] = initClient(url);
        }
    }
    return clients;
}

//Go to get shared client method

private ExchangeClient getSharedClient(URL url) {
    String key = url.getAddress();
    // Get ExchangeClient with reference count
    ReferenceCountExchangeClient client = referenceClientMap.get(key);
    if (client != null) {
        if (!client.isClosed()) {
            // Increase reference count
            client.incrementAndGetCount();
            return client;
        } else {
            referenceClientMap.remove(key);
        }
    }

    locks.putIfAbsent(key, new Object());
    synchronized (locks.get(key)) {
        if (referenceClientMap.containsKey(key)) {
            return referenceClientMap.get(key);
        }

        // Create exchange client
        ExchangeClient exchangeClient = initClient(url);
        // Pass the ExchangeClient instance to referencecounteexchangeclient, where decoration mode is used
        client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
        referenceClientMap.put(key, client);
        ghostClientMap.remove(key);
        locks.remove(key);
        return client;
    }
}

//Enter the initialize client method

private ExchangeClient initClient(URL url) {

    // Get client type, default is netty
    String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));

    // Add codec and heartbeat package parameters to url
    url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

    // Check whether the client type exists. If not, an exception will be thrown
    if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
        throw new RpcException("Unsupported client type: ...");
    }

    ExchangeClient client;
    try {
        // Get the lazy configuration and determine the client type created according to the configuration value
        if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
            // Create a lazy exchange client instance
            client = new LazyConnectExchangeClient(url, requestHandler);
        } else {
            // Create a normal exchange client instance
            client = Exchangers.connect(url, requestHandler);
        }
    } catch (RemotingException e) {
        throw new RpcException("Fail to create remoting client for service...");
    }
    return client;
}

//In the connect method, getexchange will load the HeaderExchangeClient instance through SPI. This method is relatively simple. Let's have a look at it for ourselves. Next, we analyze the implementation of HeaderExchangeClient.

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");
    // Get the exchange instance, the default is HeaderExchangeClient
    return getExchanger(url).connect(url, handler);
}

//Create HeaderExchangeClient instance 

public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    // There are several calls as follows:
    // 1. Create HeaderExchangeHandler object
    // 2. Create decidehandler object
    // 3. Build Client instance through Transporters
    // 4. Create HeaderExchangeClient object
    return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}

//Building Client instance through Transporters

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 {
        // If the number of handlers is greater than 1, create a ChannelHandler distributor
        handler = new ChannelHandlerDispatcher(handlers);
    }
    
    // Obtain the Transporter adaptive extension class and call the connect method to generate the Client instance
    return getTransporter().connect(url, handler);
}

//Create Netty object

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

The above source code is roughly divided into the following logic:

  • Enter the creation of DubboInvoker instance through the refer method. In this instance, the serviceType, url, and invokers are all unnecessary. The invokers can be said to be the stored and created invokers. The key is the getClient method. It can be said that the current Invoker is a Netty client. The Invoker of the service provider is a Wapper class.
  • In the getClient method, first, it is determined whether to obtain the shared client or create a new client instance according to the number of connections. By default, it is to obtain the shared client. However, if there is no corresponding client in the cache, a new client will be created. The final return is ExchangeClient, and the current ExchangeClient has no communication ability, so a lower level Netty client is needed.
  • The initClient method first obtains the client type configured by the user, which is Netty by default, then checks whether the client type configured by the user exists, throws an exception if it does not exist, and finally creates what type of client according to the configuration of lazy. The LazyConnectExchangeClient code is not very complex. This class will create the ExchangeClient through the connect method of exchanges when the request method is called
  • getExchanger loads the HeaderExchangeClient instance through SPI. Finally, the Netty client is created by the Transporter implementation class and calling the Netty API. At last, it returns layer by layer, and finally becomes such a class with Netty at the bottom and DubboInvoker at the top.

refer in RegistryProtocol:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // Take the registry parameter value and set it as the protocol header
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    // Get registry instance
    Registry registry = registryFactory.getRegistry(url);
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // Convert url query string to Map
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    // Get group configuration
    String group = qs.get(Constants.GROUP_KEY);
    if (group != null && group.length() > 0) {
        if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
                || "*".equals(group)) {
            // Load the MergeableCluster instance through SPI and call doRefer to continue the service reference logic
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    
    // Call doRefer to continue to execute service reference logic
    return doRefer(cluster, registry, type, url);
}
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    // Create a RegistryDirectory instance
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    // Set up registry and protocol
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    // Generate service consumer link
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);

    // Register the service consumer and create a new node in the consumers directory
    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)));
    }

    // Subscribe to node data such as providers, configurators, routers, etc
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));

    // There may be multiple service providers in a registry, so you need to merge multiple service providers into one
    Invoker invoker = cluster.join(directory);
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}

//Enter the cluster creation Invoker mode

@SPI(FailoverCluster.NAME)
public interface Cluster {

    /**
     * Merge where the Directory's Invoker is an Invoker
     */
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;
}

//Enter the MockerClusterWrapper implementation class

public class MockClusterWrapper implements Cluster {

    private Cluster cluster;

    public MockClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }

    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory, this.cluster.join(directory));
    }
}

//Specific invoke method

public Result invoke(Invocation invocation) throws RpcException {
    Result result = null;
    
    String value = directory.getUrl().getMethodParameter(invocation.getMethodName(),
                             Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); 
    if (value.length() == 0 || value.equalsIgnoreCase("false")){
        //no mock
        result = this.invoker.invoke(invocation);
    } else if (value.startsWith("force")) {
        if (logger.isWarnEnabled()) {
            logger.info("force-mock: " + invocation.getMethodName() + 
                        " force-mock enabled , url : " +  directory.getUrl());
        }
        //force:direct mock
        result = doMockInvoke(invocation, null);
    } else {
        //fail-mock
        try {
            result = this.invoker.invoke(invocation);
        }catch (RpcException e) {
            if (e.isBiz()) {
                throw e;
            } else {
                if (logger.isWarnEnabled()) {
                    logger.info("fail-mock: " + invocation.getMethodName() + 
                            " fail-mock enabled , url : " +  directory.getUrl(), e);
                }
                //fail:mock
                result = doMockInvoke(invocation, e);
            }
        }
    }
    return result;
}

To summarize the above logic:

  • Currently, the underlying layer of Invoker is still NettyClient, but at this time, the registry is a cluster building mode. Therefore, you need to merge multiple invokers into one. * * here is a logical merge. In fact, there will be multiple invockers in the underlying layer, which are only managed through a cluster mode. So what is exposed is a cluster mode Invoker. **Then enter the Cluster.join method.
  • Cluster is a general proxy class. It will locate to the actual cluster implementation class, FailoverCluster, according to the cluster parameter value in the URL. The @ SPI annotation is used here, that is to say, the ExtensionLoader extension point loading mechanism is required. In the instantiated object, the Wapper will be automatically applied after instantiation
  • But it's cluster mode, so it needs another core mechanism in Dubbo - mock. Mock can simulate all kinds of abnormal situations of service calls in the test, and also realize service degradation. In MockerClusterInvoker, Dubbo first checks whether the mock parameter exists in the URL. (this parameter can be set through the shielding and fault tolerance of the background Consumer side of service governance or the value of the mock parameter can be set directly and dynamically.) if there is a beginning of force, this does not initiate a remote call to directly execute the degradation logic. If there is a start of fail, the degradation logic is executed when the remote call exception occurs.
  • It can be said that when the registry is in the cluster mode, the Invoker will wrap a layer of mock logic outside. It is implemented through the Wapper mechanism. Finally, when calling or retrying, one of multiple invokers can be selected for calling through the load balancing mechanism inside Dubbo

Four, summary

The implementation of Invoker can be finished here. To sum up, in the service provider, Invoker is an instance of the service class created by javassist, which can call methods and modify fields inside the service class. The Invoker in the service consumer is a client based on Netty. Finally, the service class instance created by the service provider is obtained through the Netty client of the service consumer. Then the consumer needs to create a proxy class to protect the service class, so that it can call the internal methods of the service class safely and effectively without instantiating the service class and get the specific data.

Reproduced from: A deep understanding of Dubbo core model Invoker

232 original articles published, praised 23, visited 20000+
Private letter follow

Tags: Java Netty Dubbo Attribute

Posted on Sat, 15 Feb 2020 05:26:46 -0500 by hebisr