美文网首页
Dubbo源码阅读(1)搭建Dubbo源码阅读环境

Dubbo源码阅读(1)搭建Dubbo源码阅读环境

作者: NoelleMu | 来源:发表于2021-01-12 23:19 被阅读0次

    Dubbo源码阅读(1)搭建Dubbo源码阅读环境

    一直想阅读一些开源框架的源码,但是由于上学期事情太多而一直没有时间阅读。现在放寒假了,终于有足够的时间来阅读源码了,就先从Dubbo开始吧——毕竟这个寒假是有写一个自己的RPC框架的打算的,从阅读RPC框架的源码开始也是一个不错的选择。

    虽然我对Dubbo实在是说不上喜欢——明明定位是一个RPC框架却有服务注册与发现、监控等服务治理的功能,在使用时有时会发现一些莫名其妙的默认行为,根据RPC Benchmark(https://github.com/hank-whu/rpc-benchmark)的测试结果Dubbo的性能也是垫底的,给人的感觉就是这个框架在云原生时代显得毫无必要的复杂(当然这也有可能是历史包袱所致,毕竟Dubbo算是一个比较老的框架了,或许和使用Java语言也有一定关系)。相比而言,我更喜欢gRPC这种只有RPC功能不关心其他任何事情的纯粹的RPC框架,可以很方便地和Kubernetes、Istio等服务治理框架进行整合而不用担心功能重合、冲突。但是,Dubbo毕竟是Java Web开发首选的RPC框架,能发展到今天的规模(听说还要出Go语言版本的Dubbo)、能成为Java Web面试的一大考点说明它还是有很多值得学习的地方的,所以我还是认为读一读它的源码对于了解RPC框架、服务治理框架的工作原理以及后续自己写一个Kotlin RPC框架有非常大的帮助。

    Reference

    这一系列教程主要是跟随拉勾教育的《Dubbo源码解读与实战》进行的,中间可能也会参考一些其他教程或文章。

    Dubbo的架构

    image
    • Registry:注册中心。保存服务名与服务地址的映射关系, 负责服务地址的注册与查找,服务的 Provider 和 Consumer 只在启动时与注册中心交互。注册中心通过长连接感知 Provider 的存在,在 Provider 的地址出现变动的时候,注册中心会通知 Consumer。

    • Provider:服务提供者。 提供服务的接口(API)和服务的实现。在它启动的时候,会向 Registry 进行注册(远程注册、本地注册——找到API的实现类)操作,将自己服务的地址和相关配置信息封装成 URL 添加到 ZooKeeper 中。

    • Consumer:服务消费者。 在它启动的时候,会向 Registry 进行订阅操作。订阅操作会从 ZooKeeper 中获取 Provider 注册的 URL,并在 ZooKeeper 中添加相应的监听器,再将获取到的服务地址缓存到本地。获取到 Provider URL 之后,Consumer 会根据负载均衡算法从多个 Provider 中选择一个 Provider 并与其建立连接,最后发起对 Provider 的 RPC 调用。 如果 Provider URL 发生变更,Consumer 将会通过之前订阅过程中在注册中心添加的监听器,获取到最新的 Provider URL 信息,进行相应的调整,比如断开与宕机 Provider 的连接,并与新的 Provider 建立连接。Consumer 与 Provider 建立的是长连接,且 Consumer 会缓存 Provider 信息,所以一旦连接建立,即使注册中心宕机,也不会影响已运行的 Provider 和 Consumer。

    • Monitor:监控中心。 用于统计服务的调用次数和调用时间。Provider 和 Consumer 在运行过程中,会在内存中统计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。监控中心不是必要的角色,没有监控中心或者监控中心宕机不会影响RPC调用。

    Dubbo源码的下载

    官方仓库地址:https://github.com/apache/dubbo

    命令:

    git clone git@github.com:apache/dubbo.git    # 从GitHub上下载源码
    git checkout -b dubbo-2.7.7 dubbo-2.7.7      # 切换到2.7.7版本。最新版本已经是2.7.8了,但是教程使用的是2.7.7,为了和教程保持一致还是用2.7.7吧
    mvn clean install -Dmaven.test.skip=true     # 编译、安装源码
    mvn idea:idea                                                          # 转换为IDEA项目
    

    最后使用IDEA打开生成好的项目,就可以看到Dubbo的主要模块了:

    image

    其中几个核心模块的主要组成如下:

    • dubbo-common:工具类模块,提供了一些供其他模块使用的工具类。
    image
    • dubbo-remoting:通信模块,依赖各种开源组件实现远程通信。
    image
    • dubbo-rpc:对远程调用协议进行抽象的模块。
    image
    • dubbo-cluster:负责管理集群的模块,提供了负载均衡、容错、路由等一系列集群相关的功能。该模块没有子模块。

    • dubbo-registry:负责与多种开源注册中心进行交互的模块,提供注册中心的能力。其中dubbo-registry-api是顶层抽象,其他模块是具体实现。

    image
    • dubbo-monitor:监控模块,统计服务的调用次数、调用时间,进行调用链追踪。
    image
    • dubbo-config:对外暴露Dubbo的配置。
    image
    • dubbo-metadata:Dubbo的元数据模块。
    image
    • dubbo-configcenter:Dubbo的动态配置模块。
    image

    阅读源码前的准备:启动Zookeeper

    Dubbo官方推荐使用Zookeeper作为注册中心,所以我们需要在本地启动一个注册中心。比起下载安装,使用Docker启动当然是更优雅也更省力的方式,所以我们使用Docker来启动Zookeeper:

    docker pull zookeeper
    docker run -d -p 2181:2181 --name zookeeper --restart always zookeeper
    

    然后使用docker ps命令查看,如果STATUSUP,则启动成功。

    Dubbo官方Demo1:基于XML文档的配置

    在看具体的Demo之前,先来看看官方Demo提供/消费的服务是什么形式。

    Dubbo的服务以接口(interface)为粒度,这个接口可以被认为是服务提供者和服务消费者的公约——类似于gRPC的proto文件,它反映了与RPC调用有关的一些信息,例如:

    • 服务的名称、每个服务下有什么方法
    • 请求的名称、包含什么参数、每个参数是什么类型
    • 响应的名称、包含什么参数、每个参数是什么类型

    Dubbo的官方Demo都使用了dubbo-demo-interface模块下的两个接口:

    image

    服务提供者(dubbo-demo-xml-provider)

    查看dubbo-demo-xml-provider模块的pom.xml文件,可以看到有以下依赖:

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-demo-interface</artifactId>
        <version>${project.parent.version}</version>
    </dependency>
    

    该模块是基于Spring的,在该模块的resources/spring目录下有一个dubbo-provider.xml,其中配置了dubbo所需要的一些信息:

    <!-- 指定Zookeeper的地址为127.0.0.1:2181 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
    
    <!-- 使用Dubbo协议 -->
    <dubbo:protocol name="dubbo"/>
    
    <!-- 把Dubbo服务配置为Spring Bean -->
    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>
    
    <!-- 配置本地注册,将DemoService作为Dubbo服务暴露出去 -->
    <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
    

    启动该模块的main()方法在Application.java中,主要逻辑为指定Spring的配置文件为dubbo-provider.xml,然后启动ClassPathXmlApplicationContext

    服务消费者(dubbo-demo-xml-consumer)

    pom.xml中同样引用了dubbo-demo-interface模块。

    该模块的基本配置与dubbo-demo-xml-consumer基本一致,但是多了这一条:

    <dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>
    

    这条配置引入了DemoService服务,并注册为一个Spring Bean

    启动方法与服务提供者一样,但是代码中加入了调用服务提供者的逻辑。

    Dubbo官方Demo2:基于注解的配置

    其实基于注解的配置与基于XML文档的配置在原理上讲没什么不同之处,只是将冗长的XML文档简化为注解而已。

    题外话:我很讨厌XML,能不用XML我就不会用XML。当然,Android开发中的Layout布局文件除外,那个还是很友好的,不会让人有太恶心的感觉,至少比HTML和CSS要友好的多。。。

    服务提供者(dubbo-demo-annotation-provider)

    模块下有一个实现了DemoService接口的服务实现类——DemoServiceImpl,它被加上了@Service注解(注意这个Service是org.apache.dubbo.config.annotation.Service,不是Spring那个),可以被扫描到而被注册为Dubbo服务(不过在今天(2021-1-12)这个注解居然过时了,查看了一下换成了2.7.7版本新加入的注解@DubboService。注解换了但是这官方Demo居然没有跟进,迷惑)。

    这个模块下的Application.java与XML模块的有些不同:

    public class Application {
        public static void main(String[] args) throws Exception {
            // 从ProviderConfiguration这个配置类的注解上获取到相关配置信息,启动AnnotationConfigApplicationContext 
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
            context.start();
            System.in.read();
        }
    
        @Configuration
        @EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")
        @PropertySource("classpath:/spring/dubbo-provider.properties")
        static class ProviderConfiguration {
            @Bean
            public RegistryConfig registryConfig() {
                RegistryConfig registryConfig = new RegistryConfig();
                registryConfig.setAddress("zookeeper://127.0.0.1:2181");
                return registryConfig;
            }
        }
    }
    
    

    其中有这样几个注解:

    • @Configuration:这是Spring的注解,指明该类是一个配置类;
    • @EnableDubbo:指定有哪些类需要被扫描和作为Dubbo服务暴露出去;
    • @PropertySource:指定其它配置文件的位置,该配置文件中指定了Dubbo应用的名称、采取的通信协议、Dubbo监听的端口等;

    配置类中指定了Zookeeper注册中心的地址为127.0.0.1:2181

    服务消费者(dubbo-demo-annotation-consumer)

    服务消费者模块下的comp包中有一个DemoServiceComponent

    @Component("demoServiceComponent")
    public class DemoServiceComponent implements DemoService {
        @Reference
        private DemoService demoService;
    
        @Override
        public String sayHello(String name) {
            return demoService.sayHello(name);
        }
    
        @Override
        public CompletableFuture<String> sayHelloAsync(String name) {
            return null;
        }
    }
    
    

    它实现了DemoService,使用@Reference注解(已过时,最新的注解为@DubboReference)引用远程Dubbo服务,并使用@Component注解注册为Spring Bean

    再来看看Application.java

    public class Application {
        /**
         * In order to make sure multicast registry works, need to specify '-Djava.net.preferIPv4Stack=true' before
         * launch the application
         */
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
            context.start();
            DemoService service = context.getBean("demoServiceComponent", DemoServiceComponent.class);
            String hello = service.sayHello("world");
            System.out.println("result :" + hello);
        }
    
        @Configuration
        @EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.consumer.comp")
        @PropertySource("classpath:/spring/dubbo-consumer.properties")
        @ComponentScan(value = {"org.apache.dubbo.demo.consumer.comp"})
        static class ConsumerConfiguration {
    
        }
    }
    
    

    多了一个注解@ComponentScan,是用来扫描刚才提到的DemoServiceImpl类的。

    Dubbo官方Demo3:基于API的配置

    前两种配置方式都是依赖于Spring框架的,那么有没有不依赖Spring框架的配置方式呢?有!它就是基于API的配置方式。Dubbo作为一个开源的RPC框架为了保证扩展性,自然提供了不依赖其它任何框架的API配置方式。

    服务提供者(dubbo-demo-api-provider)

    服务提供者模块下有一个DemoServiceImpl实现了DemoService接口,没有什么特殊的地方。

    重点来看Application.java文件:

    public class Application {
        public static void main(String[] args) throws Exception {
            if (isClassic(args)) {
                startWithExport();
            } else {
                startWithBootstrap();
            }
        }
    
        private static boolean isClassic(String[] args) {
            return args.length > 0 && "classic".equalsIgnoreCase(args[0]);
        }
    
        private static void startWithBootstrap() {
            ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
            service.setInterface(DemoService.class);
            service.setRef(new DemoServiceImpl());
    
            DubboBootstrap bootstrap = DubboBootstrap.getInstance();
            bootstrap.application(new ApplicationConfig("dubbo-demo-api-provider"))
                    .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
                    .service(service)
                    .start()
                    .await();
        }
    
        private static void startWithExport() throws InterruptedException {
            ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
            service.setInterface(DemoService.class);
            service.setRef(new DemoServiceImpl());
            service.setApplication(new ApplicationConfig("dubbo-demo-api-provider"));
            service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
            service.export();
    
            System.out.println("dubbo service started");
            new CountDownLatch(1).await();
        }
    }
    
    

    给出了两种配置方式:基于DubboBootstrapServiceConfig。其中DubboBootstrap是个单例的类,在获取到其实例后可以使用配置信息来生成一个ApplicationConfig的实例并使用registry()方法指定注册中心的地址,使用start()方法启动服务提供者的实例;基于ServiceConfig的配置方式就是简单地将配置信息传给ServiceConfig,然后根据配置信息对Dubbo服务进行暴露。

    服务消费者(dubbo-demo-api-consumer)

    因为没有依赖其它框架,所以服务消费者只有一个Application.java文件:

    public class Application {
        public static void main(String[] args) {
            if (isClassic(args)) {
                runWithRefer();
            } else {
                runWithBootstrap();
            }
        }
    
        private static boolean isClassic(String[] args) {
            return args.length > 0 && "classic".equalsIgnoreCase(args[0]);
        }
    
        private static void runWithBootstrap() {
            ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
            reference.setInterface(DemoService.class);
            reference.setGeneric("true");
    
            DubboBootstrap bootstrap = DubboBootstrap.getInstance();
            bootstrap.application(new ApplicationConfig("dubbo-demo-api-consumer"))
                    .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
                    .reference(reference)
                    .start();
    
            DemoService demoService = ReferenceConfigCache.getCache().get(reference);
            String message = demoService.sayHello("dubbo");
            System.out.println(message);
    
            // generic invoke
            GenericService genericService = (GenericService) demoService;
            Object genericInvokeResult = genericService.$invoke("sayHello", new String[] { String.class.getName() },
                    new Object[] { "dubbo generic invoke" });
            System.out.println(genericInvokeResult);
        }
    
        private static void runWithRefer() {
            ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
            reference.setApplication(new ApplicationConfig("dubbo-demo-api-consumer"));
            reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
            reference.setInterface(DemoService.class);
            DemoService service = reference.get();
            String message = service.sayHello("dubbo");
            System.out.println(message);
        }
    }
    
    

    给出了两种配置方式:基于ReferenceConfig和基于DubboBootstrap,虽然代码不太一样但是原理都是将配置信息传递给配置类,然后用配置类来获取到服务提供者的代理并启动服务消费者进行RPC调用。

    总结

    搭建好源码阅读环境后就可以开始通过Debug官方Demo的方式阅读源码了,通过三个官方实例,我们也对Dubbo的配置方式——基于XML、注解和API有了一些了解。根据个人使用Dubbo的经验,Dubbo与Spring Boot进行整合的时候常用的是基于注解的方式,与Spring MVC进行整合的时候常用的是基于XML文档的方式,基于API的方式非常少见,毕竟大家都是把Dubbo和Spring搭配在一起使用,基于API的配置方式确实就没什么用武之地了——但是这种配置方式还是必须要保留的,毕竟Java != Spring。

    相关文章

      网友评论

          本文标题:Dubbo源码阅读(1)搭建Dubbo源码阅读环境

          本文链接:https://www.haomeiwen.com/subject/ykeyaktx.html