Dubbo: Deep understanding of the core model of Dubbo Invoker

Keywords: Java Netty Dubbo Attribute

1. Introduction of Invoker in Dubbo

Why is Invoker the core model of Dubbo?

Invoker is an entity domain in Dubbo, that is, it really exists.Other models approach it or convert it, and it also represents an executable from which invoke calls can be made.At the service provider, Invoker is used to invoke the service provider class.On the service consumer side, Invoker is used to perform remote calls.

2. Invoker of Service Provider

Invoker in the service provider is created by ProxyFactory, and the default ProxyFactory implementation class for Dubbo is JavassistProxyFactory.

Create Invoker's entry method getInvoker:

 1 public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
 2     // Create for Target Class Wrapper
 3     final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf(36) < 0 ? proxy.getClass() : type);
 4     // Create Anonymous Invoker Class object and Implementation doInvoke Method.
 5     return new AbstractProxyInvoker<T>(proxy, type, url) {
 6         @Override
 7         protected Object doInvoke(T proxy, String methodName,
 8                                   Class<?>[] parameterTypes,
 9                                   Object[] arguments) throws Throwable {
10             // call Wrapper Of invokeMethod Method, invokeMethod The target method will eventually be called
11             return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
12         }
13     };
14 }

JavassistProxyFactory creates an anonymous object that inherits from the AbstractProxyInvoker class and overrides the abstract method doInvoke.The overridden doInvoke logic is simpler, simply forwarding the call request to the invokeMethod method of the Wrapper class.And generate invokeMethod method code and some other method code.Once the code is generated, the Class object is generated through Javassist, and the Wrapper instance is created through reflection.

Note: Wapper is a packaging class.Mainly used for the Package target class, subclasses can only be created through the getWapper(Class) method.In the process of creating a subclass, the subclass code parses the incoming Class object to get information such as class methods, class member variables, and so on.This wrapper class holds the actual extension point implementation class.You can also move all the common logic of the extension point into the wrapper class, functionally as an AOP implementation.

