This article is more than one year old. Older articles may contain outdated content. Check that the information in the page has not become incorrect since its publication.
Dubbo is claimed as a high-performance RPC framework on its official website. Today, I want to talk about another great specialty of Dubbo — its scalability. As quote: Rome wasn’t built in a day. Any successful system always starts as a prototype. It is impossible to design a perfect system at the beginning. Instead, we should focus on true demand and keep improving the system. On the coding side, it requires us to pay attention on abstraction layers and high-level isolation. In that case, the system could keep a healthy structure and easy to maintain while new features or third-party extensions are added. Under some circumstances, a designer should pursue more of scalability than the system’s current performance. When talking about software design, people always mention scalability. A framework with good scalability requires the following: 1.The framework should follow opening/closed principle: software entities should be open for extension, but closed for modification; This means a framework should allow the maintainer to add new functions with as few modifications as possible. 2.The framework should allow the user to add new functions by adding code on his project without modifying the framework’s original source code base. With microkernel architecture and extension mechanism, Dubbo satisfies such requirements and achieves good scalability. In the following chapters, we will discuss Dubbo’s extension mechanism in detail.
Creating Extensible applications usually considers:
As a framework, Dubbo does not wish to rely on other IoC containers such as Spring, Guice. OSGi is too complicated to fit Dubbo. In the end, Dubbo SPI is inherited from standard JDK SPI and makes it more powerful.
We will first discuss Java SPI mechanism, which is a basis for understanding Dubbo’s extension mechanism. If you are familiar with Java SPI, you can skip this part.
Java SPI (Service Provider Interface) is a feature for discovering and loading implementations matching a given interface provided in JDK. We can create a text file with the same name as the interface under resource directory META-INF/services
. The content of the file is the fully qualified class name of the SPI implementation, in which each component is separated by a line breaker. JDK uses java.util.ServiceLoader
to load implementations of a service. Let us use a simple example to show how Java SPI works.
public interface IRepository {
void save(String data);
}
public class MysqlRepository implements IRepository {
public void save(String data) {
System.out.println("Save " + data + " to Mysql");
}
}
public class MongoRepository implements IRepository {
public void save(String data) {
System.out.println("Save " + data + " to Mongo");
}
}
META-INF/services
.The file name is META-INF/services/com.demo.IRepository
, the content of file is:
com.demo.MongoRepository
com.demo.MysqlRepository
ServiceLoader<IRepository> serviceLoader = ServiceLoader.load(IRepository.class);
Iterator<IRepository> it = serviceLoader.iterator();
while (it != null && it.hasNext()){
IRepository demoService = it.next();
System.out.println("class:" + demoService.getClass().getName());
demoService.save("tom");
}
In the above example, we created an extension and two of its applications. We created the configuration file in ClassPath and loaded the extensions using ServiceLoader. The final output is: class:testDubbo.MongoRepository Save tom to Mongo class:testDubbo.MysqlRepository Save tom to Mysql
Java SPI is simple to use. It also supports basic extension point functions, however, it has some disadvantages:
Therefore, Java SPI is good for some simple scenarios, but does not fit for Dubbo. Dubbo makes some extensions on the original SPI mechanism. We will discuss more about the Dubbo SPI mechanism in the following sections.
Before diving into Dubbo’s extension mechanism,Let us first declare some basic concepts in Dubbo SPI. Those terms will appear multiple times in the following section.
an interface of java.
an implementation class of the Extension Point
instance of an extension point implementation class
Maybe it is a little difficult to understand this concept when hearing about it the first time. It may help you understand it better by calling it an extension proxy class. The extension adaptive instance is actually an extension proxy, which implements the method of extension point interface. When calling the interface method of the extension point, it will decide which extension to use according to the actual parameters. For example, the extension point of an IRepository has one save method, and two implementations MysqlRepository and MongoRepository. When calling the method of the interface, the adaptive instance of IRepository will determine which IRepository implementation to call according to the parameters in the save method. If the parameter repository=mysql in the method, then we can call the save method of MysqlRepository. If repository=mongo, then we can call the save method of MongoRepository, which is similar to late binding in Object-oriented languages. However, why does Dubbo introduce the concept of extended adaptive instances?
@SPI annotation works on the interface of the extension point, which indicates that the interface is an extension point, and can be loaded by Dubbo ExtensionLoader. If there is no such ExtentionLoader, the call will throw an exception.
@Adaptive annotation is used on the method that extends the interface, which indicates an adaptive method. When Dubbo generates an adaptive instance for an extension point, if the function has @Adaptive annotation, then Dubbo will generate the corresponding code for the method. The method determines which extension to use according to the parameters. When @Adaptive annotation is used on the class to implement a Decorator class, it is similar to the Decorator pattern, whose major function is to return a specified class. Currently in Dubbo, both AdaptiveCompiler and AdaptiveExtensionFactory have @Adaptive annotation.
Similar to the Java SPI ServiceLoader, it is responsible for loading extensions and life-cycle maintenance.
Different from Java, each extension in Dubbo has an alias, which is used to reference them in the application, such as
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
where random, roundrobin are alias of the corresponding extensions, and we can directly use them in the configuration file.
Similar to the way Java SPI loading the extension configuration from the META-INF/services
directory, Dubbo will also load the extension configuration file from the following path:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
Now that we know some basic idea about Dubbo, let us check a practical extension point in Dubbo to get some intuition.
We take the Dubbo’s LoadBalance extension point as an example. A service in Dubbo usually has multiple providers. When a consumer calls the service, he needs to choose one of the providers. This is an example of LoadBalance. Now, let us figure out how LoadBalance becomes an extension point in Dubbo.
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
LoadBalance interface has only one select method. Select method chose one invoker among multiple invokers. In the code above, the elements related to Dubbo SPI are:
RandomLoadBalance.NAME
is a constant with value “random” and is a random load balancing implementation. The definition of random is in the configuration file META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.LoadBalance
:random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance
There are four extension implementations of LoadBalance defined in the configuration file. The implementation of load balancing will not be covered in this article. The only thing we need to know is that Dubbo provides four kinds of load balancing implementations. We can explicitly specify an implementation by using xml file, properties file or JVM parameter. If there has no explicitly specified implementation, Dubbo will use random as default.
loadbalance
indicates that the value of loadbalance in method is the extension implementation that will be actually called. However, we cannot find loadbalance parameter in select method, then how can we obtain the value of loadbalance? There is another URL-type parameter in select method, and Dubbo obtains the value of loadbalance from that URL. Here we need to use Dubbo’s URL bus pattern, in one word, URL contains all the parameters in RPC. There is a member variable Map<String, String>parameters
in the URL class, which contains loadbalance as a parameterThe code of LoadBalance in Dubbo is as follows:
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);
Using ExtensionLoader.getExtensionLoader(LoadBalance.class) method to obtain an implementation of ExtensionLoader, then we call getExtension and pass an extension alias to obtain the corresponding extension implementation.
In this session, we will use a simple example to implement a LoadBalance and integrate it into Dubbo. I will show some important steps and codes, and the complete demo can be downloaded from the following address(https://github.com/vangoleo/dubbo-spi-demo).
First, we build a LoadBalance instance. Since we just need the instance to demonstrate Dubbo extension mechanism, it will be very simple. We choose the first invoker and print a log sentence in the console.
package com.dubbo.spi.demo.consumer;
public class DemoLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
System.out.println("DemoLoadBalance: Select the first invoker...");
return invokers.get(0);
}
}
Add file:META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.LoadBalance
. The content of file is:
demo=com.dubbo.spi.demo.consumer.DemoLoadBalance
Through the above 2 steps, we have already added a LoadBalance implementation named demo, and set up the configuration file. In the next step, we need to explicitly tell Dubbo to implement the demo while doing load balancing. If we use Dubbo through spring, we could set it up in the xml file.
<dubbo:reference id="helloService" interface="com.dubbo.spi.demo.api.IHelloService" loadbalance="demo" />
Configure <loadbalance=“demo”> in dubbo:reference at consumer part.
Launch Dubbo and call IHelloService, the console will output log: DemoLoadBalance: Select the first invoker...
, which means Dubbo does use our customized LoadBalance.
So far, we learnt the basic concepts of Dubbo SPI beginning with Java SPI, and we used LoadBalance in Dubbo as an example to help us understand better. Finally, we practiced and created a customized LoadBalance and integrated it to Dubbo. We believe that combining concepts and practice, everyone can get a better idea of Dubbo’s scalability. To summarize, Dubbo SPI has the following features:
In the next article, we will go deep and check Dubbo’s source code to learn more about Dubbo’s extensibility mechanism.