1. 首页
  2. Dubbo源码解析
  3. Dubbo 源码分析 —— 服务调用(一)之本地调用(Injvm)

Dubbo 源码分析 —— 服务调用(一)之本地调用(Injvm)

  • 发布于 2024-11-09
  • 11 次阅读

Dubbo 是一个高性能的 Java RPC 框架,支持多种调用方式,包括本地调用(Injvm)、远程调用(Dubbo 协议、HTTP 协议等)。本文将重点分析 Dubbo 的本地调用(Injvm)机制。

1. 本地调用(Injvm)概述

本地调用(Injvm)是指在同一个 JVM 内部进行的服务调用,不需要通过网络传输。这种方式可以显著提高调用性能,因为它避免了网络延迟和序列化开销。

2. Injvm 调用的实现

2.1 InjvmProtocol 类

InjvmProtocolProtocol 接口的一个实现,专门用于处理本地调用。以下是 InjvmProtocol 类的关键方法:

public class InjvmProtocol implements Protocol {
​
    private static final Logger logger = LoggerFactory.getLogger(InjvmProtocol.class);
​
    private final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<>();
​
    @Override
    public int getDefaultPort() {
        return 0; // Injvm 协议不需要端口号
    }
​
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        // 将服务导出到本地
        String key = getkey(invoker.getUrl());
        if (exporterMap.containsKey(key)) {
            throw new IllegalStateException("Service already exported " + invoker.getUrl());
        }
        exporterMap.put(key, new InjvmExporter<>(invoker));
        return new InjvmExporter<>(invoker);
    }
​
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        // 从本地获取服务
        url = url.setProtocol(InjvmProtocol.class.getName())
                .addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString());
        return new InjvmInvoker<>(type, url, getInvoker(url));
    }
​
    @Override
    public void destroy() {
        for (Exporter<?> exporter : exporterMap.values()) {
            try {
                exporter.unexport();
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
        }
        exporterMap.clear();
    }
​
    private <T> Invoker<T> getInvoker(URL url) {
        String key = getkey(url);
        Exporter<T> exporter = (Exporter<T>) exporterMap.get(key);
        if (exporter == null) {
            throw new IllegalStateException("Service not found: " + key);
        }
        return exporter.getInvoker();
    }
​
    private String getkey(URL url) {
        return url.getParameterAndEncoded(Constants.EXPORT_KEY);
    }
}

2.2 InjvmExporter 类

InjvmExporter 类是 Exporter 接口的一个实现,用于将服务导出到本地。以下是 InjvmExporter 类的关键方法:

public class InjvmExporter<T> implements Exporter<T> {
​
    private final Invoker<T> invoker;
​
    public InjvmExporter(Invoker<T> invoker) {
        this.invoker = invoker;
    }
​
    @Override
    public Invoker<T> getInvoker() {
        return invoker;
    }
​
    @Override
    public void unexport() {
        // 本地导出不需要实际的网络关闭操作
    }
}

2.3 InjvmInvoker 类

InjvmInvoker 类是 Invoker 接口的一个实现,用于在本地调用服务。以下是 InjvmInvoker 类的关键方法:

public class InjvmInvoker<T> extends AbstractInvoker<T> {
​
    private final Invoker<T> invoker;
​
    public InjvmInvoker(Class<T> type, URL url, Invoker<T> invoker) {
        super(type, url, Collections.emptyList());
        this.invoker = invoker;
    }
​
    @Override
    protected Result doInvoke(Invocation invocation) throws Throwable {
        return invoker.invoke(invocation);
    }
​
    @Override
    public boolean isAvailable() {
        return invoker.isAvailable();
    }
​
    @Override
    public void destroy() {
        invoker.destroy();
    }
}

3. 本地调用的流程

  1. 服务导出

    • 当服务提供者启动时,InjvmProtocolexport 方法会被调用。

    • 服务提供者将服务导出到本地,并将 Exporter 实例存储在 exporterMap 中。

  2. 服务引用

    • 当服务消费者启动时,InjvmProtocolrefer 方法会被调用。

    • 服务消费者从 exporterMap 中获取服务的 Invoker 实例,并创建 InjvmInvoker 实例。

  3. 服务调用

    • 当服务消费者调用服务时,InjvmInvokerdoInvoke 方法会被调用。

    • doInvoke 方法会调用本地服务提供者的 Invoker 实例,完成服务调用。

4. 代码示例

以下是一个简单的示例,展示如何在 Dubbo 中使用 Injvm 调用:

