RPC 源码解析~Apache Dubbo

解析 RPC(远程过程调用)的源码可以帮助你深入理解其工作原理和实现细节。为了更好地进行源码解析,我们选择一个流行的 RPC 框架------Apache Dubbo 作为示例。Dubbo 是一个高性能、轻量级的开源 Java RPC 框架,广泛应用于企业级应用中。

Dubbo的优劣势

优势
  1. 高性能:

    • Dubbo 使用 Netty 作为底层通信框架,支持高并发场景下的高效通信。
    • 支持多种序列化方式(如 Hessian2, Kryo, FST 等),可以根据需要选择最合适的序列化方案以提升性能。
  2. 丰富的功能:

    • 提供多种负载均衡策略(如 RandomLoadBalance, RoundRobinLoadBalance 等)。
    • 内置了服务降级、熔断和限流机制,增强了系统的健壮性。
    • 支持多协议(如 Dubbo 协议, HTTP 协议等),灵活适应不同的应用场景。
  3. 良好的生态系统:

    • 拥有成熟的社区支持和丰富的文档资源。
    • 可与 Spring Boot 和 Spring Cloud 等主流框架无缝集成,简化开发流程。
  4. 细粒度控制:

    • 提供详细的配置选项,允许开发者对每个服务进行精细化管理。
    • 支持服务分组、版本控制等功能,便于维护和升级。
  5. 强大的监控能力:

    • 内置了监控中心,可以实时查看服务的调用情况和性能指标。
    • 支持与其他监控系统(如 Prometheus, Grafana)集成,实现全面的监控解决方案。
劣势
  1. 依赖复杂:

    • Dubbo 依赖较多,引入时可能需要额外配置多个组件(如注册中心、序列化库等)。
    • 学习曲线相对陡峭,初学者需要一定时间掌握其复杂的配置和使用方法。
  2. 扩展性有限:

    • 尽管 Dubbo 提供了插件机制,但相对于一些现代微服务框架(如 Spring Cloud),其扩展性和灵活性稍显不足。
    • 对于某些特定需求,可能需要自行开发插件或中间件来满足。
  3. 生态整合难度:

    • 虽然可以与 Spring Boot 和 Spring Cloud 集成,但在某些高级特性上可能存在兼容性问题。
    • 相比 Spring Cloud 生态更为丰富和成熟,Dubbo 的第三方组件和支持程度略逊一筹。
  4. 社区活跃度:

    • 虽然 Dubbo 社区活跃,但由于近年来 Spring Cloud 的崛起,部分开发者更倾向于使用后者。
    • 新的功能更新速度相对较慢,不如 Spring Cloud 快速迭代。

解析目标

我们将逐步解析 Dubbo 的核心组件和流程,包括但不限于以下部分:

  1. 服务注册与发现:如何将服务注册到注册中心,并从注册中心发现可用的服务。
  2. 代理机制:客户端和服务端如何通过动态代理来透明地进行远程调用。
  3. 序列化:数据在网络传输过程中如何被序列化和反序列化。
  4. 负载均衡:客户端如何根据不同的策略选择合适的服务提供者。
  5. 通信协议:底层网络通信是如何实现的,包括 Netty 等网络框架的应用。

准备工作

在开始解析之前,请确保你已经具备以下条件:

  1. Java 开发环境:安装 JDK 8 或更高版本。
  2. Git 工具:用于克隆 Dubbo 源码仓库。
  3. IDE:推荐使用 IntelliJ IDEA 或 Eclipse。
  4. Maven:用于构建和管理依赖。

克隆 Dubbo 源码

首先,克隆 Dubbo 的 GitHub 仓库到本地:

bash

bash 复制代码
git clone https://github.com/apache/dubbo.git
cd dubbo

构建 Dubbo 源码

使用 Maven 构建 Dubbo 源码:

bash

bash 复制代码
mvn clean install -DskipTests=true

核心组件解析

1. 服务注册与发现

Dubbo 使用 ZooKeeper 作为默认的注册中心。以下是服务注册和发现的核心流程。

