This article is part of the Alibaba Open Source series.
As the absolute cornerstone of distributed systems, remote calling capability has given rise to a range of supporting technologies including COBRA, RMI, EJB, and WebService. Today, as microservices and servitization gain in popularity, lightweight service frameworks like gRPC, Finagle, and Apache Dubbo are finding wider deployment in conjunction with supporting facilities like service registries and configuration centers, enabling them to serve as a setting for distributed servitization.
In this article, Alibaba looks in technical detail at Dubbo and methods for quickly developing applications with it, with step-by-step guides developers can explore on their own.
Java RMI Fundamentals
Java RMI is a remote method invocation that enables clients to invoke an object method in a server-end Java virtual machine as if it were a local call. RMI improves on RPC (remote procedure call) in the object-oriented language domain in that users need not rely on IDL’s help to complete distributed calls, instead relying on the simpler and more natural approach of interfaces.
The following sections discuss Java RMI’s key principles and concepts in detail.
Java RMI functional principles
The following figure illustrates the layout of a typical RMI:
According to the figure above, the server first binds its own address to the RMI registration service, after which the client obtains the target address through the RMI registration service. The client then calls the method in the local stub object in the same way the method in the local object is called. Next, the local stub object packages the call information and sends it to the server over the network. After receiving the network request, the server’s Skeleton object unpacks the call information. Finally, the server’s Skeleton object finds the real service object to invoke the call, and packages the returning result back to the client over the network.
Java RMI basic concepts
In the world of Java, Java RMI is the technological cornerstone for distributed applications, and the basic concepts involved in subsequent EJB technology and modern distributed service frameworks remain a continuation of Java RMI fundamentally.
RMI calls involve several core concepts. For the first, remote call through interface, the client needs to rely on the interface while the server needs to provide an implementation of the interface. For the second, that the remote call is disguised with the help of the client’s stub object and the server’s skeleton object, the client’s stub object and the server’s skeleton object have previously needed to be pre-compiled through rmic; in subsequent versions to J2SE 1.5, this pre-generation is no longer necessary. The third key concept, completing registration and discovery services through the RMI registration interface, is illustrated in the following two code examples.
In this code for service registration on the server end, the following steps are taken:
1. Initialize the service object instance.
2. Generate a stub object that can communicate with the server through UnicastRemoteObject.export object.
3. Create a local RMI registration service with a monitoring port of 1099; this registration service runs on the server side, or it can start a registration service process separately.
4. Bind the stub object to the resignation service so that the client can find the remote object named “Hello”.
In this code for service discovery on the client end, the following steps are taken:
1. Obtain the registration service instance. Since no parameters are passed for this example, it is assumed that the registration service instance to be obtained is deployed on the local machine and monitored on port 1099.
2. Obtain the remote object with the service name “Hello” from the registration service.
3. Initiate an RMI call with the obtained stub object and get the result.
A comprehensive understanding of the basic concepts and principles at work in RMI is essential to mastering modern distributed service frameworks. To this end, developers should consider studying the official RMI textbook in detail.
Fundamentals of Dubbo
Modern distributed service frameworks are based on a similar concept to that of RMI, using Java’s interfaces as the service contract for completing registration and discovery of services through the registry. The details of remote communication are also the same for each, being blocked through proxy classes. Specifically, Dubbo has five key roles to play:
1. Service provider; Dubbo is responsible for exposing the service on the designated port at startup and registers the service address and port to the registry.
2. Service consumer; Dubbo subscribes to services of interest from the registry to obtain the service provider’s address list.
3. Registry; Dubbo is responsible for the registration and discovery of services, and for preserving the address information reported by the service provider and pushing it to the service consumer.
4. Monitoring center; Dubbo collects the running status of both the service provider and the consumer for monitoring, including the number of service calls, latencies, and so on.
5. Execution container; Dubbo is responsible for initialization, loading, and execution lifecycle management of the service provider.
During the deployment phase, the service provider exposes the service on the designated port and registers the service information with the register, while the service consumer initiates a subscription to the service address list in the registry.
During the execution phase, the registry pushes the address list to the service consumer; after receiving the list, the service consumer selects one address to initiate a call to the target service. During the calling process, the running status of the service consumer and service provider are reported in the monitoring center.
API-based Dubbo applications
Generally, Dubbo applications are assembled through Spring. For the sake of quickly providing a workable Dubbo application, the following example abandons complex configuration and uses a Dubbo API-oriented approach to construct the service provider and service consumer. In addition, the registry and monitoring center in this example do not need to be installed and configured.
In a production environment, Dubbo’s services require a distributed service registry such as ZooKeeper to work with. To facilitate development, Dubbo provides both direct connection and multicast, thus avoiding the extra work of setting up a registry.
The following codes show an example in which the multicast method is used to complete registration and discovery of services:
In the above step for defining a service contract, a simple GreetingsService contract is defined in which only one method, “sayHI”, can be called; the input parameter is a string type, as is the return value.
In the above step for providing implementation of the contract, the service provider needs to implement the service contract GreetingsService interface. After, the implementation simply returns a welcome message; if the input parameter is “dubbo”, it will return “hi, dubbo”.
In the above step for implementing a Dubbo service provider, a ServiceConfig instance must be created with the generic parameter information being the service interface type (in this case GreetingsService). An ApplicationConfig instance is then generated and assembled into ServiceConfig. Next, a RegistryConfig instance is generated and assembled into ServiceConfig. Here, the multicast method is used, where the parameter is multicast://126.96.36.199:1234, and the legal multicast address range is 188.8.131.52–184.108.40.206. The fourth procedure is to assemble the service contract GreetingsService into ServiceConfig, after which the instance of the provider’s implementation of GreetingsServiceImpl must be assembled into ServiceConfig. ServiceConfig thus has enough information to start exposing the service, with the default monitoring port as 20880. To prevent the server from exiting, any key or ctrl-c can be pressed to exit.
In the above step for finally implementing a Dubbo service caller, a ReferenceConfig instance is first created; the generic parameter information is the service interface type, which is the GreetingService. Next, an ApplicationConfig instance is generated and assembled into ReferenceConfig. A RegistryConfig instance is then generated and assembled into ReferenceConfig; it should be noted that the multicast address information here needs to be the same as the service provider’s. Next, the service contract GreetingsService is assembled into ReferenceConfig, after which the proxy for GreetingService is obtained from ReferenceConfig. Finally, a remote call through the GreetingService agent is initiated with the passed parameter of dubbo, and the returned result “hi, dubbo” is printed.
A full example of execution can be found at https://github.com/dubbo/dubbo-samples/tree/master/dubbo-samples-api. In it, exec-maven-plugin is configured, meaning it can be easily executed by maven on the command line. It can also be executed directly in the IDE, but it should be noted that since the multicast method is used to discover the service, -Djava.net.preferIPv4Stack=true must be specified at runtime.
To construct an example, the following commands can be used to synchronize the sample code and complete construction:
1. Synchronize the code: git clone https://github.com/dubbo/dubbo-samples.git
2. Construct mvn clean package
When BUILD SUCCESS appears, construction is complete and the execution phase can begin.
To run the server end, the following maven command is used to start the service provider:
When “first-dubbo-provider is running” appears, the service provider is ready to start and waiting for the client to call.
To run the client end, the following maven command is used to call the service:
As the code indicates, “hi, dubbo” is the execution result that returns from the service provider.
Quickly Generating Dubbo Applications
Dubbo offers a public service for quickly building Spring Boot-based Dubbo applications. Developers can visit http://start.dubbo.io to generate a sample project, as shown in the following example:
Beginning from this page, the following steps should be taken:
1. Provide maven groupId in the Group field; the default value is “com.example”.
2. Provide maven artifactId in the Artifact field; the default value is “demo”.
3. Provide the service name in the DubboServiceName field; the default value is com.example.HelloService.
4. Provide the service version in the DubboServiceVersion field; the default value is 1.0.0.
5. In the Client/Server fields, select whether the project is a service provider or service consumer using the “Server” and “Client” checkboxes, respectively; the default value is “Server”.
6. Use embeddedZookeeper as the service registration discovery; this option is checked by default.
7. Choose whether to activate the gos port, which is unchecked by default; if checked, it can be accessed through port 22222.
8. Click the “Generate Project” button to download the generated project.
This example shows the generation of a service provider. Similarly, the corresponding service consumer is generated by selecting “client” in the generation interface.
Opening the generated project with IDE will show that the application is a typical Spring Boot application. The entry to the program is as follows:
For the above, the embedded ZooKeeper is started on port 2181, after which the Spring Boot context is started. This can be run in IDE directly, with the following output:
Above, the configuration information printed in the output that starts with “dubbo” is defined in main/resources/application.properties.
Managing services through telnet
In choosing to activate gos when generating a project, it is possible to manage the service and view its status through telnet or nc.
Currently, gos supports the following commands:
1. Is: listing information for the consumer and provider
2. Online: online services
3. Offline: offline services
4. Help: online help
More information is available in official documentation covering gos.
Key Points Reviewed
Starting with RMI, this guide has introduced the basic concepts underlying distributed calls in the Java domain, specifically covering interface programming, disguising remote calls through agents, and registering and discovering services through the registry.
In terms of practical examples, the illustration of how to develop a complete application in Dubbo using simple multicast registration methods and programming based directly on the Dubbo APO has been chosen for the sake of simplicity. Nevertheless, a deeper understanding of how to use ServiceConfig and ReferenceConfig is helpful in further uses of Spring XML configuration and even Spring Boot programming.
The closing example describing how to quickly build a Spring Boot-based Dubbo application through the public service start.dubbo.io, using gos to perform simple operation and maintenance for Dubbo services, has been contributed by the Dubbo team.
(The original article is written by Luo Yi罗毅)