Create construction methods for wrapper classes:

 1  public static Wrapper getWrapper(Class<?> c) {    
 2     while (ClassGenerator.isDynamicClass(c))
 3         c = c.getSuperclass();
 4 
 5     if (c == Object.class)
 6         return OBJECT_WRAPPER;
 7 
 8     // Get from Cache Wrapper Example
 9     Wrapper ret = WRAPPER_MAP.get(c);
10     if (ret == null) {
11         // Cache missed, create Wrapper
12         ret = makeWrapper(c);
13         // Write Cache
14         WRAPPER_MAP.put(c, ret);
15     }
16     return ret;
17 }

) Failure to get a Wapper in the cache will lead to the following method makeWapper:

  1 private static Wrapper makeWrapper(Class<?> c) {
  2     // Testing c Is it a base type, if it throws an exception
  3     if (c.isPrimitive())
  4         throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);
  5 
  6     String name = c.getName();
  7     ClassLoader cl = ClassHelper.getClassLoader(c);
  8 
  9     // c1 For storage setPropertyValue method code
 10     StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
 11     // c2 For storage getPropertyValue method code
 12     StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
 13     // c3 For storage invokeMethod method code
 14     StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");
 15 
 16     // Generate type conversion code and exception capture code, such as:
 17     //   DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }
 18     c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
 19     c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
 20     c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
 21 
 22     // pts Used to store member variable names and types
 23     Map<String, Class<?>> pts = new HashMap<String, Class<?>>();
 24     // ms Used to store method description information (understood as method signatures) and Method Example
 25     Map<String, Method> ms = new LinkedHashMap<String, Method>();
 26     // mns For method name list
 27     List<String> mns = new ArrayList<String>();
 28     // dmns Name used to store Method Defined in Current Class
 29     List<String> dmns = new ArrayList<String>();
 30 
 31     // --------------------------------✨ Split Line 1 ✨-------------------------------------
 32 
 33     // Obtain public Access level fields and generate conditional judgment statements for all fields
 34     for (Field f : c.getFields()) {
 35         String fn = f.getName();
 36         Class<?> ft = f.getType();
 37         if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers()))
 38             // Ignore keywords static or transient Modified variable
 39             continue;
 40 
 41         // Generate conditional judgments and assignment statements, such as:
 42         // if( $2.equals("name") ) { w.name = (java.lang.String) $3; return;}
 43         // if( $2.equals("age") ) { w.age = ((Number) $3).intValue(); return;}
 44         c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");
 45 
 46         // Generate conditional judgments and return statements, such as:
 47         // if( $2.equals("name") ) { return ($w)w.name; }
 48         c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");
 49 
 50         // storage <Field name, Field type> Key Value Pairing To pts in
 51         pts.put(fn, ft);
 52     }
 53 
 54     // --------------------------------✨ Split Line 2 ✨-------------------------------------
 55 
 56     Method[] methods = c.getMethods();
 57     // Testing c Whether or not to include methods declared in the current class
 58     boolean hasMethod = hasMethods(methods);
 59     if (hasMethod) {
 60         c3.append(" try{");
 61     }
 62     for (Method m : methods) {
 63         if (m.getDeclaringClass() == Object.class)
 64             // ignore Object Method defined in
 65             continue;
 66 
 67         String mn = m.getName();
 68         // Generate method name judgment statements, such as:
 69         // if ( "sayHello".equals( $2 )
 70         c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
 71         int len = m.getParameterTypes().length;
 72         // Generate a "Number of parameters passed in at runtime and length of method parameter list" judgment statement, such as:
 73         // && $3.length == 2
 74         c3.append(" && ").append(" $3.length == ").append(len);
 75 
 76         boolean override = false;
 77         for (Method m2 : methods) {
 78             // Detect if overload exists with different method objects && Method Name Same
 79             if (m != m2 && m.getName().equals(m2.getName())) {
 80                 override = true;
 81                 break;
 82             }
 83         }
 84         // To handle overloaded methods, consider the following:
 85         //    1. void sayHello(Integer, String)
 86         //    2. void sayHello(Integer, Integer)
 87         // The method names are the same and the parameter lists are the same length, so you can't just tell if the two methods are equal.
 88         // The parameter type of the method needs to be further determined
 89         if (override) {
 90             if (len > 0) {
 91                 for (int l = 0; l < len; l++) {
 92                     // Generate parameter types to check code, such as:
 93                     // && $3[0].getName().equals("java.lang.Integer") 
 94                     //    && $3[1].getName().equals("java.lang.String")
 95                     c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")
 96                             .append(m.getParameterTypes()[l].getName()).append("\")");
 97                 }
 98             }
 99         }
100 
101         // Add to ) {,Complete the method judgment statement, where the generated code may be as follows (formatted):
102         // if ("sayHello".equals($2) 
103         //     && $3.length == 2
104         //     && $3[0].getName().equals("java.lang.Integer") 
105         //     && $3[1].getName().equals("java.lang.String")) {
106         c3.append(" ) { ");
107 
108         // Generate target method call statements based on return value type
109         if (m.getReturnType() == Void.TYPE)
110             // w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;
111             c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
112         else
113             // return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);
114             c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");
115 
116         // Add to }, The generated code looks like (formatted):
117         // if ("sayHello".equals($2) 
118         //     && $3.length == 2
119         //     && $3[0].getName().equals("java.lang.Integer") 
120         //     && $3[1].getName().equals("java.lang.String")) {
121         //
122         //     w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); 
123         //     return null;
124         // }
125         c3.append(" }");
126 
127         // Add method name to mns In collection
128         mns.add(mn);
129         // Detect if the current method is in c Declared
130         if (m.getDeclaringClass() == c)
131             // If so, add the current method name to the dmns in
132             dmns.add(mn);
133         ms.put(ReflectUtils.getDesc(m), m);
134     }
135     if (hasMethod) {
136         // Add exception capture statement
137         c3.append(" } catch(Throwable e) { ");
138         c3.append("     throw new java.lang.reflect.InvocationTargetException(e); ");
139         c3.append(" }");
140     }
141 
142     // Add to NoSuchMethodException Exception throw code
143     c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }");
144 
145     // --------------------------------✨ Split Line 3 ✨-------------------------------------
146 
147     Matcher matcher;
148     // Handle get/set Method
149     for (Map.Entry<String, Method> entry : ms.entrySet()) {
150         String md = entry.getKey();
151         Method method = (Method) entry.getValue();
152         // Match with get Beginning Method
153         if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
154             // Get Property Name
155             String pn = propertyName(matcher.group(1));
156             // Generate attribute judgments and return statements as follows:
157             // if( $2.equals("name") ) { return ($w).w.getName(); }
158             c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
159             pts.put(pn, method.getReturnType());
160 
161         // Match with is/has/can Beginning Method
162         } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
163             String pn = propertyName(matcher.group(1));
164             // Generate attribute judgments and return statements as follows:
165             // if( $2.equals("dream") ) { return ($w).w.hasDream(); }
166             c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
167             pts.put(pn, method.getReturnType());
168 
169         // Match with set Beginning Method
170         } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
171             Class<?> pt = method.getParameterTypes()[0];
172             String pn = propertyName(matcher.group(1));
173             // Generate attribute judgments and setter Call statement, as shown in the following example:
174             // if( $2.equals("name") ) { w.setName((java.lang.String)$3); return; }
175             c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
176             pts.put(pn, pt);
177         }
178     }
179 
180     // Add to NoSuchPropertyException Exception throw code
181     c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");
182     c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");
183 
184     // --------------------------------✨ Split Line 4 ✨-------------------------------------
185 
186     long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
187     // Create Class Generator
188     ClassGenerator cc = ClassGenerator.newInstance(cl);
189     // Set Class Name and Superclass
190     cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
191     cc.setSuperClass(Wrapper.class);
192 
193     // Add Default Construction Method
194     cc.addDefaultConstructor();
195 
196     // Add Field
197     cc.addField("public static String[] pns;");
198     cc.addField("public static " + Map.class.getName() + " pts;");
199     cc.addField("public static String[] mns;");
200     cc.addField("public static String[] dmns;");
201     for (int i = 0, len = ms.size(); i < len; i++)
202         cc.addField("public static Class[] mts" + i + ";");
203 
204     // Add Method Code
205     cc.addMethod("public String[] getPropertyNames(){ return pns; }");
206     cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
207     cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
208     cc.addMethod("public String[] getMethodNames(){ return mns; }");
209     cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
210     cc.addMethod(c1.toString());
211     cc.addMethod(c2.toString());
212     cc.addMethod(c3.toString());
213 
214     try {
215         // Generate Class
216         Class<?> wc = cc.toClass();
217         
218         // Setting field values
219         wc.getField("pts").set(null, pts);
220         wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
221         wc.getField("mns").set(null, mns.toArray(new String[0]));
222         wc.getField("dmns").set(null, dmns.toArray(new String[0]));
223         int ix = 0;
224         for (Method m : ms.values())
225             wc.getField("mts" + ix++).set(null, m.getParameterTypes());
226 
227         // Establish Wrapper Example
228         return (Wrapper) wc.newInstance();
229     } catch (RuntimeException e) {
230         throw e;
231     } catch (Throwable e) {
232         throw new RuntimeException(e.getMessage(), e);
233     } finally {
234         cc.release();
235         ms.clear();
236         mns.clear();
237         dmns.clear();
238     }
239 }