服务注册

当服务提供者启动时,会将其元数据注册到注册中心。主要涉及 RegistryProtocolZookeeperRegistry 类。

  • RegistryProtocol: 负责处理服务注册和订阅逻辑。
  • ZookeeperRegistry: 实现了具体的注册中心操作,如连接 ZooKeeper 并执行注册。

关键代码位置:

  • dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/RegistryProtocol.java
  • dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java

核心方法:

  • export(Invoker<T> invoker): 导出服务并注册到注册中心。
  • doRegister(URL url): 执行实际的注册操作。

示例代码:

java

java 复制代码
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    URL registryUrl = getRegistryUrl(originInvoker);
    // 创建注册器实例
    Registry registry = getRegistry(registryUrl);
    final URL providerUrl = getProviderUrl(originInvoker, registryUrl);

    // 向注册中心注册服务
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
    ProviderModel providerModel = new ProviderModel(providerUrl.getServiceKey(), originInvoker, providerUrl);
    ApplicationModel.registerProvider(providerModel);

    registry.register(registeredProviderUrl);

    // 订阅 override 数据
    registry.subscribe(getSubscribedOverrideUrl(providerUrl), event -> {
        if (logger.isDebugEnabled()) {
            logger.debug("Notify urls for subscribe url " + event.getUrl() + ", urls: " + event.getUrls());
        }
        Map<String, String> notifiedUrls = new HashMap<>();
        if (CollectionUtils.isNotEmpty(event.getUrls())) {
            event.getUrls().forEach(url -> notifiedUrls.put(url.getServiceKey(), url.toFullString()));
        }

        refreshOverrideAndInvoker(providerUrl, notifiedUrls);
    });

    exporterMapLock.lock();
    try {
        ExporterChangeableWrapper<T> exporter = new ExporterChangeableWrapper<>(originInvoker, null);
        exporters.put(originInvoker, exporter);
        return exporter;
    } finally {
        exporterMapLock.unlock();
    }
}
服务发现

当服务消费者启动时,会从注册中心订阅服务提供者的地址列表。主要涉及 RegistryProtocolZookeeperRegistry 类。

  • RegistryProtocol: 处理订阅逻辑,并生成服务代理。
  • ZookeeperRegistry: 监听注册中心的变化,并通知消费者。

关键代码位置:

  • dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/RegistryProtocol.java
  • dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java

核心方法:

  • refer(Class<T> type, URL url): 引用远程服务并创建代理对象。
  • doSubscribe(URL url, NotifyListener listener): 执行实际的订阅操作。

示例代码:

java

java 复制代码
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, mapLocalHost(url.getParameter(Constants.REGISTER_IP_KEY, NetUtils.getLocalHost())), 0,
            type.getName(), url.getParameters());

    // 设置检查是否延迟暴露
    if (!Constants.YES_VALUE.equals(url.getParameter(Constants.LAZY_CONNECT_KEY))) {
        checkWhetherMetadataCenterExist(subscribeUrl);
        List<URL> urls = registryCache.get(subscribeUrl);
        if (urls != null && !urls.isEmpty()) {
            // 如果存在直接返回
            StaticDirectory<T> directory = new StaticDirectory<>(subscribeUrl, toInvokers(urls));
            doRefer(subscribeUrl, directory);
            return cluster.join(directory);
        }
    }

    // 创建动态目录
    DynamicDirectory<T> directory = new DynamicDirectory<>(subscribeUrl, registry, directoryFactory, false);
    directory.buildRouterChain(subscribeUrl);
    directory.setConsumerUrl(subscribeUrl);
    directoryList.add(directory);
    registry.subscribe(subscribeUrl, new CacheListener(directory, url));

    // 创建集群
    Invoker<T> clusterInvoker = cluster.join(directory);
    providersModel.setClusterInvoker(clusterInvoker);
    return clusterInvoker;
}
2. 代理机制

Dubbo 使用动态代理来简化远程调用的过程。主要涉及 ProxyFactoryJavassistProxyFactory 类。

  • ProxyFactory: 定义了代理工厂接口。
  • JavassistProxyFactory: 实现了具体的代理创建逻辑。

