Deep Dubbo Source - The Use and Benefits of SPI

background

I believe that students who have read the Dubbo source code should see that there is a @SPI annotation on many interfaces in Dubbo, and the author is no exception. But I have never known what this annotation is specifically for, in order to solve what problems, how to use it? Simple online search, the Chinese name: Service Provision Interface, see the following figure (from Baidu Encyclopedia).

Maybe dubbo itself is powerful, so I just know that dubbo can customize some strategies, such as load balancing, serialization, thread pool type, etc., but it has not been used in the officially online environment. Take advantage of the holidays to spend some time to study and record, hoping to be useful to everyone.

Code sample

The following codes are locally validated and purely hand-tapped. Detailed implementation is provided. Glory's Github
Note: Build spring-boot for test project+ spring-boot-dubbo

Verification idea

As shown in the figure above, @SPI is a simple implementation to implement a specific service. The most familiar one is the Load Balance strategy, which starts two providers locally with different ports and decides to access the specified provider through the participation of consumer.

Start the provider

The code is extremely simple, and the code framework is as follows

import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.rpc.RpcContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// Specified version and grouping
@Service(version = "1.0.0",group = "glory")
public class DemoServiceImpl implements DemoService {
    private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);

    @Override
    public String sayHello(Integer port) {
        logger.info("Hello " + port + " request from consumer: " + RpcContext.getContext().getRemoteAddress());
        return "Hello ,"+port+" response from provider: " + RpcContext.getContext().getLocalAddress();
    }

}

The following is the application.yml configuration file

server:
  port: 8083
dubbo:
  application:
    name: dubbo-common-provider
  scan:
    base-packages: com.redsun.rpc.dubbo
  protocol:
    name: dubbo
    port: 12345
  registry:
    address: zookeeper://127.0.0.1:2181

It is also important to note that different ports need to be specified at startup time, whether they cannot be started.

At this time, two local applications can be booted normally, and the boot effect is as follows:

Start consumer

consumer's code framework is also simple

As you can guess, Glory LoadBalance is a load balancing strategy implemented by itself, which chooses invoker by the parameters passed in from the front end.

public class GloryLoadBalance implements LoadBalance {

    private static final Logger logger = LoggerFactory.getLogger(GloryLoadBalance.class);

    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        Integer port = (Integer) invocation.getArguments()[0];// Front-end incoming parameters
        logger.info("The front-end incoming port is:" + port);
        // Java 8 streaming programming, interested students can study, and later will write a special article
        Invoker<T> invokerRet = invokers.stream().filter(invoker -> invoker.getUrl().getPort() == port).findFirst().get();
        return invokerRet == null ? invokers.get(0) : invokerRet;
    }
}

Here we also write a simple controller, which is convenient for changing parameters dynamically.

@RestController
@RequestMapping("/admin")
public class AdminController {
    private static final Logger logger = LoggerFactory.getLogger(AdminController.class);

    @Reference(version = "1.0.0",group = "glory", loadbalance = "glory")
    private DemoService demoService;

    @RequestMapping("/invoke")
    public String invoke(@RequestParam(name = "port") Integer port) {
        logger.info("invoke method be invoked!port = " + port);
        return demoService.sayHello(port);
    }
}

Of course, the last and most important step is to add a configuration file to the META-INF.dubbo.international directory. The glory before the equal sign below is actually the key of the loadbalance you configure. If the path is wrong or not configured, you will still get the default implementation random.

glory=com.redsun.rpc.dubbo.loadbalance.GloryLoadBalance

Dubbo loads all configuration information in the META-INF.dubbo.internal directory, and there are many default implementations in the Dubbo directory.

postman call

The following two graphs are tested to meet expectations.

Source code

Now it's time to turn over the source code. Here's a brief account of my own experience in looking at the source code.

  • Don't get bogged down in the implementation of each method. It's debug and dizzy, so it's easy to follow and lose interest at last.
  • Learn to read annotations, especially method names, class names, variable names, source code is different from the usual code written by themselves, since open source, is for everyone, so annotations, naming will often write very detailed and standardized, English do not know? Look at the translation, you will know more times.
  • When debug follows, remember several core classes, comb the whole call chain after reading, and have a general understanding of the code structure (I really don't want to see, Baidu can do it, I often do it, and then follow it with myself and authenticate it).

Load

ExtensionLoader#getExtensionClasses

    // synchronized in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

    /**
     * put clazz in extensionClasses
     */
    private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
        Class<?> c = extensionClasses.get(name);
        if (c == null) {
            // Store the extended class GloryLoadBalance in the map, and eventually store all the default offerings in the map.
            extensionClasses.put(name, clazz);
        } else if (c != clazz) {
            throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName());
        }
    }

Loading the implementation class will eventually set the load balancing parameter of ReferenceConfig to glory

Function

AbstractClusterInvoker#invoke

When consumer initiates invoke, selective instances are created based on config's key

    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();

        // binding attachments into invocation.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }

        List<Invoker<T>> invokers = list(invocation);
        // Initialize Load Balancing Class
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);
    }

ExtensionLoader#getExtensionLoader

Each file in the META-INF.dubbo.internal directory is an Extension Loader object stored in a static class variable EXTENSION_LOADERS

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        // Here, determine whether or not to be modified with SPI annotations
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }

        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

ExtensionLoader#getExtension

If it is empty at this time, it will be used through an instance of the lass information newInstance loaded above. The code is relatively simple, and you can follow it if you are interested.

summary

From the above, we can see that if you want to expand the use of dubbo, it will be very simple to add an implementation class (implementation of the corresponding interface), and then add a configuration file in the META-INF directory, key corresponding can be completed. Serialization can also be done as you wish.
When you follow the code, you can see that dubbo loads Class objects into some other extension classes that are not used, which is a little flaw.

Keywords: Dubbo Apache Spring github

Added by meltingpoint on Sun, 15 Sep 2019 11:57:35 +0300