The code is long and there are many comments.Roughly speaking, the inner logic is:

    • Create three strings, c1, c2, and c3, to store type conversion codes and exception capture codes, then pts to store member variable names and types, ms to store Method description information (which can be understood as Method signatures) and Method instances, mns to list Method names, and dmns to store the names of methods defined in the current class.I did some initial work here
    • Getting all the public fields, using c1 storage condition judgment and assignment statements, you can understand that c1 can assign values to the public fields, while c2 is a conditional judgment and return statement, and the same is getting the values of the public fields.Use pts to store <field name, field type>.That is, you can now manipulate the target class fields. To manipulate some private fields, you need to access the methods at the start of the set and the start of the get, which also use c1 to save the set, c2 to get, pts to store <property name, property type>
    • Now to the method in the class, check the parameters in the method before checking for overloaded methods.The statement calling the target method and the exception that may be thrown in the method are stored in c3, then the method name is stored in the mns collection, the declared method is stored in ms, and the undeclared but defined method exists in dmns.
    • Class classes are built from ClassGenerator for the code just generated, and objects are created from reflection.ClassGenerator is encapsulated by Dubbo itself, and its core is the overloaded method toClass(ClassLoader, ProtectionDomain) of toClass(), which builds a Class from javassist.*

Finally, when the Wapper class is created, go back to the getInvoker method above and pass the following statement

 1 return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
 2 
 3 //Enter invokeMethod in
 4 
 5 public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) throws NoSuchMethodException {
 6         if ("getClass".equals(mn)) {
 7             return instance.getClass();
 8         } else if ("hashCode".equals(mn)) {
 9             return instance.hashCode();
10         } else if ("toString".equals(mn)) {
11             return instance.toString();
12         } else if ("equals".equals(mn)) {
13             if (args.length == 1) {
14                 return instance.equals(args[0]);
15             } else {
16                 throw new IllegalArgumentException("Invoke method [" + mn + "] argument number error.");
17             }
18         } else {
19             throw new NoSuchMethodException("Method [" + mn + "] not found.");
20         }
21     }
22 };