关键代码位置:

  • dubbo-common/src/main/java/org/apache/dubbo/common/proxy/ProxyFactory.java
  • dubbo-proxy/dubbo-proxy-javassist/src/main/java/org/apache/dubbo/common/proxy/javassist/JavassistProxyFactory.java

核心方法:

  • getProxy(Invoker<?> invoker): 获取服务代理对象。
  • getInvoker(T proxy, Class<T> type, URL url): 将代理对象转换为 Invoker。

示例代码:

java

java 复制代码
@Override
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // TODO Wrapper cannot handle this scenario at this moment.
    throw new UnsupportedOperationException("Not supported.");
}
3. 序列化

Dubbo 支持多种序列化方式,如 Hessian2, Kryo, FST 等。主要涉及 Serialization 和具体序列化类。

  • Serialization: 定义了序列化接口。
  • Hessian2Serialization: 实现了 Hessian2 序列化的具体逻辑。

关键代码位置:

  • dubbo-common/src/main/java/org/apache/dubbo/common/serialize/Serialization.java
  • dubbo-serialization/dubbo-serialization-hessian2/src/main/java/org/apache/dubbo/common/serialize/hessian2/Hessian2Serialization.java

核心方法:

  • serialize(URL url, OutputStream output): 返回序列化对象。
  • deserialize(URL url, InputStream input): 返回反序列化对象。

示例代码:

java

java 复制代码
@Override
public ObjectOutput serialize(URL url, OutputStream out) throws IOException {
    Hessian2ObjectOutputStream hessian2os = new Hessian2ObjectOutputStream(out);
    com.caucho.hessian.io.SerializerFactory serializerFactory = getSerializerFactory(url);
    if (serializerFactory != null) {
        hessian2os.setSerializerFactory(serializerFactory);
    }
    return new CompatibleObjectOutput(hessian2os);
}

@Override
public ObjectInput deserialize(URL url, InputStream is) throws IOException {
    Hessian2ObjectInputStream hessian2is = new Hessian2ObjectInputStream(is);
    com.caucho.hessian.io.DeserializerFactory deserializerFactory = getDeserializerFactory(url);
    if (deserializerFactory != null) {
        hessian2is.setDeserializerFactory(deserializerFactory);
    }
    return new CompatibleObjectInput(hessian2is);
}
4. 负载均衡

Dubbo 提供多种负载均衡策略,如 RandomLoadBalance, RoundRobinLoadBalance 等。主要涉及 LoadBalance 和具体策略类。

  • LoadBalance: 定义了负载均衡接口。
  • RandomLoadBalance: 实现了随机负载均衡的具体逻辑。

关键代码位置:

  • dubbo-cluster/src/main/java/org/apache/dubbo/rpc/loadbalance/LoadBalance.java
  • dubbo-cluster/src/main/java/org/apache/dubbo/rpc/loadbalance/RandomLoadBalance.java

核心方法:

  • select(List<Invoker<T>> invokers, URL url, Invocation invocation): 选择合适的 Invoker。

示例代码:

java

java 复制代码
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    int length = invokers.size(); // Number of providers
    int totalWeight = 0; // The sum of weights
    boolean sameWeight = true; // Every provider has the same weight?
    for (int i = 0; i < length; i++) {
        int weight = getWeight(invokers.get(i), invocation);
        totalWeight += weight; // Sum
        if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) {
            sameWeight = false;
        }
    }
    if (totalWeight > 0 && !sameWeight) {
        // If (not every provider has the same weight & at least one provider's weight>0), select randomly based on totalWeight.
        int offset = ThreadLocalRandom.current().nextInt(totalWeight);
        // Return a invoker based on the random value.
        for (int i = 0; i < length; i++) {
            Invoker<T> invoker = invokers.get(i);
            offset -= getWeight(invoker, invocation);
            if (offset < 0) {
                return invoker;
            }
        }
    }
    // If all providers have the same weight value or totalWeight=0, return evenly.
    return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
