An experience and analysis of Dubbo

original
2017/02/21 22:16
Number of readings 475

What is Dubbo
Dubbo is a distributed service framework, which is committed to providing high-performance and transparent RPC remote service invocation solutions and SOA service governance solutions;
Its core parts include:
Remote communication: provides an abstract encapsulation of a variety of NIO frameworks based on long connections, including a variety of thread models, serialization, and "request response" mode of information exchange.
Cluster fault tolerance: provide transparent remote procedure calls based on interface methods, including multi protocol support, and cluster support such as soft load balancing, fault tolerance, address routing, and dynamic configuration.
Automatic discovery: based on the registry directory service, service consumers can dynamically find service providers, make addresses transparent, and enable service providers to smoothly increase or decrease machines.

The architecture is as follows: from the official website

Provider: The service provider that exposes the service.
Consumer: The service consumer who calls the remote service.
Registry: the registry for service registration and discovery.
Monitor: the monitoring center that counts the call times and call times of services.
Container: service running container.

Integration test with Spring and Zookeeper
From the above architecture diagram, we know that the whole system needs five parts. However, in order to facilitate testing, only three parts are provided here: Provider, Consumer and Registry; The code structure is shown below:

DubboTest is a public parent project, and dubboProvider and dubboConsumer are its sub projects, which correspond to two parts: Provider and Consumer. As for Registry, which is supported by Zookeeper, each part is described in detail below.

1. Public Maven dependency

 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.5.3</version> <exclusions> <exclusion> <artifactId>spring</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency>

Mainly Spring, Zookeeper and Dubbo related packages

2. Introduction to Provider
Provide an external interface class DemoService

 public interface DemoService { String syncSayHello(String name); String asyncSayHello(String name); }

Provides the implementation class DemoServiceImpl of DemoService

 public class DemoServiceImpl implements DemoService { @Override public String syncSayHello(String name) { return "sync Hello " + name; } @Override public String asyncSayHello(String name) { return "async Hello " + name; } }

Provide the provider configuration file

 <? xml version="1.0" encoding="UTF-8"?> <beans xmlns=" http://www.springframework.org/schema/beans " xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance "    xmlns:dubbo=" http://code.alibabatech.com/schema/dubbo " xsi:schemaLocation=" http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans.xsd            http://code.alibabatech.com/schema/dubbo            http://code.alibabatech.com/schema/dubbo/dubbo.xsd "> <!--  The provider application information is used to calculate dependency --> <dubbo:application name="hello-world-app" /> <!--  Use the zookeeper registry to expose the service address --> <dubbo:registry address=" zookeeper://127.0.0.1:2181 " /> <!--  Use the dubbo protocol to expose services -->on port 20880 <dubbo:protocol name="dubbo" port="20880" /> <!--  Declare the service interface to be exposed --> <dubbo:service interface="org.dubboProvider.DemoService" ref="demoService" /> <!--  Implement services like local beans --> <bean id="demoService" class="org.dubboProvider.DemoServiceImpl" /> </beans>

Dubbo: registry: provides a registry. For the convenience of configuring a local zookeeper
Dubbo: service: provides an external service interface