This is where Invoker can implement methods to call service provider classes.That is, the Invoker entity domain of the service provider class is created.The bottom level is to build objects through javassist.

3. Invoker of Service Consumer

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

refer method for DubboProtocol:

1 public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
2     optimizeSerialization(url);
3     // Establish DubboInvoker
4     DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
5     invokers.add(invoker);
6     return invoker;
7 }

The above method is simpler and the most important one is getClients.This method is used to get a client instance of type ExchangeClient.ExchangeClient does not actually have communication capabilities; it needs to communicate based on lower-level client instances.For example, NettyClient, MinaClient, and so on, by default, Dubbo uses NettyClient to communicate.Each time an Invoker is created, it is added to the invokers collection.That is, you can think of the service consumer's Invoker as a Netty client with communication capabilities

getClients method:

  1 private ExchangeClient[] getClients(URL url) {
  2     // Whether to share the connection
  3     boolean service_share_connect = false;
  4       // Gets the number of connections, defaulting to 0, indicating no configuration
  5     int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
  6     // If not configured connections,Then share the connection
  7     if (connections == 0) {
  8         service_share_connect = true;
  9         connections = 1;
 10     }
 11 
 12     ExchangeClient[] clients = new ExchangeClient[connections];
 13     for (int i = 0; i < clients.length; i++) {
 14         if (service_share_connect) {
 15             // Get Shared Clients
 16             clients[i] = getSharedClient(url);
 17         } else {
 18             // Initialize a new client
 19             clients[i] = initClient(url);
 20         }
 21     }
 22     return clients;
 23 }
 24 
 25 //Enter the Get Shared Client method
 26 
 27 private ExchangeClient getSharedClient(URL url) {
 28     String key = url.getAddress();
 29     // Gets the ExchangeClient
 30     ReferenceCountExchangeClient client = referenceClientMap.get(key);
 31     if (client != null) {
 32         if (!client.isClosed()) {
 33             // Increase Reference Count
 34             client.incrementAndGetCount();
 35             return client;
 36         } else {
 37             referenceClientMap.remove(key);
 38         }
 39     }
 40 
 41     locks.putIfAbsent(key, new Object());
 42     synchronized (locks.get(key)) {
 43         if (referenceClientMap.containsKey(key)) {
 44             return referenceClientMap.get(key);
 45         }
 46 
 47         // Establish ExchangeClient Client
 48         ExchangeClient exchangeClient = initClient(url);
 49         // take ExchangeClient Instances passed to ReferenceCountExchangeClient,Decoration mode is used here
 50         client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
 51         referenceClientMap.put(key, client);
 52         ghostClientMap.remove(key);
 53         locks.remove(key);
 54         return client;
 55     }
 56 }
 57 
 58 //Enter Initialize Client Method
 59 
 60 private ExchangeClient initClient(URL url) {
 61 
 62     // Gets the client type, which defaults to netty
 63     String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
 64 
 65     // Add codec and heartbeat package parameters to url in
 66     url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
 67     url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
 68 
 69     // Detect if client type exists, throw exception if none exists
 70     if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
 71         throw new RpcException("Unsupported client type: ...");
 72     }
 73 
 74     ExchangeClient client;
 75     try {
 76         // Obtain lazy Configure and determine the type of client to create based on the configuration value
 77         if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
 78             // Create lazy load ExchangeClient Example
 79             client = new LazyConnectExchangeClient(url, requestHandler);
 80         } else {
 81             // Create Normal ExchangeClient Example
 82             client = Exchangers.connect(url, requestHandler);
 83         }
 84     } catch (RemotingException e) {
 85         throw new RpcException("Fail to create remoting client for service...");
 86     }
 87     return client;
 88 }
 89 
 90 //Enter connect In the method, getExchanger Will pass SPI Load HeaderExchangeClient Examples, this method is relatively simple, let's take a look at it for yourself.Next analysis HeaderExchangeClient Implementation.
 91 
 92 public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
 93     if (url == null) {
 94         throw new IllegalArgumentException("url == null");
 95     }
 96     if (handler == null) {
 97         throw new IllegalArgumentException("handler == null");
 98     }
 99     url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
100     // Obtain Exchanger Instance, default to HeaderExchangeClient
101     return getExchanger(url).connect(url, handler);
102 }
103 
104 //Establish HeaderExchangeClient Example 
105 
106 public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
107     // There are several calls, as follows:
108     // 1. Establish HeaderExchangeHandler object
109     // 2. Establish DecodeHandler object
110     // 3. adopt Transporters structure Client Example
111     // 4. Establish HeaderExchangeClient object
112     return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
113 }
114 
115 //adopt Transporters structure Client Example
116 
117 public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
118     if (url == null) {
119         throw new IllegalArgumentException("url == null");
120     }
121     ChannelHandler handler;
122     if (handlers == null || handlers.length == 0) {
123         handler = new ChannelHandlerAdapter();
124     } else if (handlers.length == 1) {
125         handler = handlers[0];
126     } else {
127         // If handler Number greater than 1 creates one ChannelHandler Distributor
128         handler = new ChannelHandlerDispatcher(handlers);
129     }
130     
131     // Obtain Transporter Adaptively extend the class and call connect Method Generation Client Example
132     return getTransporter().connect(url, handler);
133 }
134 
135 //Establish Netty object
136 
137 public Client connect(URL url, ChannelHandler listener) throws RemotingException {
138     // Establish NettyClient object
139     return new NettyClient(url, listener);
140 }

The source code above can be divided into several logic parts:

    • Enter the creation of an instance of DubboInvoker by refer, in which serviceType, url, and invokers are virtually indifferent, and invokers are stored and created.The most critical is the getClient method.You can think of Invoker as a Netty client.Invoker on the service provider is a Wapper class.
    • The getClient method first determines whether to get a shared client or create a new client instance based on the number of connections. By default, it gets a shared client, but if the corresponding client is not available in the cache, it also creates a new client.The final return is the ExchangeClient, which currently has no communication capabilities and requires a more underlying Netty client.
    • The initClient method first gets the client type configured by the user, defaults to Netty, then detects if the client type configured by the user exists, throws an exception if it does not exist, and finally creates what type of client it feels like based on the lazy configuration.The LazyConnectExchangeClient code is not very complex, it creates an ExchangeClient client through Exchangers'connect method when the request method is called
    • getExchanger loads the HeaderExchangeClient instance through SPI.Finally, Netty clients are created through the Transporter implementation class and the API that calls Netty.The last layer returns, and finally becomes such a class with Netty at the bottom and DubboInvoker at the top.