5. 通信协议

Dubbo 支持多种通信协议,如 Dubbo 协议, HTTP 协议等。主要涉及 Transporter 和具体协议类。

  • Transporter: 定义了通信接口。
  • NettyTransporter: 实现了基于 Netty 的通信逻辑。

关键代码位置:

  • dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Transporter.java
  • dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporter.java

核心方法:

  • bind(URL url, ChannelHandler handler): 绑定服务器端口。
  • connect(URL url, ChannelHandler handler): 连接到服务器。

示例代码:

java 复制代码
@Override
public Server bind(URL url, ChannelHandler handler) throws RemotingException {
    return new NettyServer(url, handler);
}

@Override
public Client connect(URL url, ChannelHandler handler) throws RemotingException {
    return new NettyClient(url, wrapChannelHandler(url, handler));
}

示例项目

为了更好地理解上述解析内容,我们可以创建一个简单的 Dubbo 示例项目,包含服务提供者和消费者。

1. 创建服务接口

定义一个简单的服务接口:

UserApi.java:

java

java 复制代码
package com.example.dubbo.service;

public interface UserApi {
    String sayHello(String name);
}
2. 实现服务提供者

实现服务接口并启动服务提供者:

UserServiceImpl.java:

java

java 复制代码
package com.example.dubbo.provider;

import com.example.dubbo.service.UserApi;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService(version = "1.0.0")
public class UserServiceImpl implements UserApi {
    @Override
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

ProviderApplication.java:

java

java 复制代码
package com.example.dubbo.provider;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.example.dubbo.provider")
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

application.properties:

java 复制代码
# Dubbo Application Info
dubbo.application.name=user-provider
dubbo.registry.address=zookeeper://localhost:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
3. 实现服务消费者

创建服务消费者并调用远程服务:

ConsumerApplication.java:

java

java 复制代码
package com.example.dubbo.consumer;

import com.example.dubbo.service.UserApi;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConsumerApplication implements CommandLineRunner {

    @DubboReference(version = "1.0.0")
    private UserApi userApi;

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        String result = userApi.sayHello("World");
        System.out.println(result); // Output: Hello, World
    }
}

application.properties:

java 复制代码
# Dubbo Application Info
dubbo.application.name=user-consumer
dubbo.registry.address=zookeeper://localhost:2181

运行示例

  1. 启动 ZooKeeper : 确保 ZooKeeper 服务正在运行。如果没有安装,可以从 ZooKeeper 官网 下载并按照官方文档进行安装和启动。

  2. 启动服务提供者

bash

bash 复制代码
cd provider
mvn spring-boot:run

3.启动服务消费者

bash

bash 复制代码
cd consumer
mvn spring-boot:run

总结

通过对 Dubbo 源码的解析,我们深入了解了 RPC 框架的关键组成部分和工作原理,包括服务注册与发现、代理机制、序列化、负载均衡和通信协议。此外,我们还创建了一个简单的示例项目,展示了如何使用 Dubbo 进行服务开发和部署。

相关推荐
C182981825753 小时前
RPC实现原理,怎么跟调用本地一样
网络·网络协议·rpc
董可伦17 小时前
Spark RPC 学习总结
学习·rpc·spark
Ai 编码助手2 天前
如何使用PHP构建IoC容器,实现依赖注入!
开发语言·rpc·php
宇宙李3 天前
如何设计一个 RPC 框架?需要考虑哪些点?
java·网络·python·网络协议·rpc
自律即自由w3 天前
RPC调用初识
rpc
互联网杂货铺5 天前
接口测试总结(http与rpc)
自动化测试·软件测试·测试工具·http·rpc·测试用例·接口测试
董可伦6 天前
Netty 入门学习
rpc·spark·netty·通信
对自己不够狠7 天前
HarMonyOS 鸿蒙系统使用 Grid构建网格
网络·网络协议·rpc
檀越剑指大厂7 天前
【Git系列】解析与解决Git错误:RPC失败;curl 56 OpenSSL SSL_read: error:140943FC
git·rpc·ssl