4.1 服务提供者

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
​
public class ProviderApplication {
​
    public static void main(String[] args) {
        ServiceConfig<GreetingService> serviceConfig = new ServiceConfig<>();
        serviceConfig.setApplication(new ApplicationConfig("injvm-provider"));
        serviceConfig.setRegistry(new RegistryConfig("injvm://"));
        serviceConfig.setInterface(GreetingService.class);
        serviceConfig.setRef(new GreetingServiceImpl());
​
        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
        bootstrap.application(new ApplicationConfig("injvm-provider"))
                .service(serviceConfig)
                .start()
                .await();
    }
}
​
interface GreetingService {
    String sayHello(String name);
}
​
class GreetingServiceImpl implements GreetingService {
    @Override
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

4.2 服务消费者

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
​
public class ConsumerApplication {
​
    public static void main(String[] args) {
        ReferenceConfig<GreetingService> referenceConfig = new ReferenceConfig<>();
        referenceConfig.setApplication(new ApplicationConfig("injvm-consumer"));
        referenceConfig.setRegistry(new RegistryConfig("injvm://"));
        referenceConfig.setInterface(GreetingService.class);
​
        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
        bootstrap.application(new ApplicationConfig("injvm-consumer"))
                .reference(referenceConfig)
                .start();
​
        GreetingService greetingService = referenceConfig.get();
        String result = greetingService.sayHello("World");
        System.out.println(result);
    }
}

5. 总结

通过上述分析,我们可以看到 Dubbo 的本地调用(Injvm)机制主要通过以下步骤实现:

  1. 服务导出:服务提供者将服务导出到本地,并将 Exporter 实例存储在 exporterMap 中。

  2. 服务引用:服务消费者从 exporterMap 中获取服务的 Invoker 实例,并创建 InjvmInvoker 实例。

  3. 服务调用:服务消费者通过 InjvmInvoker 实例调用本地服务提供者的 Invoker 实例,完成服务调用。

Injvm 调用方式避免了网络传输和序列化开销,显著提高了调用性能,特别适用于同一个 JVM 内部的服务调用。希望这些内容对你理解 Dubbo 的本地调用机制有所帮助。

在Dubbo框架中,服务调用是一个核心功能,它允许服务消费者调用服务提供者的方法。在服务调用中,本地调用(Injvm)是一种特殊的调用方式,它发生在同一个JVM内部。以下是Dubbo中本地调用(Injvm)的详细分析:

本地调用(Injvm)概述

  1. Injvm协议:Dubbo中的injvm是一个伪协议,它不开启端口,不发起远程调用,只在JVM内直接关联,但执行Dubbo的Filter链。

  2. 使用场景:当一个应用既是服务的提供者,同时也是服务的消费者时,可以直接对本机提供的服务发起本地调用。

  3. 性能优势:相比于远程调用,Dubbo本地调用性能较优,因为它省去了请求、响应的编解码及网络传输的过程。

  4. 配置简单:使用Dubbo本地调用不需要特殊配置,按正常Dubbo服务暴露即可。服务在暴露远程服务的同时,也会以injvm协议暴露本地服务。

本地调用流程

  1. 服务暴露:服务提供者在启动时,会将服务以injvm协议暴露,这样在同一进程内的服务消费者可以直接调用这个服务。

  2. 服务调用:服务消费者在调用服务时,Dubbo会优先使用injvm协议进行本地调用。

  3. Filter链:Dubbo本地调用会经过Filter链,包括Consumer端的Filter链以及Provider端的Filter链。这样可以保证本地消费者和其他消费者统一对待,统一监控,服务统一进行治理。

  4. 自动暴露:从Dubbo 2.2.0版本开始,默认在本地以injvm的方式暴露服务。在引用服务时,默认优先引用本地服务。如果需要强制引用远程服务,可以使用配置<dubbo:reference ... scope="remote" />

  5. 动态配置调用行为:从Dubbo 3.2开始,Dubbo提供API可以让用户在使用中动态地配置单次调用时为本地调用或者远程调用。例如,配置单次调用为远程调用可以通过RpcContext.getServiceContext().setLocalInvoke(false);实现,配置为本地调用则设置为true

通过上述分析,我们可以看到Dubbo的本地调用(Injvm)是一种高效、便捷的调用方式,特别适用于同一JVM内部的服务调用场景。它不仅简化了配置,还保持了Dubbo框架的统一性和灵活性。