refer in RegistryProtocol:

  1 public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
  2     // take registry Parameter value and set it as protocol header
  3     url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
  4     // Get Registry Instance
  5     Registry registry = registryFactory.getRegistry(url);
  6     if (RegistryService.class.equals(type)) {
  7         return proxyFactory.getInvoker((T) registry, type, url);
  8     }
  9 
 10     // take url Query string to Map
 11     Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
 12     // Obtain group To configure
 13     String group = qs.get(Constants.GROUP_KEY);
 14     if (group != null && group.length() > 0) {
 15         if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
 16                 || "*".equals(group)) {
 17             // adopt SPI Load MergeableCluster Instance and call doRefer Continue executing service reference logic
 18             return doRefer(getMergeableCluster(), registry, type, url);
 19         }
 20     }
 21     
 22     // call doRefer Continue executing service reference logic
 23     return doRefer(cluster, registry, type, url);
 24 }
 25 private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
 26     // Establish RegistryDirectory Example
 27     RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
 28     // Set up a registry and protocol
 29     directory.setRegistry(registry);
 30     directory.setProtocol(protocol);
 31     Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
 32     // Generate service consumer links
 33     URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
 34 
 35     // Register service consumers at consumers New Node in Directory
 36     if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
 37             && url.getParameter(Constants.REGISTER_KEY, true)) {
 38         registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
 39                 Constants.CHECK_KEY, String.valueOf(false)));
 40     }
 41 
 42     // Subscribe providers,configurators,routers Equal Node Data
 43     directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
 44             Constants.PROVIDERS_CATEGORY
 45                     + "," + Constants.CONFIGURATORS_CATEGORY
 46                     + "," + Constants.ROUTERS_CATEGORY));
 47 
 48     // A registry may have multiple service providers, so you need to merge multiple service providers into one
 49     Invoker invoker = cluster.join(directory);
 50     ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
 51     return invoker;
 52 }
 53 
 54 //Enter Cluster Creation Invoker Pattern
 55 
 56 @SPI(FailoverCluster.NAME)
 57 public interface Cluster {
 58 
 59     /**
 60      * Merge the Invoker of Directory into an Invoker
 61      */
 62     @Adaptive
 63     <T> Invoker<T> join(Directory<T> directory) throws RpcException;
 64 }
 65 
 66 //Enter MockerClusterWrapper In implementation class
 67 
 68 public class MockClusterWrapper implements Cluster {
 69 
 70     private Cluster cluster;
 71 
 72     public MockClusterWrapper(Cluster cluster) {
 73         this.cluster = cluster;
 74     }
 75 
 76     public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
 77         return new MockClusterInvoker<T>(directory, this.cluster.join(directory));
 78     }
 79 }
 80 
 81 //Concrete invoke Method
 82 
 83 public Result invoke(Invocation invocation) throws RpcException {
 84     Result result = null;
 85     
 86     String value = directory.getUrl().getMethodParameter(invocation.getMethodName(),
 87                              Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); 
 88     if (value.length() == 0 || value.equalsIgnoreCase("false")){
 89         //no mock
 90         result = this.invoker.invoke(invocation);
 91     } else if (value.startsWith("force")) {
 92         if (logger.isWarnEnabled()) {
 93             logger.info("force-mock: " + invocation.getMethodName() + 
 94                         " force-mock enabled , url : " +  directory.getUrl());
 95         }
 96         //force:direct mock
 97         result = doMockInvoke(invocation, null);
 98     } else {
 99         //fail-mock
100         try {
101             result = this.invoker.invoke(invocation);
102         }catch (RpcException e) {
103             if (e.isBiz()) {
104                 throw e;
105             } else {
106                 if (logger.isWarnEnabled()) {
107                     logger.info("fail-mock: " + invocation.getMethodName() + 
108                             " fail-mock enabled , url : " +  directory.getUrl(), e);
109                 }
110                 //fail:mock
111                 result = doMockInvoke(invocation, e);
112             }
113         }
114     }
115     return result;
116 }

Roughly speaking, the logic above:

    • The current Invoker base is still NettyClient, but at this point the registry is a cluster-building mode.So you need to merge multiple Invokers into one, which is a logical merge.In fact, there will still be many at the bottom of the Invoker, managed by just one cluster mode.So what's exposed is an Invoker in cluster mode.This enters the Cluster.join method.
    • Cluster is a generic proxy class that locates the actual Cluster implementation class, FailoverCluster, based on the value of the cluster parameter in the URL.The @SPI annotation is used here, which requires the ExtensionLoader extension point loading mechanism, which automatically covers Wapper after instantiation on the instantiated object
    • But it's cluster mode that requires another core mechanism in Dubbo, Mock.Mock can simulate various exceptions to service calls in tests and also implement service downgrades.In MockkerClusterInvoker, Dubbo first checks for the presence of a mock parameter in the URL.(This parameter can be set either through shielding and fault tolerance on the Consumer side of the Service Governance Background or directly by dynamically setting the mock parameter value) If there is a force start, this directly executes the demotion logic without launching a remote call.If a fail start exists, the demotion logic is executed only when a remote call exception is made.
    • It can be said that when the registry is in cluster mode, Invoker wraps an extra layer of mock logic outside.It is implemented through the Wapper mechanism.Ultimately, one of the multiple Invokers can be selected for each call or retry through the load balancing mechanism within Dubbo

4. Summary

The implementation of Invoker is finished here. To summarize, Invoker, the service provider, is an instance of a service class created by javassist, which implements calling methods within the service class and modifying fields.The service consumer's Invoker is a Netty-based client.Finally, service class instances created by service providers are obtained through service consumer Netty clients.The consumer then needs to create a proxy class for the service class in order to protect it, so that it can safely and effectively call the internal methods of the service class remotely without instantiating the service class and get specific data.

Posted by jini01 on Fri, 14 Feb 2020 23:45:09 -0800