Provide the provider startup class Provider

 import org.springframework.context.support.ClassPathXmlApplicationContext; public class Provider { public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( new String[] { "dubbo-provider.xml" }); context.start(); System.in.read(); //  press any key to exit } }

3. Introduction to Consumer
Consumer configuration file dubbo-consumer.xml

 <? xml version="1.0" encoding="UTF-8"?> <beans xmlns=" http://www.springframework.org/schema/beans " xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance " xmlns:dubbo=" http://code.alibabatech.com/schema/dubbo " xsi:schemaLocation=" http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans.xsd            http://code.alibabatech.com/schema/dubbo            http://code.alibabatech.com/schema/dubbo/dubbo.xsd "> <!--  Consumer application name, used to calculate dependency, is not a matching condition, and should not be the same as the provider --> <dubbo:application name="consumer-of-helloworld-app" /> <dubbo:registry address=" zookeeper://127.0.0.1:2181 " /> <!--  To generate a remote service proxy, you can use demoService -->like a local bean <dubbo:reference id="demoService" interface="org.dubboProvider.DemoService" > <dubbo:method name="syncSayHello" async="false" /> <dubbo:method name="asyncSayHello" async="true" /> </dubbo:reference> </beans>

Consumer Test Class Consumer

 import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.dubboProvider.DemoService; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.alibaba.dubbo.rpc.RpcContext; public class Consumer { public static void main(String[] args) throws InterruptedException, ExecutionException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "dubbo-consumer.xml"); context.start(); DemoService demoService = (DemoService) context.getBean("demoService"); //  Get Remote Service Proxy System.out.println(demoService.syncSayHello("world")); System.out.println(demoService.asyncSayHello("world")); Future<String> futrue = RpcContext.getContext().getFuture(); System.out.println(futrue.get()); } }

Run the Consumer class, and the results are as follows:

 sync Hello world null async Hello world

 

Brief analysis of calling process
1. Start the provider and read the configuration file
The provider starts to register server related information with Zookeeper. The main interface class is RegistryService. The four implementation classes are ZookeeperRegistry, RedisRegistry, DubboRegistry and MulticastRegistry. ZookeeperRegistry is used here. When the provider is started, the doRegistry() method will be called. The code is as follows:

 protected void doRegister(URL url) { try { zkClient.create(toUrlPath(url), url.getParameter(Constants. DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }

Url details: dubbo://192.168.67.13:20880/org.dubboProvider.DemoService?anyhost=true&application=hello -world-app&dubbo=2.5.3&interface=org.dubboProvider. DemoService&methods=syncSayHello,asyncSayHello&pid=4952&side=provider&timestamp=1487671777581
Create a node on Zookeeper through zkclient to prepare for the Consumer to obtain the node.

2. Start the Consumer and read the configuration file
Listen to the server information node registered in Zookeeper, and establish a connection with the remote server through the node information. All clients inherit from AbstractClient, and the corresponding implementation classes are NettyClient, MinaClient, and GrizzlyClient; The three are the underlying communication frameworks based on java nio. NettyClient is used by default. The connection with the server is established in the AbstractClient construction method. Some codes are as follows:

 public AbstractClient(URL url, ChannelHandler handler) throws RemotingException { //... Omit connect(); //... Omit }

Corresponding doConnect() in NettyClient

 protected void doConnect() throws Throwable { long start = System.currentTimeMillis(); ChannelFuture future = bootstrap.connect(getConnectAddress()); //... Omit NettyClient.this.channel = newChannel; //... Omit }

The established connection is saved in NettyClient.

3. Generate dynamic proxy
The remote method is called through a simple interface call. In fact, Dubbo helps us generate a dynamic proxy class. All the questions about establishing remote connections, message encapsulation and coding, message sending, and message receiving and decoding are handled in the dynamic proxy class
The proxy factory classes provided by dubbo all inherit from the AbstractProxyFactory class, and the corresponding implementation classes are JdkProxyFactory and JavassistProxyFactory. JavassistProxyFactory is used by default, and the corresponding code is as follows:

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

4. Method call triggers InvokerInvocationHandler to call invoke method
The invoke method code is as follows:

 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(); }

RpcInvocation is used to encapsulate the relevant parameters of the method call, such as method name, parameter type, parameter list, etc. You can view the relevant code:

 public class RpcInvocation implements Invocation, Serializable { private static final long serialVersionUID = -4355285085441097045L; private String               methodName; private Class<?>[]           parameterTypes; private Object[]             arguments; private Map<String, String>  attachments; private transient Invoker<?> invoker; ......//The following is omitted

After being called layer by layer, we finally arrive at the doInvoke method of DubboInvoker, which is also the class we care about. The code is as follows:

 protected Result doInvoke(final Invocation invocation) throws Throwable { RpcInvocation inv = (RpcInvocation) invocation; final String methodName = RpcUtils.getMethodName(invocation); inv.setAttachment(Constants. PATH_KEY, getUrl().getPath()); inv.setAttachment(Constants. VERSION_KEY, version); ExchangeClient currentClient; if (clients.length == 1) { currentClient = clients[0]; } else { currentClient = clients[index.getAndIncrement() % clients.length]; } try { boolean isAsync = RpcUtils.isAsync(getUrl(), invocation); boolean isOneway = RpcUtils.isOneway(getUrl(), invocation); int timeout = getUrl().getMethodParameter(methodName,  Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT); if (isOneway) { boolean isSent = getUrl().getMethodParameter(methodName,  Constants.SENT_KEY, false); currentClient.send(inv, isSent); RpcContext.getContext().setFuture(null); return new RpcResult(); } else if (isAsync) { ResponseFuture future = currentClient.request(inv, timeout) ; RpcContext.getContext().setFuture(new FutureAdapter<Object>(future)); return new RpcResult(); } else { RpcContext.getContext().setFuture(null); return (Result) currentClient.request(inv, timeout).get(); } } catch (TimeoutException e) { throw new RpcException(RpcException. TIMEOUT_EXCEPTION,  "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e); } catch (RemotingException e) { throw new RpcException(RpcException. NETWORK_EXCEPTION,  "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e); } }

There are synchronous and asynchronous calls we care about:
Synchronous call: Call the get() method of ResponseFuture to wait for the return of the server
Asynchronous call: Instead of waiting for the server to return, we directly put ResponseFuture into RpcContext. getContext(), which is also why we need to use Future true=RpcContext. getContext(). getFuture() in our code; Reason for.

5. Send request
In the second step, the connection with the remote server has been established. The number of connections is related to the cluster server, so the client is an array. Get a client connection from the array

 if (clients.length == 1) { currentClient = clients[0]; } else { currentClient = clients[index.getAndIncrement() % clients.length]; }

After obtaining, execute currentClient. request (inv, timeout), which is to send a request to the server. The following code appears in the request method:

 public ResponseFuture request(Object request, int timeout) throws RemotingException { if (closed) { throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!"); } // create request. Request req = new Request(); req.setVersion("2.0.0"); req.setTwoWay(true); req.setData(request); DefaultFuture future = new DefaultFuture(channel, req, timeout); try{ channel.send(req); }catch (RemotingException e) { future.cancel(); throw e; } return future; }

Encapsulates the message to be sent into a Request object and returns the DefaultFuture (inherited from the ResponseFuture to realize synchronous and asynchronous calls). Take a look at the get() method of DefaultFuture:

 public Object get(int timeout) throws RemotingException { if (timeout <= 0) { timeout = Constants.DEFAULT_TIMEOUT; } if (!  isDone()) { long start = System.currentTimeMillis(); lock.lock(); try { while (!  isDone()) { done.await(timeout,  TimeUnit.MILLISECONDS); if (isDone() || System.currentTimeMillis() - start > timeout) { break; } } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } if (!  isDone()) { throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false)); } } return returnFromResponse(); }

While keeps cycling until isDone is true or timeout occurs. In fact, isDone judges whether the response is empty:

 public boolean isDone() { return response != null; }

6. Receive messages
The received () in DefaultFuture is used to receive messages and assign a value to the Response response, so that the isDone () method can be true

summary
The above is just a superficial use of dubbo and a simple understanding of the whole process of the call. There is not much more in-depth, mainly because the project is not actually used; However, we can see that the underlying communication of dubbo is based on the high-performance communication framework of netty and mina, and handshake is reduced through long connections; Binary stream compresses data faster than short connection protocols such as regular HTTP, which is not mentioned above. For more information, you can view the encoding and decoding class DubboCodec; It can be considered that the performance of dubbo is quite strong.

Expand to read the full text
Loading
Click to lead the topic 📣 Post and join the discussion 🔥
Reward
zero comment
two Collection
zero fabulous
 Back